Collection custom functions are a powerfull 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”.
Nextras\Orm\Mapper\Dbal\CustomFunctions\IQueryBuilderFilterFunction
Nextras\Orm\Mapper\Memory\CustomFunctions\IArrayFilterFunction
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 DbalCollaction
. 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 get
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);
}
}
}
Let's create “LIKE” filtering function. Filtering by like expression is a little bit different in each database engine.
Postgres 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 checks 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']);