[DoctrineGedmoToKnplabs] init set

This commit is contained in:
TomasVotruba 2019-12-25 12:27:58 +01:00
parent a8b194244b
commit 2ac711d2b2
18 changed files with 550 additions and 11 deletions

View File

@ -103,7 +103,8 @@
"Rector\\StrictCodeQuality\\": "packages/StrictCodeQuality/src",
"Rector\\DynamicTypeAnalysis\\": "packages/DynamicTypeAnalysis/src",
"Rector\\PhpDeglobalize\\": "packages/PhpDeglobalize/src",
"Rector\\Phalcon\\": "packages/Phalcon/src"
"Rector\\Phalcon\\": "packages/Phalcon/src",
"Rector\\DoctrineGedmoToKnplabs\\": "packages/DoctrineGedmoToKnplabs/src"
}
},
"autoload-dev": {
@ -162,7 +163,8 @@
"Rector\\StrictCodeQuality\\Tests\\": "packages/StrictCodeQuality/tests",
"Rector\\DynamicTypeAnalysis\\Tests\\": "packages/DynamicTypeAnalysis/tests",
"Rector\\PhpDeglobalize\\Tests\\": "packages/PhpDeglobalize/tests",
"Rector\\Phalcon\\Tests\\": "packages/Phalcon/tests"
"Rector\\Phalcon\\Tests\\": "packages/Phalcon/tests",
"Rector\\DoctrineGedmoToKnplabs\\Tests\\": "packages/DoctrineGedmoToKnplabs/tests"
},
"classmap": [
"packages/Symfony/tests/Rector/FrameworkBundle/AbstractToConstructorInjectionRectorSource",

View File

@ -0,0 +1,4 @@
# version gedmo/doctrine-extensions 2.x to knplabs/doctrine-behaviors 2.0
services:
Rector\DoctrineGedmoToKnplabs\Rector\Class_\TimestampableBehaviorRector: null
Rector\DoctrineGedmoToKnplabs\Rector\Class_\SluggableBehaviorRector: null

View File

@ -37,6 +37,9 @@ final class AnnotationReaderFactory
$annotationReader::addGlobalIgnoredName('Gedmo\SoftDeleteable');
$annotationReader::addGlobalIgnoredName('SoftDeleteable');
$annotationReader::addGlobalIgnoredName('Gedmo\Slug');
$annotationReader::addGlobalIgnoredName('Slug');
// nette @inject dummy annotation
$annotationReader::addGlobalIgnoredName('inject');

View File

@ -209,9 +209,11 @@ final class PhpDocInfo
foreach ($this->phpDocNode->children as $phpDocChildNode) {
if ($phpDocChildNode instanceof PhpDocTagNode) {
if (is_a($phpDocChildNode->value, $type, true)) {
return $phpDocChildNode->value;
if (! is_a($phpDocChildNode->value, $type, true)) {
continue;
}
return $phpDocChildNode->value;
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Rector\BetterPhpDocParser\PhpDocNode\Gedmo;
use Gedmo\Mapping\Annotation\Slug;
use Rector\BetterPhpDocParser\PhpDocNode\AbstractTagValueNode;
final class SlugTagValueNode extends AbstractTagValueNode
{
/**
* @var string
*/
public const SHORT_NAME = '@Gedmo\Slug';
/**
* @var string
*/
public const CLASS_NAME = Slug::class;
/**
* @var mixed[]
*/
private $fields = [];
public function __construct(array $fields)
{
$this->fields = $fields;
}
public function __toString(): string
{
return '(fields=' . $this->printArrayItem($this->fields) . ')';
}
public function getFields(): array
{
return $this->fields;
}
}

View File

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

View File

@ -148,6 +148,7 @@ final class BetterPhpDocParser extends PhpDocParser
{
// needed for reference support in params, see https://github.com/rectorphp/rector/issues/1734
$tagValueNode = null;
foreach ($this->phpDocNodeFactories as $phpDocNodeFactory) {
// to prevent circular reference of this service
if ($phpDocNodeFactory instanceof PhpDocParserAwareInterface) {

View File

@ -25,7 +25,6 @@ final class ClassAnnotationMatcher
}
$fullyQualifiedClassNode = $this->matchFullAnnotationClassWithUses($tag, $useNodes);
if ($fullyQualifiedClassNode === null) {
return false;
}

View File

@ -0,0 +1,155 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineGedmoToKnplabs\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Return_;
use PHPStan\Type\ArrayType;
use PHPStan\Type\MixedType;
use PHPStan\Type\StringType;
use Rector\BetterPhpDocParser\PhpDocNode\Gedmo\SlugTagValueNode;
use Rector\PhpParser\Node\Manipulator\ClassManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see \Rector\DoctrineGedmoToKnplabs\Tests\Rector\Class_\SluggableBehaviorRector\SluggableBehaviorRectorTest
*/
final class SluggableBehaviorRector extends AbstractRector
{
/**
* @var ClassManipulator
*/
private $classManipulator;
public function __construct(ClassManipulator $classManipulator)
{
$this->classManipulator = $classManipulator;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Change Sluggable from gedmo/doctrine-extensions to knplabs/doctrine-behaviors', [
new CodeSample(
<<<'PHP'
use Gedmo\Mapping\Annotation as Gedmo;
class SomeClass
{
/**
* @Gedmo\Slug(fields={"name"})
*/
private $slug;
public function getSlug(): ?string
{
return $this->slug;
}
public function setSlug(?string $slug): void
{
$this->slug = $slug;
}
}
PHP
,
<<<'PHP'
use Gedmo\Mapping\Annotation as Gedmo;
use Knp\DoctrineBehaviors\Model\Sluggable\SluggableTrait;
use Knp\DoctrineBehaviors\Contract\Entity\SluggableInterface;
class SomeClass implements SluggableInterface
{
use SluggableTrait;
/**
* @return string[]
*/
public function getSluggableFields(): array
{
return ['name'];
}
}
PHP
),
]);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
$slugFields = [];
$matchedProperty = null;
foreach ($node->getProperties() as $property) {
$propertyPhpDocInfo = $this->getPhpDocInfo($property);
if ($propertyPhpDocInfo === null) {
continue;
}
$slugTagValueNode = $propertyPhpDocInfo->getByType(SlugTagValueNode::class);
if ($slugTagValueNode === null) {
continue;
}
$slugFields = $slugTagValueNode->getFields();
$this->removeNode($property);
$matchedProperty = $property;
}
if ($matchedProperty === null) {
return null;
}
// remove property setter/getter
foreach ((array) $node->getMethods() as $classMethod) {
if (! $this->isNames($classMethod, ['getSlug', 'setSlug'])) {
continue;
}
$this->removeNode($classMethod);
}
$this->classManipulator->addAsFirstTrait($node, 'Knp\DoctrineBehaviors\Model\Sluggable\SluggableTrait');
$node->implements[] = new Node\Name\FullyQualified('Knp\DoctrineBehaviors\Contract\Entity\SluggableInterface');
$this->addGetSluggableFieldsClassMethod($node, $slugFields);
// change the node
return $node;
}
/**
* @param string[] $slugFields
*/
private function addGetSluggableFieldsClassMethod(Class_ $class, array $slugFields): void
{
$classMethod = $this->nodeFactory->createPublicMethod('getSluggableFields');
$classMethod->returnType = new Identifier('array');
$classMethod->stmts[] = new Return_($this->createArray($slugFields));
$this->docBlockManipulator->addReturnTag($classMethod, new ArrayType(new MixedType(), new StringType()));
$this->classManipulator->addAsFirstMethod($class, $classMethod);
}
}

View File

@ -0,0 +1,87 @@
<?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\PhpParser\Node\Manipulator\ClassManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;
/**
* @see \Rector\DoctrineGedmoToKnplabs\Tests\Rector\Class_\TimestampableBehaviorRector\TimestampableBehaviorRectorTest
*/
final class TimestampableBehaviorRector extends AbstractRector
{
/**
* @var ClassManipulator
*/
private $classManipulator;
public function __construct(ClassManipulator $classManipulator)
{
$this->classManipulator = $classManipulator;
}
public function getDefinition(): RectorDefinition
{
return new RectorDefinition(
'Change Timestampable from gedmo/doctrine-extensions to knplabs/doctrine-behaviors',
[
new CodeSample(
<<<'PHP'
use Gedmo\Timestampable\Traits\TimestampableEntity;
class SomeClass
{
use TimestampableEntity;
}
PHP
,
<<<'PHP'
use Knp\DoctrineBehaviors\Model\Timestampable\TimestampableTrait;
use Knp\DoctrineBehaviors\Contract\Entity\TimestampableInterface;
class SomeClass implements TimestampableInterface
{
use TimestampableTrait;
}
PHP
),
]
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->classManipulator->hasClassTrait($node, 'Gedmo\Timestampable\Traits\TimestampableEntity')) {
return null;
}
$this->classManipulator->replaceTrait(
$node,
'Gedmo\Timestampable\Traits\TimestampableEntity',
'Knp\DoctrineBehaviors\Model\Timestampable\TimestampableTrait'
);
$node->implements[] = new FullyQualified('Knp\DoctrineBehaviors\Contract\Entity\TimestampableInterface');
return $node;
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Rector\DoctrineGedmoToKnplabs\Tests\Rector\Class_\SluggableBehaviorRector\Fixture;
use Gedmo\Mapping\Annotation as Gedmo;
class SomeClass
{
/**
* @Gedmo\Slug(fields={"name"})
*/
private $slug;
public function getSlug(): ?string
{
return $this->slug;
}
public function setSlug(?string $slug): void
{
$this->slug = $slug;
}
}
?>
-----
<?php
namespace Rector\DoctrineGedmoToKnplabs\Tests\Rector\Class_\SluggableBehaviorRector\Fixture;
use Gedmo\Mapping\Annotation as Gedmo;
class SomeClass implements \Knp\DoctrineBehaviors\Contract\Entity\SluggableInterface
{
use \Knp\DoctrineBehaviors\Model\Sluggable\SluggableTrait;
/**
* @return string[]
*/
public function getSluggableFields(): array
{
return ['name'];
}
}
?>

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineGedmoToKnplabs\Tests\Rector\Class_\SluggableBehaviorRector;
use Iterator;
use Rector\DoctrineGedmoToKnplabs\Rector\Class_\SluggableBehaviorRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class SluggableBehaviorRectorTest 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 SluggableBehaviorRector::class;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Rector\DoctrineGedmoToKnplabs\Tests\Rector\Class_\TimestampableBehaviorRector\Fixture;
use Gedmo\Timestampable\Traits\TimestampableEntity;
class SomeClass
{
use TimestampableEntity;
}
?>
-----
<?php
namespace Rector\DoctrineGedmoToKnplabs\Tests\Rector\Class_\TimestampableBehaviorRector\Fixture;
use Gedmo\Timestampable\Traits\TimestampableEntity;
class SomeClass implements \Knp\DoctrineBehaviors\Contract\Entity\TimestampableInterface
{
use \Knp\DoctrineBehaviors\Model\Timestampable\TimestampableTrait;
}
?>

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineGedmoToKnplabs\Tests\Rector\Class_\TimestampableBehaviorRector;
use Iterator;
use Rector\DoctrineGedmoToKnplabs\Rector\Class_\TimestampableBehaviorRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
final class TimestampableBehaviorRectorTest 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 TimestampableBehaviorRector::class;
}
}

View File

@ -109,9 +109,11 @@ final class ClassManipulator
$class->stmts[] = $stmt;
}
public function addAsFirstTrait(Class_ $class, Stmt $stmt): void
public function addAsFirstTrait(Class_ $class, string $traitName): void
{
$this->addStatementToClassBeforeTypes($class, $stmt, TraitUse::class, Property::class);
$trait = new TraitUse([new Name\FullyQualified($traitName)]);
$this->addStatementToClassBeforeTypes($class, $trait, TraitUse::class, Property::class);
}
/**
@ -391,6 +393,35 @@ final class ClassManipulator
return false;
}
public function hasClassTrait(Class_ $class, string $desiredTrait): bool
{
foreach ($class->getTraitUses() as $traitUse) {
foreach ($traitUse->traits as $traitTrait) {
if (! $this->nameResolver->isName($traitTrait, $desiredTrait)) {
continue;
}
return true;
}
}
return false;
}
public function replaceTrait(Class_ $class, string $oldTrait, string $newTrait): void
{
foreach ($class->getTraitUses() as $traitUse) {
foreach ($traitUse->traits as $key => $traitTrait) {
if (! $this->nameResolver->isName($traitTrait, $oldTrait)) {
continue;
}
$traitUse->traits[$key] = new Name\FullyQualified($newTrait);
break;
}
}
}
private function tryInsertBeforeFirstMethod(Class_ $classNode, Stmt $stmt): bool
{
foreach ($classNode->stmts as $key => $classStmt) {

View File

@ -5,9 +5,7 @@ declare(strict_types=1);
namespace Rector\Rector\Class_;
use PhpParser\Node;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\TraitUse;
use Rector\PhpParser\Node\Manipulator\ClassManipulator;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\ConfiguredCodeSample;
@ -92,8 +90,7 @@ PHP
$traitNames = array_reverse($traitNames);
foreach ($traitNames as $traitName) {
$traitUseNode = new TraitUse([new FullyQualified($traitName)]);
$this->classManipulator->addAsFirstTrait($node, $traitUseNode);
$this->classManipulator->addAsFirstTrait($node, $traitName);
}
$this->removeParentClass($node);

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Gedmo\Mapping\Annotation;
if (class_exists('Gedmo\Mapping\Annotation\Slug')) {
return;
}
/**
* @Annotation
*/
class Slug
{
/** @var array<string> @Required */
public $fields = array();
/** @var boolean */
public $updatable = true;
/** @var string */
public $style = 'default'; // or "camel"
/** @var boolean */
public $unique = true;
/** @var string */
public $unique_base = null;
/** @var string */
public $separator = '-';
/** @var string */
public $prefix = '';
/** @var string */
public $suffix = '';
public $handlers = array();
/** @var string */
public $dateFormat = 'Y-m-d-H:i';
}

View File

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace Gedmo\Timestampable\Traits;
if (class_exists('Gedmo\Timestampable\Traits\TimestampableEntity')) {
return;
}
trait TimestampableEntity
{
}