Skip to content

Commit cafca27

Browse files
authored
Add PHPStan and fix all errors (#61)
1 parent c256c9c commit cafca27

16 files changed

+115
-65
lines changed

.github/workflows/tests.yml

+21-1
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,31 @@ jobs:
8080
- name: "Setup PHP"
8181
uses: shivammathur/setup-php@v2
8282
with:
83-
coverage: xdebug
83+
coverage: none
8484
php-version: '8.3'
8585

8686
- name: "Install dependencies with composer"
8787
run: composer update --no-interaction --no-progress
8888

8989
- name: "Run checkstyle with symplify/easy-coding-standard"
9090
run: vendor/bin/ecs
91+
92+
phpstan:
93+
name: "PHPStan"
94+
runs-on: ubuntu-latest
95+
96+
steps:
97+
- name: "Checkout"
98+
uses: actions/checkout@v2
99+
100+
- name: "Setup PHP"
101+
uses: shivammathur/setup-php@v2
102+
with:
103+
coverage: none
104+
php-version: '8.3'
105+
106+
- name: "Install dependencies with composer"
107+
run: composer update --no-interaction --no-progress
108+
109+
- name: "Run static analysis with phpstan/phpstan"
110+
run: vendor/bin/phpstan

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"require-dev": {
1717
"doctrine/annotations": "^1.14",
1818
"myclabs/php-enum": "^1.8",
19+
"phpstan/phpstan": "^1.11",
1920
"phpunit/phpunit": "^9.6",
2021
"symfony/form": "^7.0",
2122
"symfony/http-kernel": "^7.0",

phpstan-baseline.neon

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
parameters:
2+
ignoreErrors:
3+
-
4+
message: "#^Parameter \\#1 \\$objectOrClass of class ReflectionClass constructor expects class\\-string\\<T of object\\>\\|T of object, string given\\.$#"
5+
count: 1
6+
path: src/ConstantExtractor.php

phpstan.neon

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
includes:
2+
- phpstan-baseline.neon
3+
4+
parameters:
5+
level: max
6+
paths:
7+
- src/

src/ConstantExtractor.php

+13-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
final class ConstantExtractor
1717
{
1818
/**
19+
* @return list<mixed>
1920
* @throws LogicException
2021
*/
2122
public static function extract(string $pattern): array
@@ -29,17 +30,25 @@ public static function extract(string $pattern): array
2930
);
3031
}
3132

33+
/**
34+
* @param array<string, mixed> $constants
35+
*
36+
* @return list<mixed>
37+
*/
3238
private static function filter(array $constants, string $regexp, string $pattern): array
3339
{
3440
$matchingNames = \preg_grep($regexp, \array_keys($constants));
3541

36-
if (\count($matchingNames) === 0) {
42+
if ($matchingNames === false || \count($matchingNames) === 0) {
3743
throw LogicException::cannotExtractConstants($pattern, 'Pattern matches no constant.');
3844
}
3945

4046
return \array_values(\array_intersect_key($constants, \array_flip($matchingNames)));
4147
}
4248

49+
/**
50+
* @return array<string, mixed>
51+
*/
4352
private static function publicConstants(string $class, string $pattern): array
4453
{
4554
try {
@@ -67,6 +76,9 @@ private static function publicConstants(string $class, string $pattern): array
6776
return $list;
6877
}
6978

79+
/**
80+
* @return array{string, string}
81+
*/
7082
private static function explode(string $pattern): array
7183
{
7284
if (\substr_count($pattern, '::') !== 1) {

src/ConstantListEnum.php

+12-2
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,24 @@
44

55
namespace Yokai\EnumBundle;
66

7+
use Yokai\EnumBundle\Exception\LogicException;
8+
79
/**
810
* @author Yann Eugoné <[email protected]>
911
*/
1012
class ConstantListEnum extends Enum
1113
{
1214
public function __construct(string $constantsPattern, ?string $name = null)
1315
{
14-
$values = ConstantExtractor::extract($constantsPattern);
15-
parent::__construct(\array_combine($values, $values), $name);
16+
$choices = [];
17+
foreach (ConstantExtractor::extract($constantsPattern) as $value) {
18+
if (!\is_string($value) && !\is_int($value)) {
19+
throw new LogicException(
20+
\sprintf('Extracted constant enum value must be string or int, %s given.', \get_debug_type($value)),
21+
);
22+
}
23+
$choices[(string)$value] = $value;
24+
}
25+
parent::__construct($choices, $name);
1626
}
1727
}

src/Enum.php

+3
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ protected function build(): array
9292
throw new LogicException(static::class . '::' . __FUNCTION__ . ' should have been overridden.');
9393
}
9494

95+
/**
96+
* @phpstan-assert !null $this->choices
97+
*/
9598
private function init(): void
9699
{
97100
if ($this->choices !== null) {

src/Form/Extension/EnumTypeGuesser.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,17 @@ public function guessTypeForConstraint(Constraint $constraint): ?TypeGuess
3535
);
3636
}
3737

38-
public function guessRequired($class, $property): ?ValueGuess
38+
public function guessRequired(string $class, string $property): ?ValueGuess
3939
{
4040
return null; //override parent : not able to guess
4141
}
4242

43-
public function guessMaxLength($class, $property): ?ValueGuess
43+
public function guessMaxLength(string $class, string $property): ?ValueGuess
4444
{
4545
return null; //override parent : not able to guess
4646
}
4747

48-
public function guessPattern($class, $property): ?ValueGuess
48+
public function guessPattern(string $class, string $property): ?ValueGuess
4949
{
5050
return null; //override parent : not able to guess
5151
}

src/Form/Type/EnumType.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ function (string $name): bool {
4040
->setDefault(
4141
'choices',
4242
function (Options $options): array {
43-
$choices = $this->enumRegistry->get($options['enum'])->getChoices();
43+
/** @var string $name */
44+
$name = $options['enum'];
45+
$choices = $this->enumRegistry->get($name)->getChoices();
4446

4547
if ($options['enum_choice_value'] === null) {
4648
foreach ($choices as $value) {

src/TranslatedEnum.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
class TranslatedEnum extends Enum
1414
{
1515
/**
16-
* @var array
16+
* @var array<int|string, mixed>
1717
*/
1818
private $values;
1919

@@ -64,6 +64,9 @@ protected function build(): array
6464
if (\is_string($key)) {
6565
$transLabel = $key;
6666
}
67+
if (!\is_scalar($transLabel)) {
68+
$transLabel = $key;
69+
}
6770

6871
$label = $this->translator->trans(\sprintf($this->transPattern, $transLabel), [], $this->transDomain);
6972
$choices[$label] = $value;

src/Validator/Constraints/Enum.php

+27-51
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ final class Enum extends Choice
2020
*/
2121
public $enum;
2222

23+
/**
24+
* @param array<string, mixed> $options
25+
*/
2326
public function __construct(
24-
$enum = null,
25-
$callback = null,
27+
array $options = [],
28+
string|null $enum = null,
29+
callable|null|string $callback = null,
2630
bool $multiple = null,
2731
bool $strict = null,
2832
int $min = null,
@@ -31,57 +35,29 @@ public function __construct(
3135
string $multipleMessage = null,
3236
string $minMessage = null,
3337
string $maxMessage = null,
34-
$groups = null,
35-
$payload = null,
36-
array $options = []
38+
array|null $groups = null,
39+
mixed $payload = null,
3740
) {
38-
if (\is_array($enum)) {
39-
// Symfony 4.4 Constraints has single constructor argument containing all options
40-
parent::__construct($enum);
41-
} else {
42-
if (\is_string($enum)) {
43-
$this->enum = $enum;
44-
}
45-
// Symfony 5.x Constraints has many constructor arguments for PHP 8.0 Attributes support
46-
47-
$firstConstructorArg = (new \ReflectionClass(Choice::class))
48-
->getConstructor()->getParameters()[0]->getName();
49-
if ($firstConstructorArg === 'choices') {
50-
// Prior to Symfony 5.3, first argument of Choice was $choices
51-
parent::__construct(
52-
null,
53-
$callback,
54-
$multiple,
55-
$strict,
56-
$min,
57-
$max,
58-
$message,
59-
$multipleMessage,
60-
$minMessage,
61-
$maxMessage,
62-
$groups,
63-
$payload,
64-
$options
65-
);
66-
} else {
67-
// Since Symfony 5.3, first argument of Choice is $options
68-
parent::__construct(
69-
$options,
70-
null,
71-
$callback,
72-
$multiple,
73-
$strict,
74-
$min,
75-
$max,
76-
$message,
77-
$multipleMessage,
78-
$minMessage,
79-
$maxMessage,
80-
$groups,
81-
$payload
82-
);
83-
}
41+
if (\is_string($enum)) {
42+
$this->enum = $enum;
8443
}
44+
45+
// Since Symfony 5.3, first argument of Choice is $options
46+
parent::__construct(
47+
$options,
48+
null,
49+
$callback,
50+
$multiple,
51+
$strict,
52+
$min,
53+
$max,
54+
$message,
55+
$multipleMessage,
56+
$minMessage,
57+
$maxMessage,
58+
$groups,
59+
$payload
60+
);
8561
}
8662

8763
public function getDefaultOption(): string

src/Validator/Constraints/EnumValidator.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public function __construct(EnumRegistry $enumRegistry)
2525
$this->enumRegistry = $enumRegistry;
2626
}
2727

28-
public function validate($value, Constraint $constraint): void
28+
public function validate(mixed $value, Constraint $constraint): void
2929
{
3030
if (!$constraint instanceof Enum) {
3131
throw new UnexpectedTypeException($constraint, Enum::class);

tests/Unit/ConstantListEnumTest.php

+6
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,10 @@ public function testLabelNotFound(): void
6767

6868
$enum->getLabel('unknown');
6969
}
70+
71+
public function testConstantMustBeStringOrInt(): void
72+
{
73+
$this->expectException(LogicException::class);
74+
new ConstantListEnum(Vehicle::class . '::PERIOD_*', 'vehicle.period');
75+
}
7076
}

tests/Unit/Fixtures/Vehicle.php

+4
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,8 @@ class Vehicle
2525
public const WHEELS_TWO = 2;
2626
public const WHEELS_FOUR = 4;
2727
public const WHEELS_EIGHT = 8;
28+
29+
public const PERIOD_COLLECTION = [1817, 1980];
30+
public const PERIOD_OLD = [1981, 2010];
31+
public const PERIOD_RECENT = [2010, 2024];
2832
}

tests/Unit/Form/Extension/EnumTypeGuesserTest.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ protected function getConstraints(array $options): array
7070
->with(self::TEST_CLASS)
7171
->willReturn($metadata);
7272

73-
$this->guesser = new EnumTypeGuesser($this->metadataFactory, $this->enumRegistry);
73+
$this->guesser = new EnumTypeGuesser($this->metadataFactory);
7474

7575
parent::setUp();
7676
}

tests/Unit/Validator/Constraints/EnumValidatorTest.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,19 @@ public function testEnumIsRequired(): void
4646
public function testValidEnumIsRequired(): void
4747
{
4848
$this->expectException(ConstraintDefinitionException::class);
49-
$this->validator->validate('foo', new Enum('state'));
49+
$this->validator->validate('foo', new Enum(enum: 'state'));
5050
}
5151

5252
public function testNullIsValid(): void
5353
{
54-
$this->validator->validate(null, new Enum('type'));
54+
$this->validator->validate(null, new Enum(enum: 'type'));
5555

5656
$this->assertNoViolation();
5757
}
5858

5959
public function testValidSingleEnum(): void
6060
{
61-
$this->validator->validate('customer', new Enum('type'));
61+
$this->validator->validate('customer', new Enum(enum: 'type'));
6262

6363
$this->assertNoViolation();
6464
}

0 commit comments

Comments
 (0)