diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index fd7e72c..d68130d 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -41,3 +41,9 @@ jobs: - name: Run test suite run: composer run-script test + + - name: Run PHPStan + run: composer run-script analyse + + - name: Run PHP-CS-Fixer + run: composer run-script cs-check diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..efe746d --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,32 @@ +in(__DIR__ . '/src') + ->in(__DIR__ . '/tests') +; + +return (new PhpCsFixer\Config()) + ->setRules([ + '@PER-CS' => true, + '@Symfony' => true, + 'declare_strict_types' => true, + 'strict_param' => true, + 'array_syntax' => ['syntax' => 'short'], + 'ordered_imports' => ['sort_algorithm' => 'alpha'], + 'no_unused_imports' => true, + 'trailing_comma_in_multiline' => true, + 'single_quote' => true, + 'global_namespace_import' => [ + 'import_classes' => false, + 'import_constants' => false, + 'import_functions' => false, + ], + 'native_function_invocation' => [ + 'include' => ['@all'], + 'scope' => 'all', + 'strict' => true, + ], + ]) + ->setFinder($finder) + ->setRiskyAllowed(true) +; diff --git a/README.md b/README.md index e405007..d75f128 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ [![PHP Composer](https://github.com/chamber-orchestra/form-bundle/actions/workflows/php.yml/badge.svg)](https://github.com/chamber-orchestra/form-bundle/actions/workflows/php.yml) [![Latest Stable Version](https://poser.pugx.org/chamber-orchestra/form-bundle/v)](https://packagist.org/packages/chamber-orchestra/form-bundle) [![License](https://poser.pugx.org/chamber-orchestra/form-bundle/license)](https://packagist.org/packages/chamber-orchestra/form-bundle) +![PHP 8.5](https://img.shields.io/badge/PHP-8.5-777BB4?logo=php&logoColor=white) +![Symfony 8](https://img.shields.io/badge/Symfony-8-000000?logo=symfony&logoColor=white) +![PHPStan Max](https://img.shields.io/badge/PHPStan-max-brightgreen?logo=php&logoColor=white) +![PHP-CS-Fixer](https://img.shields.io/badge/PHP--CS--Fixer-✓-brightgreen?logo=php&logoColor=white) # ChamberOrchestra Form Bundle diff --git a/composer.json b/composer.json index bbf35e7..73fabd5 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,11 @@ "require-dev": { "phpunit/phpunit": "^13.0", "symfony/test-pack": "^1.2", - "doctrine/doctrine-bundle": "^3.2" + "doctrine/doctrine-bundle": "^3.2", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-symfony": "^2.0", + "phpstan/phpstan-doctrine": "^2.0", + "friendsofphp/php-cs-fixer": "^3.94" }, "autoload": { "psr-4": { @@ -80,6 +84,9 @@ } }, "scripts": { - "test": "vendor/bin/phpunit" + "test": "vendor/bin/phpunit", + "analyse": "vendor/bin/phpstan analyse", + "cs-check": "vendor/bin/php-cs-fixer fix --dry-run --diff", + "cs-fix": "vendor/bin/php-cs-fixer fix" } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..b2112eb --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,11 @@ +includes: + - vendor/phpstan/phpstan-symfony/extension.neon + - vendor/phpstan/phpstan-doctrine/extension.neon + +parameters: + level: max + paths: + - src + treatPhpDocTypesAsCertain: false + ignoreErrors: + - identifier: trait.unused diff --git a/src/ApiFormTrait.php b/src/ApiFormTrait.php index 7689ab1..e60b44b 100644 --- a/src/ApiFormTrait.php +++ b/src/ApiFormTrait.php @@ -13,19 +13,23 @@ use ChamberOrchestra\FormBundle\Type\Api\MutationForm; use ChamberOrchestra\ViewBundle\View\ViewInterface; +use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +/** + * @phpstan-require-extends AbstractController + */ trait ApiFormTrait { use FormTrait; - protected function handleApiCall(FormInterface|string $form, callable|null $callable = null): Response|ViewInterface + protected function handleApiCall(FormInterface|string $form, ?callable $callable = null): Response|ViewInterface { $request = $this->getCurrentRequest(); - if ($request === null) { + if (null === $request) { throw new \LogicException('Cannot handle API call without an active request.'); } @@ -46,6 +50,7 @@ protected function handleApiCall(FormInterface|string $form, callable|null $call return $this->onFormSubmitted($form, $callable); } + /** @return array */ private function convertRequestToArray(Request $request): array { $data = []; @@ -59,4 +64,4 @@ private function convertRequestToArray(Request $request): array return \array_replace_recursive($data, $request->files->all()); } -} \ No newline at end of file +} diff --git a/src/ChamberOrchestraFormBundle.php b/src/ChamberOrchestraFormBundle.php index d7d314d..2b5261b 100644 --- a/src/ChamberOrchestraFormBundle.php +++ b/src/ChamberOrchestraFormBundle.php @@ -15,4 +15,4 @@ final class ChamberOrchestraFormBundle extends Bundle { -} \ No newline at end of file +} diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php index ea99870..ab8655a 100644 --- a/src/Exception/ExceptionInterface.php +++ b/src/Exception/ExceptionInterface.php @@ -13,4 +13,4 @@ interface ExceptionInterface { -} \ No newline at end of file +} diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index a9992db..0643f96 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -13,4 +13,4 @@ class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { -} \ No newline at end of file +} diff --git a/src/Exception/LogicException.php b/src/Exception/LogicException.php index 79836e4..8f439ef 100644 --- a/src/Exception/LogicException.php +++ b/src/Exception/LogicException.php @@ -13,4 +13,4 @@ class LogicException extends \LogicException implements ExceptionInterface { -} \ No newline at end of file +} diff --git a/src/Exception/TransformationFailedException.php b/src/Exception/TransformationFailedException.php index 692f22c..48f43fb 100644 --- a/src/Exception/TransformationFailedException.php +++ b/src/Exception/TransformationFailedException.php @@ -13,7 +13,8 @@ class TransformationFailedException extends \Symfony\Component\Form\Exception\TransformationFailedException implements ExceptionInterface { - public static function notAllowedType($id, array $allowedTypes): TransformationFailedException + /** @param list $allowedTypes */ + public static function notAllowedType(mixed $id, array $allowedTypes): TransformationFailedException { return new self( \sprintf( diff --git a/src/Extension/TelExtension.php b/src/Extension/TelExtension.php index 6c46f81..3f3f0b7 100644 --- a/src/Extension/TelExtension.php +++ b/src/Extension/TelExtension.php @@ -23,14 +23,15 @@ public static function getExtendedTypes(): iterable return [TelType::class]; } + /** @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->addViewTransformer( new CallbackTransformer( - function (string|null $value): string|null { + function (?string $value): ?string { return $value; }, - function (string|null $value): string|null { + function (?string $value): ?string { if (null === $value || '' === $value) { return null; } @@ -40,4 +41,4 @@ function (string|null $value): string|null { ) ); } -} \ No newline at end of file +} diff --git a/src/FormTrait.php b/src/FormTrait.php index 00ae862..c2b89e0 100644 --- a/src/FormTrait.php +++ b/src/FormTrait.php @@ -31,9 +31,12 @@ /** * @mixin AbstractController + * + * @phpstan-require-extends AbstractController */ trait FormTrait { + /** @param array $data */ protected function createSuccessResponse(array $data = []): DataView|ResponseView { return $data ? new DataView($data) : new ResponseView(); @@ -44,13 +47,16 @@ protected function createRedirectResponse( int $status = Response::HTTP_MOVED_PERMANENTLY ): Response|RedirectView { $request = $this->getCurrentRequest(); - if ($request !== null && $request->isXmlHttpRequest()) { + if (null !== $request && $request->isXmlHttpRequest()) { return new RedirectView($url, $status); } return $this->redirect($url, $status); } + /** + * @param array $parameters + */ protected function createRedirectToRouteResponse( string $name, array $parameters = [], @@ -64,10 +70,11 @@ protected function createExceptionResponse(): FailureView return $this->createFailureResponse(Response::HTTP_INTERNAL_SERVER_ERROR); } + /** @param array $parameters */ protected function createSuccessHtmlResponse(string $view, array $parameters = []): Response|SuccessHtmlView { $request = $this->getCurrentRequest(); - if ($request !== null && $request->isXmlHttpRequest()) { + if (null !== $request && $request->isXmlHttpRequest()) { return new SuccessHtmlView([ 'html' => $this->renderView($view, $parameters), ]); @@ -88,16 +95,10 @@ protected function createValidationFailedResponse(FormInterface $form): Validati protected function handleFormCall( FormInterface|string $form, - callable|null $callable = null + ?callable $callable = null ): Response|ViewInterface { if (!\is_string($form) && !$form instanceof FormInterface) { - throw new \TypeError( - \sprintf( - 'Passed $form must be of type "%s", "%s" given.', - \implode(',', ['string', FormInterface::class]), - \get_debug_type($form) - ) - ); + throw new \TypeError(\sprintf('Passed $form must be of type "%s", "%s" given.', \implode(',', ['string', FormInterface::class]), \get_debug_type($form))); } if (\is_string($form)) { @@ -105,7 +106,7 @@ protected function handleFormCall( } $request = $this->getCurrentRequest(); - if ($request === null) { + if (null === $request) { throw new \LogicException('Cannot handle form call without an active request.'); } @@ -120,15 +121,15 @@ protected function handleFormCall( protected function createSubmittedFormResponse( FormInterface $form, - callable|null $callable = null + ?callable $callable = null ): Response|ViewInterface { return $this->onFormSubmitted($form, $callable); } /** - * @param null|callable $callable must return @see \ChamberOrchestra\ViewBundle\View\ViewInterface, array or null + * @param callable|null $callable must return @see \ChamberOrchestra\ViewBundle\View\ViewInterface, array or null */ - protected function onFormSubmitted(FormInterface $form, callable|null $callable = null): Response|ViewInterface + protected function onFormSubmitted(FormInterface $form, ?callable $callable = null): Response|ViewInterface { if (!$form->isValid()) { return $this->createValidationFailedResponse($form); @@ -139,18 +140,13 @@ protected function onFormSubmitted(FormInterface $form, callable|null $callable } if (!\is_array($response) && !$response instanceof ViewInterface && !$response instanceof Response) { - throw new \TypeError( - \sprintf( - 'Passed closure must return %s, returned %s', - \implode('|', [ViewInterface::class, Response::class, 'array']), - \get_debug_type($response) - ) - ); + throw new \TypeError(\sprintf('Passed closure must return %s, returned %s', \implode('|', [ViewInterface::class, Response::class, 'array']), \get_debug_type($response))); } return \is_array($response) ? $this->createSuccessResponse($response) : $response; } + /** @return list */ protected function serializeFormErrors(FormInterface $form): array { return $this->serializeErrors($form->getErrors(true, false)); @@ -171,6 +167,11 @@ private function getCurrentRequest(): ?Request return $this->container->get('request_stack')->getCurrentRequest(); } + /** + * @param list $paths + * + * @return list + */ private function serializeErrors(FormErrorIterator $iterator, array $paths = []): array { if ('' !== $name = $iterator->getForm()->getName()) { diff --git a/src/Resources/config/services.php b/src/Resources/config/services.php index e15ed68..e9c6326 100644 --- a/src/Resources/config/services.php +++ b/src/Resources/config/services.php @@ -11,8 +11,7 @@ $services->defaults() ->autowire() - ->autoconfigure() - ->public(false); + ->autoconfigure(); $services ->load('ChamberOrchestra\\FormBundle\\', __DIR__.'/../../') @@ -27,7 +26,8 @@ $services ->set(ProblemNormalizer::class) - ->arg('$debug', '%kernel.debug%'); + ->arg('$debug', '%kernel.debug%') + ->public(); $services ->set(TelExtension::class) diff --git a/src/Serializer/Normalizer/ProblemNormalizer.php b/src/Serializer/Normalizer/ProblemNormalizer.php index d3f362e..416bf43 100644 --- a/src/Serializer/Normalizer/ProblemNormalizer.php +++ b/src/Serializer/Normalizer/ProblemNormalizer.php @@ -17,6 +17,7 @@ class ProblemNormalizer extends Normalizer { + /** @param array $defaultContext */ public function __construct( private readonly TranslatorInterface $translator, bool $debug = false, @@ -25,6 +26,7 @@ public function __construct( parent::__construct($debug, $defaultContext); } + /** @param array $context */ public function normalize(mixed $object, ?string $format = null, array $context = []): array { $data = parent::normalize($object, $format, $context); diff --git a/src/Transformer/ArrayToStringTransformer.php b/src/Transformer/ArrayToStringTransformer.php index bf29765..a3a752c 100644 --- a/src/Transformer/ArrayToStringTransformer.php +++ b/src/Transformer/ArrayToStringTransformer.php @@ -14,9 +14,10 @@ use ChamberOrchestra\FormBundle\Exception\TransformationFailedException; use Symfony\Component\Form\DataTransformerInterface; +/** @implements DataTransformerInterface|null, string> */ readonly class ArrayToStringTransformer implements DataTransformerInterface { - public function transform($value): string + public function transform(mixed $value): string { if (null !== $value && !\is_array($value)) { throw TransformationFailedException::notAllowedType($value, ['array', 'null']); @@ -25,7 +26,8 @@ public function transform($value): string return null !== $value ? \implode(', ', $value) : ''; } - public function reverseTransform($value): array + /** @return list */ + public function reverseTransform(mixed $value): array { if (null !== $value && !\is_string($value)) { throw TransformationFailedException::notAllowedType($value, ['string', 'null']); @@ -36,7 +38,7 @@ public function reverseTransform($value): array } return \array_map( - fn(string $value): string => \preg_replace('/[^\d]/', '', $value), + static fn (string $value): string => \preg_replace('/[^\d]/', '', $value) ?? '', \explode(',', $value) ); } diff --git a/src/Transformer/DateTimeToNumberTransformer.php b/src/Transformer/DateTimeToNumberTransformer.php index c37ae62..f377b8a 100644 --- a/src/Transformer/DateTimeToNumberTransformer.php +++ b/src/Transformer/DateTimeToNumberTransformer.php @@ -13,34 +13,39 @@ use Symfony\Component\Form\DataTransformerInterface; +/** @implements DataTransformerInterface<\DateTimeInterface|null, int|null> */ readonly class DateTimeToNumberTransformer implements DataTransformerInterface { + /** @param class-string<\DateTimeInterface> $class */ public function __construct(private string $class) { - if (!\in_array(\DateTimeInterface::class, \class_implements($class) ?: [])) { - throw new \InvalidArgumentException( - \sprintf('Class "%s" must implement %s.', $class, \DateTimeInterface::class) - ); + if (!\in_array(\DateTimeInterface::class, \class_implements($class) ?: [], true)) { + throw new \InvalidArgumentException(\sprintf('Class "%s" must implement %s.', $class, \DateTimeInterface::class)); } } - public function transform($value): int|null + public function transform(mixed $value): ?int { if (null !== $value && !($value instanceof $this->class)) { throw new \TypeError(\sprintf('Passed value must be of type %s or null.', $this->class)); } - return $value?->getTimestamp(); + return $value instanceof \DateTimeInterface ? $value->getTimestamp() : null; } - public function reverseTransform($value): \DateTimeInterface|null + public function reverseTransform(mixed $value): ?\DateTimeInterface { if (null !== $value && !\is_int($value)) { - throw new \TypeError( - \sprintf('Passed value must be of type %s or null, %s given.', 'int', \get_debug_type($value)) - ); + throw new \TypeError(\sprintf('Passed value must be of type %s or null, %s given.', 'int', \get_debug_type($value))); } - return $value !== null ? (new $this->class)->setTimestamp($value) : null; + if (null === $value) { + return null; + } + + /** @var \DateTime|\DateTimeImmutable $dateTime */ + $dateTime = new $this->class(); + + return $dateTime->setTimestamp($value); } -} \ No newline at end of file +} diff --git a/src/Transformer/JsonStringToArrayTransformer.php b/src/Transformer/JsonStringToArrayTransformer.php index 0e8869d..7bd5050 100644 --- a/src/Transformer/JsonStringToArrayTransformer.php +++ b/src/Transformer/JsonStringToArrayTransformer.php @@ -14,9 +14,10 @@ use ChamberOrchestra\FormBundle\Exception\TransformationFailedException; use Symfony\Component\Form\DataTransformerInterface; +/** @implements DataTransformerInterface|null, string|null> */ readonly class JsonStringToArrayTransformer implements DataTransformerInterface { - public function transform($value): string|null + public function transform(mixed $value): ?string { if (null === $value) { return null; @@ -31,18 +32,20 @@ public function transform($value): string|null return $value; } - public function reverseTransform($value): array|null + /** @return array|null */ + public function reverseTransform(mixed $value): ?array { if (null === $value || '' === $value) { return null; } try { - $value = \json_decode($value, true, 512, JSON_BIGINT_AS_STRING | JSON_THROW_ON_ERROR); + /** @var array $decoded */ + $decoded = \json_decode($value, true, 512, JSON_BIGINT_AS_STRING | JSON_THROW_ON_ERROR); } catch (\JsonException $e) { throw new TransformationFailedException(\sprintf('Could not parse JSON into array.'), $e->getCode(), $e); } - return $value; + return $decoded; } } diff --git a/src/Transformer/TextToBoolTransformer.php b/src/Transformer/TextToBoolTransformer.php index e5b5d1e..5385169 100644 --- a/src/Transformer/TextToBoolTransformer.php +++ b/src/Transformer/TextToBoolTransformer.php @@ -14,15 +14,20 @@ use ChamberOrchestra\FormBundle\Exception\TransformationFailedException; use Symfony\Component\Form\DataTransformerInterface; +/** @implements DataTransformerInterface */ readonly class TextToBoolTransformer implements DataTransformerInterface { + /** + * @param list $trueValues + * @param list $falseValues + */ public function __construct( private array $trueValues, private array $falseValues, ) { } - public function transform($value): bool + public function transform(mixed $value): bool { if (null === $value) { return false; @@ -35,7 +40,7 @@ public function transform($value): bool return $value; } - public function reverseTransform($value): bool + public function reverseTransform(mixed $value): bool { if (null === $value) { return false; diff --git a/src/Type/Api/GetForm.php b/src/Type/Api/GetForm.php index 0107fb6..e50557c 100644 --- a/src/Type/Api/GetForm.php +++ b/src/Type/Api/GetForm.php @@ -14,6 +14,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolver; +/** @extends AbstractType */ class GetForm extends AbstractType { public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Type/Api/MutationForm.php b/src/Type/Api/MutationForm.php index fdf3073..9c4d1cf 100644 --- a/src/Type/Api/MutationForm.php +++ b/src/Type/Api/MutationForm.php @@ -14,6 +14,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolver; +/** @extends AbstractType */ abstract class MutationForm extends AbstractType { public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Type/Api/PostForm.php b/src/Type/Api/PostForm.php index fedb30a..b200d95 100644 --- a/src/Type/Api/PostForm.php +++ b/src/Type/Api/PostForm.php @@ -14,6 +14,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolver; +/** @extends AbstractType */ class PostForm extends AbstractType { public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Type/Api/QueryForm.php b/src/Type/Api/QueryForm.php index e0f5494..1d0293e 100644 --- a/src/Type/Api/QueryForm.php +++ b/src/Type/Api/QueryForm.php @@ -14,6 +14,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\OptionsResolver\OptionsResolver; +/** @extends AbstractType */ abstract class QueryForm extends AbstractType { public function configureOptions(OptionsResolver $resolver): void diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index 32d6dcb..98f8ebd 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -16,6 +16,7 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +/** @extends AbstractType */ class BooleanType extends AbstractType { public function configureOptions(OptionsResolver $resolver): void @@ -27,8 +28,13 @@ public function configureOptions(OptionsResolver $resolver): void ]); } + /** @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options): void { - $builder->addModelTransformer(new TextToBoolTransformer($options['true_values'], $options['false_values'])); + /** @var list $trueValues */ + $trueValues = $options['true_values']; + /** @var list $falseValues */ + $falseValues = $options['false_values']; + $builder->addModelTransformer(new TextToBoolTransformer($trueValues, $falseValues)); } } diff --git a/src/Type/HiddenEntityType.php b/src/Type/HiddenEntityType.php index d327211..093f88b 100644 --- a/src/Type/HiddenEntityType.php +++ b/src/Type/HiddenEntityType.php @@ -23,52 +23,60 @@ use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; +/** @extends AbstractType */ class HiddenEntityType extends AbstractType { public function __construct(private readonly EntityManagerInterface $em) { } + /** @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options): void { $em = $this->em; + /** @var class-string $entityClass */ + $entityClass = $options['class']; + /** @var string $choiceValue */ + $choiceValue = $options['choice_value']; + /** @var QueryBuilder|null $queryBuilder */ + $queryBuilder = $options['query_builder']; + $builder->addViewTransformer( new CallbackTransformer( - function (object|null $value) use ($options, $em): string|null { + function (?object $value) use ($entityClass, $choiceValue, $em): string|null { if (null === $value) { return null; } - $class = $em->getClassMetadata($options['class']); - $id = $class->getFieldValue($value, $options['choice_value']); + $class = $em->getClassMetadata($entityClass); + $id = $class->getFieldValue($value, $choiceValue); if (!\is_scalar($id) && (!\is_object($id) || !\method_exists($id, '__toString'))) { throw TransformationFailedException::notAllowedType($value, ['scalar', 'string']); } - return (string)$id; + return (string) $id; }, - function (mixed $id) use ($options, $em): object|null { + function (mixed $id) use ($entityClass, $choiceValue, $queryBuilder, $em): object|null { if (!\is_scalar($id)) { throw TransformationFailedException::notAllowedType($id, ['scalar']); } - if ($id === null || $id === false || $id === '') { + if (false === $id || '' === $id) { return null; } - if (null !== $options['query_builder']) { - $qb = $this->prepareQueryBuilder($options['query_builder'], $options['choice_value'], $id); + if (null !== $queryBuilder) { + $qb = $this->prepareQueryBuilder($queryBuilder, $choiceValue, $id); + /** @var object|null $entity */ $entity = $qb->getQuery()->getOneOrNullResult(); } else { - $er = $em->getRepository($options['class']); - $entity = $er->findOneBy([$options['choice_value'] => (string)$id]); + $er = $em->getRepository($entityClass); + $entity = $er->findOneBy([$choiceValue => (string) $id]); } if (null === $entity) { - throw new TransformationFailedException( - \sprintf("Object of class '%s' was not found.", $options['class']) - ); + throw new TransformationFailedException(\sprintf("Object of class '%s' was not found.", $entityClass)); } return $entity; @@ -91,13 +99,14 @@ public function configureOptions(OptionsResolver $resolver): void $resolver ->setRequired('class') ->setAllowedTypes('class', 'string') - ->setAllowedValues('class', function ($value) use ($em): bool { + ->setAllowedValues('class', function (string $value) use ($em): bool { if (!\class_exists($value)) { return false; } try { $em->getClassMetadata($value); + return true; } catch (\Throwable) { return false; @@ -106,23 +115,23 @@ public function configureOptions(OptionsResolver $resolver): void $resolver ->setAllowedTypes('query_builder', ['null', 'callable', QueryBuilder::class]) - ->setNormalizer('query_builder', function (Options $options, $value) use ($em): ?QueryBuilder { + ->setNormalizer('query_builder', function (Options $options, mixed $value) use ($em): ?QueryBuilder { if (null === $value || $value instanceof QueryBuilder) { return $value; } - /** @var EntityRepository $er */ - $er = $em->getRepository($options['class']); - $qb = \call_user_func($value, $er); + if (!\is_callable($value)) { + throw new InvalidArgumentException('Parameter "query_builder" must be callable, QueryBuilder or null.'); + } + + /** @var class-string $class */ + $class = $options['class']; + /** @var EntityRepository $er */ + $er = $em->getRepository($class); + $qb = $value($er); if (!$qb instanceof QueryBuilder) { - throw new InvalidArgumentException( - \sprintf( - 'Parameter "query_builder" must return instance of "%s", "%s" returned.', - QueryBuilder::class, - \get_debug_type($qb) - ) - ); + throw new InvalidArgumentException(\sprintf('Parameter "query_builder" must return instance of "%s", "%s" returned.', QueryBuilder::class, \get_debug_type($qb))); } return $qb; @@ -130,17 +139,18 @@ public function configureOptions(OptionsResolver $resolver): void $resolver ->setAllowedTypes('choice_value', ['null', 'string']) - ->setNormalizer('choice_value', function (Options $options, $value) use ($em) { - $class = $em->getClassMetadata($options['class']); + ->setNormalizer('choice_value', function (Options $options, mixed $value) use ($em): string { + /** @var class-string $entityClass */ + $entityClass = $options['class']; + $class = $em->getClassMetadata($entityClass); if (null === $value) { return $class->getSingleIdentifierFieldName(); } + /** @var string $value */ if (!$class->hasField($value)) { - throw new InvalidArgumentException( - \sprintf('Class "%s" does not have field with name "%s".', $options['class'], $value) - ); + throw new InvalidArgumentException(\sprintf('Class "%s" does not have field with name "%s".', $entityClass, $value)); } return $value; @@ -155,9 +165,7 @@ public function getParent(): string private function prepareQueryBuilder(QueryBuilder $qb, string $idFieldName, mixed $id): QueryBuilder { if (!\preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $idFieldName)) { - throw new InvalidArgumentException( - \sprintf('Invalid field name "%s".', $idFieldName) - ); + throw new InvalidArgumentException(\sprintf('Invalid field name "%s".', $idFieldName)); } $aliases = $qb->getRootAliases(); diff --git a/src/Type/TimestampType.php b/src/Type/TimestampType.php index 5f3d49f..a0ce1c0 100644 --- a/src/Type/TimestampType.php +++ b/src/Type/TimestampType.php @@ -18,6 +18,7 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; +/** @extends AbstractType<\DateTimeInterface|null> */ class TimestampType extends AbstractType { public function configureOptions(OptionsResolver $resolver): void @@ -34,6 +35,7 @@ public function configureOptions(OptionsResolver $resolver): void ]); } + /** @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->addModelTransformer(new DateTimeToNumberTransformer(DatePoint::class)); @@ -43,4 +45,4 @@ public function getParent(): string { return NumberType::class; } -} \ No newline at end of file +} diff --git a/src/Utils/CollectionUtils.php b/src/Utils/CollectionUtils.php index d48d927..442b06c 100644 --- a/src/Utils/CollectionUtils.php +++ b/src/Utils/CollectionUtils.php @@ -16,19 +16,25 @@ final class CollectionUtils { + /** + * @template T of object + * + * @param Collection $source + * @param iterable $target + */ public static function sync(Collection $source, iterable $target): void { $clone = clone $source; $target = new ArrayCollection(\is_array($target) ? $target : \iterator_to_array($target)); - //add new + // add new foreach ($target as $item) { if (!$clone->contains($item)) { $source->add($item); } } - //remove old + // remove old foreach ($clone as $item) { if (!$target->contains($item)) { $source->removeElement($item); diff --git a/src/Validator/Constraints/UniqueField.php b/src/Validator/Constraints/UniqueField.php index 8ed7f64..cc55900 100644 --- a/src/Validator/Constraints/UniqueField.php +++ b/src/Validator/Constraints/UniqueField.php @@ -17,28 +17,29 @@ class UniqueField extends Constraint { public const string ALREADY_USED_ERROR = 'a72be866-aae8-4be7-ac1d-fa4f73c167aa'; public string $message = 'This value has been already used.'; - public string|null $em = null; - public string|null $entityClass = null; - /** - * @var array OR condition - */ + public ?string $em = null; + /** @var class-string|null */ + public ?string $entityClass = null; + /** @var list OR condition */ public array $fields = []; /** * @var array|\Closure AND condition */ public array|\Closure $exclude = []; - public string|null $errorPath = null; + public ?string $errorPath = null; public ?\Closure $normalizer = null; public bool $allowEmptyString = false; protected const array ERROR_NAMES = [ self::ALREADY_USED_ERROR => 'ALREADY_USED_ERROR', ]; + /** @param array|null $options */ public function __construct(?array $options = null) { parent::__construct($options); } + /** @return list */ public function getTargets(): array { return [self::PROPERTY_CONSTRAINT]; diff --git a/src/Validator/Constraints/UniqueFieldValidator.php b/src/Validator/Constraints/UniqueFieldValidator.php index 3f71c72..c8594c2 100644 --- a/src/Validator/Constraints/UniqueFieldValidator.php +++ b/src/Validator/Constraints/UniqueFieldValidator.php @@ -30,7 +30,7 @@ public function __construct(private readonly ManagerRegistry $doctrine) { } - public function validate($value, Constraint $constraint): void + public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof UniqueField) { throw new UnexpectedTypeException($constraint, UniqueField::class); @@ -62,7 +62,7 @@ public function validate($value, Constraint $constraint): void $this->addViolation($constraint, $value); } - private function addViolation(UniqueField $constraint, $value): void + private function addViolation(UniqueField $constraint, mixed $value): void { $builder = $this->context->buildViolation($constraint->message); if ($constraint->errorPath) { @@ -82,19 +82,19 @@ private function addViolation(UniqueField $constraint, $value): void ->addViolation(); } + /** @return Selectable&\Doctrine\Persistence\ObjectRepository */ private function getRepository(UniqueField $constraint): Selectable { + if (null === $constraint->entityClass) { + throw new ConstraintDefinitionException('UniqueField constraint requires "entityClass" to be set.'); + } + $manager = $this->getManager($constraint); + /** @var \Doctrine\Persistence\ObjectRepository $repository */ $repository = $manager->getRepository($constraint->entityClass); if (!$repository instanceof Selectable) { - throw new LogicException( - \sprintf( - '%s does not implement %s which is required for Unique validation', - \get_class($repository), - Selectable::class - ) - ); + throw new LogicException(\sprintf('%s does not implement %s which is required for Unique validation', $repository::class, Selectable::class)); } return $repository; @@ -106,17 +106,19 @@ private function getManager(UniqueField $constraint): ObjectManager return $this->doctrine->getManager($constraint->em); } + if (null === $constraint->entityClass) { + throw new ConstraintDefinitionException('UniqueField constraint requires "entityClass" to be set.'); + } + $em = $this->doctrine->getManagerForClass($constraint->entityClass); if (null === $em) { - throw new ConstraintDefinitionException( - \sprintf('Class "%s" is not managed by Doctrine.', $constraint->entityClass) - ); + throw new ConstraintDefinitionException(\sprintf('Class "%s" is not managed by Doctrine.', $constraint->entityClass)); } return $em; } - private function buildComparison(string $field, $value, bool $negative = false): Comparison + private function buildComparison(string $field, mixed $value, bool $negative = false): Comparison { $operation = $negative ? Comparison::NEQ : Comparison::EQ; @@ -137,9 +139,9 @@ private function addComparisonToCriteria( $criteria->orWhere($comparison); } - private function buildCriteria(UniqueField $constraint, $value, $origin): Criteria + private function buildCriteria(UniqueField $constraint, mixed $value, mixed $origin): Criteria { - $criteria = Criteria::create(true); + $criteria = Criteria::create(); // build includes fields with OR join foreach ($constraint->fields as $field) { @@ -172,12 +174,11 @@ private function buildCriteria(UniqueField $constraint, $value, $origin): Criter return $criteria; } + /** @phpstan-assert non-empty-string $field */ private function assertValidFieldName(mixed $field): void { if (!\is_string($field) || !\preg_match('/^[a-zA-Z_][a-zA-Z0-9_.]*$/', $field)) { - throw new ConstraintDefinitionException( - \sprintf('Invalid field name "%s" in constraint criteria.', $field) - ); + throw new ConstraintDefinitionException(\sprintf('Invalid field name "%s" in constraint criteria.', \is_string($field) ? $field : \get_debug_type($field))); } } } diff --git a/src/View/FailureView.php b/src/View/FailureView.php index 641dbd2..60d2f97 100644 --- a/src/View/FailureView.php +++ b/src/View/FailureView.php @@ -26,14 +26,18 @@ public function __construct(int $status = JsonResponse::HTTP_BAD_REQUEST, string parent::__construct($status, ['Content-Type' => 'application/problem+json']); } + /** @param array $context */ public function normalize( NormalizerInterface $normalizer, ?string $format = null, array $context = [] ): array|string|int|float|bool { - return $normalizer->normalize([ + /** @var array $data */ + $data = $normalizer->normalize([ 'type' => $this->type, 'title' => $this->title, ], $format, $context); + + return $data; } -} \ No newline at end of file +} diff --git a/src/View/RedirectView.php b/src/View/RedirectView.php index 17abca4..260fc39 100644 --- a/src/View/RedirectView.php +++ b/src/View/RedirectView.php @@ -24,4 +24,4 @@ public function __construct(string $location, int $status = Response::HTTP_MOVED $this->location = $location; $this->status = $status; } -} \ No newline at end of file +} diff --git a/src/View/SuccessHtmlView.php b/src/View/SuccessHtmlView.php index 897c476..1bb0b8e 100644 --- a/src/View/SuccessHtmlView.php +++ b/src/View/SuccessHtmlView.php @@ -15,4 +15,4 @@ class SuccessHtmlView extends DataView { -} \ No newline at end of file +} diff --git a/src/View/SuccessView.php b/src/View/SuccessView.php index 14ad2b3..156cbbc 100644 --- a/src/View/SuccessView.php +++ b/src/View/SuccessView.php @@ -15,4 +15,4 @@ class SuccessView extends ResponseView { -} \ No newline at end of file +} diff --git a/src/View/ValidationFailedView.php b/src/View/ValidationFailedView.php index b2754eb..5524c8c 100644 --- a/src/View/ValidationFailedView.php +++ b/src/View/ValidationFailedView.php @@ -18,26 +18,32 @@ class ValidationFailedView extends FailureView { protected string $type = 'https://symfony.com/errors/validation'; protected string $detail; + /** @var list */ protected array $violations; + /** @param list $violations */ public function __construct(array $violations = [], string $message = 'Validation Failed') { - $this->detail = \implode("\n", \array_map(fn(ViolationView $error): string => $error->title, $violations)); + $this->detail = \implode("\n", \array_map(fn (ViolationView $error): string => $error->title, $violations)); $this->violations = $violations; parent::__construct(JsonResponse::HTTP_UNPROCESSABLE_ENTITY, $message); } + /** @param array $context */ public function normalize( NormalizerInterface $normalizer, ?string $format = null, array $context = [] ): array|string|int|float|bool { - return $normalizer->normalize([ + /** @var array $data */ + $data = $normalizer->normalize([ 'title' => $this->title, 'type' => $this->type, 'detail' => $this->detail, 'violations' => $this->violations, ], $format, $context); + + return $data; } -} \ No newline at end of file +} diff --git a/src/View/ViolationView.php b/src/View/ViolationView.php index 605e1e0..bf96d1d 100644 --- a/src/View/ViolationView.php +++ b/src/View/ViolationView.php @@ -15,12 +15,15 @@ class ViolationView extends View { + /** + * @param array $parameters + */ public function __construct( public string $id, public string $title, public array $parameters, public string $propertyPath, - public string|null $type = null, + public ?string $type = null, ) { } -} \ No newline at end of file +} diff --git a/tests/Integrational/HiddenEntityTypeIntegrationTest.php b/tests/Integrational/HiddenEntityTypeIntegrationTest.php index 9ca73ff..b6e7ca1 100644 --- a/tests/Integrational/HiddenEntityTypeIntegrationTest.php +++ b/tests/Integrational/HiddenEntityTypeIntegrationTest.php @@ -15,7 +15,7 @@ final class HiddenEntityTypeIntegrationTest extends KernelTestCase { public function testTransformsEntityToIdAndBackWithDoctrine(): void { - if (!class_exists(\Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class)) { + if (!\class_exists(\Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class)) { $this->markTestSkipped('doctrine/doctrine-bundle is required for this integration test.'); } @@ -36,13 +36,13 @@ public function testTransformsEntityToIdAndBackWithDoctrine(): void 'data_class' => null, ]); - self::assertSame((string)$user->id, $form->getViewData()); + self::assertSame((string) $user->id, $form->getViewData()); $submitForm = $factory->create(HiddenEntityType::class, null, [ 'class' => TestUser::class, 'data_class' => null, ]); - $submitForm->submit((string)$user->id); + $submitForm->submit((string) $user->id); self::assertSame($user->id, $submitForm->getData()?->id); } diff --git a/tests/Integrational/HiddenEntityTypeQueryBuilderIntegrationTest.php b/tests/Integrational/HiddenEntityTypeQueryBuilderIntegrationTest.php index 946a2e1..c5593bd 100644 --- a/tests/Integrational/HiddenEntityTypeQueryBuilderIntegrationTest.php +++ b/tests/Integrational/HiddenEntityTypeQueryBuilderIntegrationTest.php @@ -16,7 +16,7 @@ final class HiddenEntityTypeQueryBuilderIntegrationTest extends TestCase { public function testQueryBuilderAndChoiceValueAreUsed(): void { - if (!class_exists(\Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class)) { + if (!\class_exists(\Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class)) { $this->markTestSkipped('doctrine/doctrine-bundle is required for this integration test.'); } diff --git a/tests/Integrational/ProblemNormalizerIntegrationTest.php b/tests/Integrational/ProblemNormalizerIntegrationTest.php index 14d9298..71b4fc7 100644 --- a/tests/Integrational/ProblemNormalizerIntegrationTest.php +++ b/tests/Integrational/ProblemNormalizerIntegrationTest.php @@ -19,7 +19,7 @@ public function testNormalizerAddsTranslatedMessage(): void $kernel->boot(); $normalizer = $kernel->getContainer()->get(ProblemNormalizer::class); - $exception = new class() extends \RuntimeException implements TranslatableExceptionInterface { + $exception = new class extends \RuntimeException implements TranslatableExceptionInterface { public function getTranslatableMessage(): TranslatableInterface { return new TranslatableMessage('error.key'); diff --git a/tests/Integrational/TestKernel.php b/tests/Integrational/TestKernel.php index d013c10..a406358 100644 --- a/tests/Integrational/TestKernel.php +++ b/tests/Integrational/TestKernel.php @@ -11,16 +11,16 @@ namespace Tests\Integrational; -use ChamberOrchestra\ViewBundle\ChamberOrchestraViewBundle; use ChamberOrchestra\FormBundle\ChamberOrchestraFormBundle; +use ChamberOrchestra\ViewBundle\ChamberOrchestraViewBundle; use Doctrine\Bundle\DoctrineBundle\DoctrineBundle; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\Persistence\ManagerRegistry; use Symfony\Bundle\FrameworkBundle\FrameworkBundle; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; -use Doctrine\ORM\EntityManagerInterface; -use Doctrine\Persistence\ManagerRegistry; -use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Form\FormFactoryInterface; +use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\Validator\Validator\ValidatorInterface; final class TestKernel extends Kernel diff --git a/tests/Integrational/UniqueFieldValidatorIntegrationTest.php b/tests/Integrational/UniqueFieldValidatorIntegrationTest.php index eb0739d..9bfd4bc 100644 --- a/tests/Integrational/UniqueFieldValidatorIntegrationTest.php +++ b/tests/Integrational/UniqueFieldValidatorIntegrationTest.php @@ -15,7 +15,7 @@ final class UniqueFieldValidatorIntegrationTest extends KernelTestCase { public function testUniqueFieldValidatorDetectsDuplicates(): void { - if (!class_exists(\Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class)) { + if (!\class_exists(\Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class)) { $this->markTestSkipped('doctrine/doctrine-bundle is required for this integration test.'); } diff --git a/tests/Unit/ApiFormTraitTest.php b/tests/Unit/ApiFormTraitTest.php index 56825af..ff81e88 100644 --- a/tests/Unit/ApiFormTraitTest.php +++ b/tests/Unit/ApiFormTraitTest.php @@ -16,7 +16,7 @@ final class ApiFormTraitTest extends TestCase { public function testConvertRequestToArrayMergesJsonAndFiles(): void { - $host = new class() { + $host = new class { use ApiFormTrait; public function exposeConvertRequestToArray(Request $request): array @@ -32,7 +32,7 @@ public function exposeConvertRequestToArray(Request $request): array [], [], ['CONTENT_TYPE' => 'application/json'], - json_encode(['payload' => ['id' => 1]], JSON_THROW_ON_ERROR) + \json_encode(['payload' => ['id' => 1]], JSON_THROW_ON_ERROR) ); $request->files->set('file', ['name' => 'upload.txt']); @@ -43,7 +43,7 @@ public function exposeConvertRequestToArray(Request $request): array public function testConvertRequestToArrayThrowsOnInvalidJson(): void { - $host = new class() { + $host = new class { use ApiFormTrait; public function exposeConvertRequestToArray(Request $request): array @@ -69,7 +69,7 @@ public function exposeConvertRequestToArray(Request $request): array public function testConvertRequestToArrayWithFilesOnly(): void { - $host = new class() { + $host = new class { use ApiFormTrait; public function exposeConvertRequestToArray(Request $request): array @@ -91,7 +91,7 @@ public function testHandleApiCallThrowsOnNullRequest(): void $stack = new RequestStack(); $container = $this->createStub(ContainerInterface::class); - $container->method('get')->willReturnCallback(fn(string $id) => match ($id) { + $container->method('get')->willReturnCallback(fn (string $id) => match ($id) { 'request_stack' => $stack, }); diff --git a/tests/Unit/Exception/TranslatableExceptionInterfaceTest.php b/tests/Unit/Exception/TranslatableExceptionInterfaceTest.php index 8bbce51..631ee11 100644 --- a/tests/Unit/Exception/TranslatableExceptionInterfaceTest.php +++ b/tests/Unit/Exception/TranslatableExceptionInterfaceTest.php @@ -13,7 +13,7 @@ final class TranslatableExceptionInterfaceTest extends TestCase { public function testTranslatableMessageIsReturned(): void { - $exception = new class() extends \RuntimeException implements TranslatableExceptionInterface { + $exception = new class extends \RuntimeException implements TranslatableExceptionInterface { public function getTranslatableMessage(): TranslatableInterface { return new TranslatableMessage('error.key'); diff --git a/tests/Unit/Extension/TelExtensionTest.php b/tests/Unit/Extension/TelExtensionTest.php index 49c144e..30ab6ac 100644 --- a/tests/Unit/Extension/TelExtensionTest.php +++ b/tests/Unit/Extension/TelExtensionTest.php @@ -14,7 +14,7 @@ final class TelExtensionTest extends TestCase { public function testExtendedTypes(): void { - self::assertSame([TelType::class], iterator_to_array(TelExtension::getExtendedTypes())); + self::assertSame([TelType::class], \iterator_to_array(TelExtension::getExtendedTypes())); } public function testBuildFormAddsTransformer(): void diff --git a/tests/Unit/FormTraitTest.php b/tests/Unit/FormTraitTest.php index 8a1f2ca..4f98417 100644 --- a/tests/Unit/FormTraitTest.php +++ b/tests/Unit/FormTraitTest.php @@ -25,7 +25,7 @@ final class FormTraitTest extends TestCase { public function testCreateSuccessResponseReturnsDataViewWhenDataProvided(): void { - $host = new class() { + $host = new class { use FormTrait; public function exposeCreateSuccessResponse(array $data = []): DataView|ResponseView @@ -41,7 +41,7 @@ public function exposeCreateSuccessResponse(array $data = []): DataView|Response public function testCreateSuccessResponseReturnsResponseViewWhenEmpty(): void { - $host = new class() { + $host = new class { use FormTrait; public function exposeCreateSuccessResponse(array $data = []): DataView|ResponseView @@ -57,7 +57,7 @@ public function exposeCreateSuccessResponse(array $data = []): DataView|Response public function testOnFormSubmittedReturnsValidationFailedViewWhenInvalid(): void { - $host = new class() { + $host = new class { use FormTrait; public function exposeOnFormSubmitted(FormInterface $form, ?callable $callable = null) @@ -81,7 +81,7 @@ protected function createValidationFailedResponse(FormInterface $form): Validati public function testOnFormSubmittedReturnsDataViewFromCallable(): void { - $host = new class() { + $host = new class { use FormTrait; public function exposeOnFormSubmitted(FormInterface $form, ?callable $callable = null) @@ -94,14 +94,14 @@ public function exposeOnFormSubmitted(FormInterface $form, ?callable $callable = $form->method('isValid')->willReturn(true); $form->method('getData')->willReturn(['id' => 1]); - $response = $host->exposeOnFormSubmitted($form, fn() => ['ok' => true]); + $response = $host->exposeOnFormSubmitted($form, fn () => ['ok' => true]); self::assertInstanceOf(DataView::class, $response); } public function testSerializeFormErrorsBuildsViolationStructure(): void { - $host = new class() { + $host = new class { use FormTrait; public function exposeSerializeFormErrors(FormInterface $form): array @@ -144,7 +144,7 @@ public function exposeSerializeFormErrors(FormInterface $form): array public function testSerializeFormErrorsBuildsViolationStructureForEmbeddedForm(): void { - $host = new class() { + $host = new class { use FormTrait; public function exposeSerializeFormErrors(FormInterface $form): array @@ -195,7 +195,7 @@ public function testCreateRedirectResponseReturnsViewForXmlHttpRequest(): void $stack->push($request); $container = $this->createStub(ContainerInterface::class); - $container->method('get')->willReturnCallback(fn(string $id) => match ($id) { + $container->method('get')->willReturnCallback(fn (string $id) => match ($id) { 'request_stack' => $stack, }); @@ -236,7 +236,7 @@ public function testCreateRedirectResponseReturnsResponseForNonXmlHttpRequest(): $stack->push($request); $container = $this->createStub(ContainerInterface::class); - $container->method('get')->willReturnCallback(fn(string $id) => match ($id) { + $container->method('get')->willReturnCallback(fn (string $id) => match ($id) { 'request_stack' => $stack, }); @@ -277,7 +277,7 @@ public function testCreateSuccessHtmlResponseHandlesXmlHttpRequest(): void $stack->push($request); $container = $this->createStub(ContainerInterface::class); - $container->method('get')->willReturnCallback(fn(string $id) => match ($id) { + $container->method('get')->willReturnCallback(fn (string $id) => match ($id) { 'request_stack' => $stack, }); @@ -322,7 +322,7 @@ public function testCreateSuccessHtmlResponseHandlesNonXmlHttpRequest(): void $stack->push($request); $container = $this->createStub(ContainerInterface::class); - $container->method('get')->willReturnCallback(fn(string $id) => match ($id) { + $container->method('get')->willReturnCallback(fn (string $id) => match ($id) { 'request_stack' => $stack, }); @@ -398,7 +398,7 @@ public function testCreateRedirectResponseFallsBackWhenNoRequest(): void $stack = new RequestStack(); $container = $this->createStub(ContainerInterface::class); - $container->method('get')->willReturnCallback(fn(string $id) => match ($id) { + $container->method('get')->willReturnCallback(fn (string $id) => match ($id) { 'request_stack' => $stack, }); diff --git a/tests/Unit/Serializer/Normalizer/ProblemNormalizerTest.php b/tests/Unit/Serializer/Normalizer/ProblemNormalizerTest.php index dd5e66f..6e749bb 100644 --- a/tests/Unit/Serializer/Normalizer/ProblemNormalizerTest.php +++ b/tests/Unit/Serializer/Normalizer/ProblemNormalizerTest.php @@ -23,7 +23,7 @@ public function testAddsTranslatedMessageFromException(): void ->with('error.key') ->willReturn('Translated message'); - $exception = new class() extends \RuntimeException implements TranslatableExceptionInterface { + $exception = new class extends \RuntimeException implements TranslatableExceptionInterface { public function getTranslatableMessage(): TranslatableInterface { return new TranslatableMessage('error.key'); diff --git a/tests/Unit/Type/Api/MutationFormTest.php b/tests/Unit/Type/Api/MutationFormTest.php index 92ffb2b..8de1502 100644 --- a/tests/Unit/Type/Api/MutationFormTest.php +++ b/tests/Unit/Type/Api/MutationFormTest.php @@ -13,7 +13,7 @@ final class MutationFormTest extends TestCase { public function testParentAndBlockPrefix(): void { - $form = new class() extends MutationForm {}; + $form = new class extends MutationForm {}; self::assertSame('', $form->getBlockPrefix()); self::assertSame(PostForm::class, $form->getParent()); @@ -21,7 +21,7 @@ public function testParentAndBlockPrefix(): void public function testCsrfProtectionIsDisabled(): void { - $form = new class() extends MutationForm {}; + $form = new class extends MutationForm {}; $resolver = new OptionsResolver(); $form->configureOptions($resolver); diff --git a/tests/Unit/Type/Api/QueryFormTest.php b/tests/Unit/Type/Api/QueryFormTest.php index 209f64e..d6c7841 100644 --- a/tests/Unit/Type/Api/QueryFormTest.php +++ b/tests/Unit/Type/Api/QueryFormTest.php @@ -13,7 +13,7 @@ final class QueryFormTest extends TestCase { public function testParentAndBlockPrefix(): void { - $form = new class() extends QueryForm {}; + $form = new class extends QueryForm {}; self::assertSame('', $form->getBlockPrefix()); self::assertSame(GetForm::class, $form->getParent()); @@ -21,7 +21,7 @@ public function testParentAndBlockPrefix(): void public function testCsrfProtectionIsDisabled(): void { - $form = new class() extends QueryForm {}; + $form = new class extends QueryForm {}; $resolver = new OptionsResolver(); $form->configureOptions($resolver); diff --git a/tests/Unit/Type/BooleanTypeTest.php b/tests/Unit/Type/BooleanTypeTest.php index 6892329..562d26e 100644 --- a/tests/Unit/Type/BooleanTypeTest.php +++ b/tests/Unit/Type/BooleanTypeTest.php @@ -33,7 +33,7 @@ public function testBuildFormAddsTransformer(): void $builder ->expects($this->once()) ->method('addModelTransformer') - ->with($this->callback(static fn($transformer) => $transformer instanceof TextToBoolTransformer)); + ->with($this->callback(static fn ($transformer) => $transformer instanceof TextToBoolTransformer)); $type->buildForm($builder, [ 'true_values' => [1], diff --git a/tests/Unit/Type/HiddenEntityTypeEndToEndTest.php b/tests/Unit/Type/HiddenEntityTypeEndToEndTest.php index cfa4d2e..e666bc2 100644 --- a/tests/Unit/Type/HiddenEntityTypeEndToEndTest.php +++ b/tests/Unit/Type/HiddenEntityTypeEndToEndTest.php @@ -24,7 +24,7 @@ public function testTransformsEntityToIdAndBack(): void $metadata->method('getSingleIdentifierFieldName')->willReturn('id'); $metadata->method('hasField')->willReturn(true); $metadata->method('getFieldValue')->willReturnCallback( - static fn(object $value, string $field) => $value->{$field} + static fn (object $value, string $field) => $value->{$field} ); $em = $this->createStub(EntityManagerInterface::class); @@ -76,22 +76,22 @@ public function __construct(array $items) public function find(mixed $id, LockMode|int|null $lockMode = null, ?int $lockVersion = null): ?object { - return $this->items[(int)$id] ?? null; + return $this->items[(int) $id] ?? null; } public function findAll(): array { - return array_values($this->items); + return \array_values($this->items); } - public function findBy(array $criteria, array|null $orderBy = null, ?int $limit = null, ?int $offset = null): array + public function findBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array { return []; } - public function findOneBy(array $criteria, array|null $orderBy = null): ?object + public function findOneBy(array $criteria, ?array $orderBy = null): ?object { - $id = (int)($criteria['id'] ?? 0); + $id = (int) ($criteria['id'] ?? 0); return $this->items[$id] ?? null; } diff --git a/tests/Unit/Type/HiddenEntityTypeTest.php b/tests/Unit/Type/HiddenEntityTypeTest.php index fefaee0..406b45c 100644 --- a/tests/Unit/Type/HiddenEntityTypeTest.php +++ b/tests/Unit/Type/HiddenEntityTypeTest.php @@ -61,7 +61,7 @@ public function testBuildFormAddsViewTransformer(): void $builder ->expects($this->once()) ->method('addViewTransformer') - ->with($this->callback(static fn($transformer) => $transformer instanceof CallbackTransformer)); + ->with($this->callback(static fn ($transformer) => $transformer instanceof CallbackTransformer)); $type->buildForm($builder, [ 'class' => DummyEntity::class, diff --git a/tests/Unit/Type/TimestampTypeTest.php b/tests/Unit/Type/TimestampTypeTest.php index b176885..4867765 100644 --- a/tests/Unit/Type/TimestampTypeTest.php +++ b/tests/Unit/Type/TimestampTypeTest.php @@ -34,7 +34,7 @@ public function testBuildFormAddsTransformer(): void $builder ->expects($this->once()) ->method('addModelTransformer') - ->with($this->callback(static fn($transformer) => $transformer instanceof DateTimeToNumberTransformer)); + ->with($this->callback(static fn ($transformer) => $transformer instanceof DateTimeToNumberTransformer)); $type->buildForm($builder, ['input' => 'datetime_immutable']); } diff --git a/tests/Unit/Utils/CollectionUtilsTest.php b/tests/Unit/Utils/CollectionUtilsTest.php index 85d286c..d2ce2a1 100644 --- a/tests/Unit/Utils/CollectionUtilsTest.php +++ b/tests/Unit/Utils/CollectionUtilsTest.php @@ -17,6 +17,6 @@ public function testSyncAddsAndRemovesItems(): void CollectionUtils::sync($source, $target); - self::assertSame([2, 3, 4], array_values($source->toArray())); + self::assertSame([2, 3, 4], \array_values($source->toArray())); } } diff --git a/tests/Unit/Validator/Constraints/UniqueFieldValidatorTest.php b/tests/Unit/Validator/Constraints/UniqueFieldValidatorTest.php index a9d385b..ce84a27 100644 --- a/tests/Unit/Validator/Constraints/UniqueFieldValidatorTest.php +++ b/tests/Unit/Validator/Constraints/UniqueFieldValidatorTest.php @@ -32,7 +32,7 @@ protected function setUp(): void $this->objectManager = $this->createMock(ObjectManager::class); $this->objectManager ->method('getRepository') - ->willReturnCallback(fn() => $this->repository); + ->willReturnCallback(fn () => $this->repository); $this->registry = $this->createMock(ManagerRegistry::class); $this->registry @@ -91,7 +91,7 @@ public function testExcludeCallableMustReturnArray(): void $constraint = new UniqueField(); $constraint->entityClass = DummyEntity::class; $constraint->fields = ['email']; - $constraint->exclude = static fn() => 'invalid'; + $constraint->exclude = static fn () => 'invalid'; $this->expectException(ConstraintDefinitionException::class); @@ -117,7 +117,7 @@ public function testNormalizerReturningNullSkipsValidation(): void $constraint = new UniqueField(); $constraint->entityClass = DummyEntity::class; $constraint->fields = ['email']; - $constraint->normalizer = static fn() => null; + $constraint->normalizer = static fn () => null; $this->validator->validate('value', $constraint); @@ -171,7 +171,7 @@ public function __construct(private int $count) public function matching(Criteria $criteria): ArrayCollection { - return new ArrayCollection(array_fill(0, $this->count, new \stdClass())); + return new ArrayCollection(\array_fill(0, $this->count, new \stdClass())); } public function find(mixed $id): ?object diff --git a/tests/Unit/View/FailureViewTest.php b/tests/Unit/View/FailureViewTest.php index 9b7dce5..6c522bd 100644 --- a/tests/Unit/View/FailureViewTest.php +++ b/tests/Unit/View/FailureViewTest.php @@ -27,7 +27,7 @@ public function testNormalizeUsesNormalizer(): void $normalizer ->expects($this->once()) ->method('normalize') - ->willReturnCallback(static fn(array $data) => $data); + ->willReturnCallback(static fn (array $data) => $data); $data = $view->normalize($normalizer); diff --git a/tests/Unit/View/ValidationFailedViewTest.php b/tests/Unit/View/ValidationFailedViewTest.php index 2ababd1..25d09d2 100644 --- a/tests/Unit/View/ValidationFailedViewTest.php +++ b/tests/Unit/View/ValidationFailedViewTest.php @@ -25,7 +25,7 @@ public function testBuildsDetailFromViolations(): void $normalizer ->expects($this->once()) ->method('normalize') - ->willReturnCallback(static fn(array $data) => $data); + ->willReturnCallback(static fn (array $data) => $data); $data = $view->normalize($normalizer); @@ -43,7 +43,7 @@ public function testNormalizeUsesNormalizer(): void $normalizer ->expects($this->once()) ->method('normalize') - ->willReturnCallback(static fn(array $data) => $data); + ->willReturnCallback(static fn (array $data) => $data); $data = $view->normalize($normalizer);