-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow filter strategies to set parameters (for invalidating the Doctrine cache) (Case 142739) #19
base: master
Are you sure you want to change the base?
Changes from 1 commit
cbf9e6b
18bd625
4ca3c0f
85ff01a
e7a434d
8405dee
f82f81b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
|
||
namespace Webfactory\VisibilityFilterBundle\Filter; | ||
|
||
use Doctrine\DBAL\Types\Types; | ||
use InvalidArgumentException; | ||
|
||
interface ParameterCollection | ||
{ | ||
/** | ||
* Sets a parameter that can be used by the filter. | ||
* | ||
* @param string $name Name of the parameter. | ||
* @param string $value Value of the parameter. | ||
* @param Types::*|null $type The parameter type. If specified, the given value will be run through | ||
* the type conversion of this type. This is usually not needed for | ||
* strings and numeric types. | ||
*/ | ||
public function setParameter(string $name, $value, $type = null): void; | ||
|
||
/** | ||
* Gets a parameter to use in a query. | ||
* | ||
* The function is responsible for the right output escaping to use the | ||
* value in a query. | ||
* | ||
* @param string $name Name of the parameter. | ||
* | ||
* @return string The SQL escaped parameter to use in a query. | ||
* | ||
* @throws InvalidArgumentException | ||
*/ | ||
public function getParameter(string $name); | ||
|
||
/** | ||
* Checks if a parameter was set for the filter. | ||
* | ||
* @param string $name Name of the parameter. | ||
*/ | ||
public function hasParameter(string $name): bool; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
namespace Webfactory\VisibilityFilterBundle\Filter; | ||
|
||
use Doctrine\ORM\Query\Filter\SQLFilter; | ||
|
||
class SQLFilterAsParameterCollection implements ParameterCollection | ||
{ | ||
/** | ||
* @var SQLFilter | ||
*/ | ||
private $sqlFilter; | ||
|
||
public function __construct(SQLFilter $sqlFilter) | ||
{ | ||
$this->sqlFilter = $sqlFilter; | ||
} | ||
|
||
final public function setParameter($name, $value, $type = null): void | ||
{ | ||
$this->sqlFilter->setParameter($name, $value, $type); | ||
} | ||
|
||
final public function getParameter($name) | ||
{ | ||
return $this->sqlFilter->getParameter($name); | ||
} | ||
|
||
final public function hasParameter($name): bool | ||
{ | ||
return $this->sqlFilter->hasParameter($name); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,26 @@ | |
|
||
namespace Webfactory\VisibilityFilterBundle\Filter\Strategy; | ||
|
||
use Webfactory\VisibilityFilterBundle\Filter\ParameterCollection; | ||
|
||
/** | ||
* Implement this to apply custom filtering logic to your project. | ||
*/ | ||
interface FilterStrategy | ||
{ | ||
/** | ||
* Adds all parameters to the collection that affect the resulting filter SQL string. If the filter SQL depends on | ||
* factors other than parameters added to the collection, the cache won't be invalidated, which will have undesired | ||
* consequences! | ||
* | ||
* So please add all parameters to the collection. | ||
Comment on lines
+14
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "undesired consequences" and "please" seem too vague for my taste. Would you mind explaining it in more detail (or simply remove the unprecise wording)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
suggestion: which will have undesired consequences such as objects being visible for users who should not see them. (no acclamation mark)
seems to contradict
Either the method adds all parameters or the user has to do it. Maybe "Adds the parameters" would be better, as it avoids using the identical wording.
Suggestion: To avoid this, make sure that all parameters are added to the collection. |
||
*/ | ||
public function addParameters(ParameterCollection $parameters): void; | ||
|
||
/** | ||
* @param string $visibilityFieldAlias SQL alias for the visibility field of the requested entity | ||
* | ||
* @return string A string of SQL that will be included in the WHERE clause to any query requesting an entity with a visibility field | ||
*/ | ||
public function getFilterSql(string $visibilityFieldAlias): string; | ||
public function getFilterSql(string $visibilityFieldAlias, ParameterCollection $parameters): string; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,10 @@ | |
|
||
namespace Webfactory\VisibilityFilterBundle\Filter\Strategy; | ||
|
||
use Webfactory\VisibilityFilterBundle\Filter\ParameterCollection; | ||
use function is_bool; | ||
use function is_string; | ||
|
||
/** | ||
* Filters queries so that only entries with a certain value in their visibility field (@see VisibilityColumn) will | ||
* be retrieved from the database. | ||
|
@@ -21,7 +25,12 @@ public function __construct($visibleValue) | |
$this->visibleValue = $visibleValue; | ||
} | ||
|
||
public function getFilterSql(string $visibilityFieldAlias): string | ||
public function addParameters(ParameterCollection $parameters): void | ||
{ | ||
// not needed, as $this->visibleValue is immutable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not so sure about that: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having a mutable object with a Would you consider passing a wrong type according to the docblock as something to be officially supported? That would mean that changing a DockBlock type declaration into a PHP type declaration was a breaking change. The constructor uses a dockblock type declaration because it's a union type, and union types got introduced in PHP 8. This library however also supports PHP >=7.2. So we sadly can't turn it into a PHP type declaration until we drop support for PHP 7. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is a misuse, the I just would like to make accidental misuse harder. If you want to support PHP 7.2, what about something like this?
|
||
} | ||
|
||
public function getFilterSql(string $visibilityFieldAlias, ParameterCollection $parameters): string | ||
{ | ||
return $visibilityFieldAlias.' = '.$this->getSqlLiteral($this->visibleValue); | ||
} | ||
|
@@ -31,11 +40,11 @@ public function getFilterSql(string $visibilityFieldAlias): string | |
*/ | ||
private function getSqlLiteral($value): string | ||
{ | ||
if (\is_string($value)) { | ||
if (is_string($value)) { | ||
return '"'.$value.'"'; | ||
} | ||
|
||
if (\is_bool($value)) { | ||
if (is_bool($value)) { | ||
return $value ? '1' : '0'; | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class feels a bit weird to me: it cannot be used without an SQLFilter and only delegates to its methods of the name. While it's nice to have the ParameterCollection as an interface to state what exactly is needed, I'm not sure if this is worthwhile?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is one of the reasons I waited for a review – I hoped you had a strong opinion on this.
Removing the class and the interface is always easier than re-introducing it, and I think the interface makes the bundle easier to use and avoids mistakes.
On the other hand, the interface should probably be adjusted when the Doctrine SQLFilter interface changes, making this just duplicated code.
Do we need a third opinion on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realised that I forgot to add support for
setParameterList
to the interface/the SQLFilterAsParamterCollection class. Removing the interface and using the SQLFilter directly would avoid mistakes like this in the first place.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still no strong oppinion here, but I've given it some more thought:
You want to declare the type of the parameter for
FilterStrategy->addParameters($parameters)
. If you would declare it asSQLFilter
, theFilterStrategy
interface becomes tightly coupled to the SQLFilter. Which you probably want to avoid, as exchanging theFilterStrategy
is your bundle's ultimate extension point.To preserve the freedom you currently have for
FilterStrategy
implementations, you should keep theParameterCollection
interface (and therefore theSQLFilterAsParameterCollection
implementation).I'm not entirely convinced that the
FilterStrategy
is a good extension point - maybe I lack the imagination for a useful non trivial implementation without anSQLFilter
. But as I consider theFilterStrategy
central for the bundle, my bottom line suggestion is: keep theParameterCollection
andSQLFilterAsParameterCollection
👍There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping the
FilterStrategy
Interface decoupled from theSQLFilter
was indeed a reason why I introduced this interface, but it was not because I actually wanted to allow any other implementations to be used. Using other implementations than theSQLFilter
-basedSQLFilterAsParameterCollection
is actually not even possible using the current implementation of this bundle, as the code that callsaddParameters
creates an instance of this class.The
ParameterCollection
interface doesn't add any freedom to users of this bundle (except in unit tests, where the interface indeed eases mocking or the creation of test implementations).FilterStrategy
s that don't work together with anSQLFilter
behind the scenes are not possible using this bundle.The
ParameterCollection
interface was rather introduced for restraining the freedom of the implementor. I wanted to disallow calling any other methods on theSQLFilter
than the ones needed for managing parameters. Also, I thought that working with anSQLFilter
instance in aFilterStrategy
might be confusing if you don't understand the internals of this bundle. Even if you do understand the bundle, I think it would be odd (as theFilterStrategy
is there to be called by anSQLFilter
implementation, not to call it).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not sure whether these reasons suffice as an argument for the added complexity, or whether additional documentation is needed in order to make sure that people who try to understand the implementation don't stumple over this.