Перемещение по сущностям неосуществимо, если у вас есть пара тысячных сущностей - оно будет продолжать работать медленнее и потреблять больше памяти, заставляя вас использовать возможности пакетной обработки доктрин, тем самым делая ваш код более сложным (и неэффективным, потому что в конце концовдля выполнения запроса нужны только идентификаторы, а не целые acl / сущности в памяти)
Чтобы решить эту проблему, мы заменили службу acl.provider своей собственной, а в эту службу добавили методпрямой запрос к базе данных:
private function _getEntitiesIdsMatchingRoleMaskSql($className, array $roles, $requiredMask)
{
$rolesSql = array();
foreach($roles as $role) {
$rolesSql[] = 's.identifier = ' . $this->connection->quote($role);
}
$rolesSql = '(' . implode(' OR ', $rolesSql) . ')';
$sql = <<<SELECTCLAUSE
SELECT
oid.object_identifier
FROM
{$this->options['entry_table_name']} e
JOIN
{$this->options['oid_table_name']} oid ON (
oid.class_id = e.class_id
)
JOIN {$this->options['sid_table_name']} s ON (
s.id = e.security_identity_id
)
JOIN {$this->options['class_table_nambe']} class ON (
class.id = e.class_id
)
WHERE
{$this->connection->getDatabasePlatform()->getIsNotNullExpression('e.object_identity_id')} AND
(e.mask & %d) AND
$rolesSql AND
class.class_type = %s
GROUP BY
oid.object_identifier
SELECTCLAUSE;
return sprintf(
$sql,
$requiredMask,
$this->connection->quote($role),
$this->connection->quote($className)
);
}
Затем вызывается этот метод из фактического открытого метода, который получает идентификаторы сущностей:
/**
* Get the entities Ids for the className that match the given role & mask
*
* @param string $className
* @param string $roles
* @param integer $mask
* @param bool $asString - Return a comma-delimited string with the ids instead of an array
*
* @return bool|array|string - True if its allowed to all entities, false if its not
* allowed, array or string depending on $asString parameter.
*/
public function getAllowedEntitiesIds($className, array $roles, $mask, $asString = true)
{
// Check for class-level global permission (its a very similar query to the one
// posted above
// If there is a class-level grant permission, then do not query object-level
if ($this->_maskMatchesRoleForClass($className, $roles, $requiredMask)) {
return true;
}
// Query the database for ACE's matching the mask for the given roles
$sql = $this->_getEntitiesIdsMatchingRoleMaskSql($className, $roles, $mask);
$ids = $this->connection->executeQuery($sql)->fetchAll(\PDO::FETCH_COLUMN);
// No ACEs found
if (!count($ids)) {
return false;
}
if ($asString) {
return implode(',', $ids);
}
return $ids;
}
Таким образом, теперь мы можем использовать код для добавленияфильтры к DQL-запросам:
// Some action in a controller or form handler...
// This service is our own aclProvider version with the methods mentioned above
$aclProvider = $this->get('security.acl.provider');
$ids = $aclProvider->getAllowedEntitiesIds('SomeEntityClass', array('role1'), MaskBuilder::VIEW, true);
if (is_string($ids)) {
$queryBuilder->andWhere("entity.id IN ($ids)");
}
// No ACL found: deny all
elseif ($ids===false) {
$queryBuilder->andWhere("entity.id = 0")
}
elseif ($ids===true) {
// Global-class permission: allow all
}
// Run query...etc
Недостатки: Эти методы необходимо усовершенствовать, чтобы учесть сложности наследования и стратегий ACL, но для простых случаев использования он работает нормально.Также должен быть реализован кэш, чтобы избежать повторяющегося двойного запроса (один с уровнем класса, другой с уровнем objetc)