Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save stephanvierkant/a341c1c336b01022661e16a7e8a2a2fd to your computer and use it in GitHub Desktop.
Save stephanvierkant/a341c1c336b01022661e16a7e8a2a2fd to your computer and use it in GitHub Desktop.
Work in progress: AddAnnotationToRepositoryRector
<?php
declare(strict_types=1);
namespace App;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Name\FullyQualified;
use PHPStan\PhpDocParser\Ast\PhpDoc\GenericTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use Rector\Rector\AbstractRector;
use Rector\ValueObject\MethodName;
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
use Rector\Comments\NodeDocBlock\DocBlockUpdater;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
final class AddAnnotationToRepositoryRector extends AbstractRector
{
private DocBlockUpdater $docBlockUpdater;
private PhpDocInfoFactory $phpDocInfoFactory;
public function __construct(DocBlockUpdater $docBlockUpdater, PhpDocInfoFactory $phpDocInfoFactory)
{
$this->docBlockUpdater = $docBlockUpdater;
$this->phpDocInfoFactory = $phpDocInfoFactory;
}
public function getRuleDefinition(): RuleDefinition
{
return new RuleDefinition('Add @extends ServiceEntityRepository<T> annotation to repository classes', [
new CodeSample(
<<<'CODE_SAMPLE'
final class SomeEntityRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, SomeEntity::class);
}
}
CODE_SAMPLE
,
<<<'CODE_SAMPLE'
/** @extends ServiceEntityRepository<SomeEntity> */
final class SomeEntityRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, SomeEntity::class);
}
}
CODE_SAMPLE
),
]);
}
/**
* @return array<class-string<Node>>
*/
public function getNodeTypes(): array
{
return [Class_::class];
}
/**
* @param Class_ $node
*/
public function refactor(Node $node): ?Node
{
if ($this->isRepositoryClass($node)) {
$entityClass = $this->getEntityClassFromConstructor($node);
if ($entityClass !== null && !$this->hasExtendsAnnotation($node)) {
$this->addAnnotationToNode($node, $entityClass);
}
}
return $node;
}
private function isRepositoryClass(Class_ $class): bool
{
if ($class->extends instanceof FullyQualified) {
return $class->extends->toString() === 'Doctrine\\Bundle\\DoctrineBundle\\Repository\\ServiceEntityRepository';
}
return false;
}
private function getEntityClassFromConstructor(Class_ $class): ?string
{
$method = $class->getMethod(MethodName::CONSTRUCT);
foreach ($method->stmts as $stmt) {
if ($stmt instanceof Node\Stmt\Expression) {
$expr = $stmt->expr;
if ($expr instanceof Node\Expr\StaticCall &&
$expr->class->toString() === 'parent' &&
$expr->name->toString() === '__construct' &&
isset($expr->args[1]) &&
$expr->args[1]->value instanceof ClassConstFetch
) {
return $expr->args[1]->value->class->toString();
}
}
}
return null;
}
private function addAnnotationToNode(Class_ $class, string $entityClass): void
{
$phpDocInfo = $this->phpDocInfoFactory->createFromNodeOrEmpty($class);
$phpDocNode = $phpDocInfo->getPhpDocNode();
$phpDocNode->children[] = new PhpDocTagNode('@extends', new GenericTagValueNode('ServiceEntityRepository<\\' . $entityClass . '>'));
$this->docBlockUpdater->updateRefactoredNodeWithPhpDocInfo($class);
}
private function hasExtendsAnnotation(Class_ $class): bool
{
if ($class->getDocComment() !== null) {
$docComment = $class->getDocComment()->getText();
return str_contains($docComment, '@extends');
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment