[Gedmo to Knp] Add TreeBehaviorRector

This commit is contained in:
TomasVotruba 2019-12-26 00:02:37 +01:00
parent b398e8740c
commit 0dec93ead6
28 changed files with 823 additions and 4 deletions

View File

@ -2,3 +2,4 @@
services:
Rector\DoctrineGedmoToKnplabs\Rector\Class_\TimestampableBehaviorRector: null
Rector\DoctrineGedmoToKnplabs\Rector\Class_\SluggableBehaviorRector: null
Rector\DoctrineGedmoToKnplabs\Rector\Class_\TreeBehaviorRector: ~

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNode\Gedmo;
use Gedmo\Mapping\Annotation\TreeLeft;
use Rector\BetterPhpDocParser\PhpDocNode\AbstractTagValueNode;
final class TreeLeftTagValueNode extends AbstractTagValueNode
{
/**
* @var string
*/
public const CLASS_NAME = TreeLeft::class;
public function __toString(): string
{
return '';
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNode\Gedmo;
use Gedmo\Mapping\Annotation\TreeLevel;
use Rector\BetterPhpDocParser\PhpDocNode\AbstractTagValueNode;
final class TreeLevelTagValueNode extends AbstractTagValueNode
{
/**
* @var string
*/
public const CLASS_NAME = TreeLevel::class;
public function __toString(): string
{
return '';
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNode\Gedmo;
use Gedmo\Mapping\Annotation\TreeParent;
use Rector\BetterPhpDocParser\PhpDocNode\AbstractTagValueNode;
final class TreeParentTagValueNode extends AbstractTagValueNode
{
/**
* @var string
*/
public const CLASS_NAME = TreeParent::class;
public function __toString(): string
{
return '';
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNode\Gedmo;
use Gedmo\Mapping\Annotation\TreeRight;
use Rector\BetterPhpDocParser\PhpDocNode\AbstractTagValueNode;
final class TreeRightTagValueNode extends AbstractTagValueNode
{
/**
* @var string
*/
public const CLASS_NAME = TreeRight::class;
public function __toString(): string
{
return '';
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNode\Gedmo;
use Gedmo\Mapping\Annotation\TreeRoot;
use Rector\BetterPhpDocParser\PhpDocNode\AbstractTagValueNode;
final class TreeRootTagValueNode extends AbstractTagValueNode
{
/**
* @var string
*/
public const CLASS_NAME = TreeRoot::class;
public function __toString(): string
{
return '';
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNode\Gedmo;
use Gedmo\Mapping\Annotation\Tree;
use Rector\BetterPhpDocParser\PhpDocNode\AbstractTagValueNode;
final class TreeTagValueNode extends AbstractTagValueNode
{
/**
* @var string
*/
public const CLASS_NAME = Tree::class;
/**
* @var string
*/
private $type;
public function __construct(string $type)
{
$this->type = $type;
}
public function __toString(): string
{
return sprintf('(type="%s")', $this->type);
}
public function getType(): string
{
return $this->type;
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNodeFactory;
use Doctrine\ORM\Mapping\Annotation;
use PhpParser\Node;
use PhpParser\Node\Stmt\Property;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
abstract class AbstractBasicPropertyPhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
protected function createFromNode(Node $node): ?PhpDocTagValueNode
{
if (! $node instanceof Property) {
return null;
}
/** @var Annotation|null $annotation */
$annotation = $this->nodeAnnotationReader->readPropertyAnnotation($node, $this->getClass());
if ($annotation === null) {
return null;
}
$getTagValueNodeClass = $this->getTagValueNodeClass();
return new $getTagValueNodeClass();
}
abstract protected function getTagValueNodeClass(): string;
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNodeFactory\Gedmo;
use Gedmo\Mapping\Annotation\TreeLeft;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeLeftTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNodeFactory\AbstractBasicPropertyPhpDocNodeFactory;
final class TreeLeftPhpDocNodeFactory extends AbstractBasicPropertyPhpDocNodeFactory
{
public function getClass(): string
{
return TreeLeft::class;
}
/**
* @return TreeLeftTagValueNode|null
*/
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
{
return $this->createFromNode($node);
}
protected function getTagValueNodeClass(): string
{
return TreeLeftTagValueNode::class;
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNodeFactory\Gedmo;
use Gedmo\Mapping\Annotation\TreeLevel;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeLevelTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNodeFactory\AbstractBasicPropertyPhpDocNodeFactory;
final class TreeLevelPhpDocNodeFactory extends AbstractBasicPropertyPhpDocNodeFactory
{
public function getClass(): string
{
return TreeLevel::class;
}
/**
* @return TreeLevelTagValueNode|null
*/
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
{
return $this->createFromNode($node);
}
protected function getTagValueNodeClass(): string
{
return TreeLevelTagValueNode::class;
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNodeFactory\Gedmo;
use Gedmo\Mapping\Annotation\TreeParent;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeParentTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNodeFactory\AbstractBasicPropertyPhpDocNodeFactory;
final class TreeParentPhpDocNodeFactory extends AbstractBasicPropertyPhpDocNodeFactory
{
public function getClass(): string
{
return TreeParent::class;
}
/**
* @return TreeParentTagValueNode|null
*/
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
{
return $this->createFromNode($node);
}
protected function getTagValueNodeClass(): string
{
return TreeParentTagValueNode::class;
}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNodeFactory\Gedmo;
use Gedmo\Mapping\Annotation\Tree;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNodeFactory\AbstractPhpDocNodeFactory;
final class TreePhpDocNodeFactory extends AbstractPhpDocNodeFactory
{
public function getClass(): string
{
return Tree::class;
}
/**
* @return TreeTagValueNode|null
*/
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
{
if (! $node instanceof Class_) {
return null;
}
/** @var Tree|null $tree */
$tree = $this->nodeAnnotationReader->readClassAnnotation($node, $this->getClass());
if ($tree === null) {
return null;
}
return new TreeTagValueNode($tree->type);
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNodeFactory\Gedmo;
use Gedmo\Mapping\Annotation\TreeRight;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeRightTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNodeFactory\AbstractBasicPropertyPhpDocNodeFactory;
final class TreeRightPhpDocNodeFactory extends AbstractBasicPropertyPhpDocNodeFactory
{
public function getClass(): string
{
return TreeRight::class;
}
/**
* @return TreeRightTagValueNode|null
*/
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
{
return $this->createFromNode($node);
}
protected function getTagValueNodeClass(): string
{
return TreeRightTagValueNode::class;
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNodeFactory\Gedmo;
use Gedmo\Mapping\Annotation\TreeRoot;
use PhpParser\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
use PHPStan\PhpDocParser\Parser\TokenIterator;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeRootTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNodeFactory\AbstractBasicPropertyPhpDocNodeFactory;
final class TreeRootPhpDocNodeFactory extends AbstractBasicPropertyPhpDocNodeFactory
{
public function getClass(): string
{
return TreeRoot::class;
}
/**
* @return TreeRootTagValueNode|null
*/
public function createFromNodeAndTokens(Node $node, TokenIterator $tokenIterator): ?PhpDocTagValueNode
{
return $this->createFromNode($node);
}
protected function getTagValueNodeClass(): string
{
return TreeRootTagValueNode::class;
}
}

View File

@ -0,0 +1,216 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineGedmoToKnplabs\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfo;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeLeftTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeLevelTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeParentTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeRightTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeRootTagValueNode;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\TreeTagValueNode;
use Rector\PhpParser\Node\Manipulator\ClassManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see \Rector\DoctrineGedmoToKnplabs\Tests\Rector\Class_\TreeBehaviorRector\TreeBehaviorRectorTest
*/
final class TreeBehaviorRector extends AbstractRector
{
/**
* @var ClassManipulator
*/
private $classManipulator;
public function __construct(ClassManipulator $classManipulator)
{
$this->classManipulator = $classManipulator;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change Tree from gedmo/doctrine-extensions to knplabs/doctrine-behaviors', [
new CodeSample(
<<<'PHP'
use Doctrine\Common\Collections\Collection;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @Gedmo\Tree(type="nested")
*/
class SomeClass
{
/**
* @Gedmo\TreeLeft
* @ORM\Column(name="lft", type="integer")
* @var int
*/
private $lft;
/**
* @Gedmo\TreeRight
* @ORM\Column(name="rgt", type="integer")
* @var int
*/
private $rgt;
/**
* @Gedmo\TreeLevel
* @ORM\Column(name="lvl", type="integer")
* @var int
*/
private $lvl;
/**
* @Gedmo\TreeRoot
* @ORM\ManyToOne(targetEntity="Category")
* @ORM\JoinColumn(name="tree_root", referencedColumnName="id", onDelete="CASCADE")
* @var Category
*/
private $root;
/**
* @Gedmo\TreeParent
* @ORM\ManyToOne(targetEntity="Category", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
* @var Category
*/
private $parent;
/**
* @ORM\OneToMany(targetEntity="Category", mappedBy="parent")
* @var Category[]|Collection
*/
private $children;
public function getRoot(): self
{
return $this->root;
}
public function setParent(self $category): void
{
$this->parent = $category;
}
public function getParent(): self
{
return $this->parent;
}
}
PHP
,
<<<'PHP'
use Knp\DoctrineBehaviors\Contract\Entity\TreeNodeInterface;
use Knp\DoctrineBehaviors\Model\Tree\TreeNodeTrait;
class SomeClass implements TreeNodeInterface
{
use TreeNodeTrait;
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
$classPhpDocInfo = $this->getPhpDocInfo($node);
if ($classPhpDocInfo === null) {
return null;
}
$treeTagValueNode = $classPhpDocInfo->getByType(TreeTagValueNode::class);
if ($treeTagValueNode === null) {
return null;
}
// we're in a tree entity
$classPhpDocInfo->removeTagValueNodeFromNode($treeTagValueNode);
$this->docBlockManipulator->updateNodeWithPhpDocInfo($node, $classPhpDocInfo);
$node->implements[] = new FullyQualified('Knp\DoctrineBehaviors\Contract\Entity\TreeNodeInterface');
$this->classManipulator->addAsFirstTrait($node, 'Knp\DoctrineBehaviors\Model\Tree\TreeNodeTrait');
// remove all tree-related properties and their getters and setters - it's handled by behavior trait
$removedPropertyNames = [];
foreach ($node->getProperties() as $property) {
$propertyPhpDocInfo = $this->getPhpDocInfo($property);
if ($propertyPhpDocInfo === null) {
continue;
}
if (! $this->shouldRemoveProperty($propertyPhpDocInfo)) {
continue;
}
$removedPropertyNames[] = $this->getName($property);
$this->removeNode($property);
}
$this->removeClassMethodsForProperties($node, $removedPropertyNames);
return $node;
}
private function removeClassMethodsForProperties(Class_ $class, array $removedPropertyNames): void
{
foreach ($removedPropertyNames as $removedPropertyName) {
foreach ($class->getMethods() as $method) {
if ($this->isName($method, 'get' . ucfirst($removedPropertyName))) {
$this->removeNode($method);
}
if ($this->isName($method, 'set' . ucfirst($removedPropertyName))) {
$this->removeNode($method);
}
}
}
}
private function shouldRemoveProperty(PhpDocInfo $phpDocInfo): bool
{
if ($phpDocInfo->getByType(TreeLeftTagValueNode::class)) {
return true;
}
if ($phpDocInfo->getByType(TreeRightTagValueNode::class)) {
return true;
}
if ($phpDocInfo->getByType(TreeRootTagValueNode::class)) {
return true;
}
if ($phpDocInfo->getByType(TreeParentTagValueNode::class)) {
return true;
}
if ($phpDocInfo->getByType(TreeLevelTagValueNode::class)) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,93 @@
<?php
namespace Rector\DoctrineGedmoToKnplabs\Tests\Rector\Class_\TreeBehaviorRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* @Gedmo\Tree(type="nested")
*/
class SomeClass
{
/**
* @Gedmo\TreeLeft
* @ORM\Column(name="lft", type="integer")
* @var int
*/
private $lft;
/**
* @Gedmo\TreeRight
* @ORM\Column(name="rgt", type="integer")
* @var int
*/
private $rgt;
/**
* @Gedmo\TreeLevel
* @ORM\Column(name="lvl", type="integer")
* @var int
*/
private $lvl;
/**
* @Gedmo\TreeRoot
* @ORM\ManyToOne(targetEntity="Category")
* @ORM\JoinColumn(name="tree_root", referencedColumnName="id", onDelete="CASCADE")
* @var Category
*/
private $root;
/**
* @Gedmo\TreeParent
* @ORM\ManyToOne(targetEntity="Category", inversedBy="children")
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")
* @var Category
*/
private $parent;
/**
* @ORM\OneToMany(targetEntity="Category", mappedBy="parent")
* @var Category[]|Collection
*/
private $children;
public function getRoot(): self
{
return $this->root;
}
public function setParent(self $category): void
{
$this->parent = $category;
}
public function getParent(): self
{
return $this->parent;
}
}
?>
-----
<?php
namespace Rector\DoctrineGedmoToKnplabs\Tests\Rector\Class_\TreeBehaviorRector\Fixture;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Gedmo\Mapping\Annotation as Gedmo;
class SomeClass implements \Knp\DoctrineBehaviors\Contract\Entity\TreeNodeInterface
{
use \Knp\DoctrineBehaviors\Model\Tree\TreeNodeTrait;
/**
* @ORM\OneToMany(targetEntity="Category", mappedBy="parent")
* @var Category[]|Collection
*/
private $children;
}
?>

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineGedmoToKnplabs\Tests\Rector\Class_\TreeBehaviorRector;
use Iterator;
use Rector\DoctrineGedmoToKnplabs\Rector\Class_\TreeBehaviorRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class TreeBehaviorRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideDataForTest()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}
public function provideDataForTest(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}
protected function getRectorClass(): string
{
return TreeBehaviorRector::class;
}
}

View File

@ -240,3 +240,6 @@ parameters:
- '#Parameter \#1 \$error_handler of function set_error_handler expects \(callable\(int, string, string, int, array\)\: bool\)\|null, Closure\(int, string\)\: void given#'
- '#Parameter \#1 \$source of method Rector\\Scan\\ErrorScanner\:\:scanSource\(\) expects array<string\>, array<string\>\|string\|null given#'
- '#Method Rector\\BetterPhpDocParser\\PhpDocNodeFactory\\Gedmo\\(.*?)\:\:createFromNodeAndTokens\(\) should return Rector\\BetterPhpDocParser\\PhpDocNode\\Gedmo\\(.*?)\|null but returns PHPStan\\PhpDocParser\\Ast\\PhpDoc\\PhpDocTagValueNode\|null#'

View File

@ -98,7 +98,7 @@ final class ScreenFileCommand extends AbstractCommand
protected function configure(): void
{
$this->setName(CommandNaming::classToName(self::class));
$this->setDescription('Load file and print nodes meta data - super helpful to learn to build rules');
$this->setDescription('[Dev] Load file and print nodes meta data - super helpful to learn to build rules');
$this->addArgument(self::FILE_ARGUMENT, InputArgument::REQUIRED, 'Path to file to be screened');
}

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Gedmo\Mapping\Annotation;
if (class_exists('Gedmo\Mapping\Annotation\Tree')) {
return;
}
/**
* @Annotation
*/
class Tree
{
/** @var string */
public $type = 'nested';
/** @var string */
public $activateLocking = false;
/** @var integer */
public $lockingTimeout = 3;
/** @var string $identifierMethod */
public $identifierMethod;
}

View File

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace Gedmo\Mapping\Annotation;
if (class_exists('Gedmo\Mapping\Annotation\TreeLeft')) {
return;
}
/**
* @Annotation
*/
class TreeLeft
{
}

View File

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace Gedmo\Mapping\Annotation;
if (class_exists('Gedmo\Mapping\Annotation\TreeLevel')) {
return;
}
/**
* @Annotation
*/
class TreeLevel
{
}

View File

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace Gedmo\Mapping\Annotation;
if (class_exists('Gedmo\Mapping\Annotation\TreeParent')) {
return;
}
/**
* @Annotation
*/
class TreeParent
{
}

View File

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace Gedmo\Mapping\Annotation;
if (class_exists('Gedmo\Mapping\Annotation\TreeRight')) {
return;
}
/**
* @Annotation
*/
class TreeRight
{
}

View File

@ -0,0 +1,16 @@
<?php declare(strict_types=1);
namespace Gedmo\Mapping\Annotation;
if (class_exists('Gedmo\Mapping\Annotation\TreeRoot')) {
return;
}
/**
* @Annotation
*/
class TreeRoot
{
/** @var string $identifierMethod */
public $identifierMethod;
}

View File

@ -152,7 +152,7 @@ final class DumpNodesCommand extends AbstractCommand
protected function configure(): void
{
$this->setName(CommandNaming::classToName(self::class));
$this->setDescription('Dump overview of all Nodes');
$this->setDescription('[Docs] Dump overview of all Nodes');
$this->addOption(
'output-format',
'o',

View File

@ -39,7 +39,7 @@ final class DumpRectorsCommand extends AbstractCommand
protected function configure(): void
{
$this->setName(CommandNaming::classToName(self::class));
$this->setDescription('Dump overview of all Rectors');
$this->setDescription('[Docs] Dump overview of all Rectors');
$this->addOption(
'output-format',
'o',

View File

@ -96,7 +96,7 @@ final class CreateRectorCommand extends Command
protected function configure(): void
{
$this->setName(CommandNaming::classToName(self::class));
$this->setDescription('Create a new Rector, in a proper location, with new tests');
$this->setDescription('[Dev] Create a new Rector, in a proper location, with new tests');
}
protected function execute(InputInterface $input, OutputInterface $output): int