-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Description
Bug Report
| Q | A |
|---|---|
| BC Break | no |
| Version | 2.16.2 |
Summary
When using Criteria to match entities within a collection with a specific foreign key value of a relation entity, one has to use <relation>.id (for example, so targeting the referenced key in the target entity).
This works flawless when used with an initialized Collection as that uses matching on ArrayCollection which uses ClosureExpressionVisitor.
The ClosureExpressionVisitor does actually handle <relation>.id as it checks for . within the column name:
https://github.com/doctrine/collections/blob/bdba62b690650fd5b3ae29067e464a1270fb04b6/src/Expr/ClosureExpressionVisitor.php#L43-L48
When the Collection we do want to filter with matching is not initialized, the EntityPersister is used. That is usually one of:
ManyToManyPersisterOneToManyPersister
So both of those persisters are called with their loadCriteria method, which does not have this kind of . check in the column name.
Current behavior
Using referenced entity columns as criteria does only work for initialized collections while they lead to exceptions in lazy loaded criterias.
How to reproduce
<?php
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
use Doctrine\Common\Collections\Selectable;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToMany;
enum PermissionEnum: string
{
case CRUD = 'crud';
case CRU = 'cru';
case R = 'r';
case CU = 'cu';
}
#[Entity]
class Permission
{
#[Id]
#[GeneratedValue]
public int|null $id = null;
public function __construct(
#[Column(type: 'string', enumType: PermissionEnum::class)]
public PermissionEnum $permission
) {
}
}
#[Entity]
class Group
{
#[Id]
#[GeneratedValue]
public int|null $id = null;
#[OneToMany(targetEntity: Permission::class, fetch: 'EXTRA_LAZY')]
public Collection&Selectable $permissions;
public function __construct()
{
$this->permissions = new ArrayCollection();
}
}
#[Entity]
class User
{
#[Id]
#[GeneratedValue]
public int|null $id = null;
#[OneToMany(targetEntity: Group::class, fetch: 'EXTRA_LAZY')]
public Collection&Selectable $groups;
public function __construct()
{
$this->groups = new ArrayCollection();
}
}
/** @var EntityManagerInterface $entityManager */
$entityManager = null;
$user = $entityManager->find(User::class, 1);
assert($user !== null);
/**
* Groups is now `LazyCriteriaCollection` since there is `EXTRA_LAZY` passed to the relation attribute and therefore
* nothing is loaded
*/
$groups = $user->groups;
$criteria = Criteria::create();
$expression = Criteria::expr();
$criteria->andWhere($expression->eq('permission.name', PermissionEnum::CRUD->value));
/**
* This works flawless until the `groups` collection is not initialized.
*
* But depending on the request flow of an application, *maybe* at some point *all* groups were already loaded due to
* another section printing all group names of a specific user or whatever other reason, this will fail as the entity
* persister is actually running into "unknown column permission.name for entity group" (just freely phrased, might be
* a different text tho).
*/
$hasCrudPermission = !$groups->matching($criteria)->isEmpty();Expected behavior
I would expect that criterias behave the same, especially since usually one is only annotating Collection&Selectable to avoid having concrete implementations handed over to userland code.