Move Architecture to Doctrine, improve a bit (#5374)

This commit is contained in:
Tomas Votruba 2021-01-30 09:57:35 +01:00 committed by GitHub
parent 1fc272e228
commit 2df4732c6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 182 additions and 367 deletions

View File

@ -87,7 +87,6 @@
"psr-4": {
"Rector\\Testing\\": "packages/testing/src",
"Rector\\Comments\\": "packages/comments/src",
"Rector\\Architecture\\": "rules/architecture/src",
"Rector\\AttributeAwarePhpDoc\\": "packages/attribute-aware-php-doc/src",
"Rector\\Autodiscovery\\": "rules/autodiscovery/src",
"Rector\\BetterPhpDocParser\\": "packages/better-php-doc-parser/src",
@ -218,7 +217,6 @@
"tests/debug_functions.php"
],
"psr-4": {
"Rector\\Architecture\\Tests\\": "rules/architecture/tests",
"Rector\\Autodiscovery\\Tests\\": "rules/autodiscovery/tests",
"Rector\\BetterPhpDocParser\\Tests\\": "packages/better-php-doc-parser/tests",
"Rector\\Caching\\Tests\\": "packages/caching/tests",

View File

@ -2,11 +2,10 @@
declare(strict_types=1);
use Rector\Architecture\Rector\MethodCall\ReplaceParentRepositoryCallsByRepositoryPropertyRector;
use Rector\Architecture\Rector\MethodCall\ServiceLocatorToDIRector;
use Rector\DeadDocBlock\Rector\ClassLike\RemoveAnnotationRector;
use Rector\Doctrine\Rector\Class_\RemoveRepositoryFromEntityAnnotationRector;
use Rector\Doctrine\Rector\ClassMethod\ServiceEntityRepositoryParentCallToDIRector;
use Rector\Doctrine\Rector\MethodCall\ReplaceParentRepositoryCallsByRepositoryPropertyRector;
use Rector\DoctrineCodeQuality\Rector\Class_\MoveRepositoryFromParentToConstructorRector;
use Rector\Generic\Rector\Class_\AddPropertyByParentRector;
use Rector\Generic\ValueObject\AddPropertyByParent;
@ -28,7 +27,6 @@ return static function (ContainerConfigurator $containerConfigurator): void {
// covers "extends EntityRepository"
$services->set(MoveRepositoryFromParentToConstructorRector::class);
$services->set(ServiceLocatorToDIRector::class);
$services->set(ReplaceParentRepositoryCallsByRepositoryPropertyRector::class);
$services->set(RemoveRepositoryFromEntityAnnotationRector::class);

View File

@ -156,7 +156,7 @@
Handles method calls in child of Doctrine EntityRepository and moves them to `$this->repository` property.
- class: `Rector\Architecture\Rector\MethodCall\ReplaceParentRepositoryCallsByRepositoryPropertyRector`
- class: `Rector\Doctrine\Rector\MethodCall\ReplaceParentRepositoryCallsByRepositoryPropertyRector`
```diff
use Doctrine\ORM\EntityRepository;
@ -177,7 +177,7 @@ Handles method calls in child of Doctrine EntityRepository and moves them to `$t
Turns `$this->getRepository()` in Symfony Controller to constructor injection and private property access.
- class: `Rector\Architecture\Rector\MethodCall\ServiceLocatorToDIRector`
- class: `Rector\Doctrine\Rector\MethodCall\ServiceLocatorToDIRector`
```diff
class ProductController extends Controller

View File

@ -19,6 +19,11 @@ final class EntityTagValueNode extends AbstractDoctrineTagValueNode implements P
$this->items[self::REPOSITORY_CLASS] = null;
}
public function hasRepositoryClass(): bool
{
return $this->items[self::REPOSITORY_CLASS] !== null;
}
public function getShortName(): string
{
return '@ORM\Entity';

View File

@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$services = $containerConfigurator->services();
$services->defaults()
->autowire()
->public()
->autoconfigure();
$services->load('Rector\Architecture\\', __DIR__ . '/../src')
->exclude([__DIR__ . '/../src/Rector']);
};

View File

@ -1,188 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Architecture\Rector\MethodCall;
use Nette\Utils\Strings;
use PhpParser\Node;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Type\ObjectType;
use Rector\Core\Exception\Bridge\RectorProviderException;
use Rector\Core\Exception\ShouldNotHappenException;
use Rector\Core\Rector\AbstractRector;
use Rector\Doctrine\Contract\Mapper\DoctrineEntityAndRepositoryMapperInterface;
use Rector\Naming\Naming\PropertyNaming;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
/**
* @see \Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\DoctrineRepositoryAsServiceTest
*/
final class ServiceLocatorToDIRector extends AbstractRector
{
/**
* @var DoctrineEntityAndRepositoryMapperInterface
*/
private $doctrineEntityAndRepositoryMapper;
/**
* @var PropertyNaming
*/
private $propertyNaming;
public function __construct(
DoctrineEntityAndRepositoryMapperInterface $doctrineEntityAndRepositoryMapper,
PropertyNaming $propertyNaming
) {
$this->doctrineEntityAndRepositoryMapper = $doctrineEntityAndRepositoryMapper;
$this->propertyNaming = $propertyNaming;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition(
'Turns $this->getRepository() in Symfony Controller to constructor injection and private property access.',
[
new CodeSample(
<<<'CODE_SAMPLE'
class ProductController extends Controller
{
public function someAction()
{
$entityManager = $this->getDoctrine()->getManager();
$entityManager->getRepository('SomethingBundle:Product')->findSomething(...);
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
class ProductController extends Controller
{
/**
* @var ProductRepository
*/
private $productRepository;
public function __construct(ProductRepository $productRepository)
{
$this->productRepository = $productRepository;
}
public function someAction()
{
$entityManager = $this->getDoctrine()->getManager();
$this->productRepository->findSomething(...);
}
}
CODE_SAMPLE
),
]
);
}
/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [MethodCall::class];
}
/**
* @param MethodCall $node
*/
public function refactor(Node $node): ?Node
{
if (! $this->isName($node->name, 'getRepository')) {
return null;
}
$firstArgumentValue = $node->args[0]->value;
// possible mocking → skip
if ($firstArgumentValue instanceof StaticCall) {
return null;
}
/** @var string|null $className */
$className = $node->getAttribute(AttributeKey::CLASS_NAME);
if ($className === null) {
return null;
}
/** @var MethodCall $methodCallNode */
$methodCallNode = $node;
if (count($methodCallNode->args) !== 1) {
return null;
}
if ($methodCallNode->args[0]->value instanceof String_) {
/** @var String_ $string */
$string = $methodCallNode->args[0]->value;
// is alias
if (Strings::contains($string->value, ':')) {
return null;
}
}
if (Strings::endsWith($className, 'Repository')) {
return null;
}
$repositoryFqn = $this->resolveRepositoryFqnFromGetRepositoryMethodCall($node);
$classLike = $node->getAttribute(AttributeKey::CLASS_NODE);
if (! $classLike instanceof Class_) {
return null;
}
$repositoryObjectType = new ObjectType($repositoryFqn);
$this->addConstructorDependencyToClass(
$classLike,
$repositoryObjectType,
$this->propertyNaming->fqnToVariableName($repositoryObjectType)
);
return $this->createPropertyFetch('this', $this->propertyNaming->fqnToVariableName($repositoryObjectType));
}
private function resolveRepositoryFqnFromGetRepositoryMethodCall(MethodCall $methodCall): string
{
$entityFqnOrAlias = $this->entityFqnOrAlias($methodCall);
if ($entityFqnOrAlias !== null) {
$repositoryClassName = $this->doctrineEntityAndRepositoryMapper->mapEntityToRepository($entityFqnOrAlias);
if ($repositoryClassName !== null) {
return $repositoryClassName;
}
}
throw new RectorProviderException(sprintf(
'A repository was not provided for "%s" entity by your "%s" class.',
$entityFqnOrAlias,
get_class($this->doctrineEntityAndRepositoryMapper)
));
}
private function entityFqnOrAlias(MethodCall $methodCall): string
{
$repositoryArgument = $methodCall->args[0]->value;
if ($repositoryArgument instanceof String_) {
return $repositoryArgument->value;
}
if ($repositoryArgument instanceof ClassConstFetch && $repositoryArgument->class instanceof Name) {
return $this->getName($repositoryArgument->class);
}
throw new ShouldNotHappenException('Unable to resolve repository argument');
}
}

View File

@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Tests\Rector\Architecture\DoctrineRepositoryAsService\Source\Entity;
final class Post
{
}

View File

@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Tests\Rector\Architecture\DoctrineRepositoryAsService\Source;
final class EntityManagerClass
{
}

View File

@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Tests\Rector\Architecture\DoctrineRepositoryAsService\Source\Repository;
final class PostRepository
{
}

View File

@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Core\Tests\Rector\Architecture\DoctrineRepositoryAsService\Source;
class SymfonyController
{
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineCodeQuality\NodeAnalyzer;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\DoctrineCodeQuality\TypeAnalyzer\TypeFinder;
final class EntityObjectTypeResolver
{
/**
* @var PhpDocInfoFactory
*/
private $phpDocInfoFactory;
/**
* @var TypeFinder
*/
private $typeFinder;
public function __construct(PhpDocInfoFactory $phpDocInfoFactory, TypeFinder $typeFinder)
{
$this->phpDocInfoFactory = $phpDocInfoFactory;
$this->typeFinder = $typeFinder;
}
public function resolveFromRepositoryClass(Class_ $repositoryClass): Type
{
foreach ($repositoryClass->getMethods() as $classMethod) {
if (! $classMethod->isPublic()) {
continue;
}
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($classMethod);
$returnType = $phpDocInfo->getReturnType();
$objectType = $this->typeFinder->find($returnType, ObjectType::class);
if (! $objectType instanceof ObjectType) {
continue;
}
return $objectType;
}
return new MixedType();
}
}

View File

@ -12,11 +12,12 @@ use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Class_;
use PHPStan\Type\ObjectType;
use PHPStan\Type\TypeWithClassName;
use Rector\Core\Exception\Bridge\RectorProviderException;
use Rector\Core\PhpParser\Node\Manipulator\ClassDependencyManipulator;
use Rector\Core\PhpParser\Node\Manipulator\ClassInsertManipulator;
use Rector\Core\Rector\AbstractRector;
use Rector\Doctrine\Contract\Mapper\DoctrineEntityAndRepositoryMapperInterface;
use Rector\DoctrineCodeQuality\NodeAnalyzer\EntityObjectTypeResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
@ -26,11 +27,6 @@ use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
*/
final class MoveRepositoryFromParentToConstructorRector extends AbstractRector
{
/**
* @var DoctrineEntityAndRepositoryMapperInterface
*/
private $doctrineEntityAndRepositoryMapper;
/**
* @var ClassDependencyManipulator
*/
@ -41,14 +37,19 @@ final class MoveRepositoryFromParentToConstructorRector extends AbstractRector
*/
private $classInsertManipulator;
/**
* @var EntityObjectTypeResolver
*/
private $entityObjectTypeResolver;
public function __construct(
ClassDependencyManipulator $classDependencyManipulator,
ClassInsertManipulator $classInsertManipulator,
DoctrineEntityAndRepositoryMapperInterface $doctrineEntityAndRepositoryMapper
EntityObjectTypeResolver $entityObjectTypeResolver
) {
$this->doctrineEntityAndRepositoryMapper = $doctrineEntityAndRepositoryMapper;
$this->classDependencyManipulator = $classDependencyManipulator;
$this->classInsertManipulator = $classInsertManipulator;
$this->entityObjectTypeResolver = $entityObjectTypeResolver;
}
public function getRuleDefinition(): RuleDefinition
@ -141,22 +142,21 @@ CODE_SAMPLE
/**
* Creates:
* "$this->repository = $entityManager->getRepository()"
* "$this->repository = $entityManager->getRepository(SomeEntityClass::class)"
*/
private function createRepositoryAssign(Class_ $class): Assign
private function createRepositoryAssign(Class_ $repositoryClass): Assign
{
$repositoryClassName = (string) $class->getAttribute(AttributeKey::CLASS_NAME);
$entityClassName = $this->doctrineEntityAndRepositoryMapper->mapRepositoryToEntity($repositoryClassName);
$entityObjectType = $this->entityObjectTypeResolver->resolveFromRepositoryClass($repositoryClass);
$repositoryClassName = (string) $repositoryClass->getAttribute(AttributeKey::CLASS_NAME);
if ($entityClassName === null) {
if (! $entityObjectType instanceof TypeWithClassName) {
throw new RectorProviderException(sprintf(
'An entity was not provided for "%s" repository by your "%s" class.',
'An entity was not found for "%s" repository.',
$repositoryClassName,
get_class($this->doctrineEntityAndRepositoryMapper)
));
}
$classConstFetch = $this->createClassConstReference($entityClassName);
$classConstFetch = $this->createClassConstReference($entityObjectType->getClassName());
$methodCall = $this->builderFactory->methodCall(
new Variable('entityManager'),
@ -164,8 +164,7 @@ CODE_SAMPLE
[$classConstFetch]
);
$methodCall->setAttribute(AttributeKey::CLASS_NODE, $class);
$methodCall->setAttribute(AttributeKey::CLASS_NODE, $repositoryClassName);
return $this->createPropertyAssignmentWithExpr('repository', $methodCall);
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineCodeQuality\TypeAnalyzer;
use PHPStan\Type\ArrayType;
use PHPStan\Type\IntersectionType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\UnionType;
final class TypeFinder
{
public function find(Type $type, string $desiredTypeClass): Type
{
if (is_a($type, $desiredTypeClass, true)) {
return $type;
}
if ($type instanceof ArrayType && is_a($type->getItemType(), $desiredTypeClass, true)) {
return $type->getItemType();
}
if ($type instanceof UnionType) {
return $this->findInJoinedType($type, $desiredTypeClass);
}
if ($type instanceof IntersectionType) {
return $this->findInJoinedType($type, $desiredTypeClass);
}
return new MixedType();
}
/**
* @param UnionType|IntersectionType $type
*/
private function findInJoinedType(Type $type, string $desiredTypeClass): Type
{
foreach ($type->getTypes() as $joinedType) {
$foundType = $this->find($joinedType, $desiredTypeClass);
if (! $foundType instanceof MixedType) {
return $foundType;
}
}
return new MixedType();
}
}

View File

@ -5,17 +5,16 @@ declare(strict_types=1);
namespace Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService;
use Iterator;
use Rector\Architecture\Rector\MethodCall\ReplaceParentRepositoryCallsByRepositoryPropertyRector;
use Rector\Architecture\Rector\MethodCall\ServiceLocatorToDIRector;
use Rector\Doctrine\Rector\Class_\RemoveRepositoryFromEntityAnnotationRector;
use Rector\Doctrine\Rector\MethodCall\ReplaceParentRepositoryCallsByRepositoryPropertyRector;
use Rector\DoctrineCodeQuality\Rector\Class_\MoveRepositoryFromParentToConstructorRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;
use Symplify\SmartFileSystem\SmartFileInfo;
/**
* @see \Rector\Architecture\Rector\MethodCall\ReplaceParentRepositoryCallsByRepositoryPropertyRector
* @see \Rector\Doctrine\Rector\MethodCall\ReplaceParentRepositoryCallsByRepositoryPropertyRector
* @see \Rector\DoctrineCodeQuality\Rector\Class_\MoveRepositoryFromParentToConstructorRector
* @see \Rector\Architecture\Rector\MethodCall\ServiceLocatorToDIRector
* @see \Rector\Doctrine\Rector\MethodCall\EntityRepositoryServiceLocatorToDIRector
*/
final class DoctrineRepositoryAsServiceTest extends AbstractRectorTestCase
{
@ -40,7 +39,6 @@ final class DoctrineRepositoryAsServiceTest extends AbstractRectorTestCase
return [
# order matters, this needs to be first to correctly detect parent repository
MoveRepositoryFromParentToConstructorRector::class => [],
ServiceLocatorToDIRector::class => [],
ReplaceParentRepositoryCallsByRepositoryPropertyRector::class => [],
RemoveRepositoryFromEntityAnnotationRector::class => [],
];

View File

@ -2,7 +2,7 @@
namespace Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Fixture;
use Rector\Core\Tests\Rector\Architecture\DoctrineRepositoryAsService\Source\Entity\Post;
use Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Source\Entity\Post;
use Rector\Core\Tests\Rector\Architecture\DoctrineRepositoryAsService\Source\SymfonyController;
use Symfony\Component\HttpFoundation\Response;
@ -23,7 +23,7 @@ final class PostController extends SymfonyController
namespace Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Fixture;
use Rector\Core\Tests\Rector\Architecture\DoctrineRepositoryAsService\Source\Entity\Post;
use Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Source\Entity\Post;
use Rector\Core\Tests\Rector\Architecture\DoctrineRepositoryAsService\Source\SymfonyController;
use Symfony\Component\HttpFoundation\Response;

View File

@ -1,8 +1,8 @@
<?php
namespace Rector\Architecture\Tests\Rector\DoctrineRepositoryAsService\Fixture;
namespace Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Fixture;
use App\Entity\Post;
use Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Source\Entity\Post;
use Doctrine\ORM\EntityRepository;
final class FirstPostRepository extends EntityRepository
@ -18,21 +18,15 @@ final class FirstPostRepository extends EntityRepository
'author' => $authorId
]);
}
public function shouldSkip()
{
$anotherClass = new \stdClass();
$anotherClass->findBy();
}
}
?>
-----
<?php
namespace Rector\Architecture\Tests\Rector\DoctrineRepositoryAsService\Fixture;
namespace Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Fixture;
use App\Entity\Post;
use Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Source\Entity\Post;
use Doctrine\ORM\EntityRepository;
final class FirstPostRepository
@ -40,7 +34,7 @@ final class FirstPostRepository
private \Doctrine\ORM\EntityRepository $repository;
public function __construct(\Doctrine\ORM\EntityManager $entityManager)
{
$this->repository = $entityManager->getRepository(\App\Entity\FirstPost::class);
$this->repository = $entityManager->getRepository(\Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Source\Entity\Post::class);
}
/**
* Our custom method
@ -53,12 +47,6 @@ final class FirstPostRepository
'author' => $authorId
]);
}
public function shouldSkip()
{
$anotherClass = new \stdClass();
$anotherClass->findBy();
}
}
?>

View File

@ -6,7 +6,7 @@ use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
final class ItemRepositoryTest extends TestCase
final class SkipTestCase extends TestCase
{
public function testGetThrowsExceptionIfNotFound(): void
{

View File

@ -1,6 +1,6 @@
<?php
namespace Rector\Architecture\Tests\Rector\DoctrineRepositoryAsService\Fixture;
namespace Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Fixture;
use App\Entity\Post;
use Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Source\RandomClass;

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Source\Entity;
final class Post
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Source;
final class EntityManagerClass
{
}

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Rector\Core\Tests\Rector\Architecture\DoctrineRepositoryAsService\Source;
namespace Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Source;
class EntityRepositoryClass
{

View File

@ -6,5 +6,4 @@ namespace Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\So
abstract class RandomClass
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Source\Repository;
final class PostRepository
{
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Rector\DoctrineCodeQuality\Tests\Rector\DoctrineRepositoryAsService\Source;
class SymfonyController
{
}

View File

@ -2,8 +2,6 @@
declare(strict_types=1);
use Rector\Doctrine\Contract\Mapper\DoctrineEntityAndRepositoryMapperInterface;
use Rector\Doctrine\Mapper\DefaultDoctrineEntityAndRepositoryMapper;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
@ -16,9 +14,4 @@ return static function (ContainerConfigurator $containerConfigurator): void {
$services->load('Rector\Doctrine\\', __DIR__ . '/../src')
->exclude([__DIR__ . '/../src/Rector']);
$services->alias(
DoctrineEntityAndRepositoryMapperInterface::class,
DefaultDoctrineEntityAndRepositoryMapper::class
);
};

View File

@ -1,12 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Doctrine\Contract\Mapper;
interface DoctrineEntityAndRepositoryMapperInterface
{
public function mapRepositoryToEntity(string $name): ?string;
public function mapEntityToRepository(string $name): ?string;
}

View File

@ -1,46 +0,0 @@
<?php
declare(strict_types=1);
namespace Rector\Doctrine\Mapper;
use Nette\Utils\Strings;
use Rector\Doctrine\Contract\Mapper\DoctrineEntityAndRepositoryMapperInterface;
final class DefaultDoctrineEntityAndRepositoryMapper implements DoctrineEntityAndRepositoryMapperInterface
{
/**
* @var string
*/
private const REPOSITORY = 'Repository';
/**
* @var string
* @see https://regex101.com/r/WrYZ0d/1
*/
private const REPOSITORY_REGEX = '#Repository#';
/**
* @var string
* @see https://regex101.com/r/2a2CY6/1
*/
private const ENTITY_REGEX = '#Entity#';
public function mapRepositoryToEntity(string $repository): ?string
{
// "SomeRepository" => "Some"
$withoutSuffix = Strings::substring($repository, 0, - strlen(self::REPOSITORY));
// "App\Repository\Some" => "App\Entity\Some"
return Strings::replace($withoutSuffix, self::REPOSITORY_REGEX, 'Entity');
}
public function mapEntityToRepository(string $entity): ?string
{
// "Some" => "SomeRepository"
$withSuffix = $entity . self::REPOSITORY;
// "App\Entity\SomeRepository" => "App\Repository\SomeRepository"
return Strings::replace($withSuffix, self::ENTITY_REGEX, self::REPOSITORY);
}
}

View File

@ -68,6 +68,10 @@ CODE_SAMPLE
return null;
}
if (! $entityTagValueNode->hasRepositoryClass()) {
return null;
}
$entityTagValueNode->removeRepositoryClass();
$phpDocInfo->markAsChanged();

View File

@ -27,7 +27,7 @@ final class EntityAliasToClassConstantReferenceRector extends AbstractRector imp
* @api
* @var string
*/
public const ALIASES_TO_NAMESPACES = '$aliasesToNamespaces';
public const ALIASES_TO_NAMESPACES = 'aliases_to_namespaces';
/**
* @var string[]

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Rector\Architecture\Rector\MethodCall;
namespace Rector\Doctrine\Rector\MethodCall;
use PhpParser\Node;
use PhpParser\Node\Expr\MethodCall;

View File

@ -117,9 +117,9 @@ trait NodeFactoryTrait
return $this->nodeFactory->createFuncCall($name, $arguments);
}
protected function createClassConstReference(string $class): ClassConstFetch
protected function createClassConstReference(string $className): ClassConstFetch
{
return $this->nodeFactory->createClassConstReference($class);
return $this->nodeFactory->createClassConstReference($className);
}
protected function createPropertyAssignmentWithExpr(string $propertyName, Expr $expr): Assign