init without trunk
This commit is contained in:
parent
ed24ac4994
commit
bb809e7233
14652 changed files with 177862 additions and 94817 deletions
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue