数千のエンティティがある場合、エンティティを反復処理することはできません。これにより、速度が低下し、メモリの消費量が増え、ドクトリンバッチ機能を使用せざるを得なくなり、コードがより複雑になります(結局、必要なのはクエリを実行するためのID-メモリ内の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)
);
}
次に、エンティティIDを取得する実際のパブリックメソッドからこのメソッドを呼び出します。
/**
* 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の継承と戦略の複雑さを考慮して改善する必要がありますが、単純なユースケースでは問題なく機能します。また、繰り返しの二重クエリを回避するためにキャッシュを実装する必要があります(1つはクラスレベル、もう1つはobjetcレベル)