Skip to content

Commit

Permalink
[PHP 8.1] Add support for values method in SpatieEnumClassToEnumRector (
Browse files Browse the repository at this point in the history
#3196)

Co-authored-by: jools <julien.dephix.nagoya@accopilot.com>
  • Loading branch information
JoolsMcFly and jools committed Dec 15, 2022
1 parent e71df1b commit 40f6d19
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Rector\Tests\Php81\Rector\Class_\SpatieEnumClassToEnumRector\Fixture;

use Spatie\Enum\Enum;

/**
* @method static self draft()
* @method static self published()
* @method static self archived()
*/
class StatusEnum extends Enum
{
protected static function values(): array
{
return [
'draft' => 1,
'published' => 2,
'archived' => 3,
];
}
}

?>
-----
<?php

namespace Rector\Tests\Php81\Rector\Class_\SpatieEnumClassToEnumRector\Fixture;

use Spatie\Enum\Enum;

enum StatusEnum : int
{
case DRAFT = 1;
case PUBLISHED = 2;
case ARCHIVED = 3;
}

?>
73 changes: 67 additions & 6 deletions rules/Php81/NodeFactory/EnumFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@
namespace Rector\Php81\NodeFactory;

use PhpParser\BuilderFactory;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\Enum_;
use PhpParser\Node\Stmt\EnumCase;
use PhpParser\Node\Stmt\Return_;
use PHPStan\PhpDocParser\Ast\PhpDoc\MethodTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use Rector\BetterPhpDocParser\PhpDocInfo\PhpDocInfoFactory;
use Rector\Core\PhpParser\Node\BetterNodeFinder;
use Rector\Core\PhpParser\Node\Value\ValueResolver;
use Rector\NodeNameResolver\NodeNameResolver;
use Rector\NodeTypeResolver\Node\AttributeKey;
Expand All @@ -23,7 +28,8 @@ public function __construct(
private readonly NodeNameResolver $nodeNameResolver,
private readonly PhpDocInfoFactory $phpDocInfoFactory,
private readonly BuilderFactory $builderFactory,
private readonly ValueResolver $valueResolver
private readonly ValueResolver $valueResolver,
private readonly BetterNodeFinder $betterNodeFinder
) {
}

Expand Down Expand Up @@ -69,10 +75,12 @@ public function createFromSpatieClass(Class_ $class): Enum_

$docBlockMethods = $phpDocInfo->getTagsByName('@method');
if ($docBlockMethods !== []) {
$enum->scalarType = new Identifier('string');
$mapping = $this->generateMappingFromClass($class);
$identifierType = $this->getIdentifierTypeFromMappings($mapping);
$enum->scalarType = new Identifier($identifierType);

foreach ($docBlockMethods as $docBlockMethod) {
$enum->stmts[] = $this->createEnumCaseFromDocComment($docBlockMethod);
$enum->stmts[] = $this->createEnumCaseFromDocComment($docBlockMethod, $mapping);
}
}

Expand All @@ -91,14 +99,67 @@ private function createEnumCaseFromConst(ClassConst $classConst): EnumCase
return $enumCase;
}

private function createEnumCaseFromDocComment(PhpDocTagNode $phpDocTagNode): EnumCase
/**
* @param array<int|string, mixed> $mapping
*/
private function createEnumCaseFromDocComment(PhpDocTagNode $phpDocTagNode, array $mapping = []): EnumCase
{
/** @var MethodTagValueNode $nodeValue */
$nodeValue = $phpDocTagNode->value;

$enumValue = $mapping[$nodeValue->methodName] ?? $nodeValue->methodName;
$enumName = strtoupper($nodeValue->methodName);
$enumExpr = $this->builderFactory->val($nodeValue->methodName);
$enumExpr = $this->builderFactory->val($enumValue);

return new EnumCase($enumName, $enumExpr);
}

/**
* @return array<int|string, mixed>
*/
private function generateMappingFromClass(Class_ $class): array
{
$classMethod = $class->getMethod('values');
if ($classMethod === null) {
return [];
}

$returns = $this->betterNodeFinder->findInstancesOfInFunctionLikeScoped($classMethod, Return_::class);
$mapping = [];
foreach ($returns as $returnStmt) {
if (! ($returnStmt->expr instanceof Expr\Array_)) {
continue;
}
foreach ($returnStmt->expr->items as $item) {
if ($item instanceof Expr\ArrayItem
&& ($item->key instanceof LNumber || $item->key instanceof String_)
&& ($item->value instanceof LNumber || $item->value instanceof String_)
) {
$mapping[$item->key->value] = $item->value->value;
}
}
}

return $mapping;
}

/**
* @param array<int|string, mixed> $mapping
*/
private function getIdentifierTypeFromMappings(array $mapping): string
{
$valueTypes = array_map(static function ($value): string {
return gettype($value);
}, $mapping);
$uniqueValueTypes = array_unique($valueTypes);
if (count($uniqueValueTypes) === 1) {
$identifierType = reset($uniqueValueTypes);
if ($identifierType === 'integer') {
$identifierType = 'int';
}
} else {
$identifierType = 'string';
}

return $identifierType;
}
}

0 comments on commit 40f6d19

Please sign in to comment.