Skip to content

Commit

Permalink
add ltree trait
Browse files Browse the repository at this point in the history
  • Loading branch information
pvsaintpe committed Apr 30, 2019
1 parent 0d3ed9e commit dc81bea
Show file tree
Hide file tree
Showing 3 changed files with 248 additions and 6 deletions.
6 changes: 3 additions & 3 deletions Repository/LTreeEntityRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public function getTreeBuilder(): TreeBuilderInterface
* @param TreeBuilderInterface $treeBuilder
* @return LTreeEntityRepository
*/
public function setTreeBuilder(TreeBuilderInterface $treeBuilder): LTreeEntityRepositoryInterface
public function setTreeBuilder(TreeBuilderInterface $treeBuilder)
{
$this->treeBuilder = $treeBuilder;

Expand All @@ -79,7 +79,7 @@ public function getPropertyAccessor(): PropertyAccessorInterface
* @param PropertyAccessorInterface $propertyAccessor
* @return LTreeEntityRepository
*/
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor): LTreeEntityRepositoryInterface
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
{
$this->propertyAccessor = $propertyAccessor;

Expand All @@ -101,7 +101,7 @@ public function getAnnotationDriver(): AnnotationDriverInterface
* @param AnnotationDriverInterface $annotationDriver
* @return $this
*/
public function setAnnotationDriver(AnnotationDriverInterface $annotationDriver): LTreeEntityRepositoryInterface
public function setAnnotationDriver(AnnotationDriverInterface $annotationDriver)
{
$this->annotationDriver = $annotationDriver;

Expand Down
6 changes: 3 additions & 3 deletions Repository/LTreeEntityRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@ interface LTreeEntityRepositoryInterface extends ObjectRepository
* @param TreeBuilderInterface $treeBuilder
* @return LTreeEntityRepositoryInterface
*/
public function setTreeBuilder(TreeBuilderInterface $treeBuilder): self;
public function setTreeBuilder(TreeBuilderInterface $treeBuilder);

/**
* @param PropertyAccessorInterface $propertyAccessor
* @return LTreeEntityRepositoryInterface
*/
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor): self;
public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor);

/**
* @param AnnotationDriverInterface $annotationDriver
* @return LTreeEntityRepositoryInterface
*/
public function setAnnotationDriver(AnnotationDriverInterface $annotationDriver): self;
public function setAnnotationDriver(AnnotationDriverInterface $annotationDriver);

/**
* @param object $entity object entity
Expand Down
242 changes: 242 additions & 0 deletions Traits/LTreeTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
<?php

namespace LTree\Traits;

use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use InvalidArgumentException;
use LogicException;
use LTree\Annotation\Driver\AnnotationDriverInterface;
use LTree\DqlFunction\LTreeConcatFunction;
use LTree\DqlFunction\LTreeNlevelFunction;
use LTree\DqlFunction\LTreeOperatorFunction;
use LTree\DqlFunction\LTreeSubpathFunction;
use LTree\TreeBuilder\TreeBuilderInterface;
use LTree\Types\LTreeType;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;

trait LTreeTrait
{
/**
* @var AnnotationDriverInterface
*/
private $annotationDriver;

/**
* @var PropertyAccessorInterface
*/
private $propertyAccessor;

/**
* @var TreeBuilderInterface
*/
private $treeBuilder;

/**
* @param $alias
* @param null $indexBy
* @return QueryBuilder
*/
abstract public function createQueryBuilder($alias, $indexBy = null);

abstract public function getClassName();

public function getTreeBuilder(): TreeBuilderInterface
{
if ($this->treeBuilder === null) {
throw new LogicException('Repository must inject property accessor service itself');
}
return $this->treeBuilder;
}

public function setTreeBuilder(TreeBuilderInterface $treeBuilder)
{
$this->treeBuilder = $treeBuilder;
return $this;
}

public function getPropertyAccessor(): PropertyAccessorInterface
{
if ($this->propertyAccessor === null) {
throw new LogicException('Repository must inject property accessor service itself');
}
return $this->propertyAccessor;
}

public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor)
{
$this->propertyAccessor = $propertyAccessor;
return $this;
}

public function getAnnotationDriver(): AnnotationDriverInterface
{
if ($this->annotationDriver === null) {
throw new LogicException('Repository must inject annotation driver service itself');
}
return $this->annotationDriver;
}

public function setAnnotationDriver(AnnotationDriverInterface $annotationDriver)
{
$this->annotationDriver = $annotationDriver;
return $this;
}

protected function checkClass($entity): void
{
if (!is_a($entity, $this->getClassName())) {
throw new InvalidArgumentException(sprintf('Entity must be instance of %s', $this->getClassName()));
}

if (!$this->getAnnotationDriver()->classIsLTree($this->getClassName())) {
throw new InvalidArgumentException('Entity must have ltree entity annotation');
}
}

public function getAllParentQueryBuilder($entity): QueryBuilder
{
$this->checkClass($entity);
$pathName = $this->getAnnotationDriver()->getPathProperty($entity)->getName();
$pathValue = $this->getPropertyAccessor()->getValue($entity, $pathName);

$alias = $this->getLTreeAlias();
$qb = $this->createQueryBuilder($alias);
$qb->where(sprintf(LTreeOperatorFunction::FUNCTION_NAME . '(%s.%s, \'@>\', :self_path) = true', $alias, $pathName));
$qb->andWhere(sprintf('%s.%s <> :self_path', $alias, $pathName));
$qb->orderBy(sprintf('%s.%s', static::$alias, $pathName), 'DESC');
$qb->setParameter('self_path', $pathValue, LTreeType::TYPE_NAME);

return $qb;
}

public function getAllChildrenQueryBuilder($entity): QueryBuilder
{
$this->checkClass($entity);
$pathName = $this->getAnnotationDriver()->getPathProperty($entity)->getName();
$pathValue = $this->getPropertyAccessor()->getValue($entity, $pathName);
$orderFieldName = 'parent_paths_for_order';

$alias = $this->getLTreeAlias();
$qb = $this->createQueryBuilder($alias);
$qb->addSelect(sprintf(LTreeSubpathFunction::FUNCTION_NAME . '(%s.%s, 0, -1) as HIDDEN %s', $alias, $pathName, $orderFieldName));
$qb->where(sprintf(LTreeOperatorFunction::FUNCTION_NAME . '(%s.%s, \'<@\', :self_path) = true', $alias, $pathName));
$qb->andWhere(sprintf('%s.%s <> :self_path', $alias, $pathName));
$qb->orderBy($orderFieldName);
$qb->setParameter('self_path', $pathValue, LTreeType::TYPE_NAME);

return $qb;
}

public function getInverseLTreeBuilder($entity = null): QueryBuilder
{
if (empty($entity)) {
$entityClassName = $this->getClassName();
$entity = new $entityClassName;
}

$this->checkClass($entity);

$idName = $this->getAnnotationDriver()->getIdProperty($entity)->getName();
$idValue = $this->getPropertyAccessor()->getValue($entity, $idName);
$pathName = $this->getAnnotationDriver()->getPathProperty($entity)->getName();

if ($idValue) {
$pathValue = $this->getPropertyAccessor()->getValue($entity, $pathName);
$pathValue[] = '*';
} else {
$pathValue = [];
}

$alias = $this->getLTreeAlias();
$qb = $this->createQueryBuilder($alias);

if ($pathValue) {
$qb->where(sprintf(LTreeOperatorFunction::FUNCTION_NAME . '(%s.%s, \'~\', :self_path) = false', $alias, $pathName));
$qb->setParameter('self_path', $pathValue, LTreeType::TYPE_NAME);
}

$qb->orderBy(sprintf('%s.%s', $alias, $pathName), 'ASC');

return $qb;
}

public function getAllParent($entity, $hydrate = Query::HYDRATE_OBJECT)
{
return $this->getAllParentQueryBuilder($entity)->getQuery()->getResult($hydrate);
}

public function getAllLTree($hydrate = Query::HYDRATE_OBJECT)
{
return $this->getInverseLTreeBuilder()->getQuery()->getResult($hydrate);
}

public function getAllChildren($entity, $treeMode = false, $hydrate = Query::HYDRATE_OBJECT)
{
$this->checkClass($entity);
$result = $this->getAllChildrenQueryBuilder($entity)->getQuery()->getResult($hydrate);

if ($treeMode && !in_array($hydrate, [Query::HYDRATE_OBJECT, Query::HYDRATE_ARRAY], true)) {
throw new LogicException('If treeMode is true, hydration mode must be object or array');
}

if (!$treeMode) {
return $result;
}

$pathName = $this->getAnnotationDriver()->getPathProperty($entity)->getName();
$pathValue = $this->getPropertyAccessor()->getValue($entity, $pathName);
$parentName = $this->getAnnotationDriver()->getParentProperty($entity)->getName();
$childName = $this->getAnnotationDriver()->getChildsProperty($entity)->getName();

return $this->treeBuilder->buildTree($result, $pathName, $pathValue, $parentName, $childName);
}

public function moveNode($entity, $to = null)
{
$this->checkClass($entity);
$pathName = $this->getAnnotationDriver()->getPathProperty($entity)->getName();
$oldPathValue = $this->getPropertyAccessor()->getValue($entity, $pathName);

if ($to !== null) {
$this->checkClass($to);
$newPathValue = $this->getPropertyAccessor()->getValue($to, $pathName);
} else {
$newPathValue = [];
}

$alias = $this->getLTreeAlias();
$prepareString = static function ($str) use ($pathName, $alias) {
$replacement = [
'%alias%' => $alias,
'%path%' => $pathName
];
return str_replace(array_keys($replacement), array_values($replacement), $str);
};

$qb = $this->createQueryBuilder($alias)
->update()
->set(
$prepareString('%alias%.%path%'),
$prepareString(implode('', [
LTreeConcatFunction::FUNCTION_NAME,
'(:new_path, ',
LTreeSubpathFunction::FUNCTION_NAME,
'(%alias%.%path%, (',
LTreeNlevelFunction::FUNCTION_NAME,
'(:self_path) - 1)))',
]))
)
->where($prepareString(LTreeOperatorFunction::FUNCTION_NAME . '(%alias%.%path%, \'<@\', :self_path) = true'))
->setParameter(':self_path', $oldPathValue, LTreeType::TYPE_NAME)
->setParameter(':new_path', $newPathValue, LTreeType::TYPE_NAME)
;

return $qb->getQuery()->execute();
}

protected function getLTreeAlias(): string
{
return 'ltree_entity';
}
}

0 comments on commit dc81bea

Please sign in to comment.