diff --git a/docs/collection-functions.md b/docs/collection-functions.md
index 3e3ab77d..f7ad157c 100644
--- a/docs/collection-functions.md
+++ b/docs/collection-functions.md
@@ -1,8 +1,8 @@
## Collection Functions
-Collection functions are a powerful extension point that will allow you to write a custom filtering or an ordering behavior.
+Collection functions are a powerful extension point that will allow you to write a custom collection filtering or an ordering behavior.
-The custom filtering or ordering requires its own implementation for each storage implementation; ideally you write it for both Dbal & Array to allow use of persisted and un-persisted collections. Both of the Dbal & Array implementations bring their own interface you have to implement directly.
+The collection function requires its own implementation for each storage implementation: ideally for both Dbal & Array storages to allow use of persisted and un-persisted collections. Both of the Dbal & Array storages are supported in a single interface.
@@ -11,7 +11,7 @@ The custom filtering or ordering requires its own implementation for each storag
Collection itself is independent of storage implementation. It is your choice if your collection function will work in both cases - for `ArrayCollection` and `DbalCollection`. Let us remind you, `ArrayCollection`s are commonly used in relationships when you set new entities into the relationship but until the relationship is persisted, you will work with an `ArrayCollection`.
-Collection functions can be used in `ICollection::findBy()` or `ICollection::orderBy()` methods. A collection function is used as an array argument, the first value is the function identifier (it is recommended using function's class name) and then function's arguments as other array values. Collection functions may be used together, also nest together, so you can reuse them.
+Collection functions can be used in `ICollection::findBy()` or `ICollection::orderBy()` methods. A collection function is passed as an array. The first value is the function identifier (it is recommended using function's class name) and then function's arguments as other array values. Collection functions may be used together, also nest together, so you can reuse them.
```php
// collection function call
@@ -33,8 +33,7 @@ Functions are registered per repository. To do so, override `Repository::createC
```php
class UsersRepository extends Nextras\Orm\Repository\Repository
{
- // ...
- public function createCollectionFunction(string $name)
+ public function createCollectionFunction(string $name): CollectionFunction
{
if ($name === MyFunction::class) {
return new MyFunction();
@@ -45,9 +44,45 @@ class UsersRepository extends Nextras\Orm\Repository\Repository
}
```
-#### Dbal Functions
+To implement your own function, create a class implementing `Nextras\Orm\Collection\Functions\CollectionFunction` interface.
-Dbal collection functions have to implement `Nextras\Orm\Collection\Functions\IQueryBuilderFunction` interface. The only required method takes three arguments: `DbalQueryBuilderHelper` for easier user input processing, `QueryBuilder` for creating table joins, and a user input/function parameters.
+```php
+use \Nextras\Dbal\QueryBuilder\QueryBuilder;
+use \Nextras\Orm\Collection\Aggregations\Aggregator;
+use \Nextras\Orm\Collection\Functions\CollectionFunction;
+use \Nextras\Orm\Collection\Functions\Result\ArrayExpressionResult;
+use \Nextras\Orm\Collection\Functions\Result\DbalExpressionResult;
+use \Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
+use \Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
+use \Nextras\Orm\Entity\IEntity;
+
+final class LikeFunction implements CollectionFunction
+{
+ public function processDbalExpression(
+ DbalQueryBuilderHelper $helper,
+ QueryBuilder $builder,
+ array $args,
+ Aggregator|null $aggregator = null,
+ ) : DbalExpressionResult
+ {
+ // TODO: Implement processDbalExpression() method.
+ }
+
+ public function processArrayExpression(
+ rrayCollectionHelper $helper,
+ IEntity $entity,
+ array $args,
+ Aggregator|null $aggregator = null,
+ ) : ArrayExpressionResult
+ {
+ // TODO: Implement processArrayExpression() method.
+ }
+}
+```
+
+#### Dbal Implementation
+
+The Dbal's method takes four arguments: `DbalQueryBuilderHelper` for easier user input processing, `QueryBuilder` for direct access to the builder, but usually this builder is not used directly at all, `$args` is the user input/function parameters and lastly aggregator that is being passed around.
Collection function has to return a `DbalExpressionResult` object. This object holds parts of SQL query which may be processed by Nextras Dbal's SqlProcessor. Because you are not adding SQL parts directly to QueryBuilder but rather return them in `DbalExpressionResult`, you may compose multiple collection functions together.
@@ -59,68 +94,62 @@ $users->findBy(
);
```
-In the example we would like to use the custom `LikeFunction` to filter users by their phones that start with `+420` prefix. Our function will implement `IQueryBuilderFunction` interface and will receive `$args` with `phone` and `+420`. The column/property argument may be quite dynamic too. What if the user passes `address->zipcode` (i.e. a relationship expression) instead of a simple `phone`, such expression would require table joins; doing it all by hand would be difficult. Therefore, Orm provides `DbalQueryBuilderHelper` that will handle all this for you. Use `processPropertyExpr` method to obtain a `DbalExpressionResult` for the column/property argument. Then just append needed SQL to the returned expression, e.g. LIKE operator with a Dbal's argument. That's all!
+In the example we would like to use the custom `LikeFunction` to filter users by their phones that start with `+420` prefix. Our function will receive `$args` with `phone` and `+420`. The column/property argument may be quite dynamic too. What if the user passes `address->zipcode` expression (i.e. a relationship expression) instead of a simple `phone`, such expression would require table joins; doing it all by hand would be difficult. Therefore, Orm provides `DbalQueryBuilderHelper` that will handle all this for you. Use `processExpression` method to obtain a `DbalExpressionResult` for the column/property argument. Then just append needed SQL to the returned expression, e.g. LIKE operator with a Dbal's argument. That's all!
```php
-use Nextras\Dbal\QueryBuilder\QueryBuilder;
-use Nextras\Orm\Collection\Functions\Result\DbalExpressionResult;
-use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
-
-final class LikeFunction implements IQueryBuilderFunction
+public function processDbalExpression(
+ DbalQueryBuilderHelper $helper,
+ QueryBuilder $builder,
+ array $args,
+ Aggregator|null $aggregator = null,
+) : DbalExpressionResult
{
- public function processQueryBuilderExpression(
- DbalQueryBuilderHelper $helper,
- QueryBuilder $builder,
- array $args
- ): DbalExpressionResult
- {
- // $args is for example ['phone', '+420']
- \assert(\count($args) === 2 && \is_string($args[0]) && \is_string($args[1]));
+ // $args is for example ['phone', '+420']
+ \assert(\count($args) === 2 && \is_string($args[0]) && \is_string($args[1]));
- $expression = $helper->processPropertyExpr($builder, $args[0]);
- return $expression->append('LIKE %like_', $args[1]);
- }
+ $expression = $helper->processExpression($builder, $args[0], $aggregator);
+ return $expression->append('LIKE %like_', $args[1]);
}
```
-The helper processed value may not be just a column, but also another expression returned from another collection function.
+The helper processed value may not be just a column SQL name, but also a more complex expression returned from another collection function.
-If you need more advance operation than appending the expression, construct a new `DbalExpressionResult` object. Use Dbal's `%ex` modifier to expand the already processed expression. Some properties of the original expression result may be lost by creating a new expression result instance; if needed, pass the original's values as additional constructor parameters.
+If you need more advance operation than appending the expression, either construct a new `DbalExpressionResult` object (and copy over all properties to retain them) or use. Use Dbal's `%ex` modifier to expand the already processed expression. Some properties of the original expression result may be lost by creating a new expression result instance; if needed, pass the original's values as additional constructor parameters.
```php
-$expression = $helper->processPropertyExpr($builder, $args[0]);
+$expression = $helper->processExpression($builder, $args[0], $aggregator);
return new DbalExpressionResult(['SUBSTRING(%ex, 0, %i) = %s', $expression->args, \strlen($args[1]), $args[1]]);
```
-#### Array Functions
+#### Array Implementation
-Array collection functions have to implement `Netras\Orm\Collection\Functions\IArrayFunction` interface. It is different to Dbal's interface, because the filtering happens directly in PHP now. The only required method takes `ArrayCollectionHelper` for easier entity property processing, `IEntity` entity to check if it should (not) be filtered out, and user input/function parameters.
+The implementation is different to Dbal's interface, because the filtering happens directly in PHP runtime. The method takes `ArrayCollectionHelper` instance for easier expression processing, `IEntity` instance to check if the expressions requires it (not) to be filtered out, and user input/function parameters.
-Array collection function returns a mixed value, the kind depends on which context it will be evaluated. The value will be interpreted as boolean in the filtering context to indicate if the entity should be filtered out; the value will be used for comparison of two entities in the ordering context (using the spaceship operator).
+Array collection function returns `ArrayExpressionResult` instance wrapping a mixed value, the kind (type) of the wrapped value depends on which context it will be user or evaluated later. Ultimately, the value will be interpreted as a boolean to indicate if the entity should be filtered out in the `ICollection::findBy()` call; alternatively, the value will be used for comparison (`<=>`) of two entities when used for `ICollection::oderBy()` call.
-Let's see an example: a "Like" collection function; We want to compare any (property) expression to passed user-input value with a prefix comparison.
+Let's see an example: a "Like" collection function; We want to compare a property expression to the passed user-input value using a prefix comparison.
```php
-use Nette\Utils\Strings;
-use Nextras\Orm\Collection\Functions\IArrayFunction;
-use Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
-use Nextras\Orm\Entity\IEntity;
-
-final class LikeFunction implements IArrayFunction
+public function processArrayExpression(
+ ArrayCollectionHelper $helper,
+ IEntity $entity,
+ array $args,
+ ?Aggregator $aggregator = null,
+): ArrayExpressionResult
{
- public function processArrayExpression(ArrayCollectionHelper $helper, IEntity $entity, array $args)
- {
- \assert(\count($args) === 2 && \is_string($args[0]) && \is_string($args[1]));
+ // $args is for example ['phone', '+420']
+ \assert(\count($args) === 2 && \is_string($args[0]) && \is_string($args[1]));
- $value = $helper->getValue($entity, $args[0])->value;
- return Strings::startsWith($value, $args[1]);
- }
+ $valueResult = $helper->getValue($entity, $args[0], $aggregator);
+ return new ArrayExpressionResult(
+ value: Strings::startsWith($valueResult->value, $args[1]),
+ );
}
```
-Our function implements the `IArrayFunction` interface and will receive helper objects & user-input. The same as in Dbal's example, the user property argument may vary from simple property access to traversing through relationship expression. Let's use a helper to get the property expression value holder, then read the value from the holder. Finally, we simply compare the value with the user-input argument by Nette's string helper.
+Similarly as in Dbal's example, the user property expression argument may vary from a simple property name to relationship expression. Therefore, use the helper to get the property expression result for that expression, then read the value and use it for creating a new array expression result.
-Postgres is case-sensitive, so you should apply the lower function & a functional index; These modifications are case-specific, therefore the LIKE functionality is not provided in Orm by default.
+Postgres is case-sensitive, so the value should be lowered first & a functional index should be created; Orm comes with own built-in [Like functionality](collection-filtering#toc-like-filtering).