Skip to content

Commit 68cdc0d

Browse files
committed
minor #6855 Upgrade to PHPStan 2.x (javiereguiluz)
This PR was squashed before being merged into the 4.x branch. Discussion ---------- Upgrade to PHPStan 2.x There are two issues that I can't fix, even with AI help: https://github.com/EasyCorp/EasyAdminBundle/blob/4.x/src/Dto/EntityDto.php#L170-L171 ``` ------ ------------------------------------------------------------------------------------------------------------------------------------ Line Dto/EntityDto.php ------ ------------------------------------------------------------------------------------------------------------------------------------ 175 PHPDoc tag `@var` with type array|Doctrine\ORM\Mapping\FieldMapping is not subtype of native type Doctrine\ORM\Mapping\FieldMapping. 🪪 varTag.nativeType ------ ------------------------------------------------------------------------------------------------------------------------------------ ``` https://github.com/EasyCorp/EasyAdminBundle/blob/3473fcab8b485506e3f112728bc4d37f8089819a/src/Intl/IntlFormatter.php#L111-L112 ``` ------ ----------------------------------------------------------------------- Line Intl/IntlFormatter.php ------ ----------------------------------------------------------------------- 112 PHPDoc tag `@var` with type string|false is not subtype of type string. 🪪 varTag.type ------ ----------------------------------------------------------------------- ``` Commits ------- 044444e Upgrade to PHPStan 2.x
2 parents 3473fca + 044444e commit 68cdc0d

12 files changed

+52
-58
lines changed

composer.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@
4444
},
4545
"require-dev": {
4646
"doctrine/doctrine-fixtures-bundle": "^3.4|3.5.x-dev",
47-
"phpstan/extension-installer": "^1.2",
48-
"phpstan/phpstan": "^1.9",
49-
"phpstan/phpstan-phpunit": "^1.2",
50-
"phpstan/phpstan-strict-rules": "^1.4",
51-
"phpstan/phpstan-symfony": "^1.2",
47+
"phpstan/extension-installer": "^1.4",
48+
"phpstan/phpstan": "^2.0",
49+
"phpstan/phpstan-phpunit": "^2.0",
50+
"phpstan/phpstan-strict-rules": "^2.0",
51+
"phpstan/phpstan-symfony": "^2.0",
5252
"psr/log": "^1.0",
5353
"symfony/browser-kit": "^5.4|^6.0|^7.0",
5454
"symfony/css-selector": "^5.4|^6.0|^7.0",

phpstan.neon.dist

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ parameters:
88
excludePaths:
99
analyse:
1010
- src/ArgumentResolver
11+
- src/Test/Trait/CrudTestFormAsserts.php
1112
bootstrapFiles:
1213
- vendor/bin/.phpunit/phpunit/vendor/autoload.php
1314
ignoreErrors:

src/Dto/ActionDto.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ final class ActionDto
1212
{
1313
private ?string $type = null;
1414
private ?string $name = null;
15-
/** @var TranslatableInterface|string|(callable(object): string)|null */
15+
/** @var TranslatableInterface|string|(callable(object): string)|false|null */
1616
private mixed $label = null;
1717
private ?string $icon = null;
1818
private string $cssClass = '';

src/Dto/EntityDto.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
use Doctrine\ORM\Mapping\ClassMetadata;
66
use Doctrine\ORM\Mapping\FieldMapping;
77
use Doctrine\ORM\Mapping\ManyToManyAssociationMapping;
8+
use Doctrine\ORM\Mapping\ManyToManyInverseSideMapping;
9+
use Doctrine\ORM\Mapping\ManyToManyOwningSideMapping;
810
use Doctrine\ORM\Mapping\ManyToOneAssociationMapping;
911
use Doctrine\ORM\Mapping\OneToManyAssociationMapping;
1012
use Doctrine\ORM\Mapping\OneToOneAssociationMapping;
13+
use Doctrine\ORM\Mapping\OneToOneInverseSideMapping;
14+
use Doctrine\ORM\Mapping\OneToOneOwningSideMapping;
1115
use EasyCorp\Bundle\EasyAdminBundle\Collection\ActionCollection;
1216
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
1317
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore;
@@ -168,6 +172,7 @@ public function getPropertyMetadata(string $propertyName): KeyValueStore
168172
{
169173
if (\array_key_exists($propertyName, $this->metadata->fieldMappings)) {
170174
/** @var FieldMapping|array $fieldMapping */
175+
/** @phpstan-ignore-next-line */
171176
$fieldMapping = $this->metadata->fieldMappings[$propertyName];
172177
// Doctrine ORM 2.x returns an array and Doctrine ORM 3.x returns a FieldMapping object
173178
if ($fieldMapping instanceof FieldMapping) {
@@ -178,7 +183,7 @@ public function getPropertyMetadata(string $propertyName): KeyValueStore
178183
}
179184

180185
if (\array_key_exists($propertyName, $this->metadata->associationMappings)) {
181-
/** @var OneToOneAssociationMapping|OneToManyAssociationMapping|ManyToOneAssociationMapping|ManyToManyAssociationMapping|array $associationMapping */
186+
/** @var OneToOneOwningSideMapping|OneToOneInverseSideMapping|ManyToOneAssociationMapping|OneToManyAssociationMapping|ManyToManyOwningSideMapping|ManyToManyInverseSideMapping $associationMapping */
182187
$associationMapping = $this->metadata->associationMappings[$propertyName];
183188
// Doctrine ORM 2.x returns an array and Doctrine ORM 3.x returns one of the many *Mapping objects
184189
// there's not a single interface implemented by all of them, so let's only check if it's an object

src/Factory/ActionFactory.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ private function processActionLabel(ActionDto $actionDto, ?EntityDto $entityDto,
166166
}
167167

168168
if (\is_callable($label) && $label instanceof \Closure) {
169-
$label = \call_user_func_array($label, array_filter([$entityDto?->getInstance()]));
169+
$label = \call_user_func_array($label, array_filter([$entityDto?->getInstance()], static fn ($item): bool => null !== $item));
170170

171171
if (!\is_string($label) && !$label instanceof TranslatableInterface) {
172172
throw new \RuntimeException(sprintf('The callable used to define the label of the "%s" action label %s must return a string or a %s instance but it returned a(n) "%s" value instead.', $actionDto->getName(), null !== $entityDto ? 'in the "'.$entityDto->getName().'" entity' : '', TranslatableInterface::class, \gettype($label)));

src/Field/Configurator/ChoiceConfigurator.php

+32-41
Original file line numberDiff line numberDiff line change
@@ -33,53 +33,46 @@ public function configure(FieldDto $field, EntityDto $entityDto, AdminContext $c
3333
$choicesSupportTranslatableInterface = false;
3434
$isExpanded = true === $field->getCustomOption(ChoiceField::OPTION_RENDER_EXPANDED);
3535
$isMultipleChoice = true === $field->getCustomOption(ChoiceField::OPTION_ALLOW_MULTIPLE_CHOICES);
36-
// Initialize the variable since we are using it outside the $enumsAreSupported condition
37-
$allChoicesAreEnums = false;
3836

3937
$choices = $this->getChoices($field->getCustomOption(ChoiceField::OPTION_CHOICES), $entityDto, $field);
4038

41-
// using a more precise check like 'function_exists('enum_exists');' messes with IDEs like PhpStorm
42-
$enumsAreSupported = \PHP_VERSION_ID >= 80100;
43-
4439
if (null === $choices) {
4540
$choices = [];
4641
}
4742

4843
// support for enums
49-
if ($enumsAreSupported) {
50-
$elementIsEnum = array_unique(array_map(static function ($element): bool {
51-
return \is_object($element) && enum_exists($element::class);
52-
}, $choices));
53-
$allChoicesAreEnums = false === \in_array(false, $elementIsEnum, true);
54-
55-
// if no choices are passed to the field, check if it's related to an Enum;
56-
// in that case, get all the possible values of the Enum (Doctrine supports only BackedEnum as enumType)
57-
$enumTypeClass = $field->getDoctrineMetadata()->get('enumType');
58-
if (0 === \count($choices) && null !== $enumTypeClass && enum_exists($enumTypeClass)) {
59-
$choices = $enumTypeClass::cases();
60-
$allChoicesAreEnums = true;
61-
}
44+
$elementIsEnum = array_unique(array_map(static function ($element): bool {
45+
return \is_object($element) && enum_exists($element::class);
46+
}, $choices));
47+
$allChoicesAreEnums = false === \in_array(false, $elementIsEnum, true);
48+
49+
// if no choices are passed to the field, check if it's related to an Enum;
50+
// in that case, get all the possible values of the Enum (Doctrine supports only BackedEnum as enumType)
51+
$enumTypeClass = $field->getDoctrineMetadata()->get('enumType');
52+
if (0 === \count($choices) && null !== $enumTypeClass && enum_exists($enumTypeClass)) {
53+
$choices = $enumTypeClass::cases();
54+
$allChoicesAreEnums = true;
55+
}
6256

63-
// SF 6.4 and up has native support for translatable enums, we should respect that too
64-
if (is_subclass_of($enumTypeClass, TranslatableInterface::class)) {
65-
$areChoicesTranslatable = $choicesSupportTranslatableInterface = true;
66-
}
57+
// SF 6.4 and up has native support for translatable enums, we should respect that too
58+
if (is_subclass_of($enumTypeClass, TranslatableInterface::class)) {
59+
$areChoicesTranslatable = $choicesSupportTranslatableInterface = true;
60+
}
6761

68-
if ($allChoicesAreEnums && array_is_list($choices) && \count($choices) > 0) {
69-
$processedEnumChoices = [];
70-
foreach ($choices as $choice) {
71-
$processedEnumChoices[$choice->name] = $choice;
72-
}
62+
if ($allChoicesAreEnums && array_is_list($choices) && \count($choices) > 0) {
63+
$processedEnumChoices = [];
64+
foreach ($choices as $choice) {
65+
$processedEnumChoices[$choice->name] = $choice;
66+
}
7367

74-
$choices = $processedEnumChoices;
68+
$choices = $processedEnumChoices;
7569

76-
// Update form type to be EnumType if current form type is still ChoiceType
77-
// Leave the form type as is if user set something else explicitly
78-
if (ChoiceType::class === $field->getFormType()) {
79-
$field->setFormType(EnumType::class);
80-
}
81-
$field->setFormTypeOptionIfNotSet('class', $enumTypeClass);
70+
// Update form type to be EnumType if current form type is still ChoiceType
71+
// Leave the form type as is if user set something else explicitly
72+
if (ChoiceType::class === $field->getFormType()) {
73+
$field->setFormType(EnumType::class);
8274
}
75+
$field->setFormTypeOptionIfNotSet('class', $enumTypeClass);
8376
}
8477

8578
if ($areChoicesTranslatable && !$choicesSupportTranslatableInterface) {
@@ -115,13 +108,11 @@ public function configure(FieldDto $field, EntityDto $entityDto, AdminContext $c
115108
return;
116109
}
117110

118-
if ($enumsAreSupported) {
119-
// Backed enum converted to array result in array [enum->name, enum->value] as done in the loop bellow
120-
// That results in grid displaying two values when single enum is a selected value
121-
// This makes sure we pass an array of enums as selected value when single enum is selected
122-
if ($fieldValue instanceof \UnitEnum) {
123-
$fieldValue = [$fieldValue];
124-
}
111+
// Backed enum converted to array result in array [enum->name, enum->value] as done in the loop bellow
112+
// That results in grid displaying two values when single enum is a selected value
113+
// This makes sure we pass an array of enums as selected value when single enum is selected
114+
if ($fieldValue instanceof \UnitEnum) {
115+
$fieldValue = [$fieldValue];
125116
}
126117

127118
$badgeSelector = $field->getCustomOption(ChoiceField::OPTION_RENDER_AS_BADGES);

src/Form/EventListener/FormLayoutSubscriber.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;
77
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\Layout\EaFormTabListType;
88
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
9-
use Symfony\Component\Form\Form;
109
use Symfony\Component\Form\FormEvent;
1110
use Symfony\Component\Form\FormEvents;
11+
use Symfony\Component\Form\FormInterface;
1212

1313
/**
1414
* Handles some logic related to the form layout, like error counters in tabs.
@@ -31,7 +31,7 @@ public static function getSubscribedEvents(): array
3131
public function handleTabErrors(FormEvent $event)
3232
{
3333
$formTabs = [];
34-
/** @var Form $child */
34+
/** @var FormInterface $child */
3535
foreach ($event->getForm() as $child) {
3636
/** @var FieldDto $fieldDto */
3737
if (null === $fieldDto = $child->getConfig()->getAttribute('ea_field')) {

src/Intl/IntlFormatter.php

-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ final class IntlFormatter implements IntlFormatterInterface
108108
public function formatCurrency($amount, string $currency, array $attrs = [], ?string $locale = null): string
109109
{
110110
$formatter = $this->createNumberFormatter($locale, 'currency', $attrs);
111-
/** @var string|false $formattedCurrency */
112111
$formattedCurrency = $formatter->formatCurrency($amount, $currency);
113112
if (false === $formattedCurrency) {
114113
throw new RuntimeError('Unable to format the given number as a currency.');

src/Orm/EntityRepository.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ private function getSearchablePropertiesConfig(QueryBuilder $queryBuilder, Searc
325325
}
326326

327327
if (null !== $idClassType) {
328-
/** @var \ReflectionNamedType|\ReflectionUnionType|null $idClassType */
328+
/** @var \ReflectionNamedType|\ReflectionUnionType $idClassType */
329329
$idClassName = $idClassType->getName();
330330

331331
if (class_exists($idClassName)) {

src/Router/AdminUrlGenerator.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ private function initialize(): void
369369
EA::CRUD_CONTROLLER_FQCN => $adminContext->getRequest()->attributes->get(EA::CRUD_CONTROLLER_FQCN),
370370
EA::CRUD_ACTION => $adminContext->getRequest()->attributes->get(EA::CRUD_ACTION),
371371
EA::ENTITY_ID => $adminContext->getRequest()->attributes->get(EA::ENTITY_ID),
372-
]);
372+
], static fn ($value): bool => null !== $value);
373373
$currentRouteParameters = $routeParametersForReferrer = array_merge($routeParameters, $adminContext->getRequest()->query->all());
374374
unset($routeParametersForReferrer[EA::REFERRER]);
375375
$this->currentPageReferrer = sprintf('%s%s?%s', $adminContext->getRequest()->getBaseUrl(), $adminContext->getRequest()->getPathInfo(), http_build_query($routeParametersForReferrer));

src/Twig/Component/Flag.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ public function getFlagAsSvg(): string
2626
return sprintf('<svg xmlns="http://www.w3.org/2000/svg" class="country-flag" height="%d" viewBox="0 0 25 17"><title>You are not seeing a country flag here because the "%s.svg" file associated to given the "%s" country code does not exist in the assets/icons/flags/ directory of EasyAdmin.</title><rect width="100%%" height="%d" fill="#ff0000"/></svg>', $this->height, $this->countryCode, $this->countryCode, $this->height);
2727
}
2828

29-
return str_replace(['__HEIGHT__', '__COUNTRY_NAME__'], [$this->height, $this->getCountryName()], file_get_contents($flagSvgFilePath));
29+
return str_replace(['__HEIGHT__', '__COUNTRY_NAME__'], [(string) $this->height, $this->getCountryName()], file_get_contents($flagSvgFilePath));
3030
}
3131
}

src/Twig/EasyAdminTwigExtension.php

+1-3
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,7 @@ public function fileSize(int $bytes): string
110110
public function applyFilterIfExists(Environment $environment, $value, string $filterName, ...$filterArguments)
111111
{
112112
/**
113-
* Twig v2 will return TwigFilter|false.
114-
*
115-
* @var TwigFilter|false|null $filter
113+
* @var TwigFilter|null $filter
116114
*/
117115
$filter = $environment->getFilter($filterName);
118116
if (null === $filter || false === $filter) {

0 commit comments

Comments
 (0)