mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-07 03:40:50 +00:00
[PHP 8.0] Include keys in annotation to attribute transformation (#1766)
Co-authored-by: Morgan NEUTE <morgan.neute@niji.fr> Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
parent
0a63fe4d52
commit
f374b9faa8
|
@ -4,6 +4,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\Tests\PhpAttribute\AnnotationToAttributeMapper;
|
||||
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use Rector\PhpAttribute\AnnotationToAttributeMapper;
|
||||
use Rector\Testing\PHPUnit\AbstractTestCase;
|
||||
|
@ -20,10 +24,23 @@ final class AnnotationToAttributeMapperTest extends AbstractTestCase
|
|||
|
||||
public function test(): void
|
||||
{
|
||||
$mappedExpr = $this->annotationToAttributeMapper->map(false);
|
||||
$this->assertInstanceOf(ConstFetch::class, $mappedExpr);
|
||||
|
||||
$mappedExpr = $this->annotationToAttributeMapper->map('false');
|
||||
$this->assertInstanceOf(ConstFetch::class, $mappedExpr);
|
||||
|
||||
$mappedExpr = $this->annotationToAttributeMapper->map('100');
|
||||
$this->assertInstanceOf(LNumber::class, $mappedExpr);
|
||||
|
||||
$mappedExpr = $this->annotationToAttributeMapper->map('hey');
|
||||
$this->assertInstanceOf(String_::class, $mappedExpr);
|
||||
|
||||
$mappedExprs = $this->annotationToAttributeMapper->map(['hey']);
|
||||
$this->assertInstanceOf(String_::class, $mappedExprs[0]);
|
||||
$expr = $this->annotationToAttributeMapper->map(['hey']);
|
||||
$this->assertInstanceOf(Array_::class, $expr);
|
||||
|
||||
$arrayItem = $expr->items[0];
|
||||
$this->assertInstanceOf(ArrayItem::class, $arrayItem);
|
||||
$this->assertInstanceOf(String_::class, $arrayItem->value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\PhpAttribute;
|
||||
|
||||
use PhpParser\BuilderHelpers;
|
||||
use PhpParser\Node\Expr;
|
||||
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
|
||||
use Rector\PhpAttribute\Contract\AnnotationToAttributeMapperInterface;
|
||||
use Rector\PhpAttribute\Enum\DocTagNodeState;
|
||||
|
||||
|
@ -21,10 +23,7 @@ final class AnnotationToAttributeMapper
|
|||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Expr|Expr[]|string
|
||||
*/
|
||||
public function map(mixed $value): array|Expr|string
|
||||
public function map(mixed $value): Expr|string
|
||||
{
|
||||
foreach ($this->annotationToAttributeMappers as $annotationToAttributeMapper) {
|
||||
if ($annotationToAttributeMapper->isCandidate($value)) {
|
||||
|
@ -36,6 +35,12 @@ final class AnnotationToAttributeMapper
|
|||
return $value;
|
||||
}
|
||||
|
||||
return DocTagNodeState::REMOVE_ARRAY;
|
||||
// remove node, as handled elsewhere
|
||||
if ($value instanceof DoctrineAnnotationTagValueNode) {
|
||||
return DocTagNodeState::REMOVE_ARRAY;
|
||||
}
|
||||
|
||||
// fallback
|
||||
return BuilderHelpers::normalizeValue($value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,13 @@ declare(strict_types=1);
|
|||
namespace Rector\PhpAttribute\AnnotationToAttributeMapper;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use Rector\PhpAttribute\AnnotationToAttributeMapper;
|
||||
use Rector\PhpAttribute\Contract\AnnotationToAttributeMapperInterface;
|
||||
use Rector\PhpAttribute\Enum\DocTagNodeState;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/**
|
||||
* @implements AnnotationToAttributeMapperInterface<mixed[]>
|
||||
|
@ -34,23 +37,41 @@ final class ArrayAnnotationToAttributeMapper implements AnnotationToAttributeMap
|
|||
/**
|
||||
* @param mixed[] $value
|
||||
*/
|
||||
public function map($value): array|Expr
|
||||
public function map($value): Expr
|
||||
{
|
||||
$values = array_map(fn ($item): Expr|array => $this->annotationToAttributeMapper->map($item), $value);
|
||||
$arrayItems = [];
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
// remove the key and value? useful in case of unwrapping nested attributes
|
||||
if (! $this->isRemoveArrayPlaceholder($value)) {
|
||||
foreach ($value as $key => $singleValue) {
|
||||
$valueExpr = $this->annotationToAttributeMapper->map($singleValue);
|
||||
|
||||
// remove node
|
||||
if ($valueExpr === DocTagNodeState::REMOVE_ARRAY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($values[$key]);
|
||||
Assert::isInstanceOf($valueExpr, Expr::class);
|
||||
|
||||
// remove value
|
||||
if ($this->isRemoveArrayPlaceholder($singleValue)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$keyExpr = null;
|
||||
if (! is_int($key)) {
|
||||
$keyExpr = $this->annotationToAttributeMapper->map($key);
|
||||
Assert::isInstanceOf($keyExpr, Expr::class);
|
||||
}
|
||||
|
||||
$arrayItems[] = new ArrayItem($valueExpr, $keyExpr);
|
||||
}
|
||||
|
||||
return $values;
|
||||
return new Array_($arrayItems);
|
||||
}
|
||||
|
||||
private function isRemoveArrayPlaceholder(Expr|array $value): bool
|
||||
/**
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function isRemoveArrayPlaceholder($value): bool
|
||||
{
|
||||
if (! is_array($value)) {
|
||||
return false;
|
||||
|
|
|
@ -5,10 +5,14 @@ declare(strict_types=1);
|
|||
namespace Rector\PhpAttribute\AnnotationToAttributeMapper;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use Rector\BetterPhpDocParser\ValueObject\PhpDoc\DoctrineAnnotation\CurlyListNode;
|
||||
use Rector\PhpAttribute\AnnotationToAttributeMapper;
|
||||
use Rector\PhpAttribute\Contract\AnnotationToAttributeMapperInterface;
|
||||
use Rector\PhpAttribute\Enum\DocTagNodeState;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/**
|
||||
* @implements AnnotationToAttributeMapperInterface<CurlyListNode>
|
||||
|
@ -34,11 +38,28 @@ final class CurlyListNodeAnnotationToAttributeMapper implements AnnotationToAttr
|
|||
/**
|
||||
* @param CurlyListNode $value
|
||||
*/
|
||||
public function map($value): array|Expr
|
||||
public function map($value): Array_
|
||||
{
|
||||
return array_map(
|
||||
fn ($node): mixed => $this->annotationToAttributeMapper->map($node),
|
||||
$value->getValuesWithExplicitSilentAndWithoutQuotes()
|
||||
);
|
||||
$arrayItems = [];
|
||||
foreach ($value->getValuesWithExplicitSilentAndWithoutQuotes() as $key => $singleValue) {
|
||||
$valueExpr = $this->annotationToAttributeMapper->map($singleValue);
|
||||
|
||||
// remove node
|
||||
if ($valueExpr === DocTagNodeState::REMOVE_ARRAY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Assert::isInstanceOf($valueExpr, Expr::class);
|
||||
|
||||
$keyExpr = null;
|
||||
if (! is_int($key)) {
|
||||
$keyExpr = $this->annotationToAttributeMapper->map($key);
|
||||
Assert::isInstanceOf($keyExpr, Expr::class);
|
||||
}
|
||||
|
||||
$arrayItems[] = new ArrayItem($valueExpr, $keyExpr);
|
||||
}
|
||||
|
||||
return new Array_($arrayItems);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\PhpAttribute\AnnotationToAttributeMapper;
|
||||
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\New_;
|
||||
use PhpParser\Node\Name;
|
||||
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
|
||||
|
@ -11,9 +12,9 @@ use Rector\Core\Exception\ShouldNotHappenException;
|
|||
use Rector\Core\Php\PhpVersionProvider;
|
||||
use Rector\Core\ValueObject\PhpVersionFeature;
|
||||
use Rector\PhpAttribute\AnnotationToAttributeMapper;
|
||||
use Rector\PhpAttribute\AttributeArrayNameInliner;
|
||||
use Rector\PhpAttribute\Contract\AnnotationToAttributeMapperInterface;
|
||||
use Rector\PhpAttribute\Exception\InvalidNestedAttributeException;
|
||||
use Rector\PhpAttribute\NodeFactory\NamedArgsFactory;
|
||||
use Rector\PhpAttribute\UnwrapableAnnotationAnalyzer;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
|
@ -26,8 +27,8 @@ final class DoctrineAnnotationAnnotationToAttributeMapper implements AnnotationT
|
|||
|
||||
public function __construct(
|
||||
private readonly PhpVersionProvider $phpVersionProvider,
|
||||
private readonly NamedArgsFactory $namedArgsFactory,
|
||||
private readonly UnwrapableAnnotationAnalyzer $unwrapableAnnotationAnalyzer,
|
||||
private readonly AttributeArrayNameInliner $attributeArrayNameInliner,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -67,11 +68,12 @@ final class DoctrineAnnotationAnnotationToAttributeMapper implements AnnotationT
|
|||
$value->getValuesWithExplicitSilentAndWithoutQuotes()
|
||||
);
|
||||
|
||||
if (! is_array($argValues)) {
|
||||
if ($argValues instanceof Array_) {
|
||||
// create named args
|
||||
$args = $this->attributeArrayNameInliner->inlineArrayToArgs($argValues);
|
||||
} else {
|
||||
throw new ShouldNotHappenException();
|
||||
}
|
||||
|
||||
$args = $this->namedArgsFactory->createFromValues($argValues);
|
||||
} else {
|
||||
$args = [];
|
||||
}
|
||||
|
|
|
@ -4,7 +4,12 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\PhpAttribute\AnnotationToAttributeMapper;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\ConstFetch;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\PhpAttribute\Contract\AnnotationToAttributeMapperInterface;
|
||||
|
||||
/**
|
||||
|
@ -20,8 +25,33 @@ final class StringAnnotationToAttributeMapper implements AnnotationToAttributeMa
|
|||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
public function map($value): String_
|
||||
public function map($value): Expr
|
||||
{
|
||||
return new String_($value);
|
||||
if (strtolower($value) === 'true') {
|
||||
return new ConstFetch(new Name('true'));
|
||||
}
|
||||
|
||||
if (strtolower($value) === 'false') {
|
||||
return new ConstFetch(new Name('false'));
|
||||
}
|
||||
|
||||
if (strtolower($value) === 'null') {
|
||||
return new ConstFetch(new Name('null'));
|
||||
}
|
||||
|
||||
// number as string to number
|
||||
if (is_numeric($value) && strlen((string) (int) $value) === strlen($value)) {
|
||||
return LNumber::fromString($value);
|
||||
}
|
||||
|
||||
if (str_contains($value, "'") && ! str_contains($value, "\n")) {
|
||||
$kind = String_::KIND_DOUBLE_QUOTED;
|
||||
} else {
|
||||
$kind = String_::KIND_SINGLE_QUOTED;
|
||||
}
|
||||
|
||||
return new String_($value, [
|
||||
AttributeKey::KIND => $kind,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
79
packages/PhpAttribute/AttributeArrayNameInliner.php
Normal file
79
packages/PhpAttribute/AttributeArrayNameInliner.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\PhpAttribute;
|
||||
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
final class AttributeArrayNameInliner
|
||||
{
|
||||
/**
|
||||
* @param Array_|Arg[] $array
|
||||
* @return Arg[]
|
||||
*/
|
||||
public function inlineArrayToArgs(Array_|array $array): array
|
||||
{
|
||||
if (is_array($array)) {
|
||||
return $this->inlineArray($array);
|
||||
}
|
||||
|
||||
return $this->inlineArrayNode($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Arg[]
|
||||
*/
|
||||
private function inlineArrayNode(Array_ $array): array
|
||||
{
|
||||
$args = [];
|
||||
|
||||
foreach ($array->items as $arrayItem) {
|
||||
if (! $arrayItem instanceof ArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = $arrayItem->key;
|
||||
if ($key instanceof String_) {
|
||||
$args[] = new Arg($arrayItem->value, false, false, [], new Identifier($key->value));
|
||||
} else {
|
||||
$args[] = new Arg($arrayItem->value);
|
||||
}
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Arg[] $args
|
||||
* @return Arg[]
|
||||
*/
|
||||
private function inlineArray(array $args): array
|
||||
{
|
||||
Assert::allIsAOf($args, Arg::class);
|
||||
|
||||
$newArgs = [];
|
||||
|
||||
foreach ($args as $arg) {
|
||||
// matching top root array key
|
||||
if ($arg->value instanceof ArrayItem) {
|
||||
$arrayItem = $arg->value;
|
||||
if ($arrayItem->key instanceof String_) {
|
||||
$arrayItemString = $arrayItem->key;
|
||||
$newArgs[] = new Arg($arrayItem->value, false, false, [], new Identifier($arrayItemString->value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($newArgs !== []) {
|
||||
return $newArgs;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
}
|
|
@ -16,5 +16,5 @@ interface AnnotationToAttributeMapperInterface
|
|||
/**
|
||||
* @param T $value
|
||||
*/
|
||||
public function map($value): array|Expr;
|
||||
public function map($value): Expr;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Rector\PhpAttribute\NodeAnalyzer;
|
||||
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Expr\Array_;
|
||||
use PhpParser\Node\Scalar\LNumber;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PHPStan\Reflection\ParameterReflection;
|
||||
|
@ -29,8 +30,12 @@ final class ExprParameterReflectionTypeCorrector
|
|||
* @param array<string|int, Expr|mixed> $items
|
||||
* @return array<string|int, Expr|mixed>
|
||||
*/
|
||||
public function correctItemsByAttributeClass(array $items, string $attributeClass): array
|
||||
public function correctItemsByAttributeClass(array|Array_ $items, string $attributeClass): array
|
||||
{
|
||||
if ($items instanceof Array_) {
|
||||
$items = $items->items;
|
||||
}
|
||||
|
||||
if (! $this->reflectionProvider->hasClass($attributeClass)) {
|
||||
return $items;
|
||||
}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\PhpAttribute\NodeAnalyzer;
|
||||
|
||||
use PHPStan\Reflection\ParameterReflection;
|
||||
use PHPStan\Reflection\ParametersAcceptorSelector;
|
||||
use PHPStan\Reflection\ReflectionProvider;
|
||||
|
||||
final class NamedArgumentsResolver
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ReflectionProvider $reflectionProvider
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $class
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function resolveFromClass(string $class): array
|
||||
{
|
||||
// decorate args with names if attribute class was found
|
||||
if (! $this->reflectionProvider->hasClass($class)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$classReflection = $this->reflectionProvider->getClass($class);
|
||||
if (! $classReflection->hasConstructor()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$argumentNames = [];
|
||||
$constructorMethodReflection = $classReflection->getConstructor();
|
||||
|
||||
$parametersAcceptor = ParametersAcceptorSelector::selectSingle($constructorMethodReflection->getVariants());
|
||||
foreach ($parametersAcceptor->getParameters() as $key => $parameterReflection) {
|
||||
/** @var ParameterReflection $parameterReflection */
|
||||
$argumentNames[$key] = $parameterReflection->getName();
|
||||
}
|
||||
|
||||
return $argumentNames;
|
||||
}
|
||||
}
|
|
@ -8,29 +8,44 @@ use PhpParser\Node\Arg;
|
|||
use PhpParser\Node\Attribute;
|
||||
use PhpParser\Node\AttributeGroup;
|
||||
use PhpParser\Node\Expr;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Expr\ArrayItem;
|
||||
use PhpParser\Node\Name\FullyQualified;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use Rector\BetterPhpDocParser\PhpDoc\DoctrineAnnotationTagValueNode;
|
||||
use Rector\Core\Php\PhpVersionProvider;
|
||||
use Rector\Core\ValueObject\PhpVersionFeature;
|
||||
use Rector\Php80\ValueObject\AnnotationToAttribute;
|
||||
use Rector\PhpAttribute\AnnotationToAttributeMapper;
|
||||
use Rector\PhpAttribute\AttributeArrayNameInliner;
|
||||
use Rector\PhpAttribute\NodeAnalyzer\ExprParameterReflectionTypeCorrector;
|
||||
use Rector\PhpAttribute\NodeAnalyzer\NamedArgumentsResolver;
|
||||
use Rector\PhpAttribute\NodeFactory\AttributeNameFactory;
|
||||
use Rector\PhpAttribute\NodeFactory\NamedArgsFactory;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
/**
|
||||
* @see \Rector\Tests\PhpAttribute\Printer\PhpAttributeGroupFactoryTest
|
||||
*/
|
||||
final class PhpAttributeGroupFactory
|
||||
{
|
||||
/**
|
||||
* @var array<string, string[]>>
|
||||
*/
|
||||
private array $unwrappedAnnotations = [
|
||||
'Doctrine\ORM\Mapping\Table' => ['uniqueConstraints'],
|
||||
'Doctrine\ORM\Mapping\Entity' => ['uniqueConstraints'],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly NamedArgumentsResolver $namedArgumentsResolver,
|
||||
private readonly AnnotationToAttributeMapper $annotationToAttributeMapper,
|
||||
private readonly AttributeNameFactory $attributeNameFactory,
|
||||
private readonly NamedArgsFactory $namedArgsFactory,
|
||||
private readonly ExprParameterReflectionTypeCorrector $exprParameterReflectionTypeCorrector
|
||||
private readonly ExprParameterReflectionTypeCorrector $exprParameterReflectionTypeCorrector,
|
||||
private readonly AttributeArrayNameInliner $attributeArrayNameInliner,
|
||||
PhpVersionProvider $phpVersionProvider
|
||||
) {
|
||||
// nested indexes supported only since PHP 8.1
|
||||
if (! $phpVersionProvider->isAtLeastPhpVersion(PhpVersionFeature::NEW_INITIALIZERS)) {
|
||||
$this->unwrappedAnnotations['Doctrine\ORM\Mapping\Table'][] = 'indexes';
|
||||
}
|
||||
}
|
||||
|
||||
public function createFromSimpleTag(AnnotationToAttribute $annotationToAttribute): AttributeGroup
|
||||
|
@ -64,9 +79,11 @@ final class PhpAttributeGroupFactory
|
|||
$values = $doctrineAnnotationTagValueNode->getValuesWithExplicitSilentAndWithoutQuotes();
|
||||
|
||||
$args = $this->createArgsFromItems($values, $annotationToAttribute->getAttributeClass());
|
||||
$argumentNames = $this->namedArgumentsResolver->resolveFromClass($annotationToAttribute->getAttributeClass());
|
||||
|
||||
$this->completeNamedArguments($args, $argumentNames);
|
||||
// @todo this can be a different class then the unwrapped crated one
|
||||
//$argumentNames = $this->namedArgumentsResolver->resolveFromClass($annotationToAttribute->getAttributeClass());
|
||||
|
||||
$args = $this->attributeArrayNameInliner->inlineArrayToArgs($args);
|
||||
|
||||
$attributeName = $this->attributeNameFactory->create($annotationToAttribute, $doctrineAnnotationTagValueNode);
|
||||
|
||||
|
@ -80,33 +97,45 @@ final class PhpAttributeGroupFactory
|
|||
*/
|
||||
public function createArgsFromItems(array $items, string $attributeClass): array
|
||||
{
|
||||
/** @var Expr[] $items */
|
||||
/** @var Expr[]|Expr\Array_ $items */
|
||||
$items = $this->annotationToAttributeMapper->map($items);
|
||||
|
||||
$items = $this->exprParameterReflectionTypeCorrector->correctItemsByAttributeClass($items, $attributeClass);
|
||||
|
||||
$items = $this->removeUnwrappedItems($attributeClass, $items);
|
||||
|
||||
return $this->namedArgsFactory->createFromValues($items);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Arg[] $args
|
||||
* @param string[] $argumentNames
|
||||
* @param mixed[] $items
|
||||
* @return mixed[]
|
||||
*/
|
||||
private function completeNamedArguments(array $args, array $argumentNames): void
|
||||
private function removeUnwrappedItems(string $attributeClass, array $items): array
|
||||
{
|
||||
Assert::allIsAOf($args, Arg::class);
|
||||
|
||||
foreach ($args as $key => $arg) {
|
||||
$argumentName = $argumentNames[$key] ?? null;
|
||||
if ($argumentName === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($arg->name !== null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$arg->name = new Identifier($argumentName);
|
||||
// unshift annotations that can be extracted
|
||||
$unwrappeColumns = $this->unwrappedAnnotations[$attributeClass] ?? [];
|
||||
if ($unwrappeColumns === []) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
foreach ($items as $key => $item) {
|
||||
if (! $item instanceof ArrayItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$arrayItemKey = $item->key;
|
||||
if (! $arrayItemKey instanceof String_) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! in_array($arrayItemKey->value, $unwrappeColumns, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
unset($items[$key]);
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
|
|
24
phpstan.neon
24
phpstan.neon
|
@ -573,10 +573,6 @@ parameters:
|
|||
message: '#Cognitive complexity for "Rector\\Php80\\Rector\\Class_\\AnnotationToAttributeRector\:\:processDoctrineAnnotationClasses\(\)" is \d+, keep it under 10#'
|
||||
path: rules/Php80/Rector/Class_/AnnotationToAttributeRector.php
|
||||
|
||||
- '#Anonymous function should return array\|PhpParser\\Node\\Expr but returns array<PhpParser\\Node\\Expr\>\|PhpParser\\Node\\Expr\|string#'
|
||||
|
||||
- '#Method Rector\\PhpAttribute\\AnnotationToAttributeMapper\\ArrayAnnotationToAttributeMapper\:\:isRemoveArrayPlaceholder\(\) has parameter \$value with no value type specified in iterable type array#'
|
||||
|
||||
-
|
||||
message: '#Use value object over return of values#'
|
||||
path: src/Application/ApplicationFileProcessor.php
|
||||
|
@ -603,7 +599,21 @@ parameters:
|
|||
# mapper re-use
|
||||
- '#Parameter \#1 \$type of method Rector\\PHPStanStaticTypeMapper\\TypeMapper\\ObjectWithoutClassTypeMapper\:\:mapToPhpParserNode\(\) expects PHPStan\\Type\\ObjectWithoutClassType, PHPStan\\Type\\Accessory\\Has(Property|Method)Type given#'
|
||||
|
||||
# due to new https://github.com/symplify/symplify/pull/3912 NoMixedPropertyFetcherRule
|
||||
- '#Anonymous variables in a property fetch can lead to false dead property\. Make sure the variable type is known#'
|
||||
# due to new https://github.com/symplify/symplify/pull/3913 NoMixedMethodCallerRule
|
||||
# false positive
|
||||
- '#Method Rector\\VersionBonding\\PhpVersionedFilter\:\:filter\(\) should return array<T of Rector\\Core\\Contract\\Rector\\RectorInterface\> but returns array<int, \(Rector\\VersionBonding\\Contract\\MinPhpVersionInterface&T of Rector\\Core\\Contract\\Rector\\RectorInterface\)\|T of Rector\\Core\\Contract\\Rector\\RectorInterface\>#'
|
||||
|
||||
# fix later
|
||||
- '#Parameter \#1 \$value of class PhpParser\\Node\\Expr\\ArrayItem constructor expects PhpParser\\Node\\Expr, array<PhpParser\\Node\\Expr\>\|PhpParser\\Node\\Expr\|string given#'
|
||||
- '#Parameter \#2 \$key of class PhpParser\\Node\\Expr\\ArrayItem constructor expects PhpParser\\Node\\Expr\|null, array<PhpParser\\Node\\Expr\>\|PhpParser\\Node\\Expr\|string\|null given#'
|
||||
|
||||
# broken on CI
|
||||
- '#Anonymous variables in a method call can lead to false dead methods\. Make sure the variable type is known#'
|
||||
- '#Anonymous variables in a property fetch can lead to false dead property\. Make sure the variable type is known#'
|
||||
|
||||
-
|
||||
message: '#This property type might be inlined to PHP\. Do you have confidence it is correct\? Put it here#'
|
||||
path: packages/PhpAttribute/Printer/PhpAttributeGroupFactory.php
|
||||
|
||||
-
|
||||
message: '#Array with keys is not allowed\. Use value object to pass data instead#'
|
||||
path: packages/PhpAttribute/Printer/PhpAttributeGroupFactory.php
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\ConstantReference;
|
||||
|
||||
/**
|
||||
* @GenericAnnotation(
|
||||
* itemOperations={
|
||||
* "get" = {
|
||||
ConstantReference::FIRST_NAME = true
|
||||
* }
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
final class DemoClass {
|
||||
}
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\ConstantReference;
|
||||
|
||||
#[GenericAnnotation(itemOperations: ['get' => [ConstantReference::FIRST_NAME => true]])]
|
||||
final class DemoClass
|
||||
{
|
||||
}
|
||||
?>
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericSingleImplicitAnnotation;
|
||||
|
||||
/**
|
||||
* @GenericAnnotation("/")
|
||||
* @GenericSingleImplicitAnnotation("/")
|
||||
*/
|
||||
final class FromImplicitToName
|
||||
{
|
||||
|
@ -17,9 +17,9 @@ final class FromImplicitToName
|
|||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
||||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericSingleImplicitAnnotation;
|
||||
|
||||
#[GenericAnnotation(some: '/')]
|
||||
#[GenericSingleImplicitAnnotation('/')]
|
||||
final class FromImplicitToName
|
||||
{
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericA
|
|||
|
||||
final class FunctionCallInside
|
||||
{
|
||||
#[GenericAnnotation(some: "is_granted('ROLE_USER')")]
|
||||
#[GenericAnnotation("is_granted('ROLE_USER')")]
|
||||
public function action()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
|||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
|
||||
#[GenericAnnotation(some: '
|
||||
#[GenericAnnotation('
|
||||
summary: Send webcam reward
|
||||
')]
|
||||
final class MultilineContent
|
||||
|
|
|
@ -7,7 +7,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\Constant
|
|||
|
||||
/**
|
||||
* @GenericAnnotation(
|
||||
* properties={
|
||||
* summary={
|
||||
* ConstantReference::FIRST_NAME = "John",
|
||||
* }
|
||||
* )
|
||||
|
@ -25,7 +25,7 @@ namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
|||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\ConstantReference;
|
||||
|
||||
#[GenericAnnotation(properties: [ConstantReference::FIRST_NAME => 'John'])]
|
||||
#[GenericAnnotation(summary: [ConstantReference::FIRST_NAME => 'John'])]
|
||||
final class NestedArray
|
||||
{
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
|||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\ConstantReference;
|
||||
|
||||
#[GenericAnnotation(some: [ConstantReference::FIRST_NAME => ['foo' => ['bar']], 0 => ConstantReference::LAST_NAME])]
|
||||
#[GenericAnnotation(some: [ConstantReference::FIRST_NAME => ['foo' => ['bar']], ConstantReference::LAST_NAME])]
|
||||
#[GenericAnnotation(some: [ConstantReference::LAST_NAME, 'trailingValue'])]
|
||||
final class ArrayWithConstantAsKey
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
|||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
|
||||
#[GenericAnnotation(some: '
|
||||
#[GenericAnnotation('
|
||||
summary: key
|
||||
description: \'something `id => name`.\'
|
||||
')]
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Fixture;
|
|||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
|
||||
#[GenericAnnotation(some: '
|
||||
#[GenericAnnotation('
|
||||
key: value
|
||||
another_key:
|
||||
another_value/*:
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/**
|
||||
* @Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation(
|
||||
* routePrefix="/demo/"
|
||||
* some="/demo/"
|
||||
* )
|
||||
*/
|
||||
final class NonNamespacedClassWithAnnotation
|
||||
|
@ -12,7 +12,7 @@ final class NonNamespacedClassWithAnnotation
|
|||
-----
|
||||
<?php
|
||||
|
||||
#[Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation(routePrefix: '/demo/')]
|
||||
#[Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation(some: '/demo/')]
|
||||
final class NonNamespacedClassWithAnnotation
|
||||
{
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source as Asser
|
|||
|
||||
final class EntityColumnAndAssertChoice
|
||||
{
|
||||
#[Assert\GenericAnnotation(some: ['php5', 'php7', 'php8'])]
|
||||
#[Assert\GenericAnnotation(['php5', 'php7', 'php8'])]
|
||||
#[Assert\GenericAnnotation(choices: ['5.0', '5.1', 'id' => '5.2'])]
|
||||
#[Assert\GenericAnnotation(choices: [2, 3, 5, 7, 11, 13, 17, 19])]
|
||||
public $primeNumbers;
|
||||
|
|
|
@ -9,7 +9,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
|||
final class SkipUnwrapSymfonyNestedValidators
|
||||
{
|
||||
/**
|
||||
* @GenericAnnotation("hey")
|
||||
* @GenericAnnotation(some = "hey")
|
||||
*
|
||||
* @var array
|
||||
* @Assert\All(
|
||||
|
|
|
@ -8,8 +8,8 @@ final class TrailingCommadAfterArray
|
|||
{
|
||||
/**
|
||||
* @GenericAnnotation(
|
||||
* "first" = "item",
|
||||
* "next" = "item",
|
||||
* "some" = "item",
|
||||
* "summary" = "item",
|
||||
* )
|
||||
*/
|
||||
public function action()
|
||||
|
@ -27,7 +27,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericA
|
|||
|
||||
final class TrailingCommadAfterArray
|
||||
{
|
||||
#[GenericAnnotation(first: 'item', next: 'item')]
|
||||
#[GenericAnnotation(some: 'item', summary: 'item')]
|
||||
public function action()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\SourcePhp81 as
|
|||
|
||||
final class MultipleNestedAttribute
|
||||
{
|
||||
#[Assert\All(nestedAsserts: [new Assert\NotNull(), new Assert\NotNumber()])]
|
||||
#[Assert\All([new Assert\NotNull(), new Assert\NotNumber()])]
|
||||
public $value;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\SourcePhp81 as
|
|||
|
||||
final class NamedArgument
|
||||
{
|
||||
#[Assert\All(nestedAsserts: [new Assert\NotNumber(secondValue: 1000)])]
|
||||
#[Assert\All([new Assert\NotNumber(secondValue: 1000)])]
|
||||
public $value;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\SourcePhp81 as
|
|||
|
||||
final class NestedAttribute
|
||||
{
|
||||
#[Assert\All(nestedAsserts: [new Assert\NotNull()])]
|
||||
#[Assert\All([new Assert\NotNull()])]
|
||||
public $value;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\SourcePhp81 as
|
|||
|
||||
final class NestedAttributesWithArgument
|
||||
{
|
||||
#[Assert\All(nestedAsserts: [new Assert\NotNumber(1000)])]
|
||||
#[Assert\All([new Assert\NotNumber(1000)])]
|
||||
public $value;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\SourcePhp81 as
|
|||
|
||||
final class NestedAttributeWithBrackets
|
||||
{
|
||||
#[Assert\All(nestedAsserts: [new Assert\NotNull()])]
|
||||
#[Assert\All([new Assert\NotNull()])]
|
||||
public $value;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericA
|
|||
|
||||
/**
|
||||
* @GenericAnnotation(filters={
|
||||
* @GenericAnnotation(sql="
|
||||
* @GenericAnnotation(some="
|
||||
* $this.id IN(
|
||||
* SELECT id
|
||||
* FROM foo
|
||||
|
@ -27,7 +27,7 @@ namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\FixturePh
|
|||
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
|
||||
#[GenericAnnotation(filters: [new GenericAnnotation(sql: '
|
||||
#[GenericAnnotation(filters: [new GenericAnnotation(some: '
|
||||
$this.id IN(
|
||||
SELECT id
|
||||
FROM foo
|
||||
|
|
|
@ -25,7 +25,7 @@ use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\SourcePhp81 as
|
|||
|
||||
final class VariousArguments
|
||||
{
|
||||
#[Assert\All(nestedAsserts: [new Assert\Length(1000), new Assert\NotNumber(hey: 10, hi: 'hello')])]
|
||||
#[Assert\All([new Assert\Length(1000), new Assert\NotNumber(hey: 10, hi: 'hello')])]
|
||||
public $value;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source;
|
|||
*/
|
||||
final class GenericAnnotation
|
||||
{
|
||||
public function __construct($some)
|
||||
public function __construct($some = null, $itemOperations = null, $collectionOperations = null, $graphql = null, $summary = null, $title = null, $route = null, $choices = [])
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source;
|
||||
|
||||
/**
|
||||
* Single class for all the examples, as class name is often not the tested part
|
||||
* @annotation
|
||||
*/
|
||||
final class GenericSingleImplicitAnnotation
|
||||
{
|
||||
public function __construct($singleValue)
|
||||
{
|
||||
}
|
||||
}
|
|
@ -6,7 +6,7 @@ namespace Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\SourcePhp
|
|||
|
||||
final class NotNumber
|
||||
{
|
||||
public function __construct($firstValue = null, $secondValue = null)
|
||||
public function __construct($firstValue = null, $secondValue = null, $hey = null, $hi = null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use Rector\Core\ValueObject\PhpVersionFeature;
|
|||
use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
|
||||
use Rector\Php80\ValueObject\AnnotationToAttribute;
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericAnnotation;
|
||||
use Rector\Tests\Php80\Rector\Class_\AnnotationToAttributeRector\Source\GenericSingleImplicitAnnotation;
|
||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||
|
||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||
|
@ -19,6 +20,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
|||
->configure([
|
||||
// use always this annotation to test inner part of annotation - arguments, arrays, calls...
|
||||
new AnnotationToAttribute(GenericAnnotation::class),
|
||||
new AnnotationToAttribute(GenericSingleImplicitAnnotation::class),
|
||||
|
||||
new AnnotationToAttribute('inject', 'Nette\DI\Attributes\Inject'),
|
||||
new AnnotationToAttribute('Symfony\Component\Routing\Annotation\Route'),
|
||||
|
|
|
@ -32,6 +32,8 @@ final class ShouldNotHappenException extends Exception
|
|||
|
||||
$method = $class !== null ? ($class . '::' . $function) : $function;
|
||||
|
||||
/** @var string $method */
|
||||
/** @var int $line */
|
||||
return sprintf('Look at "%s()" on line %d', $method, $line);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace Rector\Core\NodeManipulator;
|
||||
|
||||
use Doctrine\ORM\Mapping\ManyToMany;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Arg;
|
||||
use PhpParser\Node\Expr;
|
||||
|
@ -40,7 +42,7 @@ use Symplify\PackageBuilder\Php\TypeChecker;
|
|||
final class PropertyManipulator
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
* @var string[]|class-string<Table>[]
|
||||
*/
|
||||
private const ALLOWED_NOT_READONLY_ANNOTATION_CLASS_OR_ATTRIBUTES = [
|
||||
'Doctrine\ORM\Mapping\Entity',
|
||||
|
@ -48,7 +50,7 @@ final class PropertyManipulator
|
|||
];
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
* @var string[]|class-string<ManyToMany>[]
|
||||
*/
|
||||
private const ALLOWED_READONLY_ANNOTATION_CLASS_OR_ATTRIBUTES = [
|
||||
'Doctrine\ORM\Mapping\Id',
|
||||
|
|
16
stubs/Doctrine/ORM/Mapping/JoinColumn.php
Normal file
16
stubs/Doctrine/ORM/Mapping/JoinColumn.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
if (class_exists('Doctrine\ORM\Mapping\JoinColumn')) {
|
||||
return;
|
||||
}
|
||||
|
||||
final class JoinColumn
|
||||
{
|
||||
public function __construct($name, $referencedColumnName = null)
|
||||
{
|
||||
}
|
||||
}
|
16
stubs/Doctrine/ORM/Mapping/ManyToMany.php
Normal file
16
stubs/Doctrine/ORM/Mapping/ManyToMany.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
if (class_exists('Doctrine\ORM\Mapping\ManyToMany')) {
|
||||
return;
|
||||
}
|
||||
|
||||
final class ManyToMany
|
||||
{
|
||||
public function __construct($targetEntity)
|
||||
{
|
||||
}
|
||||
}
|
16
stubs/Doctrine/ORM/Mapping/Table.php
Normal file
16
stubs/Doctrine/ORM/Mapping/Table.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
if (class_exists('Doctrine\ORM\Mapping\Table')) {
|
||||
return;
|
||||
}
|
||||
|
||||
final class Table
|
||||
{
|
||||
public function __construct($name, $uniqueConstraints = [])
|
||||
{
|
||||
}
|
||||
}
|
16
stubs/Doctrine/ORM/Mapping/UniqueConstraint.php
Normal file
16
stubs/Doctrine/ORM/Mapping/UniqueConstraint.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\ORM\Mapping;
|
||||
|
||||
if (class_exists('Doctrine\ORM\Mapping\UniqueConstraint')) {
|
||||
return;
|
||||
}
|
||||
|
||||
final class UniqueConstraint
|
||||
{
|
||||
public function __construct($name, $columns = [])
|
||||
{
|
||||
}
|
||||
}
|
|
@ -10,5 +10,8 @@ if (class_exists('Symfony\Component\Routing\Annotation\Route')) {
|
|||
|
||||
class Route
|
||||
{
|
||||
public function __construct($path, $name = '')
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user