init without trunk
This commit is contained in:
parent
ed24ac4994
commit
bb809e7233
14652 changed files with 177862 additions and 94817 deletions
|
|
@ -0,0 +1,199 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Document\MongoDB\Repository;
|
||||
|
||||
use Doctrine\ODM\MongoDB\DocumentRepository;
|
||||
use Doctrine\ODM\MongoDB\DocumentManager;
|
||||
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
|
||||
use Doctrine\ODM\MongoDB\UnitOfWork;
|
||||
use Gedmo\Tree\RepositoryUtils;
|
||||
use Gedmo\Tree\RepositoryUtilsInterface;
|
||||
use Gedmo\Tree\RepositoryInterface;
|
||||
|
||||
abstract class AbstractTreeRepository extends DocumentRepository implements RepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Tree listener on event manager
|
||||
*
|
||||
* @var AbstractTreeListener
|
||||
*/
|
||||
protected $listener = null;
|
||||
|
||||
/**
|
||||
* Repository utils
|
||||
*/
|
||||
protected $repoUtils = null;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(DocumentManager $em, UnitOfWork $uow, ClassMetadata $class)
|
||||
{
|
||||
parent::__construct($em, $uow, $class);
|
||||
$treeListener = null;
|
||||
foreach ($em->getEventManager()->getListeners() as $listeners) {
|
||||
foreach ($listeners as $listener) {
|
||||
if ($listener instanceof \Gedmo\Tree\TreeListener) {
|
||||
$treeListener = $listener;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($treeListener) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($treeListener)) {
|
||||
throw new \Gedmo\Exception\InvalidMappingException('This repository can be attached only to ODM MongoDB tree listener');
|
||||
}
|
||||
|
||||
$this->listener = $treeListener;
|
||||
if (!$this->validate()) {
|
||||
throw new \Gedmo\Exception\InvalidMappingException('This repository cannot be used for tree type: '.$treeListener->getStrategy($em, $class->name)->getName());
|
||||
}
|
||||
|
||||
$this->repoUtils = new RepositoryUtils($this->dm, $this->getClassMetadata(), $this->listener, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the RepositoryUtilsInterface instance
|
||||
*
|
||||
* @param \Gedmo\Tree\RepositoryUtilsInterface $repoUtils
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setRepoUtils(RepositoryUtilsInterface $repoUtils)
|
||||
{
|
||||
$this->repoUtils = $repoUtils;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RepositoryUtilsInterface instance
|
||||
*
|
||||
* @return \Gedmo\Tree\RepositoryUtilsInterface|null
|
||||
*/
|
||||
public function getRepoUtils()
|
||||
{
|
||||
return $this->repoUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function childrenHierarchy($node = null, $direct = false, array $options = array(), $includeNode = false)
|
||||
{
|
||||
return $this->repoUtils->childrenHierarchy($node, $direct, $options, $includeNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function buildTree(array $nodes, array $options = array())
|
||||
{
|
||||
return $this->repoUtils->buildTree($nodes, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Gedmo\Tree\RepositoryUtilsInterface::setChildrenIndex
|
||||
*/
|
||||
public function setChildrenIndex($childrenIndex)
|
||||
{
|
||||
$this->repoUtils->setChildrenIndex($childrenIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Gedmo\Tree\RepositoryUtilsInterface::getChildrenIndex
|
||||
*/
|
||||
public function getChildrenIndex()
|
||||
{
|
||||
return $this->repoUtils->getChildrenIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function buildTreeArray(array $nodes)
|
||||
{
|
||||
return $this->repoUtils->buildTreeArray($nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current repository is right
|
||||
* for currently used tree strategy
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function validate();
|
||||
|
||||
/**
|
||||
* Get all root nodes query builder
|
||||
*
|
||||
* @param string - Sort by field
|
||||
* @param string - Sort direction ("asc" or "desc")
|
||||
*
|
||||
* @return \Doctrine\MongoDB\Query\Builder - QueryBuilder object
|
||||
*/
|
||||
abstract public function getRootNodesQueryBuilder($sortByField = null, $direction = 'asc');
|
||||
|
||||
/**
|
||||
* Get all root nodes query
|
||||
*
|
||||
* @param string - Sort by field
|
||||
* @param string - Sort direction ("asc" or "desc")
|
||||
*
|
||||
* @return \Doctrine\MongoDB\Query\Query - Query object
|
||||
*/
|
||||
abstract public function getRootNodesQuery($sortByField = null, $direction = 'asc');
|
||||
|
||||
/**
|
||||
* Returns a QueryBuilder configured to return an array of nodes suitable for buildTree method
|
||||
*
|
||||
* @param object $node - Root node
|
||||
* @param bool $direct - Obtain direct children?
|
||||
* @param array $options - Options
|
||||
* @param boolean $includeNode - Include node in results?
|
||||
*
|
||||
* @return \Doctrine\MongoDB\Query\Builder - QueryBuilder object
|
||||
*/
|
||||
abstract public function getNodesHierarchyQueryBuilder($node = null, $direct = false, array $options = array(), $includeNode = false);
|
||||
|
||||
/**
|
||||
* Returns a Query configured to return an array of nodes suitable for buildTree method
|
||||
*
|
||||
* @param object $node - Root node
|
||||
* @param bool $direct - Obtain direct children?
|
||||
* @param array $options - Options
|
||||
* @param boolean $includeNode - Include node in results?
|
||||
*
|
||||
* @return \Doctrine\MongoDB\Query\Query - Query object
|
||||
*/
|
||||
abstract public function getNodesHierarchyQuery($node = null, $direct = false, array $options = array(), $includeNode = false);
|
||||
|
||||
/**
|
||||
* Get list of children followed by given $node. This returns a QueryBuilder object
|
||||
*
|
||||
* @param object $node - if null, all tree nodes will be taken
|
||||
* @param boolean $direct - true to take only direct children
|
||||
* @param string $sortByField - field name to sort by
|
||||
* @param string $direction - sort direction : "ASC" or "DESC"
|
||||
* @param bool $includeNode - Include the root node in results?
|
||||
*
|
||||
* @return \Doctrine\MongoDB\Query\Builder - QueryBuilder object
|
||||
*/
|
||||
abstract public function getChildrenQueryBuilder($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false);
|
||||
|
||||
/**
|
||||
* Get list of children followed by given $node. This returns a Query
|
||||
*
|
||||
* @param object $node - if null, all tree nodes will be taken
|
||||
* @param boolean $direct - true to take only direct children
|
||||
* @param string $sortByField - field name to sort by
|
||||
* @param string $direction - sort direction : "ASC" or "DESC"
|
||||
* @param bool $includeNode - Include the root node in results?
|
||||
*
|
||||
* @return \Doctrine\MongoDB\Query\Query - Query object
|
||||
*/
|
||||
abstract public function getChildrenQuery($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false);
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Document\MongoDB\Repository;
|
||||
|
||||
use Gedmo\Exception\InvalidArgumentException;
|
||||
use Gedmo\Tree\Strategy;
|
||||
use Gedmo\Tool\Wrapper\MongoDocumentWrapper;
|
||||
|
||||
/**
|
||||
* The MaterializedPathRepository has some useful functions
|
||||
* to interact with MaterializedPath tree. Repository uses
|
||||
* the strategy used by listener
|
||||
*
|
||||
* @author Gustavo Falco <comfortablynumb84@gmail.com>
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class MaterializedPathRepository extends AbstractTreeRepository
|
||||
{
|
||||
/**
|
||||
* Get tree query builder
|
||||
*
|
||||
* @param object $rootNode
|
||||
*
|
||||
* @return \Doctrine\ODM\MongoDB\Query\Builder
|
||||
*/
|
||||
public function getTreeQueryBuilder($rootNode = null)
|
||||
{
|
||||
return $this->getChildrenQueryBuilder($rootNode, false, null, 'asc', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tree query
|
||||
*
|
||||
* @param object $rootNode
|
||||
*
|
||||
* @return \Doctrine\ODM\MongoDB\Query\Query
|
||||
*/
|
||||
public function getTreeQuery($rootNode = null)
|
||||
{
|
||||
return $this->getTreeQueryBuilder($rootNode)->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tree
|
||||
*
|
||||
* @param object $rootNode
|
||||
*
|
||||
* @return \Doctrine\ODM\MongoDB\Cursor
|
||||
*/
|
||||
public function getTree($rootNode = null)
|
||||
{
|
||||
return $this->getTreeQuery($rootNode)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getRootNodesQueryBuilder($sortByField = null, $direction = 'asc')
|
||||
{
|
||||
return $this->getChildrenQueryBuilder(null, true, $sortByField, $direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getRootNodesQuery($sortByField = null, $direction = 'asc')
|
||||
{
|
||||
return $this->getRootNodesQueryBuilder($sortByField, $direction)->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getRootNodes($sortByField = null, $direction = 'asc')
|
||||
{
|
||||
return $this->getRootNodesQuery($sortByField, $direction)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function childCount($node = null, $direct = false)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
|
||||
if (is_object($node)) {
|
||||
if (!($node instanceof $meta->name)) {
|
||||
throw new InvalidArgumentException("Node is not related to this repository");
|
||||
}
|
||||
|
||||
$wrapped = new MongoDocumentWrapper($node, $this->dm);
|
||||
|
||||
if (!$wrapped->hasValidIdentifier()) {
|
||||
throw new InvalidArgumentException("Node is not managed by UnitOfWork");
|
||||
}
|
||||
}
|
||||
|
||||
$qb = $this->getChildrenQueryBuilder($node, $direct);
|
||||
|
||||
$qb->count();
|
||||
|
||||
return (int) $qb->getQuery()->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getChildrenQueryBuilder($node = null, $direct = false, $sortByField = null, $direction = 'asc', $includeNode = false)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
$config = $this->listener->getConfiguration($this->dm, $meta->name);
|
||||
$separator = preg_quote($config['path_separator']);
|
||||
$qb = $this->dm->createQueryBuilder()
|
||||
->find($meta->name);
|
||||
$regex = false;
|
||||
|
||||
if (is_object($node) && $node instanceof $meta->name) {
|
||||
$node = new MongoDocumentWrapper($node, $this->dm);
|
||||
$nodePath = preg_quote($node->getPropertyValue($config['path']));
|
||||
|
||||
if ($direct) {
|
||||
$regex = sprintf('/^%s([^%s]+%s)'.($includeNode ? '?' : '').'$/',
|
||||
$nodePath,
|
||||
$separator,
|
||||
$separator);
|
||||
} else {
|
||||
$regex = sprintf('/^%s(.+)'.($includeNode ? '?' : '').'/',
|
||||
$nodePath);
|
||||
}
|
||||
} elseif ($direct) {
|
||||
$regex = sprintf('/^([^%s]+)'.($includeNode ? '?' : '').'%s$/',
|
||||
$separator,
|
||||
$separator);
|
||||
}
|
||||
|
||||
if ($regex) {
|
||||
$qb->field($config['path'])->equals(new \MongoRegex($regex));
|
||||
}
|
||||
|
||||
$qb->sort(is_null($sortByField) ? $config['path'] : $sortByField, $direction === 'asc' ? 'asc' : 'desc');
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* G{@inheritDoc}
|
||||
*/
|
||||
public function getChildrenQuery($node = null, $direct = false, $sortByField = null, $direction = 'asc', $includeNode = false)
|
||||
{
|
||||
return $this->getChildrenQueryBuilder($node, $direct, $sortByField, $direction, $includeNode)->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getChildren($node = null, $direct = false, $sortByField = null, $direction = 'asc', $includeNode = false)
|
||||
{
|
||||
return $this->getChildrenQuery($node, $direct, $sortByField, $direction, $includeNode)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getNodesHierarchyQueryBuilder($node = null, $direct = false, array $options = array(), $includeNode = false)
|
||||
{
|
||||
$sortBy = array(
|
||||
'field' => null,
|
||||
'dir' => 'asc',
|
||||
);
|
||||
|
||||
if (isset($options['childSort'])) {
|
||||
$sortBy = array_merge($sortBy, $options['childSort']);
|
||||
}
|
||||
|
||||
return $this->getChildrenQueryBuilder($node, $direct, $sortBy['field'], $sortBy['dir'], $includeNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getNodesHierarchyQuery($node = null, $direct = false, array $options = array(), $includeNode = false)
|
||||
{
|
||||
return $this->getNodesHierarchyQueryBuilder($node, $direct, $options, $includeNode)->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getNodesHierarchy($node = null, $direct = false, array $options = array(), $includeNode = false)
|
||||
{
|
||||
$query = $this->getNodesHierarchyQuery($node, $direct, $options, $includeNode);
|
||||
$query->setHydrate(false);
|
||||
|
||||
return $query->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function validate()
|
||||
{
|
||||
return $this->listener->getStrategy($this->dm, $this->getClassMetadata()->name)->getName() === Strategy::MATERIALIZED_PATH;
|
||||
}
|
||||
}
|
||||
117
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity/MappedSuperclass/AbstractClosure.php
vendored
Normal file
117
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity/MappedSuperclass/AbstractClosure.php
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Entity\MappedSuperclass;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* @ORM\MappedSuperclass
|
||||
*/
|
||||
abstract class AbstractClosure
|
||||
{
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\GeneratedValue(strategy="IDENTITY")
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* Mapped by listener
|
||||
* Visibility must be protected
|
||||
*/
|
||||
protected $ancestor;
|
||||
|
||||
/**
|
||||
* Mapped by listener
|
||||
* Visibility must be protected
|
||||
*/
|
||||
protected $descendant;
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="integer")
|
||||
*/
|
||||
protected $depth;
|
||||
|
||||
/**
|
||||
* Get id
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ancestor
|
||||
*
|
||||
* @param object $ancestor
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setAncestor($ancestor)
|
||||
{
|
||||
$this->ancestor = $ancestor;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ancestor
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function getAncestor()
|
||||
{
|
||||
return $this->ancestor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set descendant
|
||||
*
|
||||
* @param object $descendant
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setDescendant($descendant)
|
||||
{
|
||||
$this->descendant = $descendant;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get descendant
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function getDescendant()
|
||||
{
|
||||
return $this->descendant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set depth
|
||||
*
|
||||
* @param integer $depth
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setDepth($depth)
|
||||
{
|
||||
$this->depth = $depth;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get depth
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getDepth()
|
||||
{
|
||||
return $this->depth;
|
||||
}
|
||||
}
|
||||
248
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity/Repository/AbstractTreeRepository.php
vendored
Normal file
248
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity/Repository/AbstractTreeRepository.php
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Entity\Repository;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Gedmo\Tool\Wrapper\EntityWrapper;
|
||||
use Gedmo\Tree\RepositoryUtils;
|
||||
use Gedmo\Tree\RepositoryUtilsInterface;
|
||||
use Gedmo\Tree\RepositoryInterface;
|
||||
use Gedmo\Exception\InvalidArgumentException;
|
||||
use Gedmo\Tree\TreeListener;
|
||||
|
||||
abstract class AbstractTreeRepository extends EntityRepository implements RepositoryInterface
|
||||
{
|
||||
/**
|
||||
* Tree listener on event manager
|
||||
*
|
||||
* @var TreeListener
|
||||
*/
|
||||
protected $listener = null;
|
||||
|
||||
/**
|
||||
* Repository utils
|
||||
*/
|
||||
protected $repoUtils = null;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $em, ClassMetadata $class)
|
||||
{
|
||||
parent::__construct($em, $class);
|
||||
$treeListener = null;
|
||||
foreach ($em->getEventManager()->getListeners() as $listeners) {
|
||||
foreach ($listeners as $listener) {
|
||||
if ($listener instanceof TreeListener) {
|
||||
$treeListener = $listener;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($treeListener) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_null($treeListener)) {
|
||||
throw new \Gedmo\Exception\InvalidMappingException('Tree listener was not found on your entity manager, it must be hooked into the event manager');
|
||||
}
|
||||
|
||||
$this->listener = $treeListener;
|
||||
if (!$this->validate()) {
|
||||
throw new \Gedmo\Exception\InvalidMappingException('This repository cannot be used for tree type: '.$treeListener->getStrategy($em, $class->name)->getName());
|
||||
}
|
||||
|
||||
$this->repoUtils = new RepositoryUtils($this->_em, $this->getClassMetadata(), $this->listener, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
*/
|
||||
protected function getQueryBuilder()
|
||||
{
|
||||
return $this->getEntityManager()->createQueryBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the RepositoryUtilsInterface instance
|
||||
*
|
||||
* @param \Gedmo\Tree\RepositoryUtilsInterface $repoUtils
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function setRepoUtils(RepositoryUtilsInterface $repoUtils)
|
||||
{
|
||||
$this->repoUtils = $repoUtils;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RepositoryUtilsInterface instance
|
||||
*
|
||||
* @return \Gedmo\Tree\RepositoryUtilsInterface|null
|
||||
*/
|
||||
public function getRepoUtils()
|
||||
{
|
||||
return $this->repoUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function childCount($node = null, $direct = false)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
|
||||
if (is_object($node)) {
|
||||
if (!($node instanceof $meta->name)) {
|
||||
throw new InvalidArgumentException("Node is not related to this repository");
|
||||
}
|
||||
|
||||
$wrapped = new EntityWrapper($node, $this->_em);
|
||||
|
||||
if (!$wrapped->hasValidIdentifier()) {
|
||||
throw new InvalidArgumentException("Node is not managed by UnitOfWork");
|
||||
}
|
||||
}
|
||||
|
||||
$qb = $this->getChildrenQueryBuilder($node, $direct);
|
||||
|
||||
// We need to remove the ORDER BY DQL part since some vendors could throw an error
|
||||
// in count queries
|
||||
$dqlParts = $qb->getDQLParts();
|
||||
|
||||
// We need to check first if there's an ORDER BY DQL part, because resetDQLPart doesn't
|
||||
// check if its internal array has an "orderby" index
|
||||
if (isset($dqlParts['orderBy'])) {
|
||||
$qb->resetDQLPart('orderBy');
|
||||
}
|
||||
|
||||
$aliases = $qb->getRootAliases();
|
||||
$alias = $aliases[0];
|
||||
|
||||
$qb->select('COUNT('.$alias.')');
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Gedmo\Tree\RepositoryUtilsInterface::childrenHierarchy
|
||||
*/
|
||||
public function childrenHierarchy($node = null, $direct = false, array $options = array(), $includeNode = false)
|
||||
{
|
||||
return $this->repoUtils->childrenHierarchy($node, $direct, $options, $includeNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Gedmo\Tree\RepositoryUtilsInterface::buildTree
|
||||
*/
|
||||
public function buildTree(array $nodes, array $options = array())
|
||||
{
|
||||
return $this->repoUtils->buildTree($nodes, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Gedmo\Tree\RepositoryUtilsInterface::buildTreeArray
|
||||
*/
|
||||
public function buildTreeArray(array $nodes)
|
||||
{
|
||||
return $this->repoUtils->buildTreeArray($nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Gedmo\Tree\RepositoryUtilsInterface::setChildrenIndex
|
||||
*/
|
||||
public function setChildrenIndex($childrenIndex)
|
||||
{
|
||||
$this->repoUtils->setChildrenIndex($childrenIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \Gedmo\Tree\RepositoryUtilsInterface::getChildrenIndex
|
||||
*/
|
||||
public function getChildrenIndex()
|
||||
{
|
||||
return $this->repoUtils->getChildrenIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if current repository is right
|
||||
* for currently used tree strategy
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function validate();
|
||||
|
||||
/**
|
||||
* Get all root nodes query builder
|
||||
*
|
||||
* @param string - Sort by field
|
||||
* @param string - Sort direction ("asc" or "desc")
|
||||
*
|
||||
* @return \Doctrine\ORM\QueryBuilder - QueryBuilder object
|
||||
*/
|
||||
abstract public function getRootNodesQueryBuilder($sortByField = null, $direction = 'asc');
|
||||
|
||||
/**
|
||||
* Get all root nodes query
|
||||
*
|
||||
* @param string - Sort by field
|
||||
* @param string - Sort direction ("asc" or "desc")
|
||||
*
|
||||
* @return \Doctrine\ORM\Query - Query object
|
||||
*/
|
||||
abstract public function getRootNodesQuery($sortByField = null, $direction = 'asc');
|
||||
|
||||
/**
|
||||
* Returns a QueryBuilder configured to return an array of nodes suitable for buildTree method
|
||||
*
|
||||
* @param object $node - Root node
|
||||
* @param bool $direct - Obtain direct children?
|
||||
* @param array $options - Options
|
||||
* @param boolean $includeNode - Include node in results?
|
||||
*
|
||||
* @return \Doctrine\ORM\QueryBuilder - QueryBuilder object
|
||||
*/
|
||||
abstract public function getNodesHierarchyQueryBuilder($node = null, $direct = false, array $options = array(), $includeNode = false);
|
||||
|
||||
/**
|
||||
* Returns a Query configured to return an array of nodes suitable for buildTree method
|
||||
*
|
||||
* @param object $node - Root node
|
||||
* @param bool $direct - Obtain direct children?
|
||||
* @param array $options - Options
|
||||
* @param boolean $includeNode - Include node in results?
|
||||
*
|
||||
* @return \Doctrine\ORM\Query - Query object
|
||||
*/
|
||||
abstract public function getNodesHierarchyQuery($node = null, $direct = false, array $options = array(), $includeNode = false);
|
||||
|
||||
/**
|
||||
* Get list of children followed by given $node. This returns a QueryBuilder object
|
||||
*
|
||||
* @param object $node - if null, all tree nodes will be taken
|
||||
* @param boolean $direct - true to take only direct children
|
||||
* @param string $sortByField - field name to sort by
|
||||
* @param string $direction - sort direction : "ASC" or "DESC"
|
||||
* @param bool $includeNode - Include the root node in results?
|
||||
*
|
||||
* @return \Doctrine\ORM\QueryBuilder - QueryBuilder object
|
||||
*/
|
||||
abstract public function getChildrenQueryBuilder($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false);
|
||||
|
||||
/**
|
||||
* Get list of children followed by given $node. This returns a Query
|
||||
*
|
||||
* @param object $node - if null, all tree nodes will be taken
|
||||
* @param boolean $direct - true to take only direct children
|
||||
* @param string $sortByField - field name to sort by
|
||||
* @param string $direction - sort direction : "ASC" or "DESC"
|
||||
* @param bool $includeNode - Include the root node in results?
|
||||
*
|
||||
* @return \Doctrine\ORM\Query - Query object
|
||||
*/
|
||||
abstract public function getChildrenQuery($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false);
|
||||
}
|
||||
605
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity/Repository/ClosureTreeRepository.php
vendored
Normal file
605
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity/Repository/ClosureTreeRepository.php
vendored
Normal file
|
|
@ -0,0 +1,605 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Entity\Repository;
|
||||
|
||||
use Gedmo\Exception\InvalidArgumentException;
|
||||
use Doctrine\ORM\Query;
|
||||
use Gedmo\Tree\Entity\MappedSuperclass\AbstractClosure;
|
||||
use Gedmo\Tree\Strategy;
|
||||
use Gedmo\Tool\Wrapper\EntityWrapper;
|
||||
|
||||
/**
|
||||
* The ClosureTreeRepository has some useful functions
|
||||
* to interact with Closure tree. Repository uses
|
||||
* the strategy used by listener
|
||||
*
|
||||
* @author Gustavo Adrian <comfortablynumb84@gmail.com>
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class ClosureTreeRepository extends AbstractTreeRepository
|
||||
{
|
||||
/** Alias for the level value used in the subquery of the getNodesHierarchy method */
|
||||
const SUBQUERY_LEVEL = 'level';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getRootNodesQueryBuilder($sortByField = null, $direction = 'asc')
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
$config = $this->listener->getConfiguration($this->_em, $meta->name);
|
||||
$qb = $this->getQueryBuilder();
|
||||
$qb->select('node')
|
||||
->from($config['useObjectClass'], 'node')
|
||||
->where('node.'.$config['parent']." IS NULL");
|
||||
|
||||
if ($sortByField) {
|
||||
$qb->orderBy('node.'.$sortByField, strtolower($direction) === 'asc' ? 'asc' : 'desc');
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getRootNodesQuery($sortByField = null, $direction = 'asc')
|
||||
{
|
||||
return $this->getRootNodesQueryBuilder($sortByField, $direction)->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getRootNodes($sortByField = null, $direction = 'asc')
|
||||
{
|
||||
return $this->getRootNodesQuery($sortByField, $direction)->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Tree path query by given $node
|
||||
*
|
||||
* @param object $node
|
||||
*
|
||||
* @throws InvalidArgumentException - if input is not valid
|
||||
*
|
||||
* @return Query
|
||||
*/
|
||||
public function getPathQuery($node)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
if (!$node instanceof $meta->name) {
|
||||
throw new InvalidArgumentException("Node is not related to this repository");
|
||||
}
|
||||
if (!$this->_em->getUnitOfWork()->isInIdentityMap($node)) {
|
||||
throw new InvalidArgumentException("Node is not managed by UnitOfWork");
|
||||
}
|
||||
$config = $this->listener->getConfiguration($this->_em, $meta->name);
|
||||
$closureMeta = $this->_em->getClassMetadata($config['closure']);
|
||||
|
||||
$dql = "SELECT c, node FROM {$closureMeta->name} c";
|
||||
$dql .= " INNER JOIN c.ancestor node";
|
||||
$dql .= " WHERE c.descendant = :node";
|
||||
$dql .= " ORDER BY c.depth DESC";
|
||||
$q = $this->_em->createQuery($dql);
|
||||
$q->setParameters(compact('node'));
|
||||
|
||||
return $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Tree path of Nodes by given $node
|
||||
*
|
||||
* @param object $node
|
||||
*
|
||||
* @return array - list of Nodes in path
|
||||
*/
|
||||
public function getPath($node)
|
||||
{
|
||||
return array_map(function (AbstractClosure $closure) {
|
||||
return $closure->getAncestor();
|
||||
}, $this->getPathQuery($node)->getResult());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getChildrenQueryBuilder
|
||||
*/
|
||||
public function childrenQueryBuilder($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
$config = $this->listener->getConfiguration($this->_em, $meta->name);
|
||||
|
||||
$qb = $this->getQueryBuilder();
|
||||
if ($node !== null) {
|
||||
if ($node instanceof $meta->name) {
|
||||
if (!$this->_em->getUnitOfWork()->isInIdentityMap($node)) {
|
||||
throw new InvalidArgumentException("Node is not managed by UnitOfWork");
|
||||
}
|
||||
|
||||
$where = 'c.ancestor = :node AND ';
|
||||
|
||||
$qb->select('c, node')
|
||||
->from($config['closure'], 'c')
|
||||
->innerJoin('c.descendant', 'node');
|
||||
|
||||
if ($direct) {
|
||||
$where .= 'c.depth = 1';
|
||||
} else {
|
||||
$where .= 'c.descendant <> :node';
|
||||
}
|
||||
|
||||
$qb->where($where);
|
||||
|
||||
if ($includeNode) {
|
||||
$qb->orWhere('c.ancestor = :node AND c.descendant = :node');
|
||||
}
|
||||
} else {
|
||||
throw new \InvalidArgumentException("Node is not related to this repository");
|
||||
}
|
||||
} else {
|
||||
$qb->select('node')
|
||||
->from($config['useObjectClass'], 'node');
|
||||
if ($direct) {
|
||||
$qb->where('node.'.$config['parent'].' IS NULL');
|
||||
}
|
||||
}
|
||||
|
||||
if ($sortByField) {
|
||||
if ($meta->hasField($sortByField) && in_array(strtolower($direction), array('asc', 'desc'))) {
|
||||
$qb->orderBy('node.'.$sortByField, $direction);
|
||||
} else {
|
||||
throw new InvalidArgumentException("Invalid sort options specified: field - {$sortByField}, direction - {$direction}");
|
||||
}
|
||||
}
|
||||
|
||||
if ($node) {
|
||||
$qb->setParameter('node', $node);
|
||||
}
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getChildrenQuery
|
||||
*/
|
||||
public function childrenQuery($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false)
|
||||
{
|
||||
return $this->childrenQueryBuilder($node, $direct, $sortByField, $direction, $includeNode)->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see getChildren
|
||||
*/
|
||||
public function children($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false)
|
||||
{
|
||||
$result = $this->childrenQuery($node, $direct, $sortByField, $direction, $includeNode)->getResult();
|
||||
if ($node) {
|
||||
$result = array_map(function (AbstractClosure $closure) {
|
||||
return $closure->getDescendant();
|
||||
}, $result);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getChildrenQueryBuilder($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false)
|
||||
{
|
||||
return $this->childrenQueryBuilder($node, $direct, $sortByField, $direction, $includeNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getChildrenQuery($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false)
|
||||
{
|
||||
return $this->childrenQuery($node, $direct, $sortByField, $direction, $includeNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getChildren($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false)
|
||||
{
|
||||
return $this->children($node, $direct, $sortByField, $direction, $includeNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes given $node from the tree and reparents its descendants
|
||||
*
|
||||
* @todo may be improved, to issue single query on reparenting
|
||||
*
|
||||
* @param object $node
|
||||
*
|
||||
* @throws \Gedmo\Exception\InvalidArgumentException
|
||||
* @throws \Gedmo\Exception\RuntimeException - if something fails in transaction
|
||||
*/
|
||||
public function removeFromTree($node)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
if (!$node instanceof $meta->name) {
|
||||
throw new InvalidArgumentException("Node is not related to this repository");
|
||||
}
|
||||
$wrapped = new EntityWrapper($node, $this->_em);
|
||||
if (!$wrapped->hasValidIdentifier()) {
|
||||
throw new InvalidArgumentException("Node is not managed by UnitOfWork");
|
||||
}
|
||||
$config = $this->listener->getConfiguration($this->_em, $meta->name);
|
||||
$pk = $meta->getSingleIdentifierFieldName();
|
||||
$nodeId = $wrapped->getIdentifier();
|
||||
$parent = $wrapped->getPropertyValue($config['parent']);
|
||||
|
||||
$dql = "SELECT node FROM {$config['useObjectClass']} node";
|
||||
$dql .= " WHERE node.{$config['parent']} = :node";
|
||||
$q = $this->_em->createQuery($dql);
|
||||
$q->setParameters(compact('node'));
|
||||
$nodesToReparent = $q->getResult();
|
||||
// process updates in transaction
|
||||
$this->_em->getConnection()->beginTransaction();
|
||||
try {
|
||||
foreach ($nodesToReparent as $nodeToReparent) {
|
||||
$id = $meta->getReflectionProperty($pk)->getValue($nodeToReparent);
|
||||
$meta->getReflectionProperty($config['parent'])->setValue($nodeToReparent, $parent);
|
||||
|
||||
$dql = "UPDATE {$config['useObjectClass']} node";
|
||||
$dql .= " SET node.{$config['parent']} = :parent";
|
||||
$dql .= " WHERE node.{$pk} = :id";
|
||||
|
||||
$q = $this->_em->createQuery($dql);
|
||||
$q->setParameters(compact('parent', 'id'));
|
||||
$q->getSingleScalarResult();
|
||||
|
||||
$this->listener
|
||||
->getStrategy($this->_em, $meta->name)
|
||||
->updateNode($this->_em, $nodeToReparent, $node);
|
||||
|
||||
$oid = spl_object_hash($nodeToReparent);
|
||||
$this->_em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['parent'], $parent);
|
||||
}
|
||||
|
||||
$dql = "DELETE {$config['useObjectClass']} node";
|
||||
$dql .= " WHERE node.{$pk} = :nodeId";
|
||||
|
||||
$q = $this->_em->createQuery($dql);
|
||||
$q->setParameters(compact('nodeId'));
|
||||
$q->getSingleScalarResult();
|
||||
$this->_em->getConnection()->commit();
|
||||
} catch (\Exception $e) {
|
||||
$this->_em->close();
|
||||
$this->_em->getConnection()->rollback();
|
||||
throw new \Gedmo\Exception\RuntimeException('Transaction failed: '.$e->getMessage(), null, $e);
|
||||
}
|
||||
// remove from identity map
|
||||
$this->_em->getUnitOfWork()->removeFromIdentityMap($node);
|
||||
$node = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process nodes and produce an array with the
|
||||
* structure of the tree
|
||||
*
|
||||
* @param array - Array of nodes
|
||||
*
|
||||
* @return array - Array with tree structure
|
||||
*/
|
||||
public function buildTreeArray(array $nodes)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
$config = $this->listener->getConfiguration($this->_em, $meta->name);
|
||||
$nestedTree = array();
|
||||
$idField = $meta->getSingleIdentifierFieldName();
|
||||
$hasLevelProp = !empty($config['level']);
|
||||
$levelProp = $hasLevelProp ? $config['level'] : self::SUBQUERY_LEVEL;
|
||||
$childrenIndex = $this->repoUtils->getChildrenIndex();
|
||||
|
||||
if (count($nodes) > 0) {
|
||||
$firstLevel = $hasLevelProp ? $nodes[0][0]['descendant'][$levelProp] : $nodes[0][$levelProp];
|
||||
$l = 1; // 1 is only an initial value. We could have a tree which has a root node with any level (subtrees)
|
||||
$refs = array();
|
||||
|
||||
foreach ($nodes as $n) {
|
||||
$node = $n[0]['descendant'];
|
||||
$node[$childrenIndex] = array();
|
||||
$level = $hasLevelProp ? $node[$levelProp] : $n[$levelProp];
|
||||
|
||||
if ($l < $level) {
|
||||
$l = $level;
|
||||
}
|
||||
|
||||
if ($l == $firstLevel) {
|
||||
$tmp = &$nestedTree;
|
||||
} else {
|
||||
$tmp = &$refs[$n['parent_id']][$childrenIndex];
|
||||
}
|
||||
|
||||
$key = count($tmp);
|
||||
$tmp[$key] = $node;
|
||||
$refs[$node[$idField]] = &$tmp[$key];
|
||||
}
|
||||
|
||||
unset($refs);
|
||||
}
|
||||
|
||||
return $nestedTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNodesHierarchy($node = null, $direct = false, array $options = array(), $includeNode = false)
|
||||
{
|
||||
return $this->getNodesHierarchyQuery($node, $direct, $options, $includeNode)->getArrayResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNodesHierarchyQuery($node = null, $direct = false, array $options = array(), $includeNode = false)
|
||||
{
|
||||
return $this->getNodesHierarchyQueryBuilder($node, $direct, $options, $includeNode)->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNodesHierarchyQueryBuilder($node = null, $direct = false, array $options = array(), $includeNode = false)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
$config = $this->listener->getConfiguration($this->_em, $meta->name);
|
||||
$idField = $meta->getSingleIdentifierFieldName();
|
||||
$subQuery = '';
|
||||
$hasLevelProp = isset($config['level']) && $config['level'];
|
||||
|
||||
if (!$hasLevelProp) {
|
||||
$subQuery = ', (SELECT MAX(c2.depth) + 1 FROM '.$config['closure'];
|
||||
$subQuery .= ' c2 WHERE c2.descendant = c.descendant GROUP BY c2.descendant) AS '.self::SUBQUERY_LEVEL;
|
||||
}
|
||||
|
||||
$q = $this->_em->createQueryBuilder()
|
||||
->select('c, node, p.'.$idField.' AS parent_id'.$subQuery)
|
||||
->from($config['closure'], 'c')
|
||||
->innerJoin('c.descendant', 'node')
|
||||
->leftJoin('node.parent', 'p')
|
||||
->addOrderBy(($hasLevelProp ? 'node.'.$config['level'] : self::SUBQUERY_LEVEL), 'asc');
|
||||
|
||||
if ($node !== null) {
|
||||
$q->where('c.ancestor = :node');
|
||||
$q->setParameters(compact('node'));
|
||||
} else {
|
||||
$q->groupBy('c.descendant');
|
||||
}
|
||||
|
||||
if (!$includeNode) {
|
||||
$q->andWhere('c.ancestor != c.descendant');
|
||||
}
|
||||
|
||||
$defaultOptions = array();
|
||||
$options = array_merge($defaultOptions, $options);
|
||||
|
||||
if (isset($options['childSort']) && is_array($options['childSort']) &&
|
||||
isset($options['childSort']['field']) && isset($options['childSort']['dir'])) {
|
||||
$q->addOrderBy(
|
||||
'node.'.$options['childSort']['field'],
|
||||
strtolower($options['childSort']['dir']) == 'asc' ? 'asc' : 'desc'
|
||||
);
|
||||
}
|
||||
|
||||
return $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function validate()
|
||||
{
|
||||
return $this->listener->getStrategy($this->_em, $this->getClassMetadata()->name)->getName() === Strategy::CLOSURE;
|
||||
}
|
||||
|
||||
public function verify()
|
||||
{
|
||||
$nodeMeta = $this->getClassMetadata();
|
||||
$nodeIdField = $nodeMeta->getSingleIdentifierFieldName();
|
||||
$config = $this->listener->getConfiguration($this->_em, $nodeMeta->name);
|
||||
$closureMeta = $this->_em->getClassMetadata($config['closure']);
|
||||
$errors = array();
|
||||
|
||||
$q = $this->_em->createQuery("
|
||||
SELECT COUNT(node)
|
||||
FROM {$nodeMeta->name} AS node
|
||||
LEFT JOIN {$closureMeta->name} AS c WITH c.ancestor = node AND c.depth = 0
|
||||
WHERE c.id IS NULL
|
||||
");
|
||||
|
||||
if ($missingSelfRefsCount = intval($q->getSingleScalarResult())) {
|
||||
$errors[] = "Missing $missingSelfRefsCount self referencing closures";
|
||||
}
|
||||
|
||||
$q = $this->_em->createQuery("
|
||||
SELECT COUNT(node)
|
||||
FROM {$nodeMeta->name} AS node
|
||||
INNER JOIN {$closureMeta->name} AS c1 WITH c1.descendant = node.{$config['parent']}
|
||||
LEFT JOIN {$closureMeta->name} AS c2 WITH c2.descendant = node.$nodeIdField AND c2.ancestor = c1.ancestor
|
||||
WHERE c2.id IS NULL AND node.$nodeIdField <> c1.ancestor
|
||||
");
|
||||
|
||||
if ($missingClosuresCount = intval($q->getSingleScalarResult())) {
|
||||
$errors[] = "Missing $missingClosuresCount closures";
|
||||
}
|
||||
|
||||
$q = $this->_em->createQuery("
|
||||
SELECT COUNT(c1.id)
|
||||
FROM {$closureMeta->name} AS c1
|
||||
LEFT JOIN {$nodeMeta->name} AS node WITH c1.descendant = node.$nodeIdField
|
||||
LEFT JOIN {$closureMeta->name} AS c2 WITH c2.descendant = node.{$config['parent']} AND c2.ancestor = c1.ancestor
|
||||
WHERE c2.id IS NULL AND c1.descendant <> c1.ancestor
|
||||
");
|
||||
|
||||
if ($invalidClosuresCount = intval($q->getSingleScalarResult())) {
|
||||
$errors[] = "Found $invalidClosuresCount invalid closures";
|
||||
}
|
||||
|
||||
if (!empty($config['level'])) {
|
||||
$levelField = $config['level'];
|
||||
$maxResults = 1000;
|
||||
$q = $this->_em->createQuery("
|
||||
SELECT node.$nodeIdField AS id, node.$levelField AS node_level, MAX(c.depth) AS closure_level
|
||||
FROM {$nodeMeta->name} AS node
|
||||
INNER JOIN {$closureMeta->name} AS c WITH c.descendant = node.$nodeIdField
|
||||
GROUP BY node.$nodeIdField, node.$levelField
|
||||
HAVING node.$levelField IS NULL OR node.$levelField <> MAX(c.depth) + 1
|
||||
")->setMaxResults($maxResults);
|
||||
|
||||
if ($invalidLevelsCount = count($q->getScalarResult())) {
|
||||
$errors[] = "Found $invalidLevelsCount invalid level values";
|
||||
}
|
||||
}
|
||||
|
||||
return $errors ?: true;
|
||||
}
|
||||
|
||||
public function recover()
|
||||
{
|
||||
if ($this->verify() === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cleanUpClosure();
|
||||
$this->rebuildClosure();
|
||||
}
|
||||
|
||||
public function rebuildClosure()
|
||||
{
|
||||
$nodeMeta = $this->getClassMetadata();
|
||||
$config = $this->listener->getConfiguration($this->_em, $nodeMeta->name);
|
||||
$closureMeta = $this->_em->getClassMetadata($config['closure']);
|
||||
|
||||
$insertClosures = function ($entries) use ($closureMeta) {
|
||||
$closureTable = $closureMeta->getTableName();
|
||||
$ancestorColumnName = $this->getJoinColumnFieldName($closureMeta->getAssociationMapping('ancestor'));
|
||||
$descendantColumnName = $this->getJoinColumnFieldName($closureMeta->getAssociationMapping('descendant'));
|
||||
$depthColumnName = $closureMeta->getColumnName('depth');
|
||||
|
||||
$conn = $this->_em->getConnection();
|
||||
$conn->beginTransaction();
|
||||
foreach ($entries as $entry) {
|
||||
$conn->insert($closureTable, array_combine(
|
||||
array($ancestorColumnName, $descendantColumnName, $depthColumnName),
|
||||
$entry
|
||||
));
|
||||
}
|
||||
$conn->commit();
|
||||
};
|
||||
|
||||
$buildClosures = function ($dql) use ($insertClosures) {
|
||||
$newClosuresCount = 0;
|
||||
$batchSize = 1000;
|
||||
$q = $this->_em->createQuery($dql)->setMaxResults($batchSize)->setCacheable(false);
|
||||
do {
|
||||
$entries = $q->getScalarResult();
|
||||
$insertClosures($entries);
|
||||
$newClosuresCount += count($entries);
|
||||
} while (count($entries) > 0);
|
||||
return $newClosuresCount;
|
||||
};
|
||||
|
||||
$nodeIdField = $nodeMeta->getSingleIdentifierFieldName();
|
||||
$newClosuresCount = $buildClosures("
|
||||
SELECT node.id AS ancestor, node.$nodeIdField AS descendant, 0 AS depth
|
||||
FROM {$nodeMeta->name} AS node
|
||||
LEFT JOIN {$closureMeta->name} AS c WITH c.ancestor = node AND c.depth = 0
|
||||
WHERE c.id IS NULL
|
||||
");
|
||||
$newClosuresCount += $buildClosures("
|
||||
SELECT IDENTITY(c1.ancestor) AS ancestor, node.$nodeIdField AS descendant, c1.depth + 1 AS depth
|
||||
FROM {$nodeMeta->name} AS node
|
||||
INNER JOIN {$closureMeta->name} AS c1 WITH c1.descendant = node.{$config['parent']}
|
||||
LEFT JOIN {$closureMeta->name} AS c2 WITH c2.descendant = node.$nodeIdField AND c2.ancestor = c1.ancestor
|
||||
WHERE c2.id IS NULL AND node.$nodeIdField <> c1.ancestor
|
||||
");
|
||||
|
||||
return $newClosuresCount;
|
||||
}
|
||||
|
||||
public function cleanUpClosure()
|
||||
{
|
||||
$conn = $this->_em->getConnection();
|
||||
$nodeMeta = $this->getClassMetadata();
|
||||
$nodeIdField = $nodeMeta->getSingleIdentifierFieldName();
|
||||
$config = $this->listener->getConfiguration($this->_em, $nodeMeta->name);
|
||||
$closureMeta = $this->_em->getClassMetadata($config['closure']);
|
||||
$closureTableName = $closureMeta->getTableName();
|
||||
|
||||
$dql = "
|
||||
SELECT c1.id AS id
|
||||
FROM {$closureMeta->name} AS c1
|
||||
LEFT JOIN {$nodeMeta->name} AS node WITH c1.descendant = node.$nodeIdField
|
||||
LEFT JOIN {$closureMeta->name} AS c2 WITH c2.descendant = node.{$config['parent']} AND c2.ancestor = c1.ancestor
|
||||
WHERE c2.id IS NULL AND c1.descendant <> c1.ancestor
|
||||
";
|
||||
|
||||
$deletedClosuresCount = 0;
|
||||
$batchSize = 1000;
|
||||
$q = $this->_em->createQuery($dql)->setMaxResults($batchSize)->setCacheable(false);
|
||||
|
||||
while (($ids = $q->getScalarResult()) && !empty($ids)) {
|
||||
$ids = array_map(function ($el) {
|
||||
return $el['id'];
|
||||
}, $ids);
|
||||
$query = "DELETE FROM {$closureTableName} WHERE id IN (".implode(', ', $ids).")";
|
||||
if (!$conn->executeQuery($query)) {
|
||||
throw new \RuntimeException('Failed to remove incorrect closures');
|
||||
}
|
||||
$deletedClosuresCount += count($ids);
|
||||
}
|
||||
|
||||
return $deletedClosuresCount;
|
||||
}
|
||||
|
||||
public function updateLevelValues()
|
||||
{
|
||||
$nodeMeta = $this->getClassMetadata();
|
||||
$config = $this->listener->getConfiguration($this->_em, $nodeMeta->name);
|
||||
$levelUpdatesCount = 0;
|
||||
|
||||
if (!empty($config['level'])) {
|
||||
$levelField = $config['level'];
|
||||
$nodeIdField = $nodeMeta->getSingleIdentifierFieldName();
|
||||
$closureMeta = $this->_em->getClassMetadata($config['closure']);
|
||||
|
||||
$batchSize = 1000;
|
||||
$q = $this->_em->createQuery("
|
||||
SELECT node.$nodeIdField AS id, node.$levelField AS node_level, MAX(c.depth) AS closure_level
|
||||
FROM {$nodeMeta->name} AS node
|
||||
INNER JOIN {$closureMeta->name} AS c WITH c.descendant = node.$nodeIdField
|
||||
GROUP BY node.$nodeIdField, node.$levelField
|
||||
HAVING node.$levelField IS NULL OR node.$levelField <> MAX(c.depth) + 1
|
||||
")->setMaxResults($batchSize)->setCacheable(false);
|
||||
do {
|
||||
$entries = $q->getScalarResult();
|
||||
$this->_em->getConnection()->beginTransaction();
|
||||
foreach ($entries as $entry) {
|
||||
unset($entry['node_level']);
|
||||
$this->_em->createQuery("
|
||||
UPDATE {$nodeMeta->name} AS node SET node.$levelField = (:closure_level + 1) WHERE node.$nodeIdField = :id
|
||||
")->execute($entry);
|
||||
}
|
||||
$this->_em->getConnection()->commit();
|
||||
$levelUpdatesCount += count($entries);
|
||||
} while (count($entries) > 0);
|
||||
}
|
||||
|
||||
return $levelUpdatesCount;
|
||||
}
|
||||
|
||||
protected function getJoinColumnFieldName($association)
|
||||
{
|
||||
if (count($association['joinColumnFieldNames']) > 1) {
|
||||
throw new \RuntimeException('More association on field ' . $association['fieldName']);
|
||||
}
|
||||
|
||||
return array_shift($association['joinColumnFieldNames']);
|
||||
}
|
||||
}
|
||||
286
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity/Repository/MaterializedPathRepository.php
vendored
Normal file
286
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity/Repository/MaterializedPathRepository.php
vendored
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Entity\Repository;
|
||||
|
||||
use Gedmo\Tree\Strategy;
|
||||
use Gedmo\Tool\Wrapper\EntityWrapper;
|
||||
|
||||
/**
|
||||
* The MaterializedPathRepository has some useful functions
|
||||
* to interact with MaterializedPath tree. Repository uses
|
||||
* the strategy used by listener
|
||||
*
|
||||
* @author Gustavo Falco <comfortablynumb84@gmail.com>
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class MaterializedPathRepository extends AbstractTreeRepository
|
||||
{
|
||||
/**
|
||||
* Get tree query builder
|
||||
*
|
||||
* @param object $rootNode
|
||||
*
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
*/
|
||||
public function getTreeQueryBuilder($rootNode = null)
|
||||
{
|
||||
return $this->getChildrenQueryBuilder($rootNode, false, null, 'asc', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tree query
|
||||
*
|
||||
* @param object $rootNode
|
||||
*
|
||||
* @return \Doctrine\ORM\Query
|
||||
*/
|
||||
public function getTreeQuery($rootNode = null)
|
||||
{
|
||||
return $this->getTreeQueryBuilder($rootNode)->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tree
|
||||
*
|
||||
* @param object $rootNode
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTree($rootNode = null)
|
||||
{
|
||||
return $this->getTreeQuery($rootNode)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getRootNodesQueryBuilder($sortByField = null, $direction = 'asc')
|
||||
{
|
||||
return $this->getChildrenQueryBuilder(null, true, $sortByField, $direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getRootNodesQuery($sortByField = null, $direction = 'asc')
|
||||
{
|
||||
return $this->getRootNodesQueryBuilder($sortByField, $direction)->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getRootNodes($sortByField = null, $direction = 'asc')
|
||||
{
|
||||
return $this->getRootNodesQuery($sortByField, $direction)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Tree path query builder by given $node
|
||||
*
|
||||
* @param object $node
|
||||
*
|
||||
* @return \Doctrine\ORM\QueryBuilder
|
||||
*/
|
||||
public function getPathQueryBuilder($node)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
$config = $this->listener->getConfiguration($this->_em, $meta->name);
|
||||
$alias = 'materialized_path_entity';
|
||||
$qb = $this->getQueryBuilder()
|
||||
->select($alias)
|
||||
->from($config['useObjectClass'], $alias);
|
||||
|
||||
$node = new EntityWrapper($node, $this->_em);
|
||||
$nodePath = $node->getPropertyValue($config['path']);
|
||||
$paths = array();
|
||||
$nodePathLength = strlen($nodePath);
|
||||
$separatorMatchOffset = 0;
|
||||
while ($separatorMatchOffset < $nodePathLength) {
|
||||
$separatorPos = strpos($nodePath, $config['path_separator'], $separatorMatchOffset);
|
||||
|
||||
if ($separatorPos === false || $separatorPos === $nodePathLength - 1) {
|
||||
// last node, done
|
||||
$paths[] = $nodePath;
|
||||
$separatorMatchOffset = $nodePathLength;
|
||||
} elseif ($separatorPos === 0) {
|
||||
// path starts with separator, continue
|
||||
$separatorMatchOffset = 1;
|
||||
} else {
|
||||
// add node
|
||||
$paths[] = substr($nodePath, 0, $config['path_ends_with_separator'] ? $separatorPos + 1 : $separatorPos);
|
||||
$separatorMatchOffset = $separatorPos + 1;
|
||||
}
|
||||
}
|
||||
$qb->where($qb->expr()->in(
|
||||
$alias.'.'.$config['path'],
|
||||
$paths
|
||||
));
|
||||
$qb->orderBy($alias.'.'.$config['level'], 'ASC');
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Tree path query by given $node
|
||||
*
|
||||
* @param object $node
|
||||
*
|
||||
* @return \Doctrine\ORM\Query
|
||||
*/
|
||||
public function getPathQuery($node)
|
||||
{
|
||||
return $this->getPathQueryBuilder($node)->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Tree path of Nodes by given $node
|
||||
*
|
||||
* @param object $node
|
||||
*
|
||||
* @return array - list of Nodes in path
|
||||
*/
|
||||
public function getPath($node)
|
||||
{
|
||||
return $this->getPathQuery($node)->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getChildrenQueryBuilder($node = null, $direct = false, $sortByField = null, $direction = 'asc', $includeNode = false)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
$config = $this->listener->getConfiguration($this->_em, $meta->name);
|
||||
$separator = addcslashes($config['path_separator'], '%');
|
||||
$alias = 'materialized_path_entity';
|
||||
$path = $config['path'];
|
||||
$qb = $this->getQueryBuilder()
|
||||
->select($alias)
|
||||
->from($config['useObjectClass'], $alias);
|
||||
$expr = '';
|
||||
$includeNodeExpr = '';
|
||||
|
||||
if (is_object($node) && $node instanceof $meta->name) {
|
||||
$node = new EntityWrapper($node, $this->_em);
|
||||
$nodePath = $node->getPropertyValue($path);
|
||||
$expr = $qb->expr()->andx()->add(
|
||||
$qb->expr()->like(
|
||||
$alias.'.'.$path,
|
||||
$qb->expr()->literal(
|
||||
$nodePath
|
||||
.($config['path_ends_with_separator'] ? '' : $separator).'%'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
if ($includeNode) {
|
||||
$includeNodeExpr = $qb->expr()->eq($alias.'.'.$path, $qb->expr()->literal($nodePath));
|
||||
} else {
|
||||
$expr->add($qb->expr()->neq($alias.'.'.$path, $qb->expr()->literal($nodePath)));
|
||||
}
|
||||
|
||||
if ($direct) {
|
||||
$expr->add(
|
||||
$qb->expr()->orx(
|
||||
$qb->expr()->eq($alias.'.'.$config['level'], $qb->expr()->literal($node->getPropertyValue($config['level']))),
|
||||
$qb->expr()->eq($alias.'.'.$config['level'], $qb->expr()->literal($node->getPropertyValue($config['level']) + 1))
|
||||
)
|
||||
);
|
||||
}
|
||||
} elseif ($direct) {
|
||||
$expr = $qb->expr()->not(
|
||||
$qb->expr()->like($alias.'.'.$path,
|
||||
$qb->expr()->literal(
|
||||
($config['path_starts_with_separator'] ? $separator : '')
|
||||
.'%'.$separator.'%'
|
||||
.($config['path_ends_with_separator'] ? $separator : '')
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ($expr) {
|
||||
$qb->where('('.$expr.')');
|
||||
}
|
||||
|
||||
if ($includeNodeExpr) {
|
||||
$qb->orWhere('('.$includeNodeExpr.')');
|
||||
}
|
||||
|
||||
$orderByField = is_null($sortByField) ? $alias.'.'.$config['path'] : $alias.'.'.$sortByField;
|
||||
$orderByDir = $direction === 'asc' ? 'asc' : 'desc';
|
||||
$qb->orderBy($orderByField, $orderByDir);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getChildrenQuery($node = null, $direct = false, $sortByField = null, $direction = 'asc', $includeNode = false)
|
||||
{
|
||||
return $this->getChildrenQueryBuilder($node, $direct, $sortByField, $direction, $includeNode)->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getChildren($node = null, $direct = false, $sortByField = null, $direction = 'asc', $includeNode = false)
|
||||
{
|
||||
return $this->getChildrenQuery($node, $direct, $sortByField, $direction, $includeNode)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNodesHierarchyQueryBuilder($node = null, $direct = false, array $options = array(), $includeNode = false)
|
||||
{
|
||||
$sortBy = array(
|
||||
'field' => null,
|
||||
'dir' => 'asc',
|
||||
);
|
||||
|
||||
if (isset($options['childSort'])) {
|
||||
$sortBy = array_merge($sortBy, $options['childSort']);
|
||||
}
|
||||
|
||||
return $this->getChildrenQueryBuilder($node, $direct, $sortBy['field'], $sortBy['dir'], $includeNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNodesHierarchyQuery($node = null, $direct = false, array $options = array(), $includeNode = false)
|
||||
{
|
||||
return $this->getNodesHierarchyQueryBuilder($node, $direct, $options, $includeNode)->getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNodesHierarchy($node = null, $direct = false, array $options = array(), $includeNode = false)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
$config = $this->listener->getConfiguration($this->_em, $meta->name);
|
||||
$path = $config['path'];
|
||||
|
||||
$nodes = $this->getNodesHierarchyQuery($node, $direct, $options, $includeNode)->getArrayResult();
|
||||
usort(
|
||||
$nodes,
|
||||
function ($a, $b) use ($path) {
|
||||
return strcmp($a[$path], $b[$path]);
|
||||
}
|
||||
);
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function validate()
|
||||
{
|
||||
return $this->listener->getStrategy($this->_em, $this->getClassMetadata()->name)->getName() === Strategy::MATERIALIZED_PATH;
|
||||
}
|
||||
}
|
||||
1075
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity/Repository/NestedTreeRepository.php
vendored
Normal file
1075
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity/Repository/NestedTreeRepository.php
vendored
Normal file
File diff suppressed because it is too large
Load diff
265
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Hydrator/ORM/TreeObjectHydrator.php
vendored
Normal file
265
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Hydrator/ORM/TreeObjectHydrator.php
vendored
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Hydrator\ORM;
|
||||
|
||||
use Doctrine\Common\Collections\AbstractLazyCollection;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
|
||||
use Doctrine\ORM\Proxy\Proxy;
|
||||
use Gedmo\Tool\Wrapper\EntityWrapper;
|
||||
use Gedmo\Tree\TreeListener;
|
||||
|
||||
/**
|
||||
* Automatically maps the parent and children properties of Tree nodes
|
||||
*
|
||||
* @author Ilija Tovilo <ilija.tovilo@me.com>
|
||||
* @link http://www.gediminasm.org
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class TreeObjectHydrator extends ObjectHydrator
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $config;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $idField;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $parentField;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $childrenField;
|
||||
|
||||
/**
|
||||
* We hook into the `hydrateAllData` to map the children collection of the entity
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function hydrateAllData()
|
||||
{
|
||||
$data = parent::hydrateAllData();
|
||||
|
||||
if (count($data) === 0) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$listener = $this->getTreeListener($this->_em);
|
||||
$entityClass = $this->getEntityClassFromHydratedData($data);
|
||||
$this->config = $listener->getConfiguration($this->_em, $entityClass);
|
||||
$this->idField = $this->getIdField($entityClass);
|
||||
$this->parentField = $this->getParentField();
|
||||
$this->childrenField = $this->getChildrenField($entityClass);
|
||||
|
||||
|
||||
$childrenHashmap = $this->buildChildrenHashmap($data);
|
||||
$this->populateChildrenArray($data, $childrenHashmap);
|
||||
|
||||
// Only return root elements or elements who's parents haven't been fetched
|
||||
// The sub-nodes will be accessible via the `children` property
|
||||
return $this->getRootNodes($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hashmap to quickly find the children of a node
|
||||
*
|
||||
* ```
|
||||
* [parentId => [child1, child2, ...], ...]
|
||||
* ```
|
||||
*
|
||||
* @param array $nodes
|
||||
* @return array
|
||||
*/
|
||||
protected function buildChildrenHashmap($nodes)
|
||||
{
|
||||
$r = array();
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$parentProxy = $this->getPropertyValue($node, $this->config['parent']);
|
||||
$parentId = null;
|
||||
|
||||
if ($parentProxy !== null) {
|
||||
$parentId = $this->getPropertyValue($parentProxy, $this->idField);
|
||||
}
|
||||
|
||||
$r[$parentId][] = $node;
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $nodes
|
||||
* @param array $childrenHashmap
|
||||
*/
|
||||
protected function populateChildrenArray($nodes, $childrenHashmap)
|
||||
{
|
||||
foreach ($nodes as $node) {
|
||||
$nodeId = $this->getPropertyValue($node, $this->idField);
|
||||
$childrenCollection = $this->getPropertyValue($node, $this->childrenField);
|
||||
|
||||
if ($childrenCollection === null) {
|
||||
$childrenCollection = new ArrayCollection();
|
||||
$this->setPropertyValue($node, $this->childrenField, $childrenCollection);
|
||||
}
|
||||
|
||||
// Mark all children collections as initialized to avoid select queries
|
||||
if ($childrenCollection instanceof AbstractLazyCollection) {
|
||||
$childrenCollection->setInitialized(true);
|
||||
}
|
||||
|
||||
if (!isset($childrenHashmap[$nodeId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$childrenCollection->clear();
|
||||
|
||||
foreach ($childrenHashmap[$nodeId] as $child) {
|
||||
$childrenCollection->add($child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $nodes
|
||||
* @return array
|
||||
*/
|
||||
protected function getRootNodes($nodes)
|
||||
{
|
||||
$idHashmap = $this->buildIdHashmap($nodes);
|
||||
$rootNodes = array();
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$parentProxy = $this->getPropertyValue($node, $this->config['parent']);
|
||||
$parentId = null;
|
||||
|
||||
if ($parentProxy !== null) {
|
||||
$parentId = $this->getPropertyValue($parentProxy, $this->idField);
|
||||
}
|
||||
|
||||
if ($parentId === null || !key_exists($parentId, $idHashmap)) {
|
||||
$rootNodes[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
return $rootNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hashmap of all nodes returned in the query
|
||||
*
|
||||
* ```
|
||||
* [node1.id => true, node2.id => true, ...]
|
||||
* ```
|
||||
*
|
||||
* @param array $nodes
|
||||
* @return array
|
||||
*/
|
||||
protected function buildIdHashmap(array $nodes)
|
||||
{
|
||||
$ids = array();
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$id = $this->getPropertyValue($node, $this->idField);
|
||||
$ids[$id] = true;
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getIdField($entityClass)
|
||||
{
|
||||
$meta = $this->getClassMetadata($entityClass);
|
||||
return $meta->getSingleIdentifierFieldName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getParentField()
|
||||
{
|
||||
if (!isset($this->config['parent'])) {
|
||||
throw new \Gedmo\Exception\InvalidMappingException('The `parent` property is required for the TreeHydrator to work');
|
||||
}
|
||||
|
||||
return $this->config['parent'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getChildrenField($entityClass)
|
||||
{
|
||||
$meta = $this->getClassMetadata($entityClass);
|
||||
|
||||
foreach ($meta->getReflectionProperties() as $property) {
|
||||
|
||||
// Skip properties that have no association
|
||||
if (!$meta->hasAssociation($property->getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$associationMapping = $meta->getAssociationMapping($property->getName());
|
||||
|
||||
// Make sure the association is mapped by the parent property
|
||||
if ($associationMapping['mappedBy'] !== $this->parentField) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $associationMapping['fieldName'];
|
||||
}
|
||||
|
||||
throw new \Gedmo\Exception\InvalidMappingException('The children property could not found. It is identified through the `mappedBy` annotation to your parent property.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param EntityManagerInterface $em
|
||||
* @return TreeListener
|
||||
*/
|
||||
protected function getTreeListener(EntityManagerInterface $em)
|
||||
{
|
||||
foreach ($em->getEventManager()->getListeners() as $listeners) {
|
||||
foreach ($listeners as $listener) {
|
||||
if ($listener instanceof TreeListener) {
|
||||
return $listener;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new \Gedmo\Exception\InvalidMappingException('Tree listener was not found on your entity manager, it must be hooked into the event manager');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
protected function getEntityClassFromHydratedData($data)
|
||||
{
|
||||
$firstMappedEntity = array_values($data);
|
||||
$firstMappedEntity = $firstMappedEntity[0];
|
||||
return $this->_em->getClassMetadata(get_class($firstMappedEntity))->rootEntityName;
|
||||
}
|
||||
|
||||
protected function getPropertyValue($object, $property)
|
||||
{
|
||||
$meta = $this->_em->getClassMetadata(get_class($object));
|
||||
return $meta->getReflectionProperty($property)->getValue($object);
|
||||
}
|
||||
|
||||
public function setPropertyValue($object, $property, $value)
|
||||
{
|
||||
$meta = $this->_em->getClassMetadata(get_class($object));
|
||||
$meta->getReflectionProperty($property)->setValue($object, $value);
|
||||
}
|
||||
}
|
||||
252
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Driver/Annotation.php
vendored
Normal file
252
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Driver/Annotation.php
vendored
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Mapping\Driver;
|
||||
|
||||
use Gedmo\Mapping\Driver\AbstractAnnotationDriver;
|
||||
use Gedmo\Exception\InvalidMappingException;
|
||||
use Gedmo\Tree\Mapping\Validator;
|
||||
|
||||
/**
|
||||
* This is an annotation mapping driver for Tree
|
||||
* behavioral extension. Used for extraction of extended
|
||||
* metadata from Annotations specifically for Tree
|
||||
* extension.
|
||||
*
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @author <rocco@roccosportal.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class Annotation extends AbstractAnnotationDriver
|
||||
{
|
||||
/**
|
||||
* Annotation to define the tree type
|
||||
*/
|
||||
const TREE = 'Gedmo\\Mapping\\Annotation\\Tree';
|
||||
|
||||
/**
|
||||
* Annotation to mark field as one which will store left value
|
||||
*/
|
||||
const LEFT = 'Gedmo\\Mapping\\Annotation\\TreeLeft';
|
||||
|
||||
/**
|
||||
* Annotation to mark field as one which will store right value
|
||||
*/
|
||||
const RIGHT = 'Gedmo\\Mapping\\Annotation\\TreeRight';
|
||||
|
||||
/**
|
||||
* Annotation to mark relative parent field
|
||||
*/
|
||||
const PARENT = 'Gedmo\\Mapping\\Annotation\\TreeParent';
|
||||
|
||||
/**
|
||||
* Annotation to mark node level
|
||||
*/
|
||||
const LEVEL = 'Gedmo\\Mapping\\Annotation\\TreeLevel';
|
||||
|
||||
/**
|
||||
* Annotation to mark field as tree root
|
||||
*/
|
||||
const ROOT = 'Gedmo\\Mapping\\Annotation\\TreeRoot';
|
||||
|
||||
/**
|
||||
* Annotation to specify closure tree class
|
||||
*/
|
||||
const CLOSURE = 'Gedmo\\Mapping\\Annotation\\TreeClosure';
|
||||
|
||||
/**
|
||||
* Annotation to specify path class
|
||||
*/
|
||||
const PATH = 'Gedmo\\Mapping\\Annotation\\TreePath';
|
||||
|
||||
/**
|
||||
* Annotation to specify path source class
|
||||
*/
|
||||
const PATH_SOURCE = 'Gedmo\\Mapping\\Annotation\\TreePathSource';
|
||||
|
||||
/**
|
||||
* Annotation to specify path hash class
|
||||
*/
|
||||
const PATH_HASH = 'Gedmo\\Mapping\\Annotation\\TreePathHash';
|
||||
|
||||
/**
|
||||
* Annotation to mark the field to be used to hold the lock time
|
||||
*/
|
||||
const LOCK_TIME = 'Gedmo\\Mapping\\Annotation\\TreeLockTime';
|
||||
|
||||
/**
|
||||
* List of tree strategies available
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $strategies = array(
|
||||
'nested',
|
||||
'closure',
|
||||
'materializedPath',
|
||||
);
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function readExtendedMetadata($meta, array &$config)
|
||||
{
|
||||
$validator = new Validator();
|
||||
$class = $this->getMetaReflectionClass($meta);
|
||||
// class annotations
|
||||
if ($annot = $this->reader->getClassAnnotation($class, self::TREE)) {
|
||||
if (!in_array($annot->type, $this->strategies)) {
|
||||
throw new InvalidMappingException("Tree type: {$annot->type} is not available.");
|
||||
}
|
||||
$config['strategy'] = $annot->type;
|
||||
$config['activate_locking'] = $annot->activateLocking;
|
||||
$config['locking_timeout'] = (int) $annot->lockingTimeout;
|
||||
|
||||
if ($config['locking_timeout'] < 1) {
|
||||
throw new InvalidMappingException("Tree Locking Timeout must be at least of 1 second.");
|
||||
}
|
||||
}
|
||||
if ($annot = $this->reader->getClassAnnotation($class, self::CLOSURE)) {
|
||||
if (!$cl = $this->getRelatedClassName($meta, $annot->class)) {
|
||||
throw new InvalidMappingException("Tree closure class: {$annot->class} does not exist.");
|
||||
}
|
||||
$config['closure'] = $cl;
|
||||
}
|
||||
|
||||
// property annotations
|
||||
foreach ($class->getProperties() as $property) {
|
||||
if ($meta->isMappedSuperclass && !$property->isPrivate() ||
|
||||
$meta->isInheritedField($property->name) ||
|
||||
isset($meta->associationMappings[$property->name]['inherited'])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// left
|
||||
if ($this->reader->getPropertyAnnotation($property, self::LEFT)) {
|
||||
$field = $property->getName();
|
||||
if (!$meta->hasField($field)) {
|
||||
throw new InvalidMappingException("Unable to find 'left' - [{$field}] as mapped property in entity - {$meta->name}");
|
||||
}
|
||||
if (!$validator->isValidField($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree left field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
|
||||
}
|
||||
$config['left'] = $field;
|
||||
}
|
||||
// right
|
||||
if ($this->reader->getPropertyAnnotation($property, self::RIGHT)) {
|
||||
$field = $property->getName();
|
||||
if (!$meta->hasField($field)) {
|
||||
throw new InvalidMappingException("Unable to find 'right' - [{$field}] as mapped property in entity - {$meta->name}");
|
||||
}
|
||||
if (!$validator->isValidField($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree right field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
|
||||
}
|
||||
$config['right'] = $field;
|
||||
}
|
||||
// ancestor/parent
|
||||
if ($this->reader->getPropertyAnnotation($property, self::PARENT)) {
|
||||
$field = $property->getName();
|
||||
if (!$meta->isSingleValuedAssociation($field)) {
|
||||
throw new InvalidMappingException("Unable to find ancestor/parent child relation through ancestor field - [{$field}] in class - {$meta->name}");
|
||||
}
|
||||
$config['parent'] = $field;
|
||||
}
|
||||
// root
|
||||
if ($this->reader->getPropertyAnnotation($property, self::ROOT)) {
|
||||
$field = $property->getName();
|
||||
if (!$meta->isSingleValuedAssociation($field)) {
|
||||
if (!$meta->hasField($field)) {
|
||||
throw new InvalidMappingException("Unable to find 'root' - [{$field}] as mapped property in entity - {$meta->name}");
|
||||
}
|
||||
|
||||
if (!$validator->isValidFieldForRoot($meta, $field)) {
|
||||
throw new InvalidMappingException(
|
||||
"Tree root field should be either a literal property ('integer' types or 'string') or a many-to-one association through root field - [{$field}] in class - {$meta->name}"
|
||||
);
|
||||
}
|
||||
}
|
||||
$annotation = $this->reader->getPropertyAnnotation($property, self::ROOT);
|
||||
$config['rootIdentifierMethod'] = $annotation->identifierMethod;
|
||||
$config['root'] = $field;
|
||||
}
|
||||
// level
|
||||
if ($this->reader->getPropertyAnnotation($property, self::LEVEL)) {
|
||||
$field = $property->getName();
|
||||
if (!$meta->hasField($field)) {
|
||||
throw new InvalidMappingException("Unable to find 'level' - [{$field}] as mapped property in entity - {$meta->name}");
|
||||
}
|
||||
if (!$validator->isValidField($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree level field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
|
||||
}
|
||||
$config['level'] = $field;
|
||||
}
|
||||
// path
|
||||
if ($pathAnnotation = $this->reader->getPropertyAnnotation($property, self::PATH)) {
|
||||
$field = $property->getName();
|
||||
if (!$meta->hasField($field)) {
|
||||
throw new InvalidMappingException("Unable to find 'path' - [{$field}] as mapped property in entity - {$meta->name}");
|
||||
}
|
||||
if (!$validator->isValidFieldForPath($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree Path field - [{$field}] type is not valid. It must be string or text in class - {$meta->name}");
|
||||
}
|
||||
if (strlen($pathAnnotation->separator) > 1) {
|
||||
throw new InvalidMappingException("Tree Path field - [{$field}] Separator {$pathAnnotation->separator} is invalid. It must be only one character long.");
|
||||
}
|
||||
$config['path'] = $field;
|
||||
$config['path_separator'] = $pathAnnotation->separator;
|
||||
$config['path_append_id'] = $pathAnnotation->appendId;
|
||||
$config['path_starts_with_separator'] = $pathAnnotation->startsWithSeparator;
|
||||
$config['path_ends_with_separator'] = $pathAnnotation->endsWithSeparator;
|
||||
}
|
||||
// path source
|
||||
if ($this->reader->getPropertyAnnotation($property, self::PATH_SOURCE)) {
|
||||
$field = $property->getName();
|
||||
if (!$meta->hasField($field)) {
|
||||
throw new InvalidMappingException("Unable to find 'path_source' - [{$field}] as mapped property in entity - {$meta->name}");
|
||||
}
|
||||
if (!$validator->isValidFieldForPathSource($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree PathSource field - [{$field}] type is not valid. It can be any of the integer variants, double, float or string in class - {$meta->name}");
|
||||
}
|
||||
$config['path_source'] = $field;
|
||||
}
|
||||
|
||||
// path hash
|
||||
if ($this->reader->getPropertyAnnotation($property, self::PATH_HASH)) {
|
||||
$field = $property->getName();
|
||||
if (!$meta->hasField($field)) {
|
||||
throw new InvalidMappingException("Unable to find 'path_hash' - [{$field}] as mapped property in entity - {$meta->name}");
|
||||
}
|
||||
if (!$validator->isValidFieldForPathHash($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree PathHash field - [{$field}] type is not valid. It can be any of the integer variants, double, float or string in class - {$meta->name}");
|
||||
}
|
||||
$config['path_hash'] = $field;
|
||||
}
|
||||
// lock time
|
||||
|
||||
if ($this->reader->getPropertyAnnotation($property, self::LOCK_TIME)) {
|
||||
$field = $property->getName();
|
||||
if (!$meta->hasField($field)) {
|
||||
throw new InvalidMappingException("Unable to find 'lock_time' - [{$field}] as mapped property in entity - {$meta->name}");
|
||||
}
|
||||
if (!$validator->isValidFieldForLockTime($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree PathSource field - [{$field}] type is not valid. It must be \"date\" in class - {$meta->name}");
|
||||
}
|
||||
$config['lock_time'] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['activate_locking']) && $config['activate_locking'] && !isset($config['lock_time'])) {
|
||||
throw new InvalidMappingException("You need to map a date field as the tree lock time field to activate locking support.");
|
||||
}
|
||||
|
||||
if (!$meta->isMappedSuperclass && $config) {
|
||||
if (isset($config['strategy'])) {
|
||||
if (is_array($meta->identifier) && count($meta->identifier) > 1) {
|
||||
throw new InvalidMappingException("Tree does not support composite identifiers in class - {$meta->name}");
|
||||
}
|
||||
$method = 'validate'.ucfirst($config['strategy']).'TreeMetadata';
|
||||
$validator->$method($meta, $config);
|
||||
} else {
|
||||
throw new InvalidMappingException("Cannot find Tree type for class: {$meta->name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
267
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Driver/Xml.php
vendored
Normal file
267
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Driver/Xml.php
vendored
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Mapping\Driver;
|
||||
|
||||
use Gedmo\Mapping\Driver\Xml as BaseXml;
|
||||
use Gedmo\Exception\InvalidMappingException;
|
||||
use Gedmo\Tree\Mapping\Validator;
|
||||
|
||||
/**
|
||||
* This is a xml mapping driver for Tree
|
||||
* behavioral extension. Used for extraction of extended
|
||||
* metadata from xml specifically for Tree
|
||||
* extension.
|
||||
*
|
||||
* @author Gustavo Falco <comfortablynumb84@gmail.com>
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @author Miha Vrhovnik <miha.vrhovnik@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class Xml extends BaseXml
|
||||
{
|
||||
/**
|
||||
* List of tree strategies available
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $strategies = array(
|
||||
'nested',
|
||||
'closure',
|
||||
'materializedPath',
|
||||
);
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function readExtendedMetadata($meta, array &$config)
|
||||
{
|
||||
/**
|
||||
* @var \SimpleXmlElement $xml
|
||||
*/
|
||||
$xml = $this->_getMapping($meta->name);
|
||||
$xmlDoctrine = $xml;
|
||||
$xml = $xml->children(self::GEDMO_NAMESPACE_URI);
|
||||
$validator = new Validator();
|
||||
|
||||
if (isset($xml->tree) && $this->_isAttributeSet($xml->tree, 'type')) {
|
||||
$strategy = $this->_getAttribute($xml->tree, 'type');
|
||||
if (!in_array($strategy, $this->strategies)) {
|
||||
throw new InvalidMappingException("Tree type: $strategy is not available.");
|
||||
}
|
||||
$config['strategy'] = $strategy;
|
||||
$config['activate_locking'] = $this->_getAttribute($xml->tree, 'activate-locking') === 'true' ? true : false;
|
||||
|
||||
if ($lockingTimeout = $this->_getAttribute($xml->tree, 'locking-timeout')) {
|
||||
$config['locking_timeout'] = (int) $lockingTimeout;
|
||||
|
||||
if ($config['locking_timeout'] < 1) {
|
||||
throw new InvalidMappingException("Tree Locking Timeout must be at least of 1 second.");
|
||||
}
|
||||
} else {
|
||||
$config['locking_timeout'] = 3;
|
||||
}
|
||||
}
|
||||
if (isset($xml->{'tree-closure'}) && $this->_isAttributeSet($xml->{'tree-closure'}, 'class')) {
|
||||
$class = $this->_getAttribute($xml->{'tree-closure'}, 'class');
|
||||
if (!$cl = $this->getRelatedClassName($meta, $class)) {
|
||||
throw new InvalidMappingException("Tree closure class: {$class} does not exist.");
|
||||
}
|
||||
$config['closure'] = $cl;
|
||||
}
|
||||
if (isset($xmlDoctrine->field)) {
|
||||
foreach ($xmlDoctrine->field as $mapping) {
|
||||
$mappingDoctrine = $mapping;
|
||||
$mapping = $mapping->children(self::GEDMO_NAMESPACE_URI);
|
||||
|
||||
$field = $this->_getAttribute($mappingDoctrine, 'name');
|
||||
if (isset($mapping->{'tree-left'})) {
|
||||
if (!$validator->isValidField($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree left field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
|
||||
}
|
||||
$config['left'] = $field;
|
||||
} elseif (isset($mapping->{'tree-right'})) {
|
||||
if (!$validator->isValidField($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree right field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
|
||||
}
|
||||
$config['right'] = $field;
|
||||
} elseif (isset($mapping->{'tree-root'})) {
|
||||
if (!$validator->isValidFieldForRoot($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree root field - [{$field}] type is not valid and must be any of the 'integer' types or 'string' in class - {$meta->name}");
|
||||
}
|
||||
$config['root'] = $field;
|
||||
} elseif (isset($mapping->{'tree-level'})) {
|
||||
if (!$validator->isValidField($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree level field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
|
||||
}
|
||||
$config['level'] = $field;
|
||||
} elseif (isset($mapping->{'tree-path'})) {
|
||||
if (!$validator->isValidFieldForPath($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree Path field - [{$field}] type is not valid. It must be string or text in class - {$meta->name}");
|
||||
}
|
||||
|
||||
$separator = $this->_getAttribute($mapping->{'tree-path'}, 'separator');
|
||||
|
||||
if (strlen($separator) > 1) {
|
||||
throw new InvalidMappingException("Tree Path field - [{$field}] Separator {$separator} is invalid. It must be only one character long.");
|
||||
}
|
||||
|
||||
$appendId = $this->_getAttribute($mapping->{'tree-path'}, 'append_id');
|
||||
|
||||
if (!$appendId) {
|
||||
$appendId = true;
|
||||
} else {
|
||||
$appendId = strtolower($appendId) == 'false' ? false : true;
|
||||
}
|
||||
|
||||
$startsWithSeparator = $this->_getAttribute($mapping->{'tree-path'}, 'starts_with_separator');
|
||||
|
||||
if (!$startsWithSeparator) {
|
||||
$startsWithSeparator = false;
|
||||
} else {
|
||||
$startsWithSeparator = strtolower($startsWithSeparator) == 'false' ? false : true;
|
||||
}
|
||||
|
||||
$endsWithSeparator = $this->_getAttribute($mapping->{'tree-path'}, 'ends_with_separator');
|
||||
|
||||
if (!$endsWithSeparator) {
|
||||
$endsWithSeparator = true;
|
||||
} else {
|
||||
$endsWithSeparator = strtolower($endsWithSeparator) == 'false' ? false : true;
|
||||
}
|
||||
|
||||
$config['path'] = $field;
|
||||
$config['path_separator'] = $separator;
|
||||
$config['path_append_id'] = $appendId;
|
||||
$config['path_starts_with_separator'] = $startsWithSeparator;
|
||||
$config['path_ends_with_separator'] = $endsWithSeparator;
|
||||
} elseif (isset($mapping->{'tree-path-source'})) {
|
||||
if (!$validator->isValidFieldForPathSource($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree PathSource field - [{$field}] type is not valid. It can be any of the integer variants, double, float or string in class - {$meta->name}");
|
||||
}
|
||||
$config['path_source'] = $field;
|
||||
} elseif (isset($mapping->{'tree-lock-time'})) {
|
||||
if (!$validator->isValidFieldForLockTime($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree LockTime field - [{$field}] type is not valid. It must be \"date\" in class - {$meta->name}");
|
||||
}
|
||||
$config['lock_time'] = $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['activate_locking']) && $config['activate_locking'] && !isset($config['lock_time'])) {
|
||||
throw new InvalidMappingException("You need to map a date field as the tree lock time field to activate locking support.");
|
||||
}
|
||||
|
||||
if ($xmlDoctrine->getName() == 'mapped-superclass') {
|
||||
if (isset($xmlDoctrine->{'many-to-one'})) {
|
||||
foreach ($xmlDoctrine->{'many-to-one'} as $manyToOneMapping) {
|
||||
/**
|
||||
* @var \SimpleXMLElement $manyToOneMapping
|
||||
*/
|
||||
$manyToOneMappingDoctrine = $manyToOneMapping;
|
||||
$manyToOneMapping = $manyToOneMapping->children(self::GEDMO_NAMESPACE_URI);
|
||||
if (isset($manyToOneMapping->{'tree-parent'})) {
|
||||
$field = $this->_getAttribute($manyToOneMappingDoctrine, 'field');
|
||||
$targetEntity = $meta->associationMappings[$field]['targetEntity'];
|
||||
if (!$cl = $this->getRelatedClassName($meta, $targetEntity)) {
|
||||
throw new InvalidMappingException("Unable to find ancestor/parent child relation through ancestor field - [{$field}] in class - {$meta->name}");
|
||||
}
|
||||
$config['parent'] = $field;
|
||||
}
|
||||
if (isset($manyToOneMapping->{'tree-root'})) {
|
||||
$field = $this->_getAttribute($manyToOneMappingDoctrine, 'field');
|
||||
$targetEntity = $meta->associationMappings[$field]['targetEntity'];
|
||||
if (!$cl = $this->getRelatedClassName($meta, $targetEntity)) {
|
||||
throw new InvalidMappingException("Unable to find root descendant relation through root field - [{$field}] in class - {$meta->name}");
|
||||
}
|
||||
$config['root'] = $field;
|
||||
}
|
||||
}
|
||||
} elseif (isset($xmlDoctrine->{'reference-one'})) {
|
||||
foreach ($xmlDoctrine->{'reference-one'} as $referenceOneMapping) {
|
||||
/**
|
||||
* @var \SimpleXMLElement $referenceOneMapping
|
||||
*/
|
||||
$referenceOneMappingDoctrine = $referenceOneMapping;
|
||||
$referenceOneMapping = $referenceOneMapping->children(self::GEDMO_NAMESPACE_URI);
|
||||
if (isset($referenceOneMapping->{'tree-parent'})) {
|
||||
$field = $this->_getAttribute($referenceOneMappingDoctrine, 'field');
|
||||
if (!$cl = $this->getRelatedClassName($meta, $this->_getAttribute($referenceOneMappingDoctrine, 'target-document'))) {
|
||||
throw new InvalidMappingException("Unable to find ancestor/parent child relation through ancestor field - [{$field}] in class - {$meta->name}");
|
||||
}
|
||||
$config['parent'] = $field;
|
||||
}
|
||||
if (isset($referenceOneMapping->{'tree-root'})) {
|
||||
$field = $this->_getAttribute($referenceOneMappingDoctrine, 'field');
|
||||
if (!$cl = $this->getRelatedClassName($meta, $this->_getAttribute($referenceOneMappingDoctrine, 'target-document'))) {
|
||||
throw new InvalidMappingException("Unable to find root descendant relation through root field - [{$field}] in class - {$meta->name}");
|
||||
}
|
||||
$config['root'] = $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($xmlDoctrine->getName() == 'entity') {
|
||||
if (isset($xmlDoctrine->{'many-to-one'})) {
|
||||
foreach ($xmlDoctrine->{'many-to-one'} as $manyToOneMapping) {
|
||||
/**
|
||||
* @var \SimpleXMLElement $manyToOneMapping
|
||||
*/
|
||||
$manyToOneMappingDoctrine = $manyToOneMapping;
|
||||
$manyToOneMapping = $manyToOneMapping->children(self::GEDMO_NAMESPACE_URI);
|
||||
if (isset($manyToOneMapping->{'tree-parent'})) {
|
||||
$field = $this->_getAttribute($manyToOneMappingDoctrine, 'field');
|
||||
$targetEntity = $meta->associationMappings[$field]['targetEntity'];
|
||||
if (!$cl = $this->getRelatedClassName($meta, $targetEntity)) {
|
||||
throw new InvalidMappingException("Unable to find ancestor/parent child relation through ancestor field - [{$field}] in class - {$meta->name}");
|
||||
}
|
||||
$config['parent'] = $field;
|
||||
}
|
||||
if (isset($manyToOneMapping->{'tree-root'})) {
|
||||
$field = $this->_getAttribute($manyToOneMappingDoctrine, 'field');
|
||||
$targetEntity = $meta->associationMappings[$field]['targetEntity'];
|
||||
if (!$cl = $this->getRelatedClassName($meta, $targetEntity)) {
|
||||
throw new InvalidMappingException("Unable to find root descendant relation through root field - [{$field}] in class - {$meta->name}");
|
||||
}
|
||||
$config['root'] = $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ($xmlDoctrine->getName() == 'document') {
|
||||
if (isset($xmlDoctrine->{'reference-one'})) {
|
||||
foreach ($xmlDoctrine->{'reference-one'} as $referenceOneMapping) {
|
||||
/**
|
||||
* @var \SimpleXMLElement $referenceOneMapping
|
||||
*/
|
||||
$referenceOneMappingDoctrine = $referenceOneMapping;
|
||||
$referenceOneMapping = $referenceOneMapping->children(self::GEDMO_NAMESPACE_URI);
|
||||
if (isset($referenceOneMapping->{'tree-parent'})) {
|
||||
$field = $this->_getAttribute($referenceOneMappingDoctrine, 'field');
|
||||
if (!$cl = $this->getRelatedClassName($meta, $this->_getAttribute($referenceOneMappingDoctrine, 'target-document'))) {
|
||||
throw new InvalidMappingException("Unable to find ancestor/parent child relation through ancestor field - [{$field}] in class - {$meta->name}");
|
||||
}
|
||||
$config['parent'] = $field;
|
||||
}
|
||||
if (isset($referenceOneMapping->{'tree-root'})) {
|
||||
$field = $this->_getAttribute($referenceOneMappingDoctrine, 'field');
|
||||
if (!$cl = $this->getRelatedClassName($meta, $this->_getAttribute($referenceOneMappingDoctrine, 'target-document'))) {
|
||||
throw new InvalidMappingException("Unable to find root descendant relation through root field - [{$field}] in class - {$meta->name}");
|
||||
}
|
||||
$config['root'] = $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$meta->isMappedSuperclass && $config) {
|
||||
if (isset($config['strategy'])) {
|
||||
if (is_array($meta->identifier) && count($meta->identifier) > 1) {
|
||||
throw new InvalidMappingException("Tree does not support composite identifiers in class - {$meta->name}");
|
||||
}
|
||||
$method = 'validate'.ucfirst($config['strategy']).'TreeMetadata';
|
||||
$validator->$method($meta, $config);
|
||||
} else {
|
||||
throw new InvalidMappingException("Cannot find Tree type for class: {$meta->name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
215
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Driver/Yaml.php
vendored
Normal file
215
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Driver/Yaml.php
vendored
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Mapping\Driver;
|
||||
|
||||
use Gedmo\Mapping\Driver\File;
|
||||
use Gedmo\Mapping\Driver;
|
||||
use Gedmo\Exception\InvalidMappingException;
|
||||
use Gedmo\Tree\Mapping\Validator;
|
||||
|
||||
/**
|
||||
* This is a yaml mapping driver for Tree
|
||||
* behavioral extension. Used for extraction of extended
|
||||
* metadata from yaml specifically for Tree
|
||||
* extension.
|
||||
*
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class Yaml extends File implements Driver
|
||||
{
|
||||
/**
|
||||
* File extension
|
||||
* @var string
|
||||
*/
|
||||
protected $_extension = '.dcm.yml';
|
||||
|
||||
/**
|
||||
* List of tree strategies available
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $strategies = array(
|
||||
'nested',
|
||||
'closure',
|
||||
'materializedPath',
|
||||
);
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function readExtendedMetadata($meta, array &$config)
|
||||
{
|
||||
$mapping = $this->_getMapping($meta->name);
|
||||
$validator = new Validator();
|
||||
|
||||
if (isset($mapping['gedmo'])) {
|
||||
$classMapping = $mapping['gedmo'];
|
||||
if (isset($classMapping['tree']['type'])) {
|
||||
$strategy = $classMapping['tree']['type'];
|
||||
if (!in_array($strategy, $this->strategies)) {
|
||||
throw new InvalidMappingException("Tree type: $strategy is not available.");
|
||||
}
|
||||
$config['strategy'] = $strategy;
|
||||
$config['activate_locking'] = isset($classMapping['tree']['activateLocking']) ?
|
||||
$classMapping['tree']['activateLocking'] : false;
|
||||
$config['locking_timeout'] = isset($classMapping['tree']['lockingTimeout']) ?
|
||||
(int) $classMapping['tree']['lockingTimeout'] : 3;
|
||||
|
||||
if ($config['locking_timeout'] < 1) {
|
||||
throw new InvalidMappingException("Tree Locking Timeout must be at least of 1 second.");
|
||||
}
|
||||
}
|
||||
if (isset($classMapping['tree']['closure'])) {
|
||||
if (!$class = $this->getRelatedClassName($meta, $classMapping['tree']['closure'])) {
|
||||
throw new InvalidMappingException("Tree closure class: {$classMapping['tree']['closure']} does not exist.");
|
||||
}
|
||||
$config['closure'] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($mapping['id'])) {
|
||||
foreach($mapping['id'] as $field => $fieldMapping) {
|
||||
if (isset($fieldMapping['gedmo'])) {
|
||||
if (in_array('treePathSource', $fieldMapping['gedmo'])) {
|
||||
if (!$validator->isValidFieldForPathSource($meta, $field)) {
|
||||
throw new InvalidMappingException(
|
||||
"Tree PathSource field - [{$field}] type is not valid. It can be any of the integer variants, double, float or string in class - {$meta->name}"
|
||||
);
|
||||
}
|
||||
$config['path_source'] = $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($mapping['fields'])) {
|
||||
foreach ($mapping['fields'] as $field => $fieldMapping) {
|
||||
if (isset($fieldMapping['gedmo'])) {
|
||||
if (in_array('treeLeft', $fieldMapping['gedmo'])) {
|
||||
if (!$validator->isValidField($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree left field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
|
||||
}
|
||||
$config['left'] = $field;
|
||||
} elseif (in_array('treeRight', $fieldMapping['gedmo'])) {
|
||||
if (!$validator->isValidField($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree right field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
|
||||
}
|
||||
$config['right'] = $field;
|
||||
} elseif (in_array('treeLevel', $fieldMapping['gedmo'])) {
|
||||
if (!$validator->isValidField($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree level field - [{$field}] type is not valid and must be 'integer' in class - {$meta->name}");
|
||||
}
|
||||
$config['level'] = $field;
|
||||
} elseif (in_array('treeRoot', $fieldMapping['gedmo'])) {
|
||||
if (!$validator->isValidFieldForRoot($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree root field - [{$field}] type is not valid and must be any of the 'integer' types or 'string' in class - {$meta->name}");
|
||||
}
|
||||
$config['root'] = $field;
|
||||
} elseif (in_array('treePath', $fieldMapping['gedmo']) || isset($fieldMapping['gedmo']['treePath'])) {
|
||||
if (!$validator->isValidFieldForPath($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree Path field - [{$field}] type is not valid. It must be string or text in class - {$meta->name}");
|
||||
}
|
||||
|
||||
$treePathInfo = isset($fieldMapping['gedmo']['treePath']) ? $fieldMapping['gedmo']['treePath'] :
|
||||
$fieldMapping['gedmo'][array_search('treePath', $fieldMapping['gedmo'])];
|
||||
|
||||
if (is_array($treePathInfo) && isset($treePathInfo['separator'])) {
|
||||
$separator = $treePathInfo['separator'];
|
||||
} else {
|
||||
$separator = '|';
|
||||
}
|
||||
|
||||
if (strlen($separator) > 1) {
|
||||
throw new InvalidMappingException("Tree Path field - [{$field}] Separator {$separator} is invalid. It must be only one character long.");
|
||||
}
|
||||
|
||||
if (is_array($treePathInfo) && isset($treePathInfo['appendId'])) {
|
||||
$appendId = $treePathInfo['appendId'];
|
||||
} else {
|
||||
$appendId = null;
|
||||
}
|
||||
|
||||
if (is_array($treePathInfo) && isset($treePathInfo['startsWithSeparator'])) {
|
||||
$startsWithSeparator = $treePathInfo['startsWithSeparator'];
|
||||
} else {
|
||||
$startsWithSeparator = false;
|
||||
}
|
||||
|
||||
if (is_array($treePathInfo) && isset($treePathInfo['endsWithSeparator'])) {
|
||||
$endsWithSeparator = $treePathInfo['endsWithSeparator'];
|
||||
} else {
|
||||
$endsWithSeparator = true;
|
||||
}
|
||||
|
||||
$config['path'] = $field;
|
||||
$config['path_separator'] = $separator;
|
||||
$config['path_append_id'] = $appendId;
|
||||
$config['path_starts_with_separator'] = $startsWithSeparator;
|
||||
$config['path_ends_with_separator'] = $endsWithSeparator;
|
||||
} elseif (in_array('treePathSource', $fieldMapping['gedmo'])) {
|
||||
if (!$validator->isValidFieldForPathSource($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree PathSource field - [{$field}] type is not valid. It can be any of the integer variants, double, float or string in class - {$meta->name}");
|
||||
}
|
||||
$config['path_source'] = $field;
|
||||
} elseif (in_array('treePathHash', $fieldMapping['gedmo'])) {
|
||||
if (!$validator->isValidFieldForPathSource($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree PathHash field - [{$field}] type is not valid and must be 'string' in class - {$meta->name}");
|
||||
}
|
||||
$config['path_hash'] = $field;
|
||||
} elseif (in_array('treeLockTime', $fieldMapping['gedmo'])) {
|
||||
if (!$validator->isValidFieldForLocktime($meta, $field)) {
|
||||
throw new InvalidMappingException("Tree LockTime field - [{$field}] type is not valid. It must be \"date\" in class - {$meta->name}");
|
||||
}
|
||||
$config['lock_time'] = $field;
|
||||
} elseif (in_array('treeParent', $fieldMapping['gedmo'])) {
|
||||
$config['parent'] = $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['activate_locking']) && $config['activate_locking'] && !isset($config['lock_time'])) {
|
||||
throw new InvalidMappingException("You need to map a date|datetime|timestamp field as the tree lock time field to activate locking support.");
|
||||
}
|
||||
|
||||
if (isset($mapping['manyToOne'])) {
|
||||
foreach ($mapping['manyToOne'] as $field => $relationMapping) {
|
||||
if (isset($relationMapping['gedmo'])) {
|
||||
if (in_array('treeParent', $relationMapping['gedmo'])) {
|
||||
if (!$rel = $this->getRelatedClassName($meta, $relationMapping['targetEntity'])) {
|
||||
throw new InvalidMappingException("Unable to find ancestor/parent child relation through ancestor field - [{$field}] in class - {$meta->name}");
|
||||
}
|
||||
$config['parent'] = $field;
|
||||
}
|
||||
if (in_array('treeRoot', $relationMapping['gedmo'])) {
|
||||
if (!$rel = $this->getRelatedClassName($meta, $relationMapping['targetEntity'])) {
|
||||
throw new InvalidMappingException("Unable to find root-descendant relation through root field - [{$field}] in class - {$meta->name}");
|
||||
}
|
||||
$config['root'] = $field;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$meta->isMappedSuperclass && $config) {
|
||||
if (isset($config['strategy'])) {
|
||||
if (is_array($meta->identifier) && count($meta->identifier) > 1) {
|
||||
throw new InvalidMappingException("Tree does not support composite identifiers in class - {$meta->name}");
|
||||
}
|
||||
$method = 'validate'.ucfirst($config['strategy']).'TreeMetadata';
|
||||
$validator->$method($meta, $config);
|
||||
} else {
|
||||
throw new InvalidMappingException("Cannot find Tree type for class: {$meta->name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _loadMappingFile($file)
|
||||
{
|
||||
return \Symfony\Component\Yaml\Yaml::parse(file_get_contents($file));
|
||||
}
|
||||
}
|
||||
18
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Event/Adapter/ODM.php
vendored
Normal file
18
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Event/Adapter/ODM.php
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Mapping\Event\Adapter;
|
||||
|
||||
use Gedmo\Mapping\Event\Adapter\ODM as BaseAdapterODM;
|
||||
use Gedmo\Tree\Mapping\Event\TreeAdapter;
|
||||
|
||||
/**
|
||||
* Doctrine event adapter for ODM adapted
|
||||
* for Tree behavior
|
||||
*
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
final class ODM extends BaseAdapterODM implements TreeAdapter
|
||||
{
|
||||
// Nothing specific yet
|
||||
}
|
||||
18
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Event/Adapter/ORM.php
vendored
Normal file
18
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Event/Adapter/ORM.php
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Mapping\Event\Adapter;
|
||||
|
||||
use Gedmo\Mapping\Event\Adapter\ORM as BaseAdapterORM;
|
||||
use Gedmo\Tree\Mapping\Event\TreeAdapter;
|
||||
|
||||
/**
|
||||
* Doctrine event adapter for ORM adapted
|
||||
* for Tree behavior
|
||||
*
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
final class ORM extends BaseAdapterORM implements TreeAdapter
|
||||
{
|
||||
// Nothing specific yet
|
||||
}
|
||||
16
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Event/TreeAdapter.php
vendored
Normal file
16
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Event/TreeAdapter.php
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Mapping\Event;
|
||||
|
||||
use Gedmo\Mapping\Event\AdapterInterface;
|
||||
|
||||
/**
|
||||
* Doctrine event adapter interface
|
||||
* for Tree behavior
|
||||
*
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
interface TreeAdapter extends AdapterInterface
|
||||
{
|
||||
}
|
||||
240
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Validator.php
vendored
Normal file
240
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Mapping/Validator.php
vendored
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Mapping;
|
||||
|
||||
use Gedmo\Exception\InvalidMappingException;
|
||||
|
||||
/**
|
||||
* This is a validator for all mapping drivers for Tree
|
||||
* behavioral extension, containing methods to validate
|
||||
* mapping information
|
||||
*
|
||||
* @author Gustavo Falco <comfortablynumb84@gmail.com>
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @author <rocco@roccosportal.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class Validator
|
||||
{
|
||||
/**
|
||||
* List of types which are valid for tree fields
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $validTypes = array(
|
||||
'integer',
|
||||
'smallint',
|
||||
'bigint',
|
||||
'int',
|
||||
);
|
||||
|
||||
/**
|
||||
* List of types which are valid for the path (materialized path strategy)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $validPathTypes = array(
|
||||
'string',
|
||||
'text',
|
||||
);
|
||||
|
||||
/**
|
||||
* List of types which are valid for the path source (materialized path strategy)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $validPathSourceTypes = array(
|
||||
'id',
|
||||
'integer',
|
||||
'smallint',
|
||||
'bigint',
|
||||
'string',
|
||||
'int',
|
||||
'float',
|
||||
);
|
||||
|
||||
/**
|
||||
* List of types which are valid for the path hash (materialized path strategy)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $validPathHashTypes = array(
|
||||
'string',
|
||||
);
|
||||
|
||||
/**
|
||||
* List of types which are valid for the path source (materialized path strategy)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $validRootTypes = array(
|
||||
'integer',
|
||||
'smallint',
|
||||
'bigint',
|
||||
'int',
|
||||
'string',
|
||||
'guid',
|
||||
);
|
||||
|
||||
/**
|
||||
* Checks if $field type is valid
|
||||
*
|
||||
* @param object $meta
|
||||
* @param string $field
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isValidField($meta, $field)
|
||||
{
|
||||
$mapping = $meta->getFieldMapping($field);
|
||||
|
||||
return $mapping && in_array($mapping['type'], $this->validTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $field type is valid for Path field
|
||||
*
|
||||
* @param object $meta
|
||||
* @param string $field
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isValidFieldForPath($meta, $field)
|
||||
{
|
||||
$mapping = $meta->getFieldMapping($field);
|
||||
|
||||
return $mapping && in_array($mapping['type'], $this->validPathTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $field type is valid for PathSource field
|
||||
*
|
||||
* @param object $meta
|
||||
* @param string $field
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isValidFieldForPathSource($meta, $field)
|
||||
{
|
||||
$mapping = $meta->getFieldMapping($field);
|
||||
|
||||
return $mapping && in_array($mapping['type'], $this->validPathSourceTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $field type is valid for PathHash field
|
||||
*
|
||||
* @param object $meta
|
||||
* @param string $field
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isValidFieldForPathHash($meta, $field)
|
||||
{
|
||||
$mapping = $meta->getFieldMapping($field);
|
||||
|
||||
return $mapping && in_array($mapping['type'], $this->validPathHashTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $field type is valid for LockTime field
|
||||
*
|
||||
* @param object $meta
|
||||
* @param string $field
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isValidFieldForLockTime($meta, $field)
|
||||
{
|
||||
$mapping = $meta->getFieldMapping($field);
|
||||
|
||||
return $mapping && ($mapping['type'] === 'date' || $mapping['type'] === 'datetime' || $mapping['type'] === 'timestamp');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $field type is valid for Root field
|
||||
*
|
||||
* @param object $meta
|
||||
* @param string $field
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isValidFieldForRoot($meta, $field)
|
||||
{
|
||||
$mapping = $meta->getFieldMapping($field);
|
||||
|
||||
return $mapping && in_array($mapping['type'], $this->validRootTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates metadata for nested type tree
|
||||
*
|
||||
* @param object $meta
|
||||
* @param array $config
|
||||
*
|
||||
* @throws InvalidMappingException
|
||||
*/
|
||||
public function validateNestedTreeMetadata($meta, array $config)
|
||||
{
|
||||
$missingFields = array();
|
||||
if (!isset($config['parent'])) {
|
||||
$missingFields[] = 'ancestor';
|
||||
}
|
||||
if (!isset($config['left'])) {
|
||||
$missingFields[] = 'left';
|
||||
}
|
||||
if (!isset($config['right'])) {
|
||||
$missingFields[] = 'right';
|
||||
}
|
||||
if ($missingFields) {
|
||||
throw new InvalidMappingException("Missing properties: ".implode(', ', $missingFields)." in class - {$meta->name}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates metadata for closure type tree
|
||||
*
|
||||
* @param object $meta
|
||||
* @param array $config
|
||||
*
|
||||
* @throws InvalidMappingException
|
||||
*/
|
||||
public function validateClosureTreeMetadata($meta, array $config)
|
||||
{
|
||||
$missingFields = array();
|
||||
if (!isset($config['parent'])) {
|
||||
$missingFields[] = 'ancestor';
|
||||
}
|
||||
if (!isset($config['closure'])) {
|
||||
$missingFields[] = 'closure class';
|
||||
}
|
||||
if ($missingFields) {
|
||||
throw new InvalidMappingException("Missing properties: ".implode(', ', $missingFields)." in class - {$meta->name}");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates metadata for materialized path type tree
|
||||
*
|
||||
* @param object $meta
|
||||
* @param array $config
|
||||
*
|
||||
* @throws InvalidMappingException
|
||||
*/
|
||||
public function validateMaterializedPathTreeMetadata($meta, array $config)
|
||||
{
|
||||
$missingFields = array();
|
||||
if (!isset($config['parent'])) {
|
||||
$missingFields[] = 'ancestor';
|
||||
}
|
||||
if (!isset($config['path'])) {
|
||||
$missingFields[] = 'path';
|
||||
}
|
||||
if (!isset($config['path_source'])) {
|
||||
$missingFields[] = 'path_source';
|
||||
}
|
||||
if ($missingFields) {
|
||||
throw new InvalidMappingException("Missing properties: ".implode(', ', $missingFields)." in class - {$meta->name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
39
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Node.php
vendored
Normal file
39
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Node.php
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree;
|
||||
|
||||
/**
|
||||
* This interface is not necessary but can be implemented for
|
||||
* Entities which in some cases needs to be identified as
|
||||
* Tree Node
|
||||
*
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
interface Node
|
||||
{
|
||||
// use now annotations instead of predefined methods, this interface is not necessary
|
||||
|
||||
/**
|
||||
* @gedmo:TreeLeft
|
||||
* to mark the field as "tree left" use property annotation @gedmo:TreeLeft
|
||||
* it will use this field to store tree left value
|
||||
*/
|
||||
|
||||
/**
|
||||
* @gedmo:TreeRight
|
||||
* to mark the field as "tree right" use property annotation @gedmo:TreeRight
|
||||
* it will use this field to store tree right value
|
||||
*/
|
||||
|
||||
/**
|
||||
* @gedmo:TreeParent
|
||||
* in every tree there should be link to parent. To identify a relation
|
||||
* as parent relation to child use @Tree:Ancestor annotation on the related property
|
||||
*/
|
||||
|
||||
/**
|
||||
* @gedmo:TreeLevel
|
||||
* level of node.
|
||||
*/
|
||||
}
|
||||
60
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/RepositoryInterface.php
vendored
Normal file
60
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/RepositoryInterface.php
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree;
|
||||
|
||||
/**
|
||||
* This interface ensures a consistent api between repositories for the ORM and the ODM.
|
||||
*
|
||||
* @author Gustavo Falco <comfortablynumb84@gmail.com>
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
interface RepositoryInterface extends RepositoryUtilsInterface
|
||||
{
|
||||
/**
|
||||
* Get all root nodes
|
||||
*
|
||||
* @param string $sortByField
|
||||
* @param string $direction
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRootNodes($sortByField = null, $direction = 'asc');
|
||||
|
||||
/**
|
||||
* Returns an array of nodes suitable for method buildTree
|
||||
*
|
||||
* @param object $node - Root node
|
||||
* @param bool $direct - Obtain direct children?
|
||||
* @param array $options - Options
|
||||
* @param boolean $includeNode - Include node in results?
|
||||
*
|
||||
* @return array - Array of nodes
|
||||
*/
|
||||
public function getNodesHierarchy($node = null, $direct = false, array $options = array(), $includeNode = false);
|
||||
|
||||
/**
|
||||
* Get list of children followed by given $node
|
||||
*
|
||||
* @param object $node - if null, all tree nodes will be taken
|
||||
* @param boolean $direct - true to take only direct children
|
||||
* @param string $sortByField - field name to sort by
|
||||
* @param string $direction - sort direction : "ASC" or "DESC"
|
||||
* @param bool $includeNode - Include the root node in results?
|
||||
*
|
||||
* @return array - list of given $node children, null on failure
|
||||
*/
|
||||
public function getChildren($node = null, $direct = false, $sortByField = null, $direction = 'ASC', $includeNode = false);
|
||||
|
||||
/**
|
||||
* Counts the children of given TreeNode
|
||||
*
|
||||
* @param object $node - if null counts all records in tree
|
||||
* @param boolean $direct - true to count only direct children
|
||||
*
|
||||
* @throws \Gedmo\Exception\InvalidArgumentException - if input is not valid
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function childCount($node = null, $direct = false);
|
||||
}
|
||||
183
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/RepositoryUtils.php
vendored
Normal file
183
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/RepositoryUtils.php
vendored
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree;
|
||||
|
||||
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Gedmo\Exception\InvalidArgumentException;
|
||||
|
||||
class RepositoryUtils implements RepositoryUtilsInterface
|
||||
{
|
||||
/** @var \Doctrine\Common\Persistence\Mapping\ClassMetadata */
|
||||
protected $meta;
|
||||
|
||||
/** @var \Gedmo\Tree\TreeListener */
|
||||
protected $listener;
|
||||
|
||||
/** @var \Doctrine\Common\Persistence\ObjectManager */
|
||||
protected $om;
|
||||
|
||||
/** @var \Gedmo\Tree\RepositoryInterface */
|
||||
protected $repo;
|
||||
|
||||
/**
|
||||
* This index is used to hold the children of a node
|
||||
* when using any of the buildTree related methods.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $childrenIndex = '__children';
|
||||
|
||||
public function __construct(ObjectManager $om, ClassMetadata $meta, $listener, $repo)
|
||||
{
|
||||
$this->om = $om;
|
||||
$this->meta = $meta;
|
||||
$this->listener = $listener;
|
||||
$this->repo = $repo;
|
||||
}
|
||||
|
||||
public function getClassMetadata()
|
||||
{
|
||||
return $this->meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function childrenHierarchy($node = null, $direct = false, array $options = array(), $includeNode = false)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
|
||||
if ($node !== null) {
|
||||
if ($node instanceof $meta->name) {
|
||||
$wrapperClass = $this->om instanceof \Doctrine\ORM\EntityManagerInterface ?
|
||||
'\Gedmo\Tool\Wrapper\EntityWrapper' :
|
||||
'\Gedmo\Tool\Wrapper\MongoDocumentWrapper';
|
||||
$wrapped = new $wrapperClass($node, $this->om);
|
||||
if (!$wrapped->hasValidIdentifier()) {
|
||||
throw new InvalidArgumentException("Node is not managed by UnitOfWork");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$includeNode = true;
|
||||
}
|
||||
|
||||
// Gets the array of $node results. It must be ordered by depth
|
||||
$nodes = $this->repo->getNodesHierarchy($node, $direct, $options, $includeNode);
|
||||
|
||||
return $this->repo->buildTree($nodes, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function buildTree(array $nodes, array $options = array())
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
$nestedTree = $this->repo->buildTreeArray($nodes);
|
||||
|
||||
$default = array(
|
||||
'decorate' => false,
|
||||
'rootOpen' => '<ul>',
|
||||
'rootClose' => '</ul>',
|
||||
'childOpen' => '<li>',
|
||||
'childClose' => '</li>',
|
||||
'nodeDecorator' => function ($node) use ($meta) {
|
||||
// override and change it, guessing which field to use
|
||||
if ($meta->hasField('title')) {
|
||||
$field = 'title';
|
||||
} elseif ($meta->hasField('name')) {
|
||||
$field = 'name';
|
||||
} else {
|
||||
throw new InvalidArgumentException("Cannot find any representation field");
|
||||
}
|
||||
|
||||
return $node[$field];
|
||||
},
|
||||
);
|
||||
$options = array_merge($default, $options);
|
||||
// If you don't want any html output it will return the nested array
|
||||
if (!$options['decorate']) {
|
||||
return $nestedTree;
|
||||
}
|
||||
|
||||
if (!count($nestedTree)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$childrenIndex = $this->childrenIndex;
|
||||
|
||||
$build = function ($tree) use (&$build, &$options, $childrenIndex) {
|
||||
$output = is_string($options['rootOpen']) ? $options['rootOpen'] : $options['rootOpen']($tree);
|
||||
foreach ($tree as $node) {
|
||||
$output .= is_string($options['childOpen']) ? $options['childOpen'] : $options['childOpen']($node);
|
||||
$output .= $options['nodeDecorator']($node);
|
||||
if (count($node[$childrenIndex]) > 0) {
|
||||
$output .= $build($node[$childrenIndex]);
|
||||
}
|
||||
$output .= is_string($options['childClose']) ? $options['childClose'] : $options['childClose']($node);
|
||||
}
|
||||
|
||||
return $output.(is_string($options['rootClose']) ? $options['rootClose'] : $options['rootClose']($tree));
|
||||
};
|
||||
|
||||
return $build($nestedTree);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function buildTreeArray(array $nodes)
|
||||
{
|
||||
$meta = $this->getClassMetadata();
|
||||
$config = $this->listener->getConfiguration($this->om, $meta->name);
|
||||
$nestedTree = array();
|
||||
$l = 0;
|
||||
|
||||
if (count($nodes) > 0) {
|
||||
// Node Stack. Used to help building the hierarchy
|
||||
$stack = array();
|
||||
foreach ($nodes as $child) {
|
||||
$item = $child;
|
||||
$item[$this->childrenIndex] = array();
|
||||
// Number of stack items
|
||||
$l = count($stack);
|
||||
// Check if we're dealing with different levels
|
||||
while ($l > 0 && $stack[$l - 1][$config['level']] >= $item[$config['level']]) {
|
||||
array_pop($stack);
|
||||
$l--;
|
||||
}
|
||||
// Stack is empty (we are inspecting the root)
|
||||
if ($l == 0) {
|
||||
// Assigning the root child
|
||||
$i = count($nestedTree);
|
||||
$nestedTree[$i] = $item;
|
||||
$stack[] = &$nestedTree[$i];
|
||||
} else {
|
||||
// Add child to parent
|
||||
$i = count($stack[$l - 1][$this->childrenIndex]);
|
||||
$stack[$l - 1][$this->childrenIndex][$i] = $item;
|
||||
$stack[] = &$stack[$l - 1][$this->childrenIndex][$i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $nestedTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setChildrenIndex($childrenIndex)
|
||||
{
|
||||
$this->childrenIndex = $childrenIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getChildrenIndex()
|
||||
{
|
||||
return $this->childrenIndex;
|
||||
}
|
||||
}
|
||||
74
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/RepositoryUtilsInterface.php
vendored
Normal file
74
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/RepositoryUtilsInterface.php
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree;
|
||||
|
||||
interface RepositoryUtilsInterface
|
||||
{
|
||||
/**
|
||||
* Retrieves the nested array or the decorated output.
|
||||
*
|
||||
* Uses options to handle decorations
|
||||
*
|
||||
* @throws \Gedmo\Exception\InvalidArgumentException
|
||||
*
|
||||
* @param object $node - from which node to start reordering the tree
|
||||
* @param boolean $direct - true to take only direct children
|
||||
* @param array $options :
|
||||
* decorate: boolean (false) - retrieves tree as UL->LI tree
|
||||
* nodeDecorator: Closure (null) - uses $node as argument and returns decorated item as string
|
||||
* rootOpen: string || Closure ('<ul>') - branch start, closure will be given $children as a parameter
|
||||
* rootClose: string ('</ul>') - branch close
|
||||
* childStart: string || Closure ('<li>') - start of node, closure will be given $node as a parameter
|
||||
* childClose: string ('</li>') - close of node
|
||||
* childSort: array || keys allowed: field: field to sort on, dir: direction. 'asc' or 'desc'
|
||||
* @param boolean $includeNode - Include node on results?
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public function childrenHierarchy($node = null, $direct = false, array $options = array(), $includeNode = false);
|
||||
|
||||
/**
|
||||
* Retrieves the nested array or the decorated output.
|
||||
*
|
||||
* Uses options to handle decorations
|
||||
* NOTE: nodes should be fetched and hydrated as array
|
||||
*
|
||||
* @throws \Gedmo\Exception\InvalidArgumentException
|
||||
*
|
||||
* @param array $nodes - list o nodes to build tree
|
||||
* @param array $options :
|
||||
* decorate: boolean (false) - retrieves tree as UL->LI tree
|
||||
* nodeDecorator: Closure (null) - uses $node as argument and returns decorated item as string
|
||||
* rootOpen: string || Closure ('<ul>') - branch start, closure will be given $children as a parameter
|
||||
* rootClose: string ('</ul>') - branch close
|
||||
* childStart: string || Closure ('<li>') - start of node, closure will be given $node as a parameter
|
||||
* childClose: string ('</li>') - close of node
|
||||
*
|
||||
* @return array|string
|
||||
*/
|
||||
public function buildTree(array $nodes, array $options = array());
|
||||
|
||||
/**
|
||||
* Process nodes and produce an array with the
|
||||
* structure of the tree
|
||||
*
|
||||
* @param array $nodes - Array of nodes
|
||||
*
|
||||
* @return array - Array with tree structure
|
||||
*/
|
||||
public function buildTreeArray(array $nodes);
|
||||
|
||||
/**
|
||||
* Sets the current children index.
|
||||
*
|
||||
* @param string $childrenIndex
|
||||
*/
|
||||
public function setChildrenIndex($childrenIndex);
|
||||
|
||||
/**
|
||||
* Gets the current children index.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getChildrenIndex();
|
||||
}
|
||||
151
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Strategy.php
vendored
Normal file
151
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Strategy.php
vendored
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree;
|
||||
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Gedmo\Mapping\Event\AdapterInterface;
|
||||
|
||||
interface Strategy
|
||||
{
|
||||
/**
|
||||
* NestedSet strategy
|
||||
*/
|
||||
const NESTED = 'nested';
|
||||
|
||||
/**
|
||||
* Closure strategy
|
||||
*/
|
||||
const CLOSURE = 'closure';
|
||||
|
||||
/**
|
||||
* Materialized Path strategy
|
||||
*/
|
||||
const MATERIALIZED_PATH = 'materializedPath';
|
||||
|
||||
/**
|
||||
* Get the name of strategy
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName();
|
||||
|
||||
/**
|
||||
* Initialize strategy with tree listener
|
||||
*
|
||||
* @param TreeListener $listener
|
||||
*/
|
||||
public function __construct(TreeListener $listener);
|
||||
|
||||
/**
|
||||
* Operations after metadata is loaded
|
||||
*
|
||||
* @param ObjectManager $om
|
||||
* @param object $meta
|
||||
*/
|
||||
public function processMetadataLoad($om, $meta);
|
||||
|
||||
/**
|
||||
* Operations on tree node insertion
|
||||
*
|
||||
* @param ObjectManager $om - object manager
|
||||
* @param object $object - node
|
||||
* @param AdapterInterface $ea - event adapter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processScheduledInsertion($om, $object, AdapterInterface $ea);
|
||||
|
||||
/**
|
||||
* Operations on tree node updates
|
||||
*
|
||||
* @param ObjectManager $om - object manager
|
||||
* @param object $object - node
|
||||
* @param AdapterInterface $ea - event adapter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processScheduledUpdate($om, $object, AdapterInterface $ea);
|
||||
|
||||
/**
|
||||
* Operations on tree node delete
|
||||
*
|
||||
* @param ObjectManager $om - object manager
|
||||
* @param object $object - node
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processScheduledDelete($om, $object);
|
||||
|
||||
/**
|
||||
* Operations on tree node removal
|
||||
*
|
||||
* @param ObjectManager $om - object manager
|
||||
* @param object $object - node
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processPreRemove($om, $object);
|
||||
|
||||
/**
|
||||
* Operations on tree node persist
|
||||
*
|
||||
* @param ObjectManager $om - object manager
|
||||
* @param object $object - node
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processPrePersist($om, $object);
|
||||
|
||||
/**
|
||||
* Operations on tree node update
|
||||
*
|
||||
* @param ObjectManager $om - object manager
|
||||
* @param object $object - node
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processPreUpdate($om, $object);
|
||||
|
||||
/**
|
||||
* Operations on tree node insertions
|
||||
*
|
||||
* @param ObjectManager $om - object manager
|
||||
* @param object $object - node
|
||||
* @param AdapterInterface $ea - event adapter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processPostPersist($om, $object, AdapterInterface $ea);
|
||||
|
||||
/**
|
||||
* Operations on tree node updates
|
||||
*
|
||||
* @param ObjectManager $om - object manager
|
||||
* @param object $object - node
|
||||
* @param AdapterInterface $ea - event adapter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processPostUpdate($om, $object, AdapterInterface $ea);
|
||||
|
||||
/**
|
||||
* Operations on tree node removals
|
||||
*
|
||||
* @param ObjectManager $om - object manager
|
||||
* @param object $object - node
|
||||
* @param AdapterInterface $ea - event adapter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processPostRemove($om, $object, AdapterInterface $ea);
|
||||
|
||||
/**
|
||||
* Operations on the end of flush process
|
||||
*
|
||||
* @param ObjectManager $om - object manager
|
||||
* @param AdapterInterface $ea - event adapter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onFlushEnd($om, AdapterInterface $ea);
|
||||
}
|
||||
539
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Strategy/AbstractMaterializedPath.php
vendored
Normal file
539
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Strategy/AbstractMaterializedPath.php
vendored
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Strategy;
|
||||
|
||||
use Gedmo\Tree\Strategy;
|
||||
use Gedmo\Tree\TreeListener;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Doctrine\ODM\MongoDB\UnitOfWork as MongoDBUnitOfWork;
|
||||
use Gedmo\Mapping\Event\AdapterInterface;
|
||||
use Gedmo\Exception\RuntimeException;
|
||||
use Gedmo\Exception\TreeLockingException;
|
||||
|
||||
/**
|
||||
* This strategy makes tree using materialized path strategy
|
||||
*
|
||||
* @author Gustavo Falco <comfortablynumb84@gmail.com>
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @author <rocco@roccosportal.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
|
||||
abstract class AbstractMaterializedPath implements Strategy
|
||||
{
|
||||
const ACTION_INSERT = 'insert';
|
||||
const ACTION_UPDATE = 'update';
|
||||
const ACTION_REMOVE = 'remove';
|
||||
|
||||
/**
|
||||
* TreeListener
|
||||
*
|
||||
* @var AbstractTreeListener
|
||||
*/
|
||||
protected $listener = null;
|
||||
|
||||
/**
|
||||
* Array of objects which were scheduled for path processes
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $scheduledForPathProcess = array();
|
||||
|
||||
/**
|
||||
* Array of objects which were scheduled for path process.
|
||||
* This time, this array contains the objects with their ID
|
||||
* already set
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $scheduledForPathProcessWithIdSet = array();
|
||||
|
||||
/**
|
||||
* Roots of trees which needs to be locked
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $rootsOfTreesWhichNeedsLocking = array();
|
||||
|
||||
/**
|
||||
* Objects which are going to be inserted (set only if tree locking is used)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pendingObjectsToInsert = array();
|
||||
|
||||
/**
|
||||
* Objects which are going to be updated (set only if tree locking is used)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pendingObjectsToUpdate = array();
|
||||
|
||||
/**
|
||||
* Objects which are going to be removed (set only if tree locking is used)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $pendingObjectsToRemove = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(TreeListener $listener)
|
||||
{
|
||||
$this->listener = $listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return Strategy::MATERIALIZED_PATH;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processScheduledInsertion($om, $node, AdapterInterface $ea)
|
||||
{
|
||||
$meta = $om->getClassMetadata(get_class($node));
|
||||
$config = $this->listener->getConfiguration($om, $meta->name);
|
||||
$fieldMapping = $meta->getFieldMapping($config['path_source']);
|
||||
|
||||
if ($meta->isIdentifier($config['path_source']) || $fieldMapping['type'] === 'string') {
|
||||
$this->scheduledForPathProcess[spl_object_hash($node)] = $node;
|
||||
} else {
|
||||
$this->updateNode($om, $node, $ea);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processScheduledUpdate($om, $node, AdapterInterface $ea)
|
||||
{
|
||||
$meta = $om->getClassMetadata(get_class($node));
|
||||
$config = $this->listener->getConfiguration($om, $meta->name);
|
||||
$uow = $om->getUnitOfWork();
|
||||
$changeSet = $ea->getObjectChangeSet($uow, $node);
|
||||
|
||||
if (isset($changeSet[$config['parent']]) || isset($changeSet[$config['path_source']])) {
|
||||
if (isset($changeSet[$config['path']])) {
|
||||
$originalPath = $changeSet[$config['path']][0];
|
||||
} else {
|
||||
$pathProp = $meta->getReflectionProperty($config['path']);
|
||||
$pathProp->setAccessible(true);
|
||||
$originalPath = $pathProp->getValue($node);
|
||||
}
|
||||
|
||||
$this->updateNode($om, $node, $ea);
|
||||
$this->updateChildren($om, $node, $ea, $originalPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPostPersist($om, $node, AdapterInterface $ea)
|
||||
{
|
||||
$oid = spl_object_hash($node);
|
||||
|
||||
if ($this->scheduledForPathProcess && array_key_exists($oid, $this->scheduledForPathProcess)) {
|
||||
$this->scheduledForPathProcessWithIdSet[$oid] = $node;
|
||||
|
||||
unset($this->scheduledForPathProcess[$oid]);
|
||||
|
||||
if (empty($this->scheduledForPathProcess)) {
|
||||
foreach ($this->scheduledForPathProcessWithIdSet as $oid => $node) {
|
||||
$this->updateNode($om, $node, $ea);
|
||||
|
||||
unset($this->scheduledForPathProcessWithIdSet[$oid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->processPostEventsActions($om, $ea, $node, self::ACTION_INSERT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPostUpdate($om, $node, AdapterInterface $ea)
|
||||
{
|
||||
$this->processPostEventsActions($om, $ea, $node, self::ACTION_UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPostRemove($om, $node, AdapterInterface $ea)
|
||||
{
|
||||
$this->processPostEventsActions($om, $ea, $node, self::ACTION_REMOVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onFlushEnd($om, AdapterInterface $ea)
|
||||
{
|
||||
$this->lockTrees($om, $ea);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPreRemove($om, $node)
|
||||
{
|
||||
$this->processPreLockingActions($om, $node, self::ACTION_REMOVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPrePersist($om, $node)
|
||||
{
|
||||
$this->processPreLockingActions($om, $node, self::ACTION_INSERT);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPreUpdate($om, $node)
|
||||
{
|
||||
$this->processPreLockingActions($om, $node, self::ACTION_UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processMetadataLoad($om, $meta)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processScheduledDelete($om, $node)
|
||||
{
|
||||
$meta = $om->getClassMetadata(get_class($node));
|
||||
$config = $this->listener->getConfiguration($om, $meta->name);
|
||||
|
||||
$this->removeNode($om, $meta, $config, $node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the $node
|
||||
*
|
||||
* @param ObjectManager $om
|
||||
* @param object $node - target node
|
||||
* @param AdapterInterface $ea - event adapter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateNode(ObjectManager $om, $node, AdapterInterface $ea)
|
||||
{
|
||||
$oid = spl_object_hash($node);
|
||||
$meta = $om->getClassMetadata(get_class($node));
|
||||
$config = $this->listener->getConfiguration($om, $meta->name);
|
||||
$uow = $om->getUnitOfWork();
|
||||
$parentProp = $meta->getReflectionProperty($config['parent']);
|
||||
$parentProp->setAccessible(true);
|
||||
$parent = $parentProp->getValue($node);
|
||||
$pathProp = $meta->getReflectionProperty($config['path']);
|
||||
$pathProp->setAccessible(true);
|
||||
$pathSourceProp = $meta->getReflectionProperty($config['path_source']);
|
||||
$pathSourceProp->setAccessible(true);
|
||||
$path = (string) $pathSourceProp->getValue($node);
|
||||
|
||||
// We need to avoid the presence of the path separator in the path source
|
||||
if (strpos($path, $config['path_separator']) !== false) {
|
||||
$msg = 'You can\'t use the Path separator ("%s") as a character for your PathSource field value.';
|
||||
|
||||
throw new RuntimeException(sprintf($msg, $config['path_separator']));
|
||||
}
|
||||
|
||||
$fieldMapping = $meta->getFieldMapping($config['path_source']);
|
||||
|
||||
// default behavior: if PathSource field is a string, we append the ID to the path
|
||||
// path_append_id is true: always append id
|
||||
// path_append_id is false: never append id
|
||||
if ($config['path_append_id'] === true || ($fieldMapping['type'] === 'string' && $config['path_append_id'] !== false)) {
|
||||
if (method_exists($meta, 'getIdentifierValue')) {
|
||||
$identifier = $meta->getIdentifierValue($node);
|
||||
} else {
|
||||
$identifierProp = $meta->getReflectionProperty($meta->getSingleIdentifierFieldName());
|
||||
$identifierProp->setAccessible(true);
|
||||
$identifier = $identifierProp->getValue($node);
|
||||
}
|
||||
|
||||
$path .= '-'.$identifier;
|
||||
}
|
||||
|
||||
if ($parent) {
|
||||
// Ensure parent has been initialized in the case where it's a proxy
|
||||
$om->initializeObject($parent);
|
||||
|
||||
$changeSet = $uow->isScheduledForUpdate($parent) ? $ea->getObjectChangeSet($uow, $parent) : false;
|
||||
$pathOrPathSourceHasChanged = $changeSet && (isset($changeSet[$config['path_source']]) || isset($changeSet[$config['path']]));
|
||||
|
||||
if ($pathOrPathSourceHasChanged || !$pathProp->getValue($parent)) {
|
||||
$this->updateNode($om, $parent, $ea);
|
||||
}
|
||||
|
||||
$parentPath = $pathProp->getValue($parent);
|
||||
// if parent path not ends with separator
|
||||
if ($parentPath[strlen($parentPath) - 1] !== $config['path_separator']) {
|
||||
// add separator
|
||||
$path = $pathProp->getValue($parent).$config['path_separator'].$path;
|
||||
} else {
|
||||
// don't add separator
|
||||
$path = $pathProp->getValue($parent).$path;
|
||||
}
|
||||
}
|
||||
|
||||
if ($config['path_starts_with_separator'] && (strlen($path) > 0 && $path[0] !== $config['path_separator'])) {
|
||||
$path = $config['path_separator'].$path;
|
||||
}
|
||||
|
||||
if ($config['path_ends_with_separator'] && ($path[strlen($path) - 1] !== $config['path_separator'])) {
|
||||
$path .= $config['path_separator'];
|
||||
}
|
||||
|
||||
$pathProp->setValue($node, $path);
|
||||
$changes = array(
|
||||
$config['path'] => array(null, $path),
|
||||
);
|
||||
|
||||
if (isset($config['path_hash'])) {
|
||||
$pathHash = md5($path);
|
||||
$pathHashProp = $meta->getReflectionProperty($config['path_hash']);
|
||||
$pathHashProp->setAccessible(true);
|
||||
$pathHashProp->setValue($node, $pathHash);
|
||||
$changes[$config['path_hash']] = array(null, $pathHash);
|
||||
}
|
||||
|
||||
if (isset($config['root'])) {
|
||||
$root = null;
|
||||
|
||||
// Define the root value by grabbing the top of the current path
|
||||
$rootFinderPath = explode($config['path_separator'], $path);
|
||||
$rootIndex = $config['path_starts_with_separator'] ? 1 : 0;
|
||||
$root = $rootFinderPath[$rootIndex];
|
||||
|
||||
// If it is an association, then make it an reference
|
||||
// to the entity
|
||||
if ($meta->hasAssociation($config['root'])) {
|
||||
$rootClass = $meta->getAssociationTargetClass($config['root']);
|
||||
$root = $om->getReference($rootClass, $root);
|
||||
}
|
||||
|
||||
$rootProp = $meta->getReflectionProperty($config['root']);
|
||||
$rootProp->setAccessible(true);
|
||||
$rootProp->setValue($node, $root);
|
||||
$changes[$config['root']] = array(null, $root);
|
||||
}
|
||||
|
||||
if (isset($config['level'])) {
|
||||
$level = substr_count($path, $config['path_separator']);
|
||||
$levelProp = $meta->getReflectionProperty($config['level']);
|
||||
$levelProp->setAccessible(true);
|
||||
$levelProp->setValue($node, $level);
|
||||
$changes[$config['level']] = array(null, $level);
|
||||
}
|
||||
|
||||
if (!$uow instanceof MongoDBUnitOfWork) {
|
||||
$ea->setOriginalObjectProperty($uow, $oid, $config['path'], $path);
|
||||
$uow->scheduleExtraUpdate($node, $changes);
|
||||
} else {
|
||||
$ea->recomputeSingleObjectChangeSet($uow, $meta, $node);
|
||||
}
|
||||
if (isset($config['path_hash'])) {
|
||||
$ea->setOriginalObjectProperty($uow, $oid, $config['path_hash'], $pathHash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update node's children
|
||||
*
|
||||
* @param ObjectManager $om
|
||||
* @param object $node
|
||||
* @param AdapterInterface $ea
|
||||
* @param string $originalPath
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateChildren(ObjectManager $om, $node, AdapterInterface $ea, $originalPath)
|
||||
{
|
||||
$meta = $om->getClassMetadata(get_class($node));
|
||||
$config = $this->listener->getConfiguration($om, $meta->name);
|
||||
$children = $this->getChildren($om, $meta, $config, $originalPath);
|
||||
|
||||
foreach ($children as $child) {
|
||||
$this->updateNode($om, $child, $ea);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process pre-locking actions
|
||||
*
|
||||
* @param ObjectManager $om
|
||||
* @param object $node
|
||||
* @param string $action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processPreLockingActions($om, $node, $action)
|
||||
{
|
||||
$meta = $om->getClassMetadata(get_class($node));
|
||||
$config = $this->listener->getConfiguration($om, $meta->name);
|
||||
|
||||
if ($config['activate_locking']) {
|
||||
;
|
||||
$parentProp = $meta->getReflectionProperty($config['parent']);
|
||||
$parentProp->setAccessible(true);
|
||||
$parentNode = $node;
|
||||
|
||||
while (!is_null($parent = $parentProp->getValue($parentNode))) {
|
||||
$parentNode = $parent;
|
||||
}
|
||||
|
||||
// In some cases, the parent could be a not initialized proxy. In this case, the
|
||||
// "lockTime" field may NOT be loaded yet and have null instead of the date.
|
||||
// We need to be sure that this field has its real value
|
||||
if ($parentNode !== $node && $parentNode instanceof \Doctrine\ODM\MongoDB\Proxy\Proxy) {
|
||||
$reflMethod = new \ReflectionMethod(get_class($parentNode), '__load');
|
||||
$reflMethod->setAccessible(true);
|
||||
|
||||
$reflMethod->invoke($parentNode);
|
||||
}
|
||||
|
||||
// If tree is already locked, we throw an exception
|
||||
$lockTimeProp = $meta->getReflectionProperty($config['lock_time']);
|
||||
$lockTimeProp->setAccessible(true);
|
||||
$lockTime = $lockTimeProp->getValue($parentNode);
|
||||
|
||||
if (!is_null($lockTime)) {
|
||||
$lockTime = $lockTime instanceof \MongoDate ? $lockTime->sec : $lockTime->getTimestamp();
|
||||
}
|
||||
|
||||
if (!is_null($lockTime) && ($lockTime >= (time() - $config['locking_timeout']))) {
|
||||
$msg = 'Tree with root id "%s" is locked.';
|
||||
$id = $meta->getIdentifierValue($parentNode);
|
||||
|
||||
throw new TreeLockingException(sprintf($msg, $id));
|
||||
}
|
||||
|
||||
$this->rootsOfTreesWhichNeedsLocking[spl_object_hash($parentNode)] = $parentNode;
|
||||
|
||||
$oid = spl_object_hash($node);
|
||||
|
||||
switch ($action) {
|
||||
case self::ACTION_INSERT:
|
||||
$this->pendingObjectsToInsert[$oid] = $node;
|
||||
|
||||
break;
|
||||
case self::ACTION_UPDATE:
|
||||
$this->pendingObjectsToUpdate[$oid] = $node;
|
||||
|
||||
break;
|
||||
case self::ACTION_REMOVE:
|
||||
$this->pendingObjectsToRemove[$oid] = $node;
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf('"%s" is not a valid action.', $action));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process pre-locking actions
|
||||
*
|
||||
* @param ObjectManager $om
|
||||
* @param AdapterInterface $ea
|
||||
* @param object $node
|
||||
* @param string $action
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function processPostEventsActions(ObjectManager $om, AdapterInterface $ea, $node, $action)
|
||||
{
|
||||
$meta = $om->getClassMetadata(get_class($node));
|
||||
$config = $this->listener->getConfiguration($om, $meta->name);
|
||||
|
||||
if ($config['activate_locking']) {
|
||||
switch ($action) {
|
||||
case self::ACTION_INSERT:
|
||||
unset($this->pendingObjectsToInsert[spl_object_hash($node)]);
|
||||
|
||||
break;
|
||||
case self::ACTION_UPDATE:
|
||||
unset($this->pendingObjectsToUpdate[spl_object_hash($node)]);
|
||||
|
||||
break;
|
||||
case self::ACTION_REMOVE:
|
||||
unset($this->pendingObjectsToRemove[spl_object_hash($node)]);
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf('"%s" is not a valid action.', $action));
|
||||
}
|
||||
|
||||
if (empty($this->pendingObjectsToInsert) && empty($this->pendingObjectsToUpdate) &&
|
||||
empty($this->pendingObjectsToRemove)) {
|
||||
$this->releaseTreeLocks($om, $ea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks all needed trees
|
||||
*
|
||||
* @param ObjectManager $om
|
||||
* @param AdapterInterface $ea
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function lockTrees(ObjectManager $om, AdapterInterface $ea)
|
||||
{
|
||||
// Do nothing by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases all trees which are locked
|
||||
*
|
||||
* @param ObjectManager $om
|
||||
* @param AdapterInterface $ea
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function releaseTreeLocks(ObjectManager $om, AdapterInterface $ea)
|
||||
{
|
||||
// Do nothing by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove node and its children
|
||||
*
|
||||
* @param ObjectManager $om
|
||||
* @param object $meta - Metadata
|
||||
* @param object $config - config
|
||||
* @param object $node - node to remove
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function removeNode($om, $meta, $config, $node);
|
||||
|
||||
/**
|
||||
* Returns children of the node with its original path
|
||||
*
|
||||
* @param ObjectManager $om
|
||||
* @param object $meta - Metadata
|
||||
* @param object $config - config
|
||||
* @param string $originalPath - original path of object
|
||||
*
|
||||
* @return array|\Traversable
|
||||
*/
|
||||
abstract public function getChildren($om, $meta, $config, $originalPath);
|
||||
}
|
||||
97
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Strategy/ODM/MongoDB/MaterializedPath.php
vendored
Normal file
97
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Strategy/ODM/MongoDB/MaterializedPath.php
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Strategy\ODM\MongoDB;
|
||||
|
||||
use Gedmo\Tree\Strategy\AbstractMaterializedPath;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Gedmo\Mapping\Event\AdapterInterface;
|
||||
use Gedmo\Tool\Wrapper\AbstractWrapper;
|
||||
|
||||
/**
|
||||
* This strategy makes tree using materialized path strategy
|
||||
*
|
||||
* @author Gustavo Falco <comfortablynumb84@gmail.com>
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class MaterializedPath extends AbstractMaterializedPath
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeNode($om, $meta, $config, $node)
|
||||
{
|
||||
$uow = $om->getUnitOfWork();
|
||||
$wrapped = AbstractWrapper::wrap($node, $om);
|
||||
|
||||
// Remove node's children
|
||||
$results = $om->createQueryBuilder()
|
||||
->find($meta->name)
|
||||
->field($config['path'])->equals(new \MongoRegex('/^'.preg_quote($wrapped->getPropertyValue($config['path'])).'.?+/'))
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
foreach ($results as $node) {
|
||||
$uow->scheduleForDelete($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChildren($om, $meta, $config, $originalPath)
|
||||
{
|
||||
return $om->createQueryBuilder()
|
||||
->find($meta->name)
|
||||
->field($config['path'])->equals(new \MongoRegex('/^'.preg_quote($originalPath).'.+/'))
|
||||
->sort($config['path'], 'asc') // This may save some calls to updateNode
|
||||
->getQuery()
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function lockTrees(ObjectManager $om, AdapterInterface $ea)
|
||||
{
|
||||
$uow = $om->getUnitOfWork();
|
||||
|
||||
foreach ($this->rootsOfTreesWhichNeedsLocking as $oid => $root) {
|
||||
$meta = $om->getClassMetadata(get_class($root));
|
||||
$config = $this->listener->getConfiguration($om, $meta->name);
|
||||
$lockTimeProp = $meta->getReflectionProperty($config['lock_time']);
|
||||
$lockTimeProp->setAccessible(true);
|
||||
$lockTimeValue = new \MongoDate();
|
||||
$lockTimeProp->setValue($root, $lockTimeValue);
|
||||
$changes = array(
|
||||
$config['lock_time'] => array(null, $lockTimeValue),
|
||||
);
|
||||
|
||||
$ea->recomputeSingleObjectChangeSet($uow, $meta, $root);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function releaseTreeLocks(ObjectManager $om, AdapterInterface $ea)
|
||||
{
|
||||
$uow = $om->getUnitOfWork();
|
||||
|
||||
foreach ($this->rootsOfTreesWhichNeedsLocking as $oid => $root) {
|
||||
$meta = $om->getClassMetadata(get_class($root));
|
||||
$config = $this->listener->getConfiguration($om, $meta->name);
|
||||
$lockTimeProp = $meta->getReflectionProperty($config['lock_time']);
|
||||
$lockTimeProp->setAccessible(true);
|
||||
$lockTimeValue = null;
|
||||
$lockTimeProp->setValue($root, $lockTimeValue);
|
||||
$changes = array(
|
||||
$config['lock_time'] => array(null, null),
|
||||
);
|
||||
|
||||
$ea->recomputeSingleObjectChangeSet($uow, $meta, $root);
|
||||
|
||||
unset($this->rootsOfTreesWhichNeedsLocking[$oid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
469
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Strategy/ORM/Closure.php
vendored
Normal file
469
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Strategy/ORM/Closure.php
vendored
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Strategy\ORM;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Version;
|
||||
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Gedmo\Tree\Strategy;
|
||||
use Gedmo\Tree\TreeListener;
|
||||
use Gedmo\Tool\Wrapper\AbstractWrapper;
|
||||
use Gedmo\Exception\RuntimeException;
|
||||
use Gedmo\Mapping\Event\AdapterInterface;
|
||||
|
||||
/**
|
||||
* This strategy makes tree act like
|
||||
* a closure table.
|
||||
*
|
||||
* @author Gustavo Adrian <comfortablynumb84@gmail.com>
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class Closure implements Strategy
|
||||
{
|
||||
/**
|
||||
* TreeListener
|
||||
*
|
||||
* @var TreeListener
|
||||
*/
|
||||
protected $listener = null;
|
||||
|
||||
/**
|
||||
* List of pending Nodes, which needs to
|
||||
* be post processed because of having a parent Node
|
||||
* which requires some additional calculations
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $pendingChildNodeInserts = array();
|
||||
|
||||
/**
|
||||
* List of nodes which has their parents updated, but using
|
||||
* new nodes. They have to wait until their parents are inserted
|
||||
* on DB to make the update
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $pendingNodeUpdates = array();
|
||||
|
||||
/**
|
||||
* List of pending Nodes, which needs their "level"
|
||||
* field value set
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $pendingNodesLevelProcess = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(TreeListener $listener)
|
||||
{
|
||||
$this->listener = $listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return Strategy::CLOSURE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processMetadataLoad($em, $meta)
|
||||
{
|
||||
$config = $this->listener->getConfiguration($em, $meta->name);
|
||||
$closureMetadata = $em->getClassMetadata($config['closure']);
|
||||
$cmf = $em->getMetadataFactory();
|
||||
|
||||
if (!$closureMetadata->hasAssociation('ancestor')) {
|
||||
// create ancestor mapping
|
||||
$ancestorMapping = array(
|
||||
'fieldName' => 'ancestor',
|
||||
'id' => false,
|
||||
'joinColumns' => array(
|
||||
array(
|
||||
'name' => 'ancestor',
|
||||
'referencedColumnName' => 'id',
|
||||
'unique' => false,
|
||||
'nullable' => false,
|
||||
'onDelete' => 'CASCADE',
|
||||
'onUpdate' => null,
|
||||
'columnDefinition' => null,
|
||||
),
|
||||
),
|
||||
'inversedBy' => null,
|
||||
'targetEntity' => $meta->name,
|
||||
'cascade' => null,
|
||||
'fetch' => ClassMetadataInfo::FETCH_LAZY,
|
||||
);
|
||||
$closureMetadata->mapManyToOne($ancestorMapping);
|
||||
if (Version::compare('2.3.0-dev') <= 0) {
|
||||
$closureMetadata->reflFields['ancestor'] = $cmf
|
||||
->getReflectionService()
|
||||
->getAccessibleProperty($closureMetadata->name, 'ancestor')
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$closureMetadata->hasAssociation('descendant')) {
|
||||
// create descendant mapping
|
||||
$descendantMapping = array(
|
||||
'fieldName' => 'descendant',
|
||||
'id' => false,
|
||||
'joinColumns' => array(
|
||||
array(
|
||||
'name' => 'descendant',
|
||||
'referencedColumnName' => 'id',
|
||||
'unique' => false,
|
||||
'nullable' => false,
|
||||
'onDelete' => 'CASCADE',
|
||||
'onUpdate' => null,
|
||||
'columnDefinition' => null,
|
||||
),
|
||||
),
|
||||
'inversedBy' => null,
|
||||
'targetEntity' => $meta->name,
|
||||
'cascade' => null,
|
||||
'fetch' => ClassMetadataInfo::FETCH_LAZY,
|
||||
);
|
||||
$closureMetadata->mapManyToOne($descendantMapping);
|
||||
if (Version::compare('2.3.0-dev') <= 0) {
|
||||
$closureMetadata->reflFields['descendant'] = $cmf
|
||||
->getReflectionService()
|
||||
->getAccessibleProperty($closureMetadata->name, 'descendant')
|
||||
;
|
||||
}
|
||||
}
|
||||
// create unique index on ancestor and descendant
|
||||
$indexName = substr(strtoupper("IDX_".md5($closureMetadata->name)), 0, 20);
|
||||
$closureMetadata->table['uniqueConstraints'][$indexName] = array(
|
||||
'columns' => array(
|
||||
$this->getJoinColumnFieldName($em->getClassMetadata($config['closure'])->getAssociationMapping('ancestor')),
|
||||
$this->getJoinColumnFieldName($em->getClassMetadata($config['closure'])->getAssociationMapping('descendant')),
|
||||
),
|
||||
);
|
||||
// this one may not be very useful
|
||||
$indexName = substr(strtoupper("IDX_".md5($meta->name.'depth')), 0, 20);
|
||||
$closureMetadata->table['indexes'][$indexName] = array(
|
||||
'columns' => array('depth'),
|
||||
);
|
||||
if ($cacheDriver = $cmf->getCacheDriver()) {
|
||||
$cacheDriver->save($closureMetadata->name."\$CLASSMETADATA", $closureMetadata, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onFlushEnd($em, AdapterInterface $ea)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPrePersist($em, $node)
|
||||
{
|
||||
$this->pendingChildNodeInserts[spl_object_hash($em)][spl_object_hash($node)] = $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPreUpdate($em, $node)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPreRemove($em, $node)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processScheduledInsertion($em, $node, AdapterInterface $ea)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processScheduledDelete($em, $entity)
|
||||
{
|
||||
}
|
||||
|
||||
protected function getJoinColumnFieldName($association)
|
||||
{
|
||||
if (count($association['joinColumnFieldNames']) > 1) {
|
||||
throw new RuntimeException('More association on field '.$association['fieldName']);
|
||||
}
|
||||
|
||||
return array_shift($association['joinColumnFieldNames']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPostUpdate($em, $entity, AdapterInterface $ea)
|
||||
{
|
||||
$meta = $em->getClassMetadata(get_class($entity));
|
||||
$config = $this->listener->getConfiguration($em, $meta->name);
|
||||
|
||||
// Process TreeLevel field value
|
||||
if (!empty($config)) {
|
||||
$this->setLevelFieldOnPendingNodes($em);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPostRemove($em, $entity, AdapterInterface $ea)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPostPersist($em, $entity, AdapterInterface $ea)
|
||||
{
|
||||
$uow = $em->getUnitOfWork();
|
||||
$emHash = spl_object_hash($em);
|
||||
|
||||
while ($node = array_shift($this->pendingChildNodeInserts[$emHash])) {
|
||||
$meta = $em->getClassMetadata(get_class($node));
|
||||
$config = $this->listener->getConfiguration($em, $meta->name);
|
||||
|
||||
$identifier = $meta->getSingleIdentifierFieldName();
|
||||
$nodeId = $meta->getReflectionProperty($identifier)->getValue($node);
|
||||
$parent = $meta->getReflectionProperty($config['parent'])->getValue($node);
|
||||
|
||||
$closureClass = $config['closure'];
|
||||
$closureMeta = $em->getClassMetadata($closureClass);
|
||||
$closureTable = $closureMeta->getTableName();
|
||||
|
||||
$ancestorColumnName = $this->getJoinColumnFieldName($em->getClassMetadata($config['closure'])->getAssociationMapping('ancestor'));
|
||||
$descendantColumnName = $this->getJoinColumnFieldName($em->getClassMetadata($config['closure'])->getAssociationMapping('descendant'));
|
||||
$depthColumnName = $em->getClassMetadata($config['closure'])->getColumnName('depth');
|
||||
|
||||
$entries = array(
|
||||
array(
|
||||
$ancestorColumnName => $nodeId,
|
||||
$descendantColumnName => $nodeId,
|
||||
$depthColumnName => 0,
|
||||
),
|
||||
);
|
||||
|
||||
if ($parent) {
|
||||
$dql = "SELECT c, a FROM {$closureMeta->name} c";
|
||||
$dql .= " JOIN c.ancestor a";
|
||||
$dql .= " WHERE c.descendant = :parent";
|
||||
$q = $em->createQuery($dql);
|
||||
$q->setParameters(compact('parent'));
|
||||
$ancestors = $q->getArrayResult();
|
||||
|
||||
foreach ($ancestors as $ancestor) {
|
||||
$entries[] = array(
|
||||
$ancestorColumnName => $ancestor['ancestor'][$identifier],
|
||||
$descendantColumnName => $nodeId,
|
||||
$depthColumnName => $ancestor['depth'] + 1,
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($config['level'])) {
|
||||
$this->pendingNodesLevelProcess[$nodeId] = $node;
|
||||
}
|
||||
} elseif (isset($config['level'])) {
|
||||
$uow->scheduleExtraUpdate($node, array($config['level'] => array(null, 1)));
|
||||
$ea->setOriginalObjectProperty($uow, spl_object_hash($node), $config['level'], 1);
|
||||
$levelProp = $meta->getReflectionProperty($config['level']);
|
||||
$levelProp->setValue($node, 1);
|
||||
}
|
||||
|
||||
foreach ($entries as $closure) {
|
||||
if (!$em->getConnection()->insert($closureTable, $closure)) {
|
||||
throw new RuntimeException('Failed to insert new Closure record');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process pending node updates
|
||||
if (!empty($this->pendingNodeUpdates)) {
|
||||
foreach ($this->pendingNodeUpdates as $info) {
|
||||
$this->updateNode($em, $info['node'], $info['oldParent']);
|
||||
}
|
||||
|
||||
$this->pendingNodeUpdates = array();
|
||||
}
|
||||
|
||||
// Process TreeLevel field value
|
||||
$this->setLevelFieldOnPendingNodes($em);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process pending entities to set their "level" value
|
||||
*
|
||||
* @param \Doctrine\Common\Persistence\ObjectManager $em
|
||||
*/
|
||||
protected function setLevelFieldOnPendingNodes(ObjectManager $em)
|
||||
{
|
||||
if (!empty($this->pendingNodesLevelProcess)) {
|
||||
$first = array_slice($this->pendingNodesLevelProcess, 0, 1);
|
||||
$first = array_shift($first);
|
||||
$meta = $em->getClassMetadata(get_class($first));
|
||||
unset($first);
|
||||
$identifier = $meta->getIdentifier();
|
||||
$mapping = $meta->getFieldMapping($identifier[0]);
|
||||
$config = $this->listener->getConfiguration($em, $meta->name);
|
||||
$closureClass = $config['closure'];
|
||||
$closureMeta = $em->getClassMetadata($closureClass);
|
||||
$uow = $em->getUnitOfWork();
|
||||
|
||||
foreach ($this->pendingNodesLevelProcess as $node) {
|
||||
$children = $em->getRepository($meta->name)->children($node);
|
||||
|
||||
foreach ($children as $child) {
|
||||
$this->pendingNodesLevelProcess[AbstractWrapper::wrap($child, $em)->getIdentifier()] = $child;
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid type conversion performance penalty
|
||||
$type = 'integer' === $mapping['type'] ? Connection::PARAM_INT_ARRAY : Connection::PARAM_STR_ARRAY;
|
||||
|
||||
// We calculate levels for all nodes
|
||||
$sql = 'SELECT c.descendant, MAX(c.depth) + 1 AS levelNum ';
|
||||
$sql .= 'FROM '.$closureMeta->getTableName().' c ';
|
||||
$sql .= 'WHERE c.descendant IN (?) ';
|
||||
$sql .= 'GROUP BY c.descendant';
|
||||
|
||||
$levelsAssoc = $em->getConnection()->executeQuery($sql, array(array_keys($this->pendingNodesLevelProcess)), array($type))->fetchAll(\PDO::FETCH_NUM);
|
||||
|
||||
//create key pair array with resultset
|
||||
$levels = array();
|
||||
foreach( $levelsAssoc as $level )
|
||||
{
|
||||
$levels[$level[0]] = $level[1];
|
||||
}
|
||||
$levelsAssoc = null;
|
||||
|
||||
// Now we update levels
|
||||
foreach ($this->pendingNodesLevelProcess as $nodeId => $node) {
|
||||
// Update new level
|
||||
$level = $levels[$nodeId];
|
||||
$levelProp = $meta->getReflectionProperty($config['level']);
|
||||
$uow->scheduleExtraUpdate(
|
||||
$node,
|
||||
array($config['level'] => array(
|
||||
$levelProp->getValue($node), $level,
|
||||
))
|
||||
);
|
||||
$levelProp->setValue($node, $level);
|
||||
$uow->setOriginalEntityProperty(spl_object_hash($node), $config['level'], $level);
|
||||
}
|
||||
|
||||
$this->pendingNodesLevelProcess = array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processScheduledUpdate($em, $node, AdapterInterface $ea)
|
||||
{
|
||||
$meta = $em->getClassMetadata(get_class($node));
|
||||
$config = $this->listener->getConfiguration($em, $meta->name);
|
||||
$uow = $em->getUnitOfWork();
|
||||
$changeSet = $uow->getEntityChangeSet($node);
|
||||
|
||||
if (array_key_exists($config['parent'], $changeSet)) {
|
||||
// If new parent is new, we need to delay the update of the node
|
||||
// until it is inserted on DB
|
||||
$parent = $changeSet[$config['parent']][1] ? AbstractWrapper::wrap($changeSet[$config['parent']][1], $em) : null;
|
||||
|
||||
if ($parent && !$parent->getIdentifier()) {
|
||||
$this->pendingNodeUpdates[spl_object_hash($node)] = array(
|
||||
'node' => $node,
|
||||
'oldParent' => $changeSet[$config['parent']][0],
|
||||
);
|
||||
} else {
|
||||
$this->updateNode($em, $node, $changeSet[$config['parent']][0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update node and closures
|
||||
*
|
||||
* @param EntityManagerInterface $em
|
||||
* @param object $node
|
||||
* @param object $oldParent
|
||||
*/
|
||||
public function updateNode(EntityManagerInterface $em, $node, $oldParent)
|
||||
{
|
||||
$wrapped = AbstractWrapper::wrap($node, $em);
|
||||
$meta = $wrapped->getMetadata();
|
||||
$config = $this->listener->getConfiguration($em, $meta->name);
|
||||
$closureMeta = $em->getClassMetadata($config['closure']);
|
||||
|
||||
$nodeId = $wrapped->getIdentifier();
|
||||
$parent = $wrapped->getPropertyValue($config['parent']);
|
||||
$table = $closureMeta->getTableName();
|
||||
$conn = $em->getConnection();
|
||||
// ensure integrity
|
||||
if ($parent) {
|
||||
$dql = "SELECT COUNT(c) FROM {$closureMeta->name} c";
|
||||
$dql .= " WHERE c.ancestor = :node";
|
||||
$dql .= " AND c.descendant = :parent";
|
||||
$q = $em->createQuery($dql);
|
||||
$q->setParameters(compact('node', 'parent'));
|
||||
if ($q->getSingleScalarResult()) {
|
||||
throw new \Gedmo\Exception\UnexpectedValueException("Cannot set child as parent to node: {$nodeId}");
|
||||
}
|
||||
}
|
||||
|
||||
if ($oldParent) {
|
||||
$subQuery = "SELECT c2.id FROM {$table} c1";
|
||||
$subQuery .= " JOIN {$table} c2 ON c1.descendant = c2.descendant";
|
||||
$subQuery .= " WHERE c1.ancestor = :nodeId AND c2.depth > c1.depth";
|
||||
|
||||
$ids = $conn->executeQuery($subQuery, compact('nodeId'))->fetchAll(\PDO::FETCH_COLUMN);
|
||||
if ($ids) {
|
||||
// using subquery directly, sqlite acts unfriendly
|
||||
$query = "DELETE FROM {$table} WHERE id IN (".implode(', ', $ids).")";
|
||||
if (!empty($ids) && !$conn->executeQuery($query)) {
|
||||
throw new RuntimeException('Failed to remove old closures');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($parent) {
|
||||
$wrappedParent = AbstractWrapper::wrap($parent, $em);
|
||||
$parentId = $wrappedParent->getIdentifier();
|
||||
$query = "SELECT c1.ancestor, c2.descendant, (c1.depth + c2.depth + 1) AS depth";
|
||||
$query .= " FROM {$table} c1, {$table} c2";
|
||||
$query .= " WHERE c1.descendant = :parentId";
|
||||
$query .= " AND c2.ancestor = :nodeId";
|
||||
|
||||
$closures = $conn->fetchAll($query, compact('nodeId', 'parentId'));
|
||||
|
||||
foreach ($closures as $closure) {
|
||||
if (!$conn->insert($table, $closure)) {
|
||||
throw new RuntimeException('Failed to insert new Closure record');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['level'])) {
|
||||
$this->pendingNodesLevelProcess[$nodeId] = $node;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Strategy/ORM/MaterializedPath.php
vendored
Normal file
66
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Strategy/ORM/MaterializedPath.php
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Strategy\ORM;
|
||||
|
||||
use Gedmo\Tree\Strategy\AbstractMaterializedPath;
|
||||
use Gedmo\Tool\Wrapper\AbstractWrapper;
|
||||
|
||||
/**
|
||||
* This strategy makes tree using materialized path strategy
|
||||
*
|
||||
* @author Gustavo Falco <comfortablynumb84@gmail.com>
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class MaterializedPath extends AbstractMaterializedPath
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function removeNode($om, $meta, $config, $node)
|
||||
{
|
||||
$uow = $om->getUnitOfWork();
|
||||
$wrapped = AbstractWrapper::wrap($node, $om);
|
||||
|
||||
$path = addcslashes($wrapped->getPropertyValue($config['path']), '%');
|
||||
|
||||
// Remove node's children
|
||||
$qb = $om->createQueryBuilder();
|
||||
$qb->select('e')
|
||||
->from($config['useObjectClass'], 'e')
|
||||
->where($qb->expr()->like('e.'.$config['path'], $qb->expr()->literal($path.'%')));
|
||||
|
||||
if (isset($config['level'])) {
|
||||
$lvlField = $config['level'];
|
||||
$lvl = $wrapped->getPropertyValue($lvlField);
|
||||
if (!empty($lvl)) {
|
||||
$qb->andWhere($qb->expr()->gt('e.' . $lvlField, $qb->expr()->literal($lvl)));
|
||||
}
|
||||
}
|
||||
|
||||
$results = $qb->getQuery()
|
||||
->execute();
|
||||
|
||||
foreach ($results as $node) {
|
||||
$uow->scheduleForDelete($node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getChildren($om, $meta, $config, $path)
|
||||
{
|
||||
$path = addcslashes($path, '%');
|
||||
$qb = $om->createQueryBuilder($config['useObjectClass']);
|
||||
$qb->select('e')
|
||||
->from($config['useObjectClass'], 'e')
|
||||
->where($qb->expr()->like('e.'.$config['path'], $qb->expr()->literal($path.'%')))
|
||||
->andWhere('e.'.$config['path'].' != :path')
|
||||
->orderBy('e.'.$config['path'], 'asc'); // This may save some calls to updateNode
|
||||
$qb->setParameter('path', $path);
|
||||
|
||||
return $qb->getQuery()
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
718
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Strategy/ORM/Nested.php
vendored
Normal file
718
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Strategy/ORM/Nested.php
vendored
Normal file
|
|
@ -0,0 +1,718 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Strategy\ORM;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||
use Gedmo\Exception\UnexpectedValueException;
|
||||
use Doctrine\ORM\Proxy\Proxy;
|
||||
use Gedmo\Tool\Wrapper\AbstractWrapper;
|
||||
use Gedmo\Tree\Strategy;
|
||||
use Gedmo\Tree\TreeListener;
|
||||
use Gedmo\Mapping\Event\AdapterInterface;
|
||||
|
||||
/**
|
||||
* This strategy makes the tree act like a nested set.
|
||||
*
|
||||
* This behavior can impact the performance of your application
|
||||
* since nested set trees are slow on inserts and updates.
|
||||
*
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class Nested implements Strategy
|
||||
{
|
||||
/**
|
||||
* Previous sibling position
|
||||
*/
|
||||
const PREV_SIBLING = 'PrevSibling';
|
||||
|
||||
/**
|
||||
* Next sibling position
|
||||
*/
|
||||
const NEXT_SIBLING = 'NextSibling';
|
||||
|
||||
/**
|
||||
* Last child position
|
||||
*/
|
||||
const LAST_CHILD = 'LastChild';
|
||||
|
||||
/**
|
||||
* First child position
|
||||
*/
|
||||
const FIRST_CHILD = 'FirstChild';
|
||||
|
||||
/**
|
||||
* TreeListener
|
||||
*
|
||||
* @var TreeListener
|
||||
*/
|
||||
protected $listener = null;
|
||||
|
||||
/**
|
||||
* The max number of "right" field of the
|
||||
* tree in case few root nodes will be persisted
|
||||
* on one flush for node classes
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $treeEdges = array();
|
||||
|
||||
/**
|
||||
* Stores a list of node position strategies
|
||||
* for each node by object hash
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $nodePositions = array();
|
||||
|
||||
/**
|
||||
* Stores a list of delayed nodes for correct order of updates
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $delayedNodes = array();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(TreeListener $listener)
|
||||
{
|
||||
$this->listener = $listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return Strategy::NESTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set node position strategy
|
||||
*
|
||||
* @param string $oid
|
||||
* @param string $position
|
||||
*/
|
||||
public function setNodePosition($oid, $position)
|
||||
{
|
||||
$valid = array(
|
||||
self::FIRST_CHILD,
|
||||
self::LAST_CHILD,
|
||||
self::NEXT_SIBLING,
|
||||
self::PREV_SIBLING,
|
||||
);
|
||||
if (!in_array($position, $valid, false)) {
|
||||
throw new \Gedmo\Exception\InvalidArgumentException("Position: {$position} is not valid in nested set tree");
|
||||
}
|
||||
$this->nodePositions[$oid] = $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processScheduledInsertion($em, $node, AdapterInterface $ea)
|
||||
{
|
||||
/** @var ClassMetadata $meta */
|
||||
$meta = $em->getClassMetadata(get_class($node));
|
||||
$config = $this->listener->getConfiguration($em, $meta->name);
|
||||
|
||||
$meta->getReflectionProperty($config['left'])->setValue($node, 0);
|
||||
$meta->getReflectionProperty($config['right'])->setValue($node, 0);
|
||||
if (isset($config['level'])) {
|
||||
$meta->getReflectionProperty($config['level'])->setValue($node, 0);
|
||||
}
|
||||
if (isset($config['root']) && !$meta->hasAssociation($config['root']) && !isset($config['rootIdentifierMethod'])) {
|
||||
$meta->getReflectionProperty($config['root'])->setValue($node, 0);
|
||||
} else if (isset($config['rootIdentifierMethod']) && is_null($meta->getReflectionProperty($config['root'])->getValue($node))) {
|
||||
$meta->getReflectionProperty($config['root'])->setValue($node, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processScheduledUpdate($em, $node, AdapterInterface $ea)
|
||||
{
|
||||
$meta = $em->getClassMetadata(get_class($node));
|
||||
$config = $this->listener->getConfiguration($em, $meta->name);
|
||||
$uow = $em->getUnitOfWork();
|
||||
|
||||
$changeSet = $uow->getEntityChangeSet($node);
|
||||
if (isset($config['root']) && isset($changeSet[$config['root']])) {
|
||||
throw new \Gedmo\Exception\UnexpectedValueException("Root cannot be changed manually, change parent instead");
|
||||
}
|
||||
|
||||
$oid = spl_object_hash($node);
|
||||
if (isset($changeSet[$config['left']]) && isset($this->nodePositions[$oid])) {
|
||||
$wrapped = AbstractWrapper::wrap($node, $em);
|
||||
$parent = $wrapped->getPropertyValue($config['parent']);
|
||||
// revert simulated changeset
|
||||
$uow->clearEntityChangeSet($oid);
|
||||
$wrapped->setPropertyValue($config['left'], $changeSet[$config['left']][0]);
|
||||
$uow->setOriginalEntityProperty($oid, $config['left'], $changeSet[$config['left']][0]);
|
||||
// set back all other changes
|
||||
foreach ($changeSet as $field => $set) {
|
||||
if ($field !== $config['left']) {
|
||||
if (is_array($set) && array_key_exists(0, $set) && array_key_exists(1, $set)) {
|
||||
$uow->setOriginalEntityProperty($oid, $field, $set[0]);
|
||||
$wrapped->setPropertyValue($field, $set[1]);
|
||||
} else {
|
||||
$uow->setOriginalEntityProperty($oid, $field, $set);
|
||||
$wrapped->setPropertyValue($field, $set);
|
||||
}
|
||||
}
|
||||
}
|
||||
$uow->recomputeSingleEntityChangeSet($meta, $node);
|
||||
$this->updateNode($em, $node, $parent);
|
||||
} elseif (isset($changeSet[$config['parent']])) {
|
||||
$this->updateNode($em, $node, $changeSet[$config['parent']][1]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPostPersist($em, $node, AdapterInterface $ea)
|
||||
{
|
||||
$meta = $em->getClassMetadata(get_class($node));
|
||||
|
||||
$config = $this->listener->getConfiguration($em, $meta->name);
|
||||
$parent = $meta->getReflectionProperty($config['parent'])->getValue($node);
|
||||
$this->updateNode($em, $node, $parent, self::LAST_CHILD);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processScheduledDelete($em, $node)
|
||||
{
|
||||
$meta = $em->getClassMetadata(get_class($node));
|
||||
$config = $this->listener->getConfiguration($em, $meta->name);
|
||||
$uow = $em->getUnitOfWork();
|
||||
|
||||
$wrapped = AbstractWrapper::wrap($node, $em);
|
||||
$leftValue = $wrapped->getPropertyValue($config['left']);
|
||||
$rightValue = $wrapped->getPropertyValue($config['right']);
|
||||
|
||||
if (!$leftValue || !$rightValue) {
|
||||
return;
|
||||
}
|
||||
$rootId = isset($config['root']) ? $wrapped->getPropertyValue($config['root']) : null;
|
||||
$diff = $rightValue - $leftValue + 1;
|
||||
if ($diff > 2) {
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select('node')
|
||||
->from($config['useObjectClass'], 'node')
|
||||
->where($qb->expr()->between('node.' . $config['left'], '?1', '?2'))
|
||||
->setParameters(array(1 => $leftValue, 2 => $rightValue));
|
||||
|
||||
if (isset($config['root'])) {
|
||||
$qb->andWhere($qb->expr()->eq('node.' . $config['root'], ':rid'));
|
||||
$qb->setParameter('rid', $rootId);
|
||||
}
|
||||
$q = $qb->getQuery();
|
||||
// get nodes for deletion
|
||||
$nodes = $q->getResult();
|
||||
foreach ((array)$nodes as $removalNode) {
|
||||
$uow->scheduleForDelete($removalNode);
|
||||
}
|
||||
}
|
||||
$this->shiftRL($em, $config['useObjectClass'], $rightValue + 1, -$diff, $rootId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function onFlushEnd($em, AdapterInterface $ea)
|
||||
{
|
||||
// reset values
|
||||
$this->treeEdges = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPreRemove($em, $node)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPrePersist($em, $node)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPreUpdate($em, $node)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processMetadataLoad($em, $meta)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPostUpdate($em, $entity, AdapterInterface $ea)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processPostRemove($em, $entity, AdapterInterface $ea)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the $node with a diferent $parent
|
||||
* destination
|
||||
*
|
||||
* @param EntityManagerInterface $em
|
||||
* @param object $node - target node
|
||||
* @param object $parent - destination node
|
||||
* @param string $position
|
||||
*
|
||||
* @throws \Gedmo\Exception\UnexpectedValueException
|
||||
*/
|
||||
public function updateNode(EntityManagerInterface $em, $node, $parent, $position = 'FirstChild')
|
||||
{
|
||||
$wrapped = AbstractWrapper::wrap($node, $em);
|
||||
|
||||
/** @var ClassMetadata $meta */
|
||||
$meta = $wrapped->getMetadata();
|
||||
$config = $this->listener->getConfiguration($em, $meta->name);
|
||||
|
||||
$root = isset($config['root']) ? $wrapped->getPropertyValue($config['root']) : null;
|
||||
$identifierField = $meta->getSingleIdentifierFieldName();
|
||||
$nodeId = $wrapped->getIdentifier();
|
||||
|
||||
$left = $wrapped->getPropertyValue($config['left']);
|
||||
$right = $wrapped->getPropertyValue($config['right']);
|
||||
|
||||
$isNewNode = empty($left) && empty($right);
|
||||
if ($isNewNode) {
|
||||
$left = 1;
|
||||
$right = 2;
|
||||
}
|
||||
|
||||
$oid = spl_object_hash($node);
|
||||
if (isset($this->nodePositions[$oid])) {
|
||||
$position = $this->nodePositions[$oid];
|
||||
}
|
||||
$level = 0;
|
||||
$treeSize = $right - $left + 1;
|
||||
$newRoot = null;
|
||||
if ($parent) { // || (!$parent && isset($config['rootIdentifierMethod']))
|
||||
$wrappedParent = AbstractWrapper::wrap($parent, $em);
|
||||
|
||||
$parentRoot = isset($config['root']) ? $wrappedParent->getPropertyValue($config['root']) : null;
|
||||
$parentOid = spl_object_hash($parent);
|
||||
$parentLeft = $wrappedParent->getPropertyValue($config['left']);
|
||||
$parentRight = $wrappedParent->getPropertyValue($config['right']);
|
||||
if (empty($parentLeft) && empty($parentRight)) {
|
||||
// parent node is a new node, but wasn't processed yet (due to Doctrine commit order calculator redordering)
|
||||
// We delay processing of node to the moment parent node will be processed
|
||||
if (!isset($this->delayedNodes[$parentOid])) {
|
||||
$this->delayedNodes[$parentOid] = array();
|
||||
}
|
||||
$this->delayedNodes[$parentOid][] = array('node' => $node, 'position' => $position);
|
||||
|
||||
return;
|
||||
}
|
||||
if (!$isNewNode && $root === $parentRoot && $parentLeft >= $left && $parentRight <= $right) {
|
||||
throw new UnexpectedValueException("Cannot set child as parent to node: {$nodeId}");
|
||||
}
|
||||
if (isset($config['level'])) {
|
||||
$level = $wrappedParent->getPropertyValue($config['level']);
|
||||
}
|
||||
switch ($position) {
|
||||
case self::PREV_SIBLING:
|
||||
if (property_exists($node, 'sibling')) {
|
||||
$wrappedSibling = AbstractWrapper::wrap($node->sibling, $em);
|
||||
$start = $wrappedSibling->getPropertyValue($config['left']);
|
||||
$level++;
|
||||
} else {
|
||||
$newParent = $wrappedParent->getPropertyValue($config['parent']);
|
||||
|
||||
if (is_null($newParent) && ((isset($config['root']) && $config['root'] == $config['parent']) || $isNewNode)) {
|
||||
throw new UnexpectedValueException("Cannot persist sibling for a root node, tree operation is not possible");
|
||||
} else if (is_null($newParent) && (isset($config['root']) || $isNewNode)) {
|
||||
// root is a different column from parent (pointing to another table?), do nothing
|
||||
} else {
|
||||
$wrapped->setPropertyValue($config['parent'], $newParent);
|
||||
}
|
||||
|
||||
$em->getUnitOfWork()->recomputeSingleEntityChangeSet($meta, $node);
|
||||
$start = $parentLeft;
|
||||
}
|
||||
break;
|
||||
|
||||
case self::NEXT_SIBLING:
|
||||
if (property_exists($node, 'sibling')) {
|
||||
$wrappedSibling = AbstractWrapper::wrap($node->sibling, $em);
|
||||
$start = $wrappedSibling->getPropertyValue($config['right']) + 1;
|
||||
$level++;
|
||||
} else {
|
||||
$newParent = $wrappedParent->getPropertyValue($config['parent']);
|
||||
if (is_null($newParent) && ((isset($config['root']) && $config['root'] == $config['parent']) || $isNewNode)) {
|
||||
throw new UnexpectedValueException("Cannot persist sibling for a root node, tree operation is not possible");
|
||||
} else if (is_null($newParent) && (isset($config['root']) || $isNewNode)) {
|
||||
// root is a different column from parent (pointing to another table?), do nothing
|
||||
} else {
|
||||
$wrapped->setPropertyValue($config['parent'], $newParent);
|
||||
}
|
||||
|
||||
$em->getUnitOfWork()->recomputeSingleEntityChangeSet($meta, $node);
|
||||
$start = $parentRight + 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case self::LAST_CHILD:
|
||||
$start = $parentRight;
|
||||
$level++;
|
||||
break;
|
||||
|
||||
case self::FIRST_CHILD:
|
||||
default:
|
||||
$start = $parentLeft + 1;
|
||||
$level++;
|
||||
break;
|
||||
}
|
||||
$this->shiftRL($em, $config['useObjectClass'], $start, $treeSize, $parentRoot);
|
||||
if (!$isNewNode && $root === $parentRoot && $left >= $start) {
|
||||
$left += $treeSize;
|
||||
$wrapped->setPropertyValue($config['left'], $left);
|
||||
}
|
||||
if (!$isNewNode && $root === $parentRoot && $right >= $start) {
|
||||
$right += $treeSize;
|
||||
$wrapped->setPropertyValue($config['right'], $right);
|
||||
}
|
||||
$newRoot = $parentRoot;
|
||||
} elseif (!isset($config['root']) ||
|
||||
($meta->isSingleValuedAssociation($config['root']) && ($newRoot = $meta->getFieldValue($node, $config['root'])))) {
|
||||
|
||||
if (!isset($this->treeEdges[$meta->name])) {
|
||||
$this->treeEdges[$meta->name] = $this->max($em, $config['useObjectClass'], $newRoot) + 1;
|
||||
}
|
||||
|
||||
$level = 0;
|
||||
$parentLeft = 0;
|
||||
$parentRight = $this->treeEdges[$meta->name];
|
||||
$this->treeEdges[$meta->name] += 2;
|
||||
|
||||
switch ($position) {
|
||||
case self::PREV_SIBLING:
|
||||
if (property_exists($node, 'sibling')) {
|
||||
$wrappedSibling = AbstractWrapper::wrap($node->sibling, $em);
|
||||
$start = $wrappedSibling->getPropertyValue($config['left']);
|
||||
} else {
|
||||
$wrapped->setPropertyValue($config['parent'], null);
|
||||
$em->getUnitOfWork()->recomputeSingleEntityChangeSet($meta, $node);
|
||||
$start = $parentLeft + 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case self::NEXT_SIBLING:
|
||||
if (property_exists($node, 'sibling')) {
|
||||
$wrappedSibling = AbstractWrapper::wrap($node->sibling, $em);
|
||||
$start = $wrappedSibling->getPropertyValue($config['right']) + 1;
|
||||
} else {
|
||||
$wrapped->setPropertyValue($config['parent'], null);
|
||||
$em->getUnitOfWork()->recomputeSingleEntityChangeSet($meta, $node);
|
||||
$start = $parentRight;
|
||||
}
|
||||
break;
|
||||
|
||||
case self::LAST_CHILD:
|
||||
$start = $parentRight;
|
||||
break;
|
||||
|
||||
case self::FIRST_CHILD:
|
||||
default:
|
||||
$start = $parentLeft + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
$this->shiftRL($em, $config['useObjectClass'], $start, $treeSize, null);
|
||||
|
||||
if (!$isNewNode && $left >= $start) {
|
||||
$left += $treeSize;
|
||||
$wrapped->setPropertyValue($config['left'], $left);
|
||||
}
|
||||
if (!$isNewNode && $right >= $start) {
|
||||
$right += $treeSize;
|
||||
$wrapped->setPropertyValue($config['right'], $right);
|
||||
}
|
||||
} else {
|
||||
$start = 1;
|
||||
if (isset($config['rootIdentifierMethod'])) {
|
||||
$method = $config['rootIdentifierMethod'];
|
||||
$newRoot = $node->$method();
|
||||
$repo = $em->getRepository($config['useObjectClass']);
|
||||
|
||||
$criteria = new Criteria();
|
||||
$criteria->andWhere(Criteria::expr()->notIn($wrapped->getMetadata()->identifier[0], [$wrapped->getIdentifier()]));
|
||||
$criteria->andWhere(Criteria::expr()->eq($config['root'], $node->$method()));
|
||||
$criteria->andWhere(Criteria::expr()->isNull($config['parent']));
|
||||
$criteria->andWhere(Criteria::expr()->eq($config['level'], 0));
|
||||
$criteria->orderBy([$config['right'] => Criteria::ASC]);
|
||||
$roots = $repo->matching($criteria)->toArray();
|
||||
$last = array_pop($roots);
|
||||
|
||||
$start = ($last) ? $meta->getFieldValue($last, $config['right']) + 1 : 1;
|
||||
|
||||
} else if ($meta->isSingleValuedAssociation($config['root'])) {
|
||||
$newRoot = $node;
|
||||
} else {
|
||||
$newRoot = $wrapped->getIdentifier();
|
||||
}
|
||||
}
|
||||
|
||||
$diff = $start - $left;
|
||||
|
||||
if (!$isNewNode) {
|
||||
$levelDiff = isset($config['level']) ? $level - $wrapped->getPropertyValue($config['level']) : null;
|
||||
$this->shiftRangeRL(
|
||||
$em,
|
||||
$config['useObjectClass'],
|
||||
$left,
|
||||
$right,
|
||||
$diff,
|
||||
$root,
|
||||
$newRoot,
|
||||
$levelDiff
|
||||
);
|
||||
$this->shiftRL($em, $config['useObjectClass'], $left, -$treeSize, $root);
|
||||
} else {
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->update($config['useObjectClass'], 'node');
|
||||
if (isset($config['root'])) {
|
||||
$qb->set('node.' . $config['root'], ':rid');
|
||||
$qb->setParameter('rid', $newRoot);
|
||||
$wrapped->setPropertyValue($config['root'], $newRoot);
|
||||
$em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['root'], $newRoot);
|
||||
}
|
||||
if (isset($config['level'])) {
|
||||
$qb->set('node.' . $config['level'], $level);
|
||||
$wrapped->setPropertyValue($config['level'], $level);
|
||||
$em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['level'], $level);
|
||||
}
|
||||
if (isset($newParent)) {
|
||||
$wrappedNewParent = AbstractWrapper::wrap($newParent, $em);
|
||||
$newParentId = $wrappedNewParent->getIdentifier();
|
||||
$qb->set('node.' . $config['parent'], ':pid');
|
||||
$qb->setParameter('pid', $newParentId);
|
||||
$wrapped->setPropertyValue($config['parent'], $newParent);
|
||||
$em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['parent'], $newParent);
|
||||
}
|
||||
$qb->set('node.' . $config['left'], $left + $diff);
|
||||
$qb->set('node.' . $config['right'], $right + $diff);
|
||||
// node id cannot be null
|
||||
$qb->where($qb->expr()->eq('node.' . $identifierField, ':id'));
|
||||
$qb->setParameter('id', $nodeId);
|
||||
$qb->getQuery()->getSingleScalarResult();
|
||||
$wrapped->setPropertyValue($config['left'], $left + $diff);
|
||||
$wrapped->setPropertyValue($config['right'], $right + $diff);
|
||||
$em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['left'], $left + $diff);
|
||||
$em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['right'], $right + $diff);
|
||||
}
|
||||
if (isset($this->delayedNodes[$oid])) {
|
||||
foreach ($this->delayedNodes[$oid] as $nodeData) {
|
||||
$this->updateNode($em, $nodeData['node'], $node, $nodeData['position']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edge of tree
|
||||
*
|
||||
* @param EntityManagerInterface $em
|
||||
* @param string $class
|
||||
* @param integer $rootId
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function max(EntityManagerInterface $em, $class, $rootId = 0)
|
||||
{
|
||||
$meta = $em->getClassMetadata($class);
|
||||
$config = $this->listener->getConfiguration($em, $meta->name);
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->select($qb->expr()->max('node.' . $config['right']))
|
||||
->from($config['useObjectClass'], 'node');
|
||||
|
||||
if (isset($config['root']) && $rootId) {
|
||||
$qb->where($qb->expr()->eq('node.' . $config['root'], ':rid'));
|
||||
$qb->setParameter('rid', $rootId);
|
||||
}
|
||||
$query = $qb->getQuery();
|
||||
$right = $query->getSingleScalarResult();
|
||||
|
||||
return intval($right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift tree left and right values by delta
|
||||
*
|
||||
* @param EntityManager $em
|
||||
* @param string $class
|
||||
* @param integer $first
|
||||
* @param integer $delta
|
||||
* @param EntityManagerInterface $em
|
||||
* @param string $class
|
||||
* @param integer $first
|
||||
* @param integer $delta
|
||||
* @param integer|string $root
|
||||
*/
|
||||
public function shiftRL(EntityManagerInterface $em, $class, $first, $delta, $root = null)
|
||||
{
|
||||
$meta = $em->getClassMetadata($class);
|
||||
$config = $this->listener->getConfiguration($em, $class);
|
||||
|
||||
$sign = ($delta >= 0) ? ' + ' : ' - ';
|
||||
$absDelta = abs($delta);
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->update($config['useObjectClass'], 'node')
|
||||
->set('node.' . $config['left'], "node.{$config['left']} {$sign} {$absDelta}")
|
||||
->where($qb->expr()->gte('node.' . $config['left'], $first));
|
||||
if (isset($config['root'])) {
|
||||
$qb->andWhere($qb->expr()->eq('node.' . $config['root'], ':rid'));
|
||||
$qb->setParameter('rid', $root);
|
||||
}
|
||||
$qb->getQuery()->getSingleScalarResult();
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->update($config['useObjectClass'], 'node')
|
||||
->set('node.' . $config['right'], "node.{$config['right']} {$sign} {$absDelta}")
|
||||
->where($qb->expr()->gte('node.' . $config['right'], $first));
|
||||
if (isset($config['root'])) {
|
||||
$qb->andWhere($qb->expr()->eq('node.' . $config['root'], ':rid'));
|
||||
$qb->setParameter('rid', $root);
|
||||
}
|
||||
|
||||
$qb->getQuery()->getSingleScalarResult();
|
||||
// update in memory nodes increases performance, saves some IO
|
||||
foreach ($em->getUnitOfWork()->getIdentityMap() as $className => $nodes) {
|
||||
// for inheritance mapped classes, only root is always in the identity map
|
||||
if ($className !== $meta->rootEntityName) {
|
||||
continue;
|
||||
}
|
||||
foreach ($nodes as $node) {
|
||||
if ($node instanceof Proxy && !$node->__isInitialized__) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nodeMeta = $em->getClassMetadata(get_class($node));
|
||||
|
||||
if (!array_key_exists($config['left'], $nodeMeta->getReflectionProperties())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$oid = spl_object_hash($node);
|
||||
$left = $meta->getReflectionProperty($config['left'])->getValue($node);
|
||||
$currentRoot = isset($config['root']) ? $meta->getReflectionProperty($config['root'])->getValue($node) : null;
|
||||
if ($currentRoot === $root && $left >= $first) {
|
||||
$meta->getReflectionProperty($config['left'])->setValue($node, $left + $delta);
|
||||
$em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['left'], $left + $delta);
|
||||
}
|
||||
$right = $meta->getReflectionProperty($config['right'])->getValue($node);
|
||||
if ($currentRoot === $root && $right >= $first) {
|
||||
$meta->getReflectionProperty($config['right'])->setValue($node, $right + $delta);
|
||||
$em->getUnitOfWork()->setOriginalEntityProperty($oid, $config['right'], $right + $delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shift range of right and left values on tree
|
||||
* depending on tree level difference also
|
||||
*
|
||||
* @param EntityManagerInterface $em
|
||||
* @param string $class
|
||||
* @param integer $first
|
||||
* @param integer $last
|
||||
* @param integer $delta
|
||||
* @param integer|string $root
|
||||
* @param integer|string $destRoot
|
||||
* @param integer $levelDelta
|
||||
*/
|
||||
public function shiftRangeRL(EntityManagerInterface $em, $class, $first, $last, $delta, $root = null, $destRoot = null, $levelDelta = null)
|
||||
{
|
||||
$meta = $em->getClassMetadata($class);
|
||||
$config = $this->listener->getConfiguration($em, $class);
|
||||
|
||||
$sign = ($delta >= 0) ? ' + ' : ' - ';
|
||||
$absDelta = abs($delta);
|
||||
$levelSign = ($levelDelta >= 0) ? ' + ' : ' - ';
|
||||
$absLevelDelta = abs($levelDelta);
|
||||
|
||||
$qb = $em->createQueryBuilder();
|
||||
$qb->update($config['useObjectClass'], 'node')
|
||||
->set('node.' . $config['left'], "node.{$config['left']} {$sign} {$absDelta}")
|
||||
->set('node.' . $config['right'], "node.{$config['right']} {$sign} {$absDelta}")
|
||||
->where($qb->expr()->gte('node.' . $config['left'], $first))
|
||||
->andWhere($qb->expr()->lte('node.' . $config['right'], $last));
|
||||
if (isset($config['root'])) {
|
||||
$qb->set('node.' . $config['root'], ':drid');
|
||||
$qb->setParameter('drid', $destRoot);
|
||||
$qb->andWhere($qb->expr()->eq('node.' . $config['root'], ':rid'));
|
||||
$qb->setParameter('rid', $root);
|
||||
}
|
||||
if (isset($config['level'])) {
|
||||
$qb->set('node.' . $config['level'], "node.{$config['level']} {$levelSign} {$absLevelDelta}");
|
||||
}
|
||||
$qb->getQuery()->getSingleScalarResult();
|
||||
// update in memory nodes increases performance, saves some IO
|
||||
foreach ($em->getUnitOfWork()->getIdentityMap() as $className => $nodes) {
|
||||
// for inheritance mapped classes, only root is always in the identity map
|
||||
if ($className !== $meta->rootEntityName) {
|
||||
continue;
|
||||
}
|
||||
foreach ($nodes as $node) {
|
||||
if ($node instanceof Proxy && !$node->__isInitialized__) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$nodeMeta = $em->getClassMetadata(get_class($node));
|
||||
|
||||
if (!array_key_exists($config['left'], $nodeMeta->getReflectionProperties())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$left = $meta->getReflectionProperty($config['left'])->getValue($node);
|
||||
$right = $meta->getReflectionProperty($config['right'])->getValue($node);
|
||||
$currentRoot = isset($config['root']) ? $meta->getReflectionProperty($config['root'])->getValue($node) : null;
|
||||
if ($currentRoot === $root && $left >= $first && $right <= $last) {
|
||||
$oid = spl_object_hash($node);
|
||||
$uow = $em->getUnitOfWork();
|
||||
|
||||
$meta->getReflectionProperty($config['left'])->setValue($node, $left + $delta);
|
||||
$uow->setOriginalEntityProperty($oid, $config['left'], $left + $delta);
|
||||
$meta->getReflectionProperty($config['right'])->setValue($node, $right + $delta);
|
||||
$uow->setOriginalEntityProperty($oid, $config['right'], $right + $delta);
|
||||
if (isset($config['root'])) {
|
||||
$meta->getReflectionProperty($config['root'])->setValue($node, $destRoot);
|
||||
$uow->setOriginalEntityProperty($oid, $config['root'], $destRoot);
|
||||
}
|
||||
if (isset($config['level'])) {
|
||||
$level = $meta->getReflectionProperty($config['level'])->getValue($node);
|
||||
$meta->getReflectionProperty($config['level'])->setValue($node, $level + $levelDelta);
|
||||
$uow->setOriginalEntityProperty($oid, $config['level'], $level + $levelDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
124
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Traits/MaterializedPath.php
vendored
Normal file
124
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Traits/MaterializedPath.php
vendored
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Traits;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
|
||||
/**
|
||||
* MaterializedPath Trait
|
||||
*
|
||||
* @author Steffen Roßkamp <steffen.rosskamp@gimmickmedia.de>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
trait MaterializedPath
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
/**
|
||||
* @var self
|
||||
*/
|
||||
protected $parent;
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
protected $level;
|
||||
/**
|
||||
* @var Collection|self[]
|
||||
*/
|
||||
protected $children;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $hash;
|
||||
|
||||
/**
|
||||
* @param self $parent
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setParent(self $parent = null)
|
||||
{
|
||||
$this->parent = $parent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
public function getParent()
|
||||
{
|
||||
return $this->parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPath($path)
|
||||
{
|
||||
$this->path = $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return integer
|
||||
*/
|
||||
public function getLevel()
|
||||
{
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $hash
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setHash($hash)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getHash()
|
||||
{
|
||||
return $this->hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection|self[] $children
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setChildren($children)
|
||||
{
|
||||
$this->children = $children;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection|self[]
|
||||
*/
|
||||
public function getChildren()
|
||||
{
|
||||
return $this->children = $this->children ?: new ArrayCollection();
|
||||
}
|
||||
}
|
||||
34
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Traits/NestedSet.php
vendored
Normal file
34
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Traits/NestedSet.php
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Traits;
|
||||
|
||||
/**
|
||||
* NestedSet Trait, usable with PHP >= 5.4
|
||||
*
|
||||
* @author Renaat De Muynck <renaat.demuynck@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
trait NestedSet
|
||||
{
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $root;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $level;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $left;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
*/
|
||||
private $right;
|
||||
|
||||
}
|
||||
43
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Traits/NestedSetEntity.php
vendored
Normal file
43
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Traits/NestedSetEntity.php
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Traits;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
|
||||
/**
|
||||
* NestedSet Trait, usable with PHP >= 5.4
|
||||
*
|
||||
* @author Renaat De Muynck <renaat.demuynck@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
trait NestedSetEntity
|
||||
{
|
||||
/**
|
||||
* @var integer
|
||||
* @Gedmo\TreeRoot
|
||||
* @ORM\Column(name="root", type="integer", nullable=true)
|
||||
*/
|
||||
private $root;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
* @Gedmo\TreeLevel
|
||||
* @ORM\Column(name="lvl", type="integer")
|
||||
*/
|
||||
private $level;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
* @Gedmo\TreeLeft
|
||||
* @ORM\Column(name="lft", type="integer")
|
||||
*/
|
||||
private $left;
|
||||
|
||||
/**
|
||||
* @var integer
|
||||
* @Gedmo\TreeRight
|
||||
* @ORM\Column(name="rgt", type="integer")
|
||||
*/
|
||||
private $right;
|
||||
}
|
||||
25
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Traits/NestedSetEntityUuid.php
vendored
Normal file
25
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Traits/NestedSetEntityUuid.php
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree\Traits;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Gedmo\Mapping\Annotation as Gedmo;
|
||||
|
||||
/**
|
||||
* NestedSet Trait with UUid, usable with PHP >= 5.4
|
||||
*
|
||||
* @author Benjamin Lazarecki <benjamin.lazarecki@sensiolabs.com>
|
||||
*
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
trait NestedSetEntityUuid
|
||||
{
|
||||
use NestedSetEntity;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @Gedmo\TreeRoot
|
||||
* @ORM\Column(name="root", type="string", nullable=true)
|
||||
*/
|
||||
private $root;
|
||||
}
|
||||
285
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/TreeListener.php
vendored
Normal file
285
vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/TreeListener.php
vendored
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
<?php
|
||||
|
||||
namespace Gedmo\Tree;
|
||||
|
||||
use Doctrine\Common\EventArgs;
|
||||
use Gedmo\Mapping\MappedEventSubscriber;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
|
||||
/**
|
||||
* The tree listener handles the synchronization of
|
||||
* tree nodes. Can implement different
|
||||
* strategies on handling the tree.
|
||||
*
|
||||
* @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
|
||||
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
|
||||
*/
|
||||
class TreeListener extends MappedEventSubscriber
|
||||
{
|
||||
/**
|
||||
* Tree processing strategies for object classes
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $strategies = array();
|
||||
|
||||
/**
|
||||
* List of strategy instances
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $strategyInstances = array();
|
||||
|
||||
/**
|
||||
* List of used classes on flush
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $usedClassesOnFlush = array();
|
||||
|
||||
/**
|
||||
* Specifies the list of events to listen
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSubscribedEvents()
|
||||
{
|
||||
return array(
|
||||
'prePersist',
|
||||
'preRemove',
|
||||
'preUpdate',
|
||||
'onFlush',
|
||||
'loadClassMetadata',
|
||||
'postPersist',
|
||||
'postUpdate',
|
||||
'postRemove',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the used strategy for tree processing
|
||||
*
|
||||
* @param ObjectManager $om
|
||||
* @param string $class
|
||||
*
|
||||
* @return Strategy
|
||||
*/
|
||||
public function getStrategy(ObjectManager $om, $class)
|
||||
{
|
||||
if (!isset($this->strategies[$class])) {
|
||||
$config = $this->getConfiguration($om, $class);
|
||||
if (!$config) {
|
||||
throw new \Gedmo\Exception\UnexpectedValueException("Tree object class: {$class} must have tree metadata at this point");
|
||||
}
|
||||
$managerName = 'UnsupportedManager';
|
||||
if ($om instanceof \Doctrine\ORM\EntityManagerInterface) {
|
||||
$managerName = 'ORM';
|
||||
} elseif ($om instanceof \Doctrine\ODM\MongoDB\DocumentManager) {
|
||||
$managerName = 'ODM\\MongoDB';
|
||||
}
|
||||
if (!isset($this->strategyInstances[$config['strategy']])) {
|
||||
$strategyClass = $this->getNamespace().'\\Strategy\\'.$managerName.'\\'.ucfirst($config['strategy']);
|
||||
|
||||
if (!class_exists($strategyClass)) {
|
||||
throw new \Gedmo\Exception\InvalidArgumentException($managerName." TreeListener does not support tree type: {$config['strategy']}");
|
||||
}
|
||||
$this->strategyInstances[$config['strategy']] = new $strategyClass($this);
|
||||
}
|
||||
$this->strategies[$class] = $config['strategy'];
|
||||
}
|
||||
|
||||
return $this->strategyInstances[$this->strategies[$class]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for Tree objects being updated
|
||||
* for further processing
|
||||
*
|
||||
* @param EventArgs $args
|
||||
*/
|
||||
public function onFlush(EventArgs $args)
|
||||
{
|
||||
$ea = $this->getEventAdapter($args);
|
||||
$om = $ea->getObjectManager();
|
||||
$uow = $om->getUnitOfWork();
|
||||
|
||||
// check all scheduled updates for TreeNodes
|
||||
foreach ($ea->getScheduledObjectInsertions($uow) as $object) {
|
||||
$meta = $om->getClassMetadata(get_class($object));
|
||||
if ($this->getConfiguration($om, $meta->name)) {
|
||||
$this->usedClassesOnFlush[$meta->name] = null;
|
||||
$this->getStrategy($om, $meta->name)->processScheduledInsertion($om, $object, $ea);
|
||||
$ea->recomputeSingleObjectChangeSet($uow, $meta, $object);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ea->getScheduledObjectUpdates($uow) as $object) {
|
||||
$meta = $om->getClassMetadata(get_class($object));
|
||||
if ($this->getConfiguration($om, $meta->name)) {
|
||||
$this->usedClassesOnFlush[$meta->name] = null;
|
||||
$this->getStrategy($om, $meta->name)->processScheduledUpdate($om, $object, $ea);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ea->getScheduledObjectDeletions($uow) as $object) {
|
||||
$meta = $om->getClassMetadata(get_class($object));
|
||||
if ($this->getConfiguration($om, $meta->name)) {
|
||||
$this->usedClassesOnFlush[$meta->name] = null;
|
||||
$this->getStrategy($om, $meta->name)->processScheduledDelete($om, $object);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getStrategiesUsedForObjects($this->usedClassesOnFlush) as $strategy) {
|
||||
$strategy->onFlushEnd($om, $ea);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates tree on Node removal
|
||||
*
|
||||
* @param EventArgs $args
|
||||
*/
|
||||
public function preRemove(EventArgs $args)
|
||||
{
|
||||
$ea = $this->getEventAdapter($args);
|
||||
$om = $ea->getObjectManager();
|
||||
$object = $ea->getObject();
|
||||
$meta = $om->getClassMetadata(get_class($object));
|
||||
|
||||
if ($this->getConfiguration($om, $meta->name)) {
|
||||
$this->getStrategy($om, $meta->name)->processPreRemove($om, $object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for persisted Nodes
|
||||
*
|
||||
* @param EventArgs $args
|
||||
*/
|
||||
public function prePersist(EventArgs $args)
|
||||
{
|
||||
$ea = $this->getEventAdapter($args);
|
||||
$om = $ea->getObjectManager();
|
||||
$object = $ea->getObject();
|
||||
$meta = $om->getClassMetadata(get_class($object));
|
||||
|
||||
if ($this->getConfiguration($om, $meta->name)) {
|
||||
$this->getStrategy($om, $meta->name)->processPrePersist($om, $object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for updated Nodes
|
||||
*
|
||||
* @param EventArgs $args
|
||||
*/
|
||||
public function preUpdate(EventArgs $args)
|
||||
{
|
||||
$ea = $this->getEventAdapter($args);
|
||||
$om = $ea->getObjectManager();
|
||||
$object = $ea->getObject();
|
||||
$meta = $om->getClassMetadata(get_class($object));
|
||||
|
||||
if ($this->getConfiguration($om, $meta->name)) {
|
||||
$this->getStrategy($om, $meta->name)->processPreUpdate($om, $object);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for pending Nodes to fully synchronize
|
||||
* the tree
|
||||
*
|
||||
* @param EventArgs $args
|
||||
*/
|
||||
public function postPersist(EventArgs $args)
|
||||
{
|
||||
$ea = $this->getEventAdapter($args);
|
||||
$om = $ea->getObjectManager();
|
||||
$object = $ea->getObject();
|
||||
$meta = $om->getClassMetadata(get_class($object));
|
||||
|
||||
if ($this->getConfiguration($om, $meta->name)) {
|
||||
$this->getStrategy($om, $meta->name)->processPostPersist($om, $object, $ea);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for pending Nodes to fully synchronize
|
||||
* the tree
|
||||
*
|
||||
* @param EventArgs $args
|
||||
*/
|
||||
public function postUpdate(EventArgs $args)
|
||||
{
|
||||
$ea = $this->getEventAdapter($args);
|
||||
$om = $ea->getObjectManager();
|
||||
$object = $ea->getObject();
|
||||
$meta = $om->getClassMetadata(get_class($object));
|
||||
|
||||
if ($this->getConfiguration($om, $meta->name)) {
|
||||
$this->getStrategy($om, $meta->name)->processPostUpdate($om, $object, $ea);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for pending Nodes to fully synchronize
|
||||
* the tree
|
||||
*
|
||||
* @param EventArgs $args
|
||||
*/
|
||||
public function postRemove(EventArgs $args)
|
||||
{
|
||||
$ea = $this->getEventAdapter($args);
|
||||
$om = $ea->getObjectManager();
|
||||
$object = $ea->getObject();
|
||||
$meta = $om->getClassMetadata(get_class($object));
|
||||
|
||||
if ($this->getConfiguration($om, $meta->name)) {
|
||||
$this->getStrategy($om, $meta->name)->processPostRemove($om, $object, $ea);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapps additional metadata
|
||||
*
|
||||
* @param EventArgs $eventArgs
|
||||
*/
|
||||
public function loadClassMetadata(EventArgs $eventArgs)
|
||||
{
|
||||
$ea = $this->getEventAdapter($eventArgs);
|
||||
$om = $ea->getObjectManager();
|
||||
$meta = $eventArgs->getClassMetadata();
|
||||
$this->loadMetadataForObjectClass($om, $meta);
|
||||
if (isset(self::$configurations[$this->name][$meta->name]) && self::$configurations[$this->name][$meta->name]) {
|
||||
$this->getStrategy($om, $meta->name)->processMetadataLoad($om, $meta);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function getNamespace()
|
||||
{
|
||||
return __NAMESPACE__;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of strategy instances used for
|
||||
* given object classes
|
||||
*
|
||||
* @param array $classes
|
||||
*
|
||||
* @return Strategy[]
|
||||
*/
|
||||
protected function getStrategiesUsedForObjects(array $classes)
|
||||
{
|
||||
$strategies = array();
|
||||
foreach ($classes as $name => $opt) {
|
||||
if (isset($this->strategies[$name]) && !isset($strategies[$this->strategies[$name]])) {
|
||||
$strategies[$this->strategies[$name]] = $this->strategyInstances[$this->strategies[$name]];
|
||||
}
|
||||
}
|
||||
|
||||
return $strategies;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue