Edit

Collection Functions

Collection custom functions are a powerful extension point that will allow you to write a custom collection filtering or sorting behavior.

First, you have to define if you want to write “filter function” or just a plain “function”.

  • filter functions
    • easy to write
    • can be nested
    • limited functionality
    • Nextras\Orm\Mapper\Dbal\CustomFunctions\IQueryBuilderFilterFunction
    • Nextras\Orm\Mapper\Memory\CustomFunctions\IArrayFilterFunction
  • functions
    • difficult to write
    • cannot be nested
    • unlimited functionality
    • Nextras\Orm\Mapper\Dbal\CustomFunctions\IQueryBuilderFunction
    • Nextras\Orm\Mapper\Memory\CustomFunctions\IArrayFunction

Custom functions are defined as classes implementing specific interfaces. Orm comes with two sets of interfaces: Dbal interfaces and Array interfaces.

Why we have ArrayCollection and DbalCollection?

Collection itself is independent from storage implementation, it's your choice if your custom function will work in both cases – for ArrayCollection and DbalCollection. Let's remind you, ArrayCollections are commonly used in relationships, when you set new entities into the relationship, until the relationship is persisted, you will use an ArrayCollection.

Filtering functions can be used in ICollection::findBy() method. The expression is similar to using OR function (which is internally implemented as a custom function). Pass the function identifier (we recommend function's class name) and then function's arguments to apply the function.

$collection->findBy([FilterFunction::class, 'arg1', 'arg2']);

// or nested
$collection->findBy([
    ICollection::OR,
    [FilterFunction::class, 'arg1', 'arg2'],
    [AnotherFunction::class, 'arg3'],
]);

The plain functions must be applied by ICollection::applyFunction() method.

$collection->applyFunction(Function::class, 'arg1', 'arg2');

Functions are registered per Repository. To do so override Repository::createCollectionFunction($name) method to return your custom function instances.

class UsersRepository extends Nextras\Orm\Repository\Repository
{
    // ...
    public function createCollectionFunction(string $name)
    {
        if ($name === MyCustomFunction::class) {
            return new MyCustomFUnction();
        } else {
            return parent::createCollectionFunction($name);
        }
    }
}

Like Filtering Function#

Let's create “LIKE” filtering function. Filtering by like expression is a little bit different in each database engine.

PostgreSQL is case-sensitive, so you should apply lower function & functional index; These modifications are case-specific, therefore the LIKE functionality is not provided in Orm by default.

use Nette\Utils\Strings;
use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Mapper\Dbal\CustomFunctions\IQueryBuilderFilterFunction;
use Nextras\Orm\Mapper\Dbal\QueryBuilderHelper;
use Nextras\Orm\Mapper\Memory\CustomFunctions\IArrayFilterFunction;

final class LikeFilterFunction implements IArrayFilterFunction, IQueryBuilderFilterFunction
{
    public function processArrayFilter(ArrayCollectionHelper $helper, IEntity $entity, array $args): bool
    {
        // check if we received enough arguments
        assert(count($args) === 2 && is_string($args[0]) && is_string($args[1]));

        // get the value and check if it starts with the requested string
        $value = $helper->getValue($entity, $args[0])->value;
        return Strings::startsWith($value, $args[1]);
    }


    public function processQueryBuilderFilter(QueryBuilderHelper $helper, QueryBuilder $builder, array $args): array
    {
        // check if we received enough arguments
        assert(count($args) === 2 && is_string($args[0]) && is_string($args[1]));

        // convert expression to column name (also this autojoins needed tables)
        $column = $helper->processPropertyExpr($builder, $args[0])->column;
        return ['%column LIKE %like_', $column, $args[1]];
    }
}

In the example we implement LIKE (with placeholder at the end) both for Array & Dbal collection. As you can see, you can write custom SQL as well as you can filter the entity by any PHP code. The final usage is quite simple.

$users->findBy([LikeFilterFunction::class, 'name', 'Jon']);