vendor/symfony/src/Symfony/Component/Security/Acl/Dbal/AclProvider.php
changeset 0 7f95f8617b0b
equal deleted inserted replaced
-1:000000000000 0:7f95f8617b0b
       
     1 <?php
       
     2 
       
     3 /*
       
     4  * This file is part of the Symfony package.
       
     5  *
       
     6  * (c) Fabien Potencier <fabien@symfony.com>
       
     7  *
       
     8  * For the full copyright and license information, please view the LICENSE
       
     9  * file that was distributed with this source code.
       
    10  */
       
    11 
       
    12 namespace Symfony\Component\Security\Acl\Dbal;
       
    13 
       
    14 use Doctrine\DBAL\Driver\Connection;
       
    15 use Doctrine\DBAL\Driver\Statement;
       
    16 use Symfony\Component\Security\Acl\Model\AclInterface;
       
    17 use Symfony\Component\Security\Acl\Domain\Acl;
       
    18 use Symfony\Component\Security\Acl\Domain\Entry;
       
    19 use Symfony\Component\Security\Acl\Domain\FieldEntry;
       
    20 use Symfony\Component\Security\Acl\Domain\ObjectIdentity;
       
    21 use Symfony\Component\Security\Acl\Domain\RoleSecurityIdentity;
       
    22 use Symfony\Component\Security\Acl\Domain\UserSecurityIdentity;
       
    23 use Symfony\Component\Security\Acl\Exception\AclNotFoundException;
       
    24 use Symfony\Component\Security\Acl\Exception\NotAllAclsFoundException;
       
    25 use Symfony\Component\Security\Acl\Model\AclCacheInterface;
       
    26 use Symfony\Component\Security\Acl\Model\AclProviderInterface;
       
    27 use Symfony\Component\Security\Acl\Model\ObjectIdentityInterface;
       
    28 use Symfony\Component\Security\Acl\Model\PermissionGrantingStrategyInterface;
       
    29 
       
    30 /**
       
    31  * An ACL provider implementation.
       
    32  *
       
    33  * This provider assumes that all ACLs share the same PermissionGrantingStrategy.
       
    34  *
       
    35  * @author Johannes M. Schmitt <schmittjoh@gmail.com>
       
    36  */
       
    37 class AclProvider implements AclProviderInterface
       
    38 {
       
    39     const MAX_BATCH_SIZE = 30;
       
    40 
       
    41     protected $cache;
       
    42     protected $connection;
       
    43     protected $loadedAces;
       
    44     protected $loadedAcls;
       
    45     protected $options;
       
    46     private $permissionGrantingStrategy;
       
    47 
       
    48     /**
       
    49      * Constructor.
       
    50      *
       
    51      * @param Connection                          $connection
       
    52      * @param PermissionGrantingStrategyInterface $permissionGrantingStrategy
       
    53      * @param array                               $options
       
    54      * @param AclCacheInterface                   $cache
       
    55      */
       
    56     public function __construct(Connection $connection, PermissionGrantingStrategyInterface $permissionGrantingStrategy, array $options, AclCacheInterface $cache = null)
       
    57     {
       
    58         $this->cache = $cache;
       
    59         $this->connection = $connection;
       
    60         $this->loadedAces = array();
       
    61         $this->loadedAcls = array();
       
    62         $this->options = $options;
       
    63         $this->permissionGrantingStrategy = $permissionGrantingStrategy;
       
    64     }
       
    65 
       
    66     /**
       
    67      * {@inheritDoc}
       
    68      */
       
    69     public function findChildren(ObjectIdentityInterface $parentOid, $directChildrenOnly = false)
       
    70     {
       
    71         $sql = $this->getFindChildrenSql($parentOid, $directChildrenOnly);
       
    72 
       
    73         $children = array();
       
    74         foreach ($this->connection->executeQuery($sql)->fetchAll() as $data) {
       
    75             $children[] = new ObjectIdentity($data['object_identifier'], $data['class_type']);
       
    76         }
       
    77 
       
    78         return $children;
       
    79     }
       
    80 
       
    81     /**
       
    82      * {@inheritDoc}
       
    83      */
       
    84     public function findAcl(ObjectIdentityInterface $oid, array $sids = array())
       
    85     {
       
    86         return $this->findAcls(array($oid), $sids)->offsetGet($oid);
       
    87     }
       
    88 
       
    89     /**
       
    90      * {@inheritDoc}
       
    91      */
       
    92     public function findAcls(array $oids, array $sids = array())
       
    93     {
       
    94         $result = new \SplObjectStorage();
       
    95         $currentBatch = array();
       
    96         $oidLookup = array();
       
    97 
       
    98         for ($i=0,$c=count($oids); $i<$c; $i++) {
       
    99             $oid = $oids[$i];
       
   100             $oidLookupKey = $oid->getIdentifier().$oid->getType();
       
   101             $oidLookup[$oidLookupKey] = $oid;
       
   102             $aclFound = false;
       
   103 
       
   104             // check if result already contains an ACL
       
   105             if ($result->contains($oid)) {
       
   106                 $aclFound = true;
       
   107             }
       
   108 
       
   109             // check if this ACL has already been hydrated
       
   110             if (!$aclFound && isset($this->loadedAcls[$oid->getType()][$oid->getIdentifier()])) {
       
   111                 $acl = $this->loadedAcls[$oid->getType()][$oid->getIdentifier()];
       
   112 
       
   113                 if (!$acl->isSidLoaded($sids)) {
       
   114                     // FIXME: we need to load ACEs for the missing SIDs. This is never
       
   115                     //        reached by the default implementation, since we do not
       
   116                     //        filter by SID
       
   117                     throw new \RuntimeException('This is not supported by the default implementation.');
       
   118                 } else {
       
   119                     $result->attach($oid, $acl);
       
   120                     $aclFound = true;
       
   121                 }
       
   122             }
       
   123 
       
   124             // check if we can locate the ACL in the cache
       
   125             if (!$aclFound && null !== $this->cache) {
       
   126                 $acl = $this->cache->getFromCacheByIdentity($oid);
       
   127 
       
   128                 if (null !== $acl) {
       
   129                     if ($acl->isSidLoaded($sids)) {
       
   130                         // check if any of the parents has been loaded since we need to
       
   131                         // ensure that there is only ever one ACL per object identity
       
   132                         $parentAcl = $acl->getParentAcl();
       
   133                         while (null !== $parentAcl) {
       
   134                             $parentOid = $parentAcl->getObjectIdentity();
       
   135 
       
   136                             if (isset($this->loadedAcls[$parentOid->getType()][$parentOid->getIdentifier()])) {
       
   137                                 $acl->setParentAcl($this->loadedAcls[$parentOid->getType()][$parentOid->getIdentifier()]);
       
   138                                 break;
       
   139                             } else {
       
   140                                 $this->loadedAcls[$parentOid->getType()][$parentOid->getIdentifier()] = $parentAcl;
       
   141                                 $this->updateAceIdentityMap($parentAcl);
       
   142                             }
       
   143 
       
   144                             $parentAcl = $parentAcl->getParentAcl();
       
   145                         }
       
   146 
       
   147                         $this->loadedAcls[$oid->getType()][$oid->getIdentifier()] = $acl;
       
   148                         $this->updateAceIdentityMap($acl);
       
   149                         $result->attach($oid, $acl);
       
   150                         $aclFound = true;
       
   151                     } else {
       
   152                         $this->cache->evictFromCacheByIdentity($oid);
       
   153 
       
   154                         foreach ($this->findChildren($oid) as $childOid) {
       
   155                             $this->cache->evictFromCacheByIdentity($childOid);
       
   156                         }
       
   157                     }
       
   158                 }
       
   159             }
       
   160 
       
   161             // looks like we have to load the ACL from the database
       
   162             if (!$aclFound) {
       
   163                 $currentBatch[] = $oid;
       
   164             }
       
   165 
       
   166             // Is it time to load the current batch?
       
   167             if ((self::MAX_BATCH_SIZE === count($currentBatch) || ($i + 1) === $c) && count($currentBatch) > 0) {
       
   168                 $loadedBatch = $this->lookupObjectIdentities($currentBatch, $sids, $oidLookup);
       
   169 
       
   170                 foreach ($loadedBatch as $loadedOid) {
       
   171                     $loadedAcl = $loadedBatch->offsetGet($loadedOid);
       
   172 
       
   173                     if (null !== $this->cache) {
       
   174                         $this->cache->putInCache($loadedAcl);
       
   175                     }
       
   176 
       
   177                     if (isset($oidLookup[$loadedOid->getIdentifier().$loadedOid->getType()])) {
       
   178                         $result->attach($loadedOid, $loadedAcl);
       
   179                     }
       
   180                 }
       
   181 
       
   182                 $currentBatch = array();
       
   183             }
       
   184         }
       
   185 
       
   186         // check that we got ACLs for all the identities
       
   187         foreach ($oids as $oid) {
       
   188             if (!$result->contains($oid)) {
       
   189                 if (1 === count($oids)) {
       
   190                     throw new AclNotFoundException(sprintf('No ACL found for %s.', $oid));
       
   191                 }
       
   192 
       
   193                 $partialResultException = new NotAllAclsFoundException('The provider could not find ACLs for all object identities.');
       
   194                 $partialResultException->setPartialResult($result);
       
   195 
       
   196                 throw $partialResultException;
       
   197             }
       
   198         }
       
   199 
       
   200         return $result;
       
   201     }
       
   202 
       
   203     /**
       
   204      * Constructs the query used for looking up object identities and associated
       
   205      * ACEs, and security identities.
       
   206      *
       
   207      * @param array $ancestorIds
       
   208      * @return string
       
   209      */
       
   210     protected function getLookupSql(array $ancestorIds)
       
   211     {
       
   212         // FIXME: add support for filtering by sids (right now we select all sids)
       
   213 
       
   214         $sql = <<<SELECTCLAUSE
       
   215             SELECT
       
   216                 o.id as acl_id,
       
   217                 o.object_identifier,
       
   218                 o.parent_object_identity_id,
       
   219                 o.entries_inheriting,
       
   220                 c.class_type,
       
   221                 e.id as ace_id,
       
   222                 e.object_identity_id,
       
   223                 e.field_name,
       
   224                 e.ace_order,
       
   225                 e.mask,
       
   226                 e.granting,
       
   227                 e.granting_strategy,
       
   228                 e.audit_success,
       
   229                 e.audit_failure,
       
   230                 s.username,
       
   231                 s.identifier as security_identifier
       
   232             FROM
       
   233                 {$this->options['oid_table_name']} o
       
   234             INNER JOIN {$this->options['class_table_name']} c ON c.id = o.class_id
       
   235             LEFT JOIN {$this->options['entry_table_name']} e ON (
       
   236                 e.class_id = o.class_id AND (e.object_identity_id = o.id OR {$this->connection->getDatabasePlatform()->getIsNullExpression('e.object_identity_id')})
       
   237             )
       
   238             LEFT JOIN {$this->options['sid_table_name']} s ON (
       
   239                 s.id = e.security_identity_id
       
   240             )
       
   241 
       
   242             WHERE (o.id =
       
   243 SELECTCLAUSE;
       
   244 
       
   245         $sql .= implode(' OR o.id = ', $ancestorIds).')';
       
   246 
       
   247         return $sql;
       
   248     }
       
   249 
       
   250     protected function getAncestorLookupSql(array $batch)
       
   251     {
       
   252         $sql = <<<SELECTCLAUSE
       
   253             SELECT a.ancestor_id
       
   254             FROM
       
   255                 {$this->options['oid_table_name']} o
       
   256             INNER JOIN {$this->options['class_table_name']} c ON c.id = o.class_id
       
   257             INNER JOIN {$this->options['oid_ancestors_table_name']} a ON a.object_identity_id = o.id
       
   258                WHERE (
       
   259 SELECTCLAUSE;
       
   260 
       
   261         $where = '(o.object_identifier = %s AND c.class_type = %s)';
       
   262         for ($i=0,$c=count($batch); $i<$c; $i++) {
       
   263             $sql .= sprintf(
       
   264                 $where,
       
   265                 $this->connection->quote($batch[$i]->getIdentifier()),
       
   266                 $this->connection->quote($batch[$i]->getType())
       
   267             );
       
   268 
       
   269             if ($i+1 < $c) {
       
   270                 $sql .= ' OR ';
       
   271             }
       
   272         }
       
   273 
       
   274         $sql .= ')';
       
   275 
       
   276         return $sql;
       
   277     }
       
   278 
       
   279     /**
       
   280      * Constructs the SQL for retrieving child object identities for the given
       
   281      * object identities.
       
   282      *
       
   283      * @param ObjectIdentityInterface $oid
       
   284      * @param Boolean                 $directChildrenOnly
       
   285      * @return string
       
   286      */
       
   287     protected function getFindChildrenSql(ObjectIdentityInterface $oid, $directChildrenOnly)
       
   288     {
       
   289         if (false === $directChildrenOnly) {
       
   290             $query = <<<FINDCHILDREN
       
   291                 SELECT o.object_identifier, c.class_type
       
   292                 FROM
       
   293                     {$this->options['oid_table_name']} as o
       
   294                 INNER JOIN {$this->options['class_table_name']} as c ON c.id = o.class_id
       
   295                 INNER JOIN {$this->options['oid_ancestors_table_name']} as a ON a.object_identity_id = o.id
       
   296                 WHERE
       
   297                     a.ancestor_id = %d AND a.object_identity_id != a.ancestor_id
       
   298 FINDCHILDREN;
       
   299         } else {
       
   300             $query = <<<FINDCHILDREN
       
   301                 SELECT o.object_identifier, c.class_type
       
   302                 FROM {$this->options['oid_table_name']} as o
       
   303                 INNER JOIN {$this->options['class_table_name']} as c ON c.id = o.class_id
       
   304                 WHERE o.parent_object_identity_id = %d
       
   305 FINDCHILDREN;
       
   306         }
       
   307 
       
   308         return sprintf($query, $this->retrieveObjectIdentityPrimaryKey($oid));
       
   309     }
       
   310 
       
   311     /**
       
   312      * Constructs the SQL for retrieving the primary key of the given object
       
   313      * identity.
       
   314      *
       
   315      * @param ObjectIdentityInterface $oid
       
   316      * @return string
       
   317      */
       
   318     protected function getSelectObjectIdentityIdSql(ObjectIdentityInterface $oid)
       
   319     {
       
   320         $query = <<<QUERY
       
   321             SELECT o.id
       
   322             FROM %s o
       
   323             INNER JOIN %s c ON c.id = o.class_id
       
   324             WHERE o.object_identifier = %s AND c.class_type = %s
       
   325 QUERY;
       
   326 
       
   327         return sprintf(
       
   328             $query,
       
   329             $this->options['oid_table_name'],
       
   330             $this->options['class_table_name'],
       
   331             $this->connection->quote($oid->getIdentifier()),
       
   332             $this->connection->quote($oid->getType())
       
   333         );
       
   334     }
       
   335 
       
   336     /**
       
   337      * Returns the primary key of the passed object identity.
       
   338      *
       
   339      * @param ObjectIdentityInterface $oid
       
   340      * @return integer
       
   341      */
       
   342     protected final function retrieveObjectIdentityPrimaryKey(ObjectIdentityInterface $oid)
       
   343     {
       
   344         return $this->connection->executeQuery($this->getSelectObjectIdentityIdSql($oid))->fetchColumn();
       
   345     }
       
   346 
       
   347     /**
       
   348      * This method is called when an ACL instance is retrieved from the cache.
       
   349      *
       
   350      * @param AclInterface $acl
       
   351      * @return void
       
   352      */
       
   353     private function updateAceIdentityMap(AclInterface $acl)
       
   354     {
       
   355         foreach (array('classAces', 'classFieldAces', 'objectAces', 'objectFieldAces') as $property) {
       
   356             $reflection = new \ReflectionProperty($acl, $property);
       
   357             $reflection->setAccessible(true);
       
   358             $value = $reflection->getValue($acl);
       
   359 
       
   360             if ('classAces' === $property || 'objectAces' === $property) {
       
   361                 $this->doUpdateAceIdentityMap($value);
       
   362             } else {
       
   363                 foreach ($value as $field => $aces) {
       
   364                     $this->doUpdateAceIdentityMap($value[$field]);
       
   365                 }
       
   366             }
       
   367 
       
   368             $reflection->setValue($acl, $value);
       
   369             $reflection->setAccessible(false);
       
   370         }
       
   371     }
       
   372 
       
   373     /**
       
   374      * Retrieves all the ids which need to be queried from the database
       
   375      * including the ids of parent ACLs.
       
   376      *
       
   377      * @param array $batch
       
   378      * @return array
       
   379      */
       
   380     private function getAncestorIds(array $batch)
       
   381     {
       
   382         $sql = $this->getAncestorLookupSql($batch);
       
   383 
       
   384         $ancestorIds = array();
       
   385         foreach ($this->connection->executeQuery($sql)->fetchAll() as $data) {
       
   386             // FIXME: skip ancestors which are cached
       
   387 
       
   388             $ancestorIds[] = $data['ancestor_id'];
       
   389         }
       
   390 
       
   391         return $ancestorIds;
       
   392     }
       
   393 
       
   394     /**
       
   395      * Does either overwrite the passed ACE, or saves it in the global identity
       
   396      * map to ensure every ACE only gets instantiated once.
       
   397      *
       
   398      * @param array $aces
       
   399      * @return void
       
   400      */
       
   401     private function doUpdateAceIdentityMap(array &$aces)
       
   402     {
       
   403         foreach ($aces as $index => $ace) {
       
   404             if (isset($this->loadedAces[$ace->getId()])) {
       
   405                 $aces[$index] = $this->loadedAces[$ace->getId()];
       
   406             } else {
       
   407                 $this->loadedAces[$ace->getId()] = $ace;
       
   408             }
       
   409         }
       
   410     }
       
   411 
       
   412     /**
       
   413      * This method is called for object identities which could not be retrieved
       
   414      * from the cache, and for which thus a database query is required.
       
   415      *
       
   416      * @param array $batch
       
   417      * @param array $sids
       
   418      * @param array $oidLookup
       
   419      *
       
   420      * @return \SplObjectStorage mapping object identities to ACL instances
       
   421      */
       
   422     private function lookupObjectIdentities(array $batch, array $sids, array $oidLookup)
       
   423     {
       
   424         $ancestorIds = $this->getAncestorIds($batch);
       
   425         if (!$ancestorIds) {
       
   426             throw new AclNotFoundException('There is no ACL for the given object identity.');
       
   427         }
       
   428 
       
   429         $sql = $this->getLookupSql($ancestorIds);
       
   430         $stmt = $this->connection->executeQuery($sql);
       
   431 
       
   432         return $this->hydrateObjectIdentities($stmt, $oidLookup, $sids);
       
   433     }
       
   434 
       
   435     /**
       
   436      * This method is called to hydrate ACLs and ACEs.
       
   437      *
       
   438      * This method was designed for performance; thus, a lot of code has been
       
   439      * inlined at the cost of readability, and maintainability.
       
   440      *
       
   441      * Keep in mind that changes to this method might severely reduce the
       
   442      * performance of the entire ACL system.
       
   443      *
       
   444      * @param Statement $stmt
       
   445      * @param array     $oidLookup
       
   446      * @param array     $sids
       
   447      * @throws \RuntimeException
       
   448      * @return \SplObjectStorage
       
   449      */
       
   450     private function hydrateObjectIdentities(Statement $stmt, array $oidLookup, array $sids) {
       
   451         $parentIdToFill = new \SplObjectStorage();
       
   452         $acls = $aces = $emptyArray = array();
       
   453         $oidCache = $oidLookup;
       
   454         $result = new \SplObjectStorage();
       
   455         $loadedAces =& $this->loadedAces;
       
   456         $loadedAcls =& $this->loadedAcls;
       
   457         $permissionGrantingStrategy = $this->permissionGrantingStrategy;
       
   458 
       
   459         // we need these to set protected properties on hydrated objects
       
   460         $aclReflection = new \ReflectionClass('Symfony\Component\Security\Acl\Domain\Acl');
       
   461         $aclClassAcesProperty = $aclReflection->getProperty('classAces');
       
   462         $aclClassAcesProperty->setAccessible(true);
       
   463         $aclClassFieldAcesProperty = $aclReflection->getProperty('classFieldAces');
       
   464         $aclClassFieldAcesProperty->setAccessible(true);
       
   465         $aclObjectAcesProperty = $aclReflection->getProperty('objectAces');
       
   466         $aclObjectAcesProperty->setAccessible(true);
       
   467         $aclObjectFieldAcesProperty = $aclReflection->getProperty('objectFieldAces');
       
   468         $aclObjectFieldAcesProperty->setAccessible(true);
       
   469         $aclParentAclProperty = $aclReflection->getProperty('parentAcl');
       
   470         $aclParentAclProperty->setAccessible(true);
       
   471 
       
   472         // fetchAll() consumes more memory than consecutive calls to fetch(),
       
   473         // but it is faster
       
   474         foreach ($stmt->fetchAll(\PDO::FETCH_NUM) as $data) {
       
   475             list($aclId,
       
   476                  $objectIdentifier,
       
   477                  $parentObjectIdentityId,
       
   478                  $entriesInheriting,
       
   479                  $classType,
       
   480                  $aceId,
       
   481                  $objectIdentityId,
       
   482                  $fieldName,
       
   483                  $aceOrder,
       
   484                  $mask,
       
   485                  $granting,
       
   486                  $grantingStrategy,
       
   487                  $auditSuccess,
       
   488                  $auditFailure,
       
   489                  $username,
       
   490                  $securityIdentifier) = $data;
       
   491 
       
   492             // has the ACL been hydrated during this hydration cycle?
       
   493             if (isset($acls[$aclId])) {
       
   494                 $acl = $acls[$aclId];
       
   495             // has the ACL been hydrated during any previous cycle, or was possibly loaded
       
   496             // from cache?
       
   497             } else if (isset($loadedAcls[$classType][$objectIdentifier])) {
       
   498                 $acl = $loadedAcls[$classType][$objectIdentifier];
       
   499 
       
   500                 // keep reference in local array (saves us some hash calculations)
       
   501                 $acls[$aclId] = $acl;
       
   502 
       
   503                 // attach ACL to the result set; even though we do not enforce that every
       
   504                 // object identity has only one instance, we must make sure to maintain
       
   505                 // referential equality with the oids passed to findAcls()
       
   506                 if (!isset($oidCache[$objectIdentifier.$classType])) {
       
   507                     $oidCache[$objectIdentifier.$classType] = $acl->getObjectIdentity();
       
   508                 }
       
   509                 $result->attach($oidCache[$objectIdentifier.$classType], $acl);
       
   510             // so, this hasn't been hydrated yet
       
   511             } else {
       
   512                 // create object identity if we haven't done so yet
       
   513                 $oidLookupKey = $objectIdentifier.$classType;
       
   514                 if (!isset($oidCache[$oidLookupKey])) {
       
   515                     $oidCache[$oidLookupKey] = new ObjectIdentity($objectIdentifier, $classType);
       
   516                 }
       
   517 
       
   518                 $acl = new Acl((integer) $aclId, $oidCache[$oidLookupKey], $permissionGrantingStrategy, $emptyArray, !!$entriesInheriting);
       
   519 
       
   520                 // keep a local, and global reference to this ACL
       
   521                 $loadedAcls[$classType][$objectIdentifier] = $acl;
       
   522                 $acls[$aclId] = $acl;
       
   523 
       
   524                 // try to fill in parent ACL, or defer until all ACLs have been hydrated
       
   525                 if (null !== $parentObjectIdentityId) {
       
   526                     if (isset($acls[$parentObjectIdentityId])) {
       
   527                         $aclParentAclProperty->setValue($acl, $acls[$parentObjectIdentityId]);
       
   528                     } else {
       
   529                         $parentIdToFill->attach($acl, $parentObjectIdentityId);
       
   530                     }
       
   531                 }
       
   532 
       
   533                 $result->attach($oidCache[$oidLookupKey], $acl);
       
   534             }
       
   535 
       
   536             // check if this row contains an ACE record
       
   537             if (null !== $aceId) {
       
   538                 // have we already hydrated ACEs for this ACL?
       
   539                 if (!isset($aces[$aclId])) {
       
   540                     $aces[$aclId] = array($emptyArray, $emptyArray, $emptyArray, $emptyArray);
       
   541                 }
       
   542 
       
   543                 // has this ACE already been hydrated during a previous cycle, or
       
   544                 // possible been loaded from cache?
       
   545                 // It is important to only ever have one ACE instance per actual row since
       
   546                 // some ACEs are shared between ACL instances
       
   547                 if (!isset($loadedAces[$aceId])) {
       
   548                     if (!isset($sids[$key = ($username?'1':'0').$securityIdentifier])) {
       
   549                         if ($username) {
       
   550                             $sids[$key] = new UserSecurityIdentity(
       
   551                                 substr($securityIdentifier, 1 + $pos = strpos($securityIdentifier, '-')),
       
   552                                 substr($securityIdentifier, 0, $pos)
       
   553                             );
       
   554                         } else {
       
   555                             $sids[$key] = new RoleSecurityIdentity($securityIdentifier);
       
   556                         }
       
   557                     }
       
   558 
       
   559                     if (null === $fieldName) {
       
   560                         $loadedAces[$aceId] = new Entry((integer) $aceId, $acl, $sids[$key], $grantingStrategy, (integer) $mask, !!$granting, !!$auditFailure, !!$auditSuccess);
       
   561                     } else {
       
   562                         $loadedAces[$aceId] = new FieldEntry((integer) $aceId, $acl, $fieldName, $sids[$key], $grantingStrategy, (integer) $mask, !!$granting, !!$auditFailure, !!$auditSuccess);
       
   563                     }
       
   564                 }
       
   565                 $ace = $loadedAces[$aceId];
       
   566 
       
   567                 // assign ACE to the correct property
       
   568                 if (null === $objectIdentityId) {
       
   569                     if (null === $fieldName) {
       
   570                         $aces[$aclId][0][$aceOrder] = $ace;
       
   571                     } else {
       
   572                         $aces[$aclId][1][$fieldName][$aceOrder] = $ace;
       
   573                     }
       
   574                 } else {
       
   575                     if (null === $fieldName) {
       
   576                         $aces[$aclId][2][$aceOrder] = $ace;
       
   577                     } else {
       
   578                         $aces[$aclId][3][$fieldName][$aceOrder] = $ace;
       
   579                     }
       
   580                 }
       
   581             }
       
   582         }
       
   583 
       
   584         // We do not sort on database level since we only want certain subsets to be sorted,
       
   585         // and we are going to read the entire result set anyway.
       
   586         // Sorting on DB level increases query time by an order of magnitude while it is
       
   587         // almost negligible when we use PHPs array sort functions.
       
   588         foreach ($aces as $aclId => $aceData) {
       
   589             $acl = $acls[$aclId];
       
   590 
       
   591             ksort($aceData[0]);
       
   592             $aclClassAcesProperty->setValue($acl, $aceData[0]);
       
   593 
       
   594             foreach (array_keys($aceData[1]) as $fieldName) {
       
   595                 ksort($aceData[1][$fieldName]);
       
   596             }
       
   597             $aclClassFieldAcesProperty->setValue($acl, $aceData[1]);
       
   598 
       
   599             ksort($aceData[2]);
       
   600             $aclObjectAcesProperty->setValue($acl, $aceData[2]);
       
   601 
       
   602             foreach (array_keys($aceData[3]) as $fieldName) {
       
   603                 ksort($aceData[3][$fieldName]);
       
   604             }
       
   605             $aclObjectFieldAcesProperty->setValue($acl, $aceData[3]);
       
   606         }
       
   607 
       
   608         // fill-in parent ACLs where this hasn't been done yet cause the parent ACL was not
       
   609         // yet available
       
   610         $processed = 0;
       
   611         foreach ($parentIdToFill as $acl) {
       
   612             $parentId = $parentIdToFill->offsetGet($acl);
       
   613 
       
   614             // let's see if we have already hydrated this
       
   615             if (isset($acls[$parentId])) {
       
   616                 $aclParentAclProperty->setValue($acl, $acls[$parentId]);
       
   617                 $processed += 1;
       
   618 
       
   619                 continue;
       
   620             }
       
   621         }
       
   622 
       
   623         // reset reflection changes
       
   624         $aclClassAcesProperty->setAccessible(false);
       
   625         $aclClassFieldAcesProperty->setAccessible(false);
       
   626         $aclObjectAcesProperty->setAccessible(false);
       
   627         $aclObjectFieldAcesProperty->setAccessible(false);
       
   628         $aclParentAclProperty->setAccessible(false);
       
   629 
       
   630         // this should never be true if the database integrity hasn't been compromised
       
   631         if ($processed < count($parentIdToFill)) {
       
   632             throw new \RuntimeException('Not all parent ids were populated. This implies an integrity problem.');
       
   633         }
       
   634 
       
   635         return $result;
       
   636     }
       
   637 }