diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md index b6c3ac96..531d6e62 100644 --- a/UPGRADE-2.0.md +++ b/UPGRADE-2.0.md @@ -1,5 +1,17 @@ +UPGRADE FROM 2.0-BETA10 to 2.0-BETA13 +===================================== + +### Core + + * The `Rollerworks\Component\Search\Extension\Core\DataTransformer\IntegerToStringTransformer` + deprecated usage of accepting a `null` value for `$roundingMode`, use `IntegerToStringTransformer::ROUND_DOWN` instead. + + * The `Rollerworks\Component\Search\Extension\Core\DataTransformer\NumberToLocalizedStringTransformer` + deprecated usage of accepting a `null` value for `$grouping` and `$roundingMode` + use `NumberToLocalizedStringTransformer::ROUND_DOWN` for `$roundingMode` and `false` for `$grouping` instead. + UPGRADE FROM 2.0-BETA9 to 2.0-BETA10 -=================================== +==================================== ### Api-Platform diff --git a/composer.json b/composer.json index eed2ab2d..3fff8d0e 100644 --- a/composer.json +++ b/composer.json @@ -15,14 +15,15 @@ ], "homepage": "https://rollerworks.github.io/", "require": { - "php": ">=8.1", + "php": "^8.1", "nesbot/carbon": "^2.38 || ^3.0", "psr/container": "^1.1 || ^2.0", "symfony/intl": "^6.4 || ^7.4 || ^8.0", "symfony/options-resolver": "^6.4 || ^7.4 || ^8.0", "symfony/property-access": "^6.4 || ^7.4 || ^8.0", "symfony/string": "^6.4 || ^7.4 || ^8.0", - "symfony/translation-contracts": "^3.4" + "symfony/translation-contracts": "^3.4", + "symfony/polyfill-php84": "^1.33" }, "require-dev": { "api-platform/core": "^4.2", diff --git a/lib/ApiPlatform/Doctrine/Orm/Extension/SearchExtension.php b/lib/ApiPlatform/Doctrine/Orm/Extension/SearchExtension.php index a94f6d89..5dec7626 100644 --- a/lib/ApiPlatform/Doctrine/Orm/Extension/SearchExtension.php +++ b/lib/ApiPlatform/Doctrine/Orm/Extension/SearchExtension.php @@ -31,13 +31,10 @@ */ final class SearchExtension implements QueryCollectionExtensionInterface { - private RequestStack $requestStack; - private DoctrineOrmFactory $ormFactory; - - public function __construct(RequestStack $requestStack, DoctrineOrmFactory $ormFactory) - { - $this->requestStack = $requestStack; - $this->ormFactory = $ormFactory; + public function __construct( + private readonly RequestStack $requestStack, + private readonly DoctrineOrmFactory $ormFactory, + ) { } public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void diff --git a/lib/ApiPlatform/Elasticsearch/Extension/SearchExtension.php b/lib/ApiPlatform/Elasticsearch/Extension/SearchExtension.php index 33ee53f9..bb72b2c8 100644 --- a/lib/ApiPlatform/Elasticsearch/Extension/SearchExtension.php +++ b/lib/ApiPlatform/Elasticsearch/Extension/SearchExtension.php @@ -32,18 +32,15 @@ */ class SearchExtension implements QueryCollectionExtensionInterface { - private $requestStack; - private $registry; - private $elasticsearchFactory; - private $client; - private $identifierNames = []; - - public function __construct(RequestStack $requestStack, ManagerRegistry $registry, ElasticsearchFactory $elasticsearchFactory, Client $client) - { - $this->requestStack = $requestStack; - $this->registry = $registry; - $this->elasticsearchFactory = $elasticsearchFactory; - $this->client = $client; + /** @var array */ + private array $identifierNames = []; + + public function __construct( + private readonly RequestStack $requestStack, + private readonly ManagerRegistry $registry, + private readonly ElasticsearchFactory $elasticsearchFactory, + private readonly Client $client, + ) { } public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, ?Operation $operation = null, array $context = []): void @@ -132,9 +129,9 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator // NOTE: written like this so we only check if we have a normalizer once if ($normalizer !== null) { - $callable = static fn (Document $document) => \call_user_func($normalizer, $document->getId()); + $callable = static fn (Document $document) => $normalizer($document->getId()); } else { - $callable = static fn (Document $document) => $document->getId(); + $callable = static fn (Document $document): ?string => $document->getId(); } $ids = array_map($callable, $response->getDocuments()); diff --git a/lib/ApiPlatform/EventListener/SearchConditionListener.php b/lib/ApiPlatform/EventListener/SearchConditionListener.php index fc6274dd..61065141 100644 --- a/lib/ApiPlatform/EventListener/SearchConditionListener.php +++ b/lib/ApiPlatform/EventListener/SearchConditionListener.php @@ -44,19 +44,13 @@ */ final class SearchConditionListener { - private $searchFactory; - private $inputProcessorLoader; - private ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory; - private $eventDispatcher; - private $cache; - - public function __construct(SearchFactory $searchFactory, InputProcessorLoader $inputProcessorLoader, ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory, EventDispatcherInterface $eventDispatcher, ?CacheInterface $cache = null) - { - $this->searchFactory = $searchFactory; - $this->inputProcessorLoader = $inputProcessorLoader; - $this->resourceMetadataCollectionFactory = $resourceMetadataFactory; - $this->eventDispatcher = $eventDispatcher; - $this->cache = $cache; + public function __construct( + private readonly SearchFactory $searchFactory, + private readonly InputProcessorLoader $inputProcessorLoader, + private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory, + private readonly EventDispatcherInterface $eventDispatcher, + private readonly ?CacheInterface $cache = null, + ) { } /** diff --git a/lib/ApiPlatform/Exception/InvalidConditionException.php b/lib/ApiPlatform/Exception/InvalidConditionException.php index 1bd0aaa7..40a5deb6 100644 --- a/lib/ApiPlatform/Exception/InvalidConditionException.php +++ b/lib/ApiPlatform/Exception/InvalidConditionException.php @@ -59,11 +59,11 @@ class InvalidConditionException extends RuntimeException implements ProblemExcep private int $status = 422; /** @var ConditionErrorMessage[] */ - private array $errors; + private readonly array $errors; #[Groups(['jsonld', 'json', 'jsonapi'])] #[ApiProperty(writable: false, initializable: false)] - private string $detail; + private readonly string $detail; public function __construct(ErrorList $error, int $code = 0, ?\Throwable $previous = null) { diff --git a/lib/ApiPlatform/Metadata/DefaultConfigurationMetadataFactory.php b/lib/ApiPlatform/Metadata/DefaultConfigurationMetadataFactory.php index c21f7832..91129134 100644 --- a/lib/ApiPlatform/Metadata/DefaultConfigurationMetadataFactory.php +++ b/lib/ApiPlatform/Metadata/DefaultConfigurationMetadataFactory.php @@ -26,8 +26,9 @@ */ final class DefaultConfigurationMetadataFactory implements ResourceMetadataCollectionFactoryInterface { - public function __construct(private readonly ResourceMetadataCollectionFactoryInterface $decorated) - { + public function __construct( + private readonly ResourceMetadataCollectionFactoryInterface $decorated, + ) { } public function create(string $resourceClass): ResourceMetadataCollection diff --git a/lib/ApiPlatform/SearchConditionEvent.php b/lib/ApiPlatform/SearchConditionEvent.php index b4202391..a84ee676 100644 --- a/lib/ApiPlatform/SearchConditionEvent.php +++ b/lib/ApiPlatform/SearchConditionEvent.php @@ -31,15 +31,11 @@ final class SearchConditionEvent extends Event */ public const SEARCH_CONDITION_EVENT = 'rollerworks_search.process.primary_condition'; - private $searchCondition; - private $resourceClass; - private $request; - - public function __construct(?SearchCondition $searchCondition, string $resourceClass, Request $request) - { - $this->searchCondition = $searchCondition; - $this->resourceClass = $resourceClass; - $this->request = $request; + public function __construct( + private readonly ?SearchCondition $searchCondition, + private readonly string $resourceClass, + private readonly Request $request, + ) { } public function getSearchCondition(): ?SearchCondition diff --git a/lib/ApiPlatform/Serializer/ConditionErrorMessageNormalizer.php b/lib/ApiPlatform/Serializer/ConditionErrorMessageNormalizer.php index caadeaae..51aa0597 100644 --- a/lib/ApiPlatform/Serializer/ConditionErrorMessageNormalizer.php +++ b/lib/ApiPlatform/Serializer/ConditionErrorMessageNormalizer.php @@ -33,12 +33,12 @@ public function normalize(mixed $data, ?string $format = null, array $context = public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool { - return ($format === 'json' || $format === 'jsonproblem' || $format === 'jsonld') && $data instanceof ConditionErrorMessage; + return \in_array($format, ['json', 'jsonproblem', 'jsonld'], true) && $data instanceof ConditionErrorMessage; } public function getSupportedTypes(?string $format): array { - if ($format === 'json' || $format === 'jsonproblem' || $format === 'jsonld') { + if (\in_array($format, ['json', 'jsonproblem', 'jsonld'], true)) { return [ ConditionErrorMessage::class => true, ]; diff --git a/lib/ApiPlatform/Serializer/InvalidSearchConditionNormalizer.php b/lib/ApiPlatform/Serializer/InvalidSearchConditionNormalizer.php index b2ab582f..55edd22b 100644 --- a/lib/ApiPlatform/Serializer/InvalidSearchConditionNormalizer.php +++ b/lib/ApiPlatform/Serializer/InvalidSearchConditionNormalizer.php @@ -30,13 +30,13 @@ */ final class InvalidSearchConditionNormalizer implements NormalizerInterface { - private $serializePayloadFields; - private $nameConverter; - - public function __construct(?array $serializePayloadFields = null, ?NameConverterInterface $nameConverter = null) - { - $this->nameConverter = $nameConverter; - $this->serializePayloadFields = $serializePayloadFields; + /** + * @param string[]|null $serializePayloadFields + */ + public function __construct( + private readonly ?array $serializePayloadFields = null, + private readonly ?NameConverterInterface $nameConverter = null, + ) { } /** diff --git a/lib/ApiPlatform/Tests/Elasticsearch/Extension/SearchExtensionTest.php b/lib/ApiPlatform/Tests/Elasticsearch/Extension/SearchExtensionTest.php index 8df3a662..dc9e91e5 100644 --- a/lib/ApiPlatform/Tests/Elasticsearch/Extension/SearchExtensionTest.php +++ b/lib/ApiPlatform/Tests/Elasticsearch/Extension/SearchExtensionTest.php @@ -16,6 +16,7 @@ use ApiPlatform\Doctrine\Orm\Util\QueryNameGenerator; use ApiPlatform\Metadata\Get; use Doctrine\ORM\Query\Expr; +use Doctrine\ORM\Query\Expr\Func; use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\Mapping\ClassMetadata; @@ -51,7 +52,7 @@ public function apply_to_collection_with_valid_condition(): void $elasticaResponse = $this->createResponse($ids); $searchCondition = $this->createCondition(); - $queryFunctionProphecy = $this->prophesize(Expr\Func::class); + $queryFunctionProphecy = $this->prophesize(Func::class); $queryFunction = $queryFunctionProphecy->reveal(); $queryExpressionProphecy = $this->prophesize(Expr::class); diff --git a/lib/ApiPlatform/Tests/EventListener/SearchConditionListenerTest.php b/lib/ApiPlatform/Tests/EventListener/SearchConditionListenerTest.php index eb655d58..55e9df92 100644 --- a/lib/ApiPlatform/Tests/EventListener/SearchConditionListenerTest.php +++ b/lib/ApiPlatform/Tests/EventListener/SearchConditionListenerTest.php @@ -461,7 +461,7 @@ private function createProcessorLoader(InputProcessor $inputProcessor, string $n return new InputProcessorLoader( new ClosureContainer( [ - $name => static fn () => $inputProcessor, + $name => static fn (): InputProcessor => $inputProcessor, ] ), [$name => $name] diff --git a/lib/ApiPlatform/Tests/Fixtures/Dummy.php b/lib/ApiPlatform/Tests/Fixtures/Dummy.php index 9f35b657..3eac8e16 100644 --- a/lib/ApiPlatform/Tests/Fixtures/Dummy.php +++ b/lib/ApiPlatform/Tests/Fixtures/Dummy.php @@ -14,243 +14,11 @@ namespace Rollerworks\Component\Search\ApiPlatform\Tests\Fixtures; use ApiPlatform\Metadata\ApiResource; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Validator\Constraints as Assert; /** - * Dummy. - * - * @author Kévin Dunglas - * - * @ORM\Entity + * @internal */ #[ApiResource(filters: ['my_dummy.search', 'my_dummy.order', 'my_dummy.date', 'my_dummy.range', 'my_dummy.boolean', 'my_dummy.numeric'])] class Dummy { - /** - * @var int The id - * - * @ORM\Column(type="integer") - * - * @ORM\Id - * - * @ORM\GeneratedValue(strategy="AUTO") - */ - private $id; - - /** - * @var string The dummy name - * - * @ORM\Column - * - * @Assert\NotBlank - * - * @ApiProperty(iri="http://schema.org/name") - */ - private $name; - - /** - * @var string The dummy name alias - * - * @ORM\Column(nullable=true) - * - * @ApiProperty(iri="https://schema.org/alternateName") - */ - private $alias; - - /** - * @var array foo - */ - protected $foo; - - /** - * @var string A short description of the item - * - * @ORM\Column(nullable=true) - * - * @ApiProperty(iri="https://schema.org/description") - */ - public $description; - - /** - * @var string A dummy - * - * @ORM\Column(nullable=true) - */ - public $dummy; - - /** - * @var bool A dummy boolean - * - * @ORM\Column(type="boolean", nullable=true) - */ - public $dummyBoolean; - - /** - * @var \DateTimeImmutable A dummy date - * - * @ORM\Column(type="datetime", nullable=true) - * - * @Assert\DateTime - */ - public $dummyDate; - - /** - * @var string A dummy float - * - * @ORM\Column(type="float", nullable=true) - */ - public $dummyFloat; - - /** - * @var string A dummy price - * - * @ORM\Column(type="decimal", precision=10, scale=2, nullable=true) - */ - public $dummyPrice; - - /** - * @var RelatedDummy A related dummy - * - * @ORM\ManyToOne(targetEntity="RelatedDummy") - */ - public $relatedDummy; - - /** - * @var ArrayCollection Several dummies - * - * @ORM\ManyToMany(targetEntity="RelatedDummy") - */ - public $relatedDummies; - - /** - * @var array serialize data - * - * @ORM\Column(type="json_array", nullable=true) - */ - public $jsonData; - - /** - * @var string - * - * @ORM\Column(nullable=true) - */ - public $nameConverted; - - public static function staticMethod(): void - { - } - - public function __construct() - { - $this->relatedDummies = new ArrayCollection(); - $this->jsonData = []; - } - - public function getId() - { - return $this->id; - } - - public function setName($name): void - { - $this->name = $name; - } - - public function getName() - { - return $this->name; - } - - public function setAlias($alias): void - { - $this->alias = $alias; - } - - public function getAlias() - { - return $this->alias; - } - - public function setDescription($description): void - { - $this->description = $description; - } - - public function getDescription() - { - return $this->description; - } - - public function hasRole($role): void - { - } - - public function setFoo(?array $foo = null): void - { - } - - public function setDummyDate(?\DateTimeImmutable $dummyDate = null): void - { - $this->dummyDate = $dummyDate; - } - - public function getDummyDate() - { - return $this->dummyDate; - } - - public function setDummyPrice($dummyPrice) - { - $this->dummyPrice = $dummyPrice; - - return $this; - } - - public function getDummyPrice() - { - return $this->dummyPrice; - } - - public function setJsonData($jsonData): void - { - $this->jsonData = $jsonData; - } - - public function getJsonData() - { - return $this->jsonData; - } - - public function getRelatedDummy() - { - return $this->relatedDummy; - } - - /** - * @return bool - */ - public function isDummyBoolean() - { - return $this->dummyBoolean; - } - - /** - * @param bool $dummyBoolean - */ - public function setDummyBoolean($dummyBoolean): void - { - $this->dummyBoolean = $dummyBoolean; - } - - public function setDummy($dummy = null): void - { - $this->dummy = $dummy; - } - - public function getDummy() - { - return $this->dummy; - } } diff --git a/lib/ApiPlatform/Tests/Fixtures/DummyRelatedDummy.php b/lib/ApiPlatform/Tests/Fixtures/DummyRelatedDummy.php index dabad489..f06a4cc4 100644 --- a/lib/ApiPlatform/Tests/Fixtures/DummyRelatedDummy.php +++ b/lib/ApiPlatform/Tests/Fixtures/DummyRelatedDummy.php @@ -13,7 +13,9 @@ namespace Rollerworks\Component\Search\ApiPlatform\Tests\Fixtures; +/** + * @internal + */ final class DummyRelatedDummy { - } diff --git a/lib/ApiPlatform/Tests/Mock/SpyingInputProcessor.php b/lib/ApiPlatform/Tests/Mock/SpyingInputProcessor.php index c3d33364..83112e3c 100644 --- a/lib/ApiPlatform/Tests/Mock/SpyingInputProcessor.php +++ b/lib/ApiPlatform/Tests/Mock/SpyingInputProcessor.php @@ -21,8 +21,8 @@ final class SpyingInputProcessor implements InputProcessor { - private $config; - private $input; + private ?ProcessorConfig $config = null; + private mixed $input; public static function getCondition(): SearchCondition { @@ -32,7 +32,7 @@ public static function getCondition(): SearchCondition return new SearchCondition(new FieldSetStub(), $valuesGroup); } - public function process(ProcessorConfig $config, $input): SearchCondition + public function process(ProcessorConfig $config, mixed $input): SearchCondition { $this->config = $config; $this->input = $input; diff --git a/lib/ApiPlatform/composer.json b/lib/ApiPlatform/composer.json index 4047a3f1..3f00bd79 100644 --- a/lib/ApiPlatform/composer.json +++ b/lib/ApiPlatform/composer.json @@ -10,7 +10,7 @@ } ], "require": { - "php": ">=8.1", + "php": "^8.1", "api-platform/core": "^4.2", "rollerworks/search": ">=2.0.0-BETA2 ^2.0@dev", "symfony/http-foundation": "^6.4 || ^7.4 || ^8.0" diff --git a/lib/Core/AbstractExtension.php b/lib/Core/AbstractExtension.php index 3503477e..cbdfca14 100644 --- a/lib/Core/AbstractExtension.php +++ b/lib/Core/AbstractExtension.php @@ -21,7 +21,7 @@ /** * The AbstractExtension can be used as a base class for SearchExtensions. * - * An added bonus for extending this class rather then the implementing the the + * A bonus for extending this class rather then the implementing the the * {@link SearchExtensionInterface} is that any new methods added the * SearchExtensionInterface will not break existing implementations. * @@ -29,11 +29,11 @@ */ abstract class AbstractExtension implements SearchExtension { - /** @var array|null */ - private $typesExtensions; + /** @var array|null */ + private ?array $types = null; - /** @var FieldType[]|null */ - private $types; + /** @var array|null */ + private ?array $typesExtensions = null; public function getType(string $name): FieldType { @@ -68,13 +68,13 @@ public function hasTypeExtensions(string $type): bool return isset($this->typesExtensions[$type]); } - public function getTypeExtensions(string $type): array + public function getTypeExtensions(string $name): array { if ($this->typesExtensions === null) { $this->initTypesExtensions(); } - return $this->typesExtensions[$type] ?? []; + return $this->typesExtensions[$name] ?? []; } /** diff --git a/lib/Core/ConditionErrorMessage.php b/lib/Core/ConditionErrorMessage.php index 4736298d..d8bfe3f2 100644 --- a/lib/Core/ConditionErrorMessage.php +++ b/lib/Core/ConditionErrorMessage.php @@ -25,45 +25,10 @@ * * @author Sebastiaan Stok */ -final class ConditionErrorMessage implements TranslatableInterface +final class ConditionErrorMessage implements TranslatableInterface, \Stringable { - /** - * @var string - */ - public $path; - - /** - * @var string - */ - public $message; - - /** - * The template for the error message. - * - * @var string|null - */ - public $messageTemplate; - - /** - * The parameters that should be substituted in the message template. - * - * @var array - */ - public $messageParameters; - - /** - * The value for error message pluralization. - * - * @var int|null - */ - public $messagePluralization; - - public $cause; - - /** - * @var string[] - */ - public $translatedParameters = []; + /** @var string[] */ + public array $translatedParameters = []; public string $translatedDomain = 'validators'; @@ -71,26 +36,30 @@ final class ConditionErrorMessage implements TranslatableInterface * Any array key in $messageParameters will be used as a placeholder in * $messageTemplate. * - * @param string $path Path of the error, this is dependent - * on the values structure - * @param string $message The translated error message - * @param string|null $messageTemplate The template for the error message - * @param array $messageParameters The parameters that should be - * substituted in the message template - * @param int|null $messagePluralization The value for error message pluralization - * @param mixed $cause The cause of the error + * @param string $path Path of the error, this is dependent + * on the values structure + * @param string $message The translated error message + * @param string|null $messageTemplate The template for the error message + * @param array $messageParameters The parameters that should be + * substituted in the message template + * @param int|null $messagePluralization The value for error message pluralization + * @param mixed $cause The cause of the error */ - public function __construct(string $path, string $message, ?string $messageTemplate = null, array $messageParameters = [], ?int $messagePluralization = null, $cause = null) - { - $this->path = $path; - $this->message = $message; - $this->messageTemplate = $messageTemplate ?: $message; - $this->messageParameters = $messageParameters; - $this->messagePluralization = $messagePluralization; - $this->cause = $cause; + public function __construct( + public string $path, + public string $message, + public ?string $messageTemplate = null, + public array $messageParameters = [], + public ?int $messagePluralization = null, + public mixed $cause = null, + ) { + $this->messageTemplate ??= $this->message; } - public static function withMessageTemplate(string $path, string $messageTemplate, array $messageParameters = [], ?int $messagePluralization = null, $cause = null): self + /** + * @param array $messageParameters + */ + public static function withMessageTemplate(string $path, string $messageTemplate, array $messageParameters = [], ?int $messagePluralization = null, mixed $cause = null): self { return new self( $path, @@ -102,7 +71,7 @@ public static function withMessageTemplate(string $path, string $messageTemplate ); } - public static function rawMessage(string $path, string $message, $cause = null): self + public static function rawMessage(string $path, string $message, mixed $cause = null): self { $obj = new self($path, $message, null, [], null, $cause); $obj->messageTemplate = null; // Mark as untranslated @@ -111,8 +80,8 @@ public static function rawMessage(string $path, string $message, $cause = null): } /** - * @param array $translatedParameters An array of parameter names that need - * to be translated prior to there usage + * @param string[] $translatedParameters An array of parameter names that need + * to be translated prior to there usage */ public function setTranslatedParameters(array $translatedParameters): self { @@ -152,13 +121,18 @@ public function trans(TranslatorInterface $translator, ?string $locale = null): return $translator->trans($this->messageTemplate, $parameters, $this->translatedDomain, $locale); } + /** + * @param array $messageParameters + * + * @return array + */ private function formatParameters(array $messageParameters): array { $newParams = []; foreach ($messageParameters as $name => $value) { if (\is_array($value)) { - $value = implode(', ', array_map([$this, 'formatValue'], $value)); + $value = implode(', ', array_map($this->formatValue(...), $value)); } else { $value = $this->formatValue($value); } @@ -169,7 +143,7 @@ private function formatParameters(array $messageParameters): array return $newParams; } - private function formatValue($value): string + private function formatValue(string | int | float $value): string { $value = (string) $value; diff --git a/lib/Core/ErrorList.php b/lib/Core/ErrorList.php index fce403ff..f813d15c 100644 --- a/lib/Core/ErrorList.php +++ b/lib/Core/ErrorList.php @@ -15,6 +15,8 @@ /** * @author Sebastiaan Stok + * + * @extends \ArrayObject */ final class ErrorList extends \ArrayObject { diff --git a/lib/Core/Exception/InputProcessorException.php b/lib/Core/Exception/InputProcessorException.php index a22ba217..04100911 100644 --- a/lib/Core/Exception/InputProcessorException.php +++ b/lib/Core/Exception/InputProcessorException.php @@ -20,28 +20,18 @@ */ class InputProcessorException extends \InvalidArgumentException implements SearchException { - /** @var string */ - public $path = ''; - - /** @var string */ - public $messageTemplate; - - /** @var array */ - public $messageParameters; - - /** @var array */ - public $translatedParameters = []; - - /** @var int|null */ - public $plural; - - public function __construct(string $path, string $messageTemplate, array $messageParameters = [], ?int $plural = null, ?\Exception $previous = null) - { - $this->path = $path; - $this->messageTemplate = $messageTemplate; - $this->messageParameters = $messageParameters; - $this->plural = $plural; + public array $translatedParameters = []; + /** + * @param array $messageParameters + */ + public function __construct( + public string $path, + public string $messageTemplate, + public array $messageParameters = [], + public ?int $plural = null, + ?\Exception $previous = null, + ) { parent::__construct(strtr($messageTemplate, $this->formatParameters($messageParameters)), 0, $previous); } @@ -66,8 +56,8 @@ public function toErrorMessageObj(): ConditionErrorMessage * This helps with the translating of normalized types to * a localized format. * - * @param array $translatedParameters An array of parameter names that need - * to be translated prior to their usage + * @param string[] $translatedParameters An array of parameter names that need + * to be translated prior to their usage */ protected function setTranslatedParameters(array $translatedParameters): self { @@ -82,7 +72,7 @@ private function formatParameters(array $messageParameters): array foreach ($messageParameters as $name => $value) { if (\is_array($value)) { - $value = implode(', ', array_map([$this, 'formatValue'], $value)); + $value = implode(', ', array_map($this->formatValue(...), $value)); } else { $value = $this->formatValue($value); } @@ -93,7 +83,7 @@ private function formatParameters(array $messageParameters): array return $newParams; } - private function formatValue($value): string + private function formatValue(string | int | float $value): string { $value = (string) $value; diff --git a/lib/Core/Exception/InvalidSearchConditionException.php b/lib/Core/Exception/InvalidSearchConditionException.php index 9dfce505..a776feea 100644 --- a/lib/Core/Exception/InvalidSearchConditionException.php +++ b/lib/Core/Exception/InvalidSearchConditionException.php @@ -20,13 +20,13 @@ */ final class InvalidSearchConditionException extends \InvalidArgumentException implements SearchException { - private $errors; - - public function __construct(array $errors) - { + /** + * @param ConditionErrorMessage[] $errors + */ + public function __construct( + private readonly array $errors, + ) { parent::__construct('The search-condition contains one or more errors.'); - - $this->errors = $errors; } /** diff --git a/lib/Core/Exception/TransformationFailedException.php b/lib/Core/Exception/TransformationFailedException.php index 16cd9920..f8ece144 100644 --- a/lib/Core/Exception/TransformationFailedException.php +++ b/lib/Core/Exception/TransformationFailedException.php @@ -15,23 +15,25 @@ final class TransformationFailedException extends \RuntimeException implements SearchException { - private $invalidMessage; - private $invalidMessageParameters; - private mixed $value; - - public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, ?string $invalidMessage = null, array $invalidMessageParameters = [], mixed $value = null) - { + /** + * @param array $invalidMessageParameters + */ + public function __construct( + string $message = '', + int $code = 0, + ?\Throwable $previous = null, + private ?string $invalidMessage = null, + private array $invalidMessageParameters = [], + private readonly mixed $value = null, + ) { parent::__construct($message, $code, $previous); - - $this->setInvalidMessage($invalidMessage, $invalidMessageParameters); - $this->value = $value; } /** * Sets the message that will be shown to the user. * - * @param string|null $invalidMessage The message or message key - * @param array $invalidMessageParameters Data to be passed into the translator + * @param string|null $invalidMessage The message or message key + * @param array $invalidMessageParameters Data to be passed into the translator */ public function setInvalidMessage(?string $invalidMessage = null, array $invalidMessageParameters = []): void { @@ -44,6 +46,9 @@ public function getInvalidMessage(): ?string return $this->invalidMessage; } + /** + * @return array + */ public function getInvalidMessageParameters(): array { return $this->invalidMessageParameters; diff --git a/lib/Core/Exception/TranslatedArgument.php b/lib/Core/Exception/TranslatedArgument.php index 3c011ff2..6411fccb 100644 --- a/lib/Core/Exception/TranslatedArgument.php +++ b/lib/Core/Exception/TranslatedArgument.php @@ -16,17 +16,16 @@ use Symfony\Contracts\Translation\TranslatableInterface; use Symfony\Contracts\Translation\TranslatorInterface; -final class TranslatedArgument implements TranslatableInterface +final class TranslatedArgument implements TranslatableInterface, \Stringable { - private string $message; - private array $parameters; - private ?string $domain; - - public function __construct(string $message, array $parameters = [], ?string $domain = null) - { - $this->message = $message; - $this->parameters = $parameters; - $this->domain = $domain; + /** + * @param array $parameters + */ + public function __construct( + private readonly string $message, + private readonly array $parameters = [], + private readonly ?string $domain = null, + ) { } public function __toString(): string @@ -39,6 +38,9 @@ public function getMessage(): string return $this->message; } + /** + * @return array + */ public function getParameters(): array { return $this->parameters; diff --git a/lib/Core/Exception/UnexpectedTypeException.php b/lib/Core/Exception/UnexpectedTypeException.php index 0b6bf183..1b28396e 100644 --- a/lib/Core/Exception/UnexpectedTypeException.php +++ b/lib/Core/Exception/UnexpectedTypeException.php @@ -31,7 +31,7 @@ public function __construct($value, $expectedType) \sprintf( 'Expected argument of type "%s", "%s" given', $expectedType, - \is_object($value) ? $value::class : \gettype($value) + get_debug_type($value) ) ); } diff --git a/lib/Core/Exporter/AbstractExporter.php b/lib/Core/Exporter/AbstractExporter.php index 61e36301..fe7e5e5f 100644 --- a/lib/Core/Exporter/AbstractExporter.php +++ b/lib/Core/Exporter/AbstractExporter.php @@ -50,7 +50,7 @@ public function modelToView($value, FieldConfig $field): string 'converted to a string. You must set a viewTransformer for field "%s" with type "%s".', \gettype($value), $field->getName(), - \get_class($field->getType()->getInnerType()) + $field->getType()->getInnerType()::class ) ); } @@ -78,7 +78,7 @@ public function modelToNorm($value, FieldConfig $field): string 'converted to a string. You must set a normTransformer for field "%s" with type "%s".', \gettype($value), $field->getName(), - \get_class($field->getType()->getInnerType()) + $field->getType()->getInnerType()::class ) ); } diff --git a/lib/Core/Exporter/JsonExporter.php b/lib/Core/Exporter/JsonExporter.php index d6c9431b..7a9247e2 100644 --- a/lib/Core/Exporter/JsonExporter.php +++ b/lib/Core/Exporter/JsonExporter.php @@ -34,7 +34,7 @@ public function exportCondition(SearchCondition $condition): string { $fieldSet = $condition->getFieldSet(); - return (string) json_encode( + return json_encode( array_merge( $this->exportOrder($condition, $fieldSet), $this->exportGroup($condition->getValuesGroup(), $fieldSet, true) diff --git a/lib/Core/Exporter/NormStringQueryExporter.php b/lib/Core/Exporter/NormStringQueryExporter.php index 2e67b2db..ed640de6 100644 --- a/lib/Core/Exporter/NormStringQueryExporter.php +++ b/lib/Core/Exporter/NormStringQueryExporter.php @@ -33,7 +33,7 @@ protected function modelToExported($value, FieldConfig $field, string $allowedNe } if (\is_callable($valueExporter)) { - return $valueExporter($value, [$this, 'modelToNorm'], $field); + return $valueExporter($value, $this->modelToNorm(...), $field); } return $this->exportValueAsString($this->modelToNorm($value, $field)); diff --git a/lib/Core/Exporter/StringExporter.php b/lib/Core/Exporter/StringExporter.php index 7e67b42f..2b4d2f3e 100644 --- a/lib/Core/Exporter/StringExporter.php +++ b/lib/Core/Exporter/StringExporter.php @@ -32,8 +32,8 @@ */ abstract class StringExporter extends AbstractExporter { - /** @var string[] */ - protected $fields = []; + /** @var array */ + protected array $fields = []; public function exportCondition(SearchCondition $condition): string { @@ -48,7 +48,7 @@ public function exportCondition(SearchCondition $condition): string $result .= $this->exportOrder($condition, $fieldSet); - return trim($result . $this->exportGroup($valuesGroup, $fieldSet, true)); + return mb_trim($result . $this->exportGroup($valuesGroup, $fieldSet, true)); } abstract protected function resolveLabels(FieldSet $fieldSet): array; @@ -68,7 +68,7 @@ protected function exportOrder(SearchCondition $condition, FieldSet $fieldSet): $result .= ': ' . $this->modelToExported($direction, $fieldSet->get($name)) . '; '; } - return ltrim($result); + return mb_ltrim($result); } protected function exportGroup(ValuesGroup $valuesGroup, FieldSet $fieldSet, bool $isRoot = false): string @@ -86,7 +86,7 @@ protected function exportGroup(ValuesGroup $valuesGroup, FieldSet $fieldSet, boo } foreach ($valuesGroup->getGroups() as $group) { - $exportedGroup = '( ' . trim($this->exportGroup($group, $fieldSet), ' ;') . ' ); '; + $exportedGroup = '( ' . mb_trim($this->exportGroup($group, $fieldSet), ' ;') . ' ); '; if ($exportedGroup !== '( ); ' && $group->getGroupLogical() === ValuesGroup::GROUP_LOGICAL_OR) { $exportedGroups .= '*'; @@ -97,7 +97,7 @@ protected function exportGroup(ValuesGroup $valuesGroup, FieldSet $fieldSet, boo $result .= $exportedGroups; - return trim($result); + return mb_trim($result); } protected function modelToExported($value, FieldConfig $field): string @@ -109,7 +109,7 @@ protected function modelToExported($value, FieldConfig $field): string } if (\is_callable($valueExporter)) { - return $valueExporter($value, [$this, 'modelToView'], $field); + return $valueExporter($value, $this->modelToView(...), $field); } return $this->exportValueAsString($this->modelToView($value, $field)); @@ -167,7 +167,7 @@ private function exportValues(ValuesBag $valuesBag, FieldConfig $field): string $exportedValues .= $this->getPatternMatchOperator($value) . ' ' . $this->exportValueAsString($value->getValue()) . ', '; } - return rtrim($exportedValues, ', '); + return mb_rtrim($exportedValues, ', '); } private function getPatternMatchOperator(PatternMatch $patternMatch): string @@ -178,39 +178,18 @@ private function getPatternMatchOperator(PatternMatch $patternMatch): string $operator .= '!'; } - switch ($patternMatch->getType()) { - case PatternMatch::PATTERN_CONTAINS: - case PatternMatch::PATTERN_NOT_CONTAINS: - $operator .= '*'; - - break; - - case PatternMatch::PATTERN_STARTS_WITH: - case PatternMatch::PATTERN_NOT_STARTS_WITH: - $operator .= '>'; - - break; - - case PatternMatch::PATTERN_ENDS_WITH: - case PatternMatch::PATTERN_NOT_ENDS_WITH: - $operator .= '<'; - - break; - - case PatternMatch::PATTERN_EQUALS: - case PatternMatch::PATTERN_NOT_EQUALS: - $operator .= '='; - - break; - - default: - throw new \RuntimeException( - \sprintf( - 'Unsupported pattern-match type "%s" found. Please report this bug.', - $patternMatch->getType() - ) - ); - } + match ($patternMatch->getType()) { + PatternMatch::PATTERN_CONTAINS, PatternMatch::PATTERN_NOT_CONTAINS => $operator .= '*', + PatternMatch::PATTERN_STARTS_WITH, PatternMatch::PATTERN_NOT_STARTS_WITH => $operator .= '>', + PatternMatch::PATTERN_ENDS_WITH, PatternMatch::PATTERN_NOT_ENDS_WITH => $operator .= '<', + PatternMatch::PATTERN_EQUALS, PatternMatch::PATTERN_NOT_EQUALS => $operator .= '=', + default => throw new \RuntimeException( + \sprintf( + 'Unsupported pattern-match type "%s" found. Please report this bug.', + $patternMatch->getType() + ) + ), + }; return $operator; } diff --git a/lib/Core/Exporter/StringQueryExporter.php b/lib/Core/Exporter/StringQueryExporter.php index 54d0a86a..89ad245a 100644 --- a/lib/Core/Exporter/StringQueryExporter.php +++ b/lib/Core/Exporter/StringQueryExporter.php @@ -23,7 +23,8 @@ */ final class StringQueryExporter extends StringExporter { - private $labelResolver; + /** @var callable */ + private mixed $labelResolver; /** * @param callable|null $labelResolver a callable to resolve the actual label diff --git a/lib/Core/Extension/Core/ChoiceList/ArrayChoiceList.php b/lib/Core/Extension/Core/ChoiceList/ArrayChoiceList.php index d623b3df..2edd6361 100644 --- a/lib/Core/Extension/Core/ChoiceList/ArrayChoiceList.php +++ b/lib/Core/Extension/Core/ChoiceList/ArrayChoiceList.php @@ -25,34 +25,27 @@ */ class ArrayChoiceList implements ChoiceList { - /** - * @var array - */ - protected $choices; + /** @var array */ + protected array $choices; /** * The values indexed by the original keys. * * @var array */ - protected $structuredValues; + protected array $structuredValues; - /** - * @var array - */ - protected $originalKeys; + /** @var array */ + protected array $originalKeys; /** * The callback for creating the value for a choice. * * @var callable|null */ - protected $valueCallback; + protected mixed $valueCallback = null; - /** - * @var bool - */ - protected $valuesAreConstant; + protected bool $valuesAreConstant; /** * The given choice array must have the same array keys as the value array. @@ -63,14 +56,14 @@ class ArrayChoiceList implements ChoiceList * incrementing integers are used as * values */ - public function __construct($choices, ?callable $value = null) + public function __construct(array | \Traversable $choices, ?callable $value = null) { if ($choices instanceof \Traversable) { $choices = iterator_to_array($choices); } if ($value === null && $this->castableToString($choices)) { - $value = static fn ($choice) => $choice === false ? '0' : (string) $choice; + $value = static fn ($choice): string => $choice === false ? '0' : (string) $choice; } if ($value !== null) { @@ -103,7 +96,7 @@ public function getChoices(): array public function getValues(): array { - return array_map('strval', array_keys($this->choices)); + return array_map(strval(...), array_keys($this->choices)); } public function getStructuredValues(): array @@ -138,7 +131,7 @@ public function getValuesForChoices(array $choices): array $givenValues = []; foreach ($choices as $i => $givenChoice) { - $givenValues[$i] = (string) \call_user_func($this->valueCallback, $givenChoice); + $givenValues[$i] = (string) ($this->valueCallback)($givenChoice); } return array_intersect($givenValues, array_keys($this->choices)); @@ -189,7 +182,7 @@ private function flatten(array $choices, callable $value, &$choicesByValues, &$k continue; } - $choiceValue = (string) \call_user_func($value, $choice); + $choiceValue = (string) $value($choice); $choicesByValues[$choiceValue] = $choice; $keysByValues[$choiceValue] = $key; $structuredValues[$key] = $choiceValue; diff --git a/lib/Core/Extension/Core/ChoiceList/ChoiceLoaderTrait.php b/lib/Core/Extension/Core/ChoiceList/ChoiceLoaderTrait.php index f4b7f293..0a4f1a4c 100644 --- a/lib/Core/Extension/Core/ChoiceList/ChoiceLoaderTrait.php +++ b/lib/Core/Extension/Core/ChoiceList/ChoiceLoaderTrait.php @@ -22,15 +22,12 @@ */ trait ChoiceLoaderTrait { - /** - * @var ArrayChoiceList|null - */ - protected $choiceList; + protected ?ArrayChoiceList $choiceList = null; public function loadChoicesForValues(array $values, ?callable $value = null): array { // Optimize - if (empty($values)) { + if ($values === []) { return []; } @@ -45,7 +42,7 @@ public function loadChoicesForValues(array $values, ?callable $value = null): ar public function loadValuesForChoices(array $choices, ?callable $value = null): array { // Optimize - if (empty($choices)) { + if ($choices === []) { return []; } diff --git a/lib/Core/Extension/Core/ChoiceList/Factory/CachingFactoryDecorator.php b/lib/Core/Extension/Core/ChoiceList/Factory/CachingFactoryDecorator.php index 3ea55635..31fb9e6d 100644 --- a/lib/Core/Extension/Core/ChoiceList/Factory/CachingFactoryDecorator.php +++ b/lib/Core/Extension/Core/ChoiceList/Factory/CachingFactoryDecorator.php @@ -24,24 +24,15 @@ */ final class CachingFactoryDecorator implements ChoiceListFactory { - /** - * @var ChoiceListFactory - */ - private $decoratedFactory; - - /** - * @var ChoiceList[] - */ - private $lists = []; + /** @var ChoiceList[] */ + private array $lists = []; - /** - * @var ChoiceListView[] - */ - private $views = []; + /** @var ChoiceListView[] */ + private array $views = []; - public function __construct(ChoiceListFactory $decoratedFactory) - { - $this->decoratedFactory = $decoratedFactory; + public function __construct( + private readonly ChoiceListFactory $decoratedFactory, + ) { } public function getDecoratedFactory(): ChoiceListFactory diff --git a/lib/Core/Extension/Core/ChoiceList/Factory/DefaultChoiceListFactory.php b/lib/Core/Extension/Core/ChoiceList/Factory/DefaultChoiceListFactory.php index 7c3b5970..978daa7a 100644 --- a/lib/Core/Extension/Core/ChoiceList/Factory/DefaultChoiceListFactory.php +++ b/lib/Core/Extension/Core/ChoiceList/Factory/DefaultChoiceListFactory.php @@ -46,7 +46,7 @@ public function createView(ChoiceList $list, $preferredChoices = null, $label = $keys = $list->getOriginalKeys(); if (! \is_callable($preferredChoices) && ! empty($preferredChoices)) { - $preferredChoices = static fn ($choice) => array_search($choice, $preferredChoices, true) !== false; + $preferredChoices = static fn ($choice): bool => array_search($choice, $preferredChoices, true) !== false; } // The names are generated from an incrementing integer by default @@ -109,7 +109,7 @@ private static function addChoiceView($choice, $value, $label, $keys, &$index, $ // $value may be an integer or a string, since it's stored in the array // keys. We want to guarantee it's a string though. $key = $keys[$value]; - $nextIndex = \is_int($index) ? $index++ : \call_user_func($index, $choice, $key, $value); + $nextIndex = \is_int($index) ? $index++ : $index($choice, $key, $value); // BC normalize label to accept a false value if ($label === null) { @@ -118,7 +118,7 @@ private static function addChoiceView($choice, $value, $label, $keys, &$index, $ } elseif ($label !== false) { // If "choice_label" is set to false and "expanded" is true, the value false // should be passed on to the "label" option of the checkboxes/radio buttons - $dynamicLabel = \call_user_func($label, $choice, $key, $value); + $dynamicLabel = $label($choice, $key, $value); $label = $dynamicLabel === false ? false : (string) $dynamicLabel; } @@ -128,11 +128,11 @@ private static function addChoiceView($choice, $value, $label, $keys, &$index, $ $label, // The attributes may be a callable or a mapping from choice indices // to nested arrays - \is_callable($attr) ? \call_user_func($attr, $choice, $key, $value) : ($attr[$key] ?? []) + \is_callable($attr) ? $attr($choice, $key, $value) : ($attr[$key] ?? []) ); // $isPreferred may be null if no choices are preferred - if ($isPreferred && \call_user_func($isPreferred, $choice, $key, $value)) { + if ($isPreferred && $isPreferred($choice, $key, $value)) { $preferredViews[$nextIndex] = $view; } else { $otherViews[$nextIndex] = $view; @@ -191,7 +191,7 @@ private static function addChoiceViewsGroupedBy($groupBy, $label, $choices, $key private static function addChoiceViewGroupedBy($groupBy, $choice, $value, $label, $keys, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews): void { - $groupLabel = \call_user_func($groupBy, $choice, $keys[$value], $value); + $groupLabel = $groupBy($choice, $keys[$value], $value); if ($groupLabel === null) { // If the callable returns null, don't group the choice diff --git a/lib/Core/Extension/Core/ChoiceList/Factory/PropertyAccessDecorator.php b/lib/Core/Extension/Core/ChoiceList/Factory/PropertyAccessDecorator.php index d0556ae5..fcdafb6b 100644 --- a/lib/Core/Extension/Core/ChoiceList/Factory/PropertyAccessDecorator.php +++ b/lib/Core/Extension/Core/ChoiceList/Factory/PropertyAccessDecorator.php @@ -43,13 +43,13 @@ */ final class PropertyAccessDecorator implements ChoiceListFactory { - private $decoratedFactory; - private $propertyAccessor; + private readonly PropertyAccessor $propertyAccessor; - public function __construct(ChoiceListFactory $decoratedFactory, ?PropertyAccessor $propertyAccessor = null) - { - $this->decoratedFactory = $decoratedFactory; - $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); + public function __construct( + private readonly ChoiceListFactory $decoratedFactory, + ?PropertyAccessor $propertyAccessor = null, + ) { + $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); } public function getDecoratedFactory(): ChoiceListFactory @@ -139,7 +139,7 @@ public function createView(ChoiceList $list, $preferredChoices = null, $label = $preferredChoices = static function ($choice) use ($accessor, $preferredChoices) { try { return $accessor->getValue($choice, $preferredChoices); - } catch (UnexpectedTypeException $e) { + } catch (UnexpectedTypeException) { // Assume not preferred if not readable return false; } @@ -162,7 +162,7 @@ public function createView(ChoiceList $list, $preferredChoices = null, $label = $groupBy = static function ($choice) use ($accessor, $groupBy) { try { return $accessor->getValue($choice, $groupBy); - } catch (UnexpectedTypeException $e) { + } catch (UnexpectedTypeException) { // Don't group if path is not readable } }; diff --git a/lib/Core/Extension/Core/ChoiceList/LazyChoiceList.php b/lib/Core/Extension/Core/ChoiceList/LazyChoiceList.php index dc5fd29a..0286cb57 100644 --- a/lib/Core/Extension/Core/ChoiceList/LazyChoiceList.php +++ b/lib/Core/Extension/Core/ChoiceList/LazyChoiceList.php @@ -29,11 +29,6 @@ */ final class LazyChoiceList implements ChoiceList { - /** - * @var ChoiceLoader - */ - private $loader; - /** * The callable creating string values for each choice. * @@ -41,7 +36,7 @@ final class LazyChoiceList implements ChoiceList * * @var callable|null */ - private $value; + private mixed $value; /** * Optionally, a callable can be passed for generating the choice values. @@ -52,9 +47,10 @@ final class LazyChoiceList implements ChoiceList * @param callable|null $value The callable generating the choice * values */ - public function __construct(ChoiceLoader $loader, ?callable $value = null) - { - $this->loader = $loader; + public function __construct( + private readonly ChoiceLoader $loader, + ?callable $value = null, + ) { $this->value = $value; } diff --git a/lib/Core/Extension/Core/ChoiceList/Loader/CallbackChoiceLoader.php b/lib/Core/Extension/Core/ChoiceList/Loader/CallbackChoiceLoader.php index 493d9b28..a6ac3894 100644 --- a/lib/Core/Extension/Core/ChoiceList/Loader/CallbackChoiceLoader.php +++ b/lib/Core/Extension/Core/ChoiceList/Loader/CallbackChoiceLoader.php @@ -26,25 +26,19 @@ class CallbackChoiceLoader implements ChoiceLoader { use ChoiceLoaderTrait; - /** - * @var callable - */ - private $callback; - - /** - * @var bool - */ - private $valuesAreConstant; + /** @var callable */ + private mixed $callback; /** * @param callable $callback The callable returning an array of choices * @param bool $valuesAreConstant Indicate whether values are constant * (not dependent of there position) */ - public function __construct(callable $callback, bool $valuesAreConstant = false) - { + public function __construct( + callable $callback, + private bool $valuesAreConstant = false, + ) { $this->callback = $callback; - $this->valuesAreConstant = $valuesAreConstant; } public function loadChoiceList(?callable $value = null): ChoiceList @@ -53,7 +47,7 @@ public function loadChoiceList(?callable $value = null): ChoiceList return $this->choiceList; } - return $this->choiceList = new ArrayChoiceList(\call_user_func($this->callback), $value); + return $this->choiceList = new ArrayChoiceList(($this->callback)(), $value); } public function isValuesConstant(): bool diff --git a/lib/Core/Extension/Core/ChoiceList/View/ChoiceGroupView.php b/lib/Core/Extension/Core/ChoiceList/View/ChoiceGroupView.php index ccd073fc..78358971 100644 --- a/lib/Core/Extension/Core/ChoiceList/View/ChoiceGroupView.php +++ b/lib/Core/Extension/Core/ChoiceList/View/ChoiceGroupView.php @@ -20,15 +20,10 @@ */ class ChoiceGroupView implements \IteratorAggregate { - /** - * @var string - */ - public $label; + public string $label; - /** - * @var ChoiceGroupView[]|ChoiceView[] - */ - public $choices; + /** @var ChoiceGroupView[]|ChoiceView[] */ + public array $choices; /** * @param string $label The label of the group diff --git a/lib/Core/Extension/Core/ChoiceList/View/ChoiceListView.php b/lib/Core/Extension/Core/ChoiceList/View/ChoiceListView.php index f1bec4d0..cb5a0663 100644 --- a/lib/Core/Extension/Core/ChoiceList/View/ChoiceListView.php +++ b/lib/Core/Extension/Core/ChoiceList/View/ChoiceListView.php @@ -25,37 +25,27 @@ */ class ChoiceListView { - /** - * The choices. - * - * @var ChoiceGroupView[]|ChoiceView[] - */ - public $choices; + /** @var ChoiceGroupView[]|ChoiceView[] */ + public array $choices = []; - /** - * The preferred choices. - * - * @var ChoiceGroupView[]|ChoiceView[] - */ - public $preferredChoices; + /** @var ChoiceGroupView[]|ChoiceView[] */ + public array $preferredChoices = []; /** * All the choices (without grouping). * * @var ChoiceView[]|null */ - public $choicesByLabel; + public ?array $choicesByLabel = null; /** * Label by the choice-value. * * @var string[] */ - public $labelsByValue; + public array $labelsByValue = []; /** - * Creates a new choice list view. - * * @param ChoiceGroupView[]|ChoiceView[] $choices The choice views * @param ChoiceGroupView[]|ChoiceView[] $preferredChoices the preferred * choice views diff --git a/lib/Core/Extension/Core/ChoiceList/View/ChoiceView.php b/lib/Core/Extension/Core/ChoiceList/View/ChoiceView.php index ddcee645..c596b020 100644 --- a/lib/Core/Extension/Core/ChoiceList/View/ChoiceView.php +++ b/lib/Core/Extension/Core/ChoiceList/View/ChoiceView.php @@ -21,42 +21,16 @@ class ChoiceView { /** - * The label displayed to humans. - * - * @var string + * @param mixed $data The original choice + * @param string $value The view representation of the choice + * @param string $label The label displayed to humans + * @param array $attr Additional attributes for the HTML tag */ - public $label; - - /** - * The view representation of the choice. - * - * @var string - */ - public $value; - - /** - * The original choice value. - */ - public $data; - - /** - * Additional attributes for the HTML tag. - * - * @var array - */ - public $attr; - - /** - * @param mixed $data The original choice - * @param string $value The view representation of the choice - * @param string $label The label displayed to humans - * @param array $attr Additional attributes for the HTML tag - */ - public function __construct($data, string $value, string $label, array $attr = []) - { - $this->data = $data; - $this->value = $value; - $this->label = $label; - $this->attr = $attr; + public function __construct( + public mixed $data, + public string $value, + public string $label, + public array $attr = [], + ) { } } diff --git a/lib/Core/Extension/Core/DataTransformer/BaseDateTimeTransformer.php b/lib/Core/Extension/Core/DataTransformer/BaseDateTimeTransformer.php index 0841287c..9a488342 100644 --- a/lib/Core/Extension/Core/DataTransformer/BaseDateTimeTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/BaseDateTimeTransformer.php @@ -22,7 +22,7 @@ */ abstract class BaseDateTimeTransformer implements DataTransformer { - /** @var array */ + /** @var int[] */ protected static $formats = [ \IntlDateFormatter::NONE, \IntlDateFormatter::FULL, @@ -31,11 +31,8 @@ abstract class BaseDateTimeTransformer implements DataTransformer \IntlDateFormatter::SHORT, ]; - /** @var string */ - protected $inputTimezone; - - /** @var string */ - protected $outputTimezone; + protected string $inputTimezone; + protected string $outputTimezone; /** * @throws InvalidArgumentException if a timezone is not valid diff --git a/lib/Core/Extension/Core/DataTransformer/BirthdayTransformer.php b/lib/Core/Extension/Core/DataTransformer/BirthdayTransformer.php index f7444096..c166b384 100644 --- a/lib/Core/Extension/Core/DataTransformer/BirthdayTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/BirthdayTransformer.php @@ -22,26 +22,11 @@ */ final class BirthdayTransformer implements DataTransformer { - /** - * @var DataTransformer - */ - private $transformer; - - /** - * @var bool - */ - private $allowAge; - - /** - * @var bool - */ - private $allowFutureDate; - - public function __construct(DataTransformer $transformer, bool $allowAge = true, bool $allowFutureDate = false) - { - $this->transformer = $transformer; - $this->allowFutureDate = $allowFutureDate; - $this->allowAge = $allowAge; + public function __construct( + private readonly DataTransformer $transformer, + private readonly bool $allowAge = true, + private readonly bool $allowFutureDate = false, + ) { } public function transform($value) @@ -84,7 +69,7 @@ public function reverseTransform($value) private function transformWhenInteger($value) { - if (ctype_digit($value)) { + if (ctype_digit((string) $value)) { return (int) $value; } diff --git a/lib/Core/Extension/Core/DataTransformer/BooleanToLocalizedValueTransformer.php b/lib/Core/Extension/Core/DataTransformer/BooleanToLocalizedValueTransformer.php index 8c31f05d..d7ce80e3 100644 --- a/lib/Core/Extension/Core/DataTransformer/BooleanToLocalizedValueTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/BooleanToLocalizedValueTransformer.php @@ -18,11 +18,15 @@ final class BooleanToLocalizedValueTransformer implements DataTransformer { + /** + * @param array $trueValues + * @param array $falseValues + */ public function __construct( - private string $trueLabel = 'yes', - private string $falseLabel = 'no', - private array $trueValues = ['true', '1', 1, 'on', 'yes'], - private array $falseValues = ['false', '0', 0, 'off', 'no'], + private readonly string $trueLabel = 'yes', + private readonly string $falseLabel = 'no', + private readonly array $trueValues = ['true', '1', 1, 'on', 'yes'], + private readonly array $falseValues = ['false', '0', 0, 'off', 'no'], ) { } diff --git a/lib/Core/Extension/Core/DataTransformer/BooleanToNormValueTransformer.php b/lib/Core/Extension/Core/DataTransformer/BooleanToNormValueTransformer.php index 95fd3e70..7d48c8aa 100644 --- a/lib/Core/Extension/Core/DataTransformer/BooleanToNormValueTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/BooleanToNormValueTransformer.php @@ -18,8 +18,10 @@ final class BooleanToNormValueTransformer implements DataTransformer { - public function __construct(private string $trueValue = 'true', private string $falseValue = 'false') - { + public function __construct( + private readonly string $trueValue = 'true', + private readonly string $falseValue = 'false', + ) { } public function transform(mixed $value): mixed diff --git a/lib/Core/Extension/Core/DataTransformer/ChoiceToLabelTransformer.php b/lib/Core/Extension/Core/DataTransformer/ChoiceToLabelTransformer.php index 0ad60730..8bfabd37 100644 --- a/lib/Core/Extension/Core/DataTransformer/ChoiceToLabelTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/ChoiceToLabelTransformer.php @@ -23,13 +23,10 @@ */ final class ChoiceToLabelTransformer implements DataTransformer { - private $choiceList; - private $choiceListView; - - public function __construct(ChoiceList $choiceList, ChoiceListView $choiceListView) - { - $this->choiceList = $choiceList; - $this->choiceListView = $choiceListView; + public function __construct( + private readonly ChoiceList $choiceList, + private readonly ChoiceListView $choiceListView, + ) { } public function transform($choice) diff --git a/lib/Core/Extension/Core/DataTransformer/ChoiceToValueTransformer.php b/lib/Core/Extension/Core/DataTransformer/ChoiceToValueTransformer.php index 3da19a4e..8a705cc4 100644 --- a/lib/Core/Extension/Core/DataTransformer/ChoiceToValueTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/ChoiceToValueTransformer.php @@ -22,21 +22,19 @@ */ final class ChoiceToValueTransformer implements DataTransformer { - private $choiceList; - - public function __construct(ChoiceList $choiceList) - { - $this->choiceList = $choiceList; + public function __construct( + private readonly ChoiceList $choiceList, + ) { } - public function transform($choice) + public function transform(mixed $value): mixed { - $value = $this->choiceList->getValuesForChoices([$choice]); + $value = $this->choiceList->getValuesForChoices([$value]); return (string) current($value); } - public function reverseTransform($value) + public function reverseTransform(mixed $value): mixed { if ($value !== null && ! \is_string($value)) { throw new TransformationFailedException('Expected a string or null.'); @@ -46,7 +44,7 @@ public function reverseTransform($value) if (\count($choices) !== 1) { if ($value === null || $value === '') { - return; + return null; } throw new TransformationFailedException(\sprintf('The choice "%s" does not exist or is not unique', $value)); diff --git a/lib/Core/Extension/Core/DataTransformer/DateIntervalTransformer.php b/lib/Core/Extension/Core/DataTransformer/DateIntervalTransformer.php index 30d83070..bc653edb 100644 --- a/lib/Core/Extension/Core/DataTransformer/DateIntervalTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/DateIntervalTransformer.php @@ -21,15 +21,12 @@ final class DateIntervalTransformer implements DataTransformer { - /** @var string */ - private $fromLocale; + private string $toLocale; - /** @var string */ - private $toLocale; - - public function __construct(string $fromLocale, ?string $toLocale = null) - { - $this->fromLocale = $fromLocale; + public function __construct( + private readonly string $fromLocale, + ?string $toLocale = null, + ) { $this->toLocale = $toLocale ?? $fromLocale; } @@ -59,7 +56,7 @@ public function transform($value): string /** * @param string $value */ - public function reverseTransform($value): ?CarbonInterval + public function reverseTransform(mixed $value): mixed { if (! \is_scalar($value)) { throw new TransformationFailedException('Expected a scalar.'); @@ -97,7 +94,7 @@ private function translateNumberWords(string $timeString): string $messages = $translations[$this->fromLocale]; foreach (['year', 'month', 'week', 'day', 'hour', 'minute', 'second'] as $item) { - foreach (explode('|', $messages[$item]) as $idx => $messagePart) { + foreach (explode('|', (string) $messages[$item]) as $idx => $messagePart) { if (preg_match('/[:%](count|time)/', $messagePart)) { continue; } @@ -123,6 +120,6 @@ private static function cleanWordFromTranslationString(string $word): string $word = strtr($word, ['’' => "'"]); $word = preg_replace('/({\d+(,(\d+|Inf))?}|[\[\]]\d+(,(\d+|Inf))?[\[\]])/', '', $word); - return trim($word); + return mb_trim((string) $word); } } diff --git a/lib/Core/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/lib/Core/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php index e6ce5bde..e939ab2d 100644 --- a/lib/Core/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php @@ -22,45 +22,54 @@ * * @author Bernhard Schussek * @author Florian Eckerstorfer + * + * @psalm-type DateFormat \IntlDateFormatter::NONE|\IntlDateFormatter::FULL|\IntlDateFormatter::LONG|\IntlDateFormatter::MEDIUM|\IntlDateFormatter::SHORT + * @psalm-type CalendarType \IntlDateFormatter::GREGORIAN|\IntlDateFormatter::TRADITIONAL|\IntlCalendar */ final class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer { - private $dateFormat; - private $timeFormat; - private $pattern; - private $calendar; + private int $dateFormat; + private int $timeFormat; + private int | \IntlCalendar $calendar; /** - * @see BaseDateTimeTransformer::formats for available format options - * * @param int $calendar One of the \IntlDateFormatter calendar constants * @param string $pattern A pattern to pass to \IntlDateFormatter * + * @psalm-param DateFormat $dateFormat + * @psalm-param DateFormat $timeFormat + * @psalm-param CalendarType $calendar + * * @throws UnexpectedTypeException If a format is not supported or if a timezone is not a string */ - public function __construct(?string $inputTimezone = null, ?string $outputTimezone = null, ?int $dateFormat = null, ?int $timeFormat = null, int|\IntlCalendar $calendar = \IntlDateFormatter::GREGORIAN, ?string $pattern = null) - { + public function __construct( + ?string $inputTimezone = null, + ?string $outputTimezone = null, + ?int $dateFormat = null, + ?int $timeFormat = null, + int | \IntlCalendar $calendar = \IntlDateFormatter::GREGORIAN, + private readonly ?string $pattern = null, + ) { parent::__construct($inputTimezone, $outputTimezone); $dateFormat ??= \IntlDateFormatter::MEDIUM; $timeFormat ??= \IntlDateFormatter::SHORT; - if (!\in_array($dateFormat, self::$formats, true)) { + if (! \in_array($dateFormat, self::$formats, true)) { throw new UnexpectedTypeException($dateFormat, implode('", "', self::$formats)); } - if (!\in_array($timeFormat, self::$formats, true)) { + if (! \in_array($timeFormat, self::$formats, true)) { throw new UnexpectedTypeException($timeFormat, implode('", "', self::$formats)); } - if (\is_int($calendar) && !\in_array($calendar, [\IntlDateFormatter::GREGORIAN, \IntlDateFormatter::TRADITIONAL], true)) { + if (\is_int($calendar) && ! \in_array($calendar, [\IntlDateFormatter::GREGORIAN, \IntlDateFormatter::TRADITIONAL], true)) { throw new InvalidArgumentException('The "calendar" option should be either an \IntlDateFormatter constant or an \IntlCalendar instance.'); } $this->dateFormat = $dateFormat; $this->timeFormat = $timeFormat; $this->calendar = $calendar; - $this->pattern = $pattern; } /** @@ -171,7 +180,7 @@ public function reverseTransform(mixed $value): ?\DateTimeImmutable * * @throws TransformationFailedException in case the date formatter can not be constructed */ - protected function getIntlDateFormatter(bool $ignoreTimezone = false, bool $ignoreStrict = false): \IntlDateFormatter + private function getIntlDateFormatter(bool $ignoreTimezone = false, bool $ignoreStrict = false): \IntlDateFormatter { $dateFormat = $this->dateFormat; $timeFormat = $this->timeFormat; @@ -192,7 +201,7 @@ protected function getIntlDateFormatter(bool $ignoreTimezone = false, bool $igno return $intlDateFormatter; } - protected function isPatternDateOnly(): bool + private function isPatternDateOnly(): bool { if ($this->pattern === null) { return false; @@ -202,6 +211,6 @@ protected function isPatternDateOnly(): bool $pattern = preg_replace("#'(.*?)'#", '', $this->pattern); // check for the absence of time-related placeholders - return preg_match('#[ahHkKmsSAzZOvVxX]#', $pattern) === 0; + return preg_match('#[ahHkKmsSAzZOvVxX]#', (string) $pattern) === 0; } } diff --git a/lib/Core/Extension/Core/DataTransformer/DateTimeToStringTransformer.php b/lib/Core/Extension/Core/DataTransformer/DateTimeToStringTransformer.php index 5ee52602..fdde9130 100644 --- a/lib/Core/Extension/Core/DataTransformer/DateTimeToStringTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/DateTimeToStringTransformer.php @@ -23,7 +23,7 @@ */ final class DateTimeToStringTransformer extends BaseDateTimeTransformer { - private string $generateFormat; + private readonly string $generateFormat; /** * Format used for parsing strings. diff --git a/lib/Core/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php b/lib/Core/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php index b9f40e86..234b7f58 100644 --- a/lib/Core/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformer.php @@ -24,8 +24,8 @@ final class IntegerToLocalizedStringTransformer extends NumberToLocalizedStringTransformer { /** - * @param bool $grouping Whether thousands should be grouped - * @param int $roundingMode One of the ROUND_ constants in this class + * @param bool $grouping Whether thousands should be grouped + * @param self::ROUND_* $roundingMode One of the ROUND_ constants in this class */ public function __construct(?bool $grouping = false, ?int $roundingMode = self::ROUND_DOWN) { @@ -48,7 +48,7 @@ public function reverseTransform($value): ?int /** * @internal */ - protected function castParsedValue($value) + protected function castParsedValue(float | int $value): float | int { return $value; } diff --git a/lib/Core/Extension/Core/DataTransformer/IntegerToStringTransformer.php b/lib/Core/Extension/Core/DataTransformer/IntegerToStringTransformer.php index 6f728377..cd981d34 100644 --- a/lib/Core/Extension/Core/DataTransformer/IntegerToStringTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/IntegerToStringTransformer.php @@ -22,11 +22,17 @@ final class IntegerToStringTransformer extends NumberToStringTransformer { /** - * @param int|null $roundingMode One of the ROUND_ constants in this class + * @param self::ROUND_* $roundingMode */ - public function __construct(?int $roundingMode = null, bool $grouping = false) + public function __construct(?int $roundingMode = self::ROUND_DOWN, bool $grouping = false) { - parent::__construct(0, $grouping, $roundingMode ?? self::ROUND_DOWN); + if ($roundingMode === null) { + trigger_deprecation('rollerworks/search', '2.0-BETA13', 'Passing null as the first argument to "%s()" is deprecated, pass IntegerToStringTransformer::ROUND_DOWN instead. This will fail 3.0', __CLASS__); + + $roundingMode = self::ROUND_DOWN; + } + + parent::__construct(0, $grouping, $roundingMode); } public function reverseTransform($value): ?int diff --git a/lib/Core/Extension/Core/DataTransformer/LocalizedBirthdayTransformer.php b/lib/Core/Extension/Core/DataTransformer/LocalizedBirthdayTransformer.php index 2c450ac7..a898dadc 100644 --- a/lib/Core/Extension/Core/DataTransformer/LocalizedBirthdayTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/LocalizedBirthdayTransformer.php @@ -22,15 +22,11 @@ */ final class LocalizedBirthdayTransformer implements DataTransformer { - private $dateTransformer; - private $allowAge; - private $allowFutureDate; - - public function __construct(DataTransformer $dateTransformer, bool $allowAge = true, bool $allowFutureDate = false) - { - $this->dateTransformer = $dateTransformer; - $this->allowFutureDate = $allowFutureDate; - $this->allowAge = $allowAge; + public function __construct( + private readonly DataTransformer $dateTransformer, + private readonly bool $allowAge = true, + private readonly bool $allowFutureDate = false, + ) { } public function transform($value) diff --git a/lib/Core/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php b/lib/Core/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php index 66da8d3d..580582cf 100644 --- a/lib/Core/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformer.php @@ -14,8 +14,6 @@ namespace Rollerworks\Component\Search\Extension\Core\DataTransformer; use Money\Currencies\ISOCurrencies; -use Money\Currency; -use Money\Exception\ParserException; use Money\Formatter\IntlMoneyFormatter; use Money\Parser\IntlMoneyParser; use Rollerworks\Component\Search\Exception\TransformationFailedException; @@ -28,14 +26,14 @@ */ final class MoneyToLocalizedStringTransformer extends NumberToLocalizedStringTransformer { - private $defaultCurrency; - + /** @var array> ['locale' => ['currency' => ['pattern', 'symbol']] */ private static $patterns = []; - public function __construct(string $defaultCurrency, bool $grouping = false) - { - $this->defaultCurrency = $defaultCurrency; - $this->grouping = $grouping; + public function __construct( + private readonly string $defaultCurrency, + bool $grouping = false, + ) { + parent::__construct(null, $grouping); } /** @@ -76,7 +74,7 @@ public function transform($value): string * @throws TransformationFailedException if the given value is not a string * or if the value can not be transformed */ - public function reverseTransform($value): ?MoneyValue + public function reverseTransform(mixed $value): ?MoneyValue { if ($value === null || $value === '') { return null; @@ -125,8 +123,6 @@ public function reverseTransform($value): ?MoneyValue $money = (new IntlMoneyParser($formatter, new ISOCurrencies()))->parse($value); return new MoneyValue($money, $withCurrency); - } catch (ParserException $e) { - throw new TransformationFailedException($e->getMessage(), 0, $e); } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), 0, $e); } @@ -197,6 +193,6 @@ private function removeCurrencySymbol(string $value, string $currency): string $this->addCurrencySymbol('123', $currency); } - return preg_replace('#(\s?' . preg_quote(self::$patterns[$locale][$currency][1], '#') . '\s?)#u', '', $value); + return preg_replace('#(\s?' . preg_quote((string) self::$patterns[$locale][$currency][1], '#') . '\s?)#u', '', $value); } } diff --git a/lib/Core/Extension/Core/DataTransformer/MoneyToStringTransformer.php b/lib/Core/Extension/Core/DataTransformer/MoneyToStringTransformer.php index 5b71f691..78fc4a06 100644 --- a/lib/Core/Extension/Core/DataTransformer/MoneyToStringTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/MoneyToStringTransformer.php @@ -30,9 +30,9 @@ */ final class MoneyToStringTransformer implements DataTransformer { - private $defaultCurrency; - private $moneyParser; - private $formatter; + private Currency $defaultCurrency; + private DecimalMoneyParser $moneyParser; + private DecimalMoneyFormatter $formatter; public function __construct(string $defaultCurrency) { @@ -47,7 +47,7 @@ public function __construct(string $defaultCurrency) * @throws TransformationFailedException If the given value is not numeric or * if the value can not be transformed */ - public function transform($value): ?string + public function transform($value): string { if ($value === null) { return ''; @@ -57,7 +57,7 @@ public function transform($value): ?string throw new TransformationFailedException('Expected a MoneyValue object.'); } - if (! $this->formatter) { + if (! isset($this->formatter)) { $this->formatter = new DecimalMoneyFormatter(new ISOCurrencies()); } @@ -104,13 +104,13 @@ public function reverseTransform($value): ?MoneyValue $currency = $this->defaultCurrency; } - if (! $this->moneyParser) { + if (! isset($this->moneyParser)) { $this->moneyParser = new DecimalMoneyParser(new ISOCurrencies()); } try { return new MoneyValue($this->moneyParser->parse($result, $currency), $withCurrency); - } catch (ParserException|UnknownCurrencyException $e) { + } catch (ParserException | UnknownCurrencyException $e) { throw new TransformationFailedException($e->getMessage(), 0, $e); } } diff --git a/lib/Core/Extension/Core/DataTransformer/MultiTypeDataTransformer.php b/lib/Core/Extension/Core/DataTransformer/MultiTypeDataTransformer.php index 266f24b1..8d010f32 100644 --- a/lib/Core/Extension/Core/DataTransformer/MultiTypeDataTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/MultiTypeDataTransformer.php @@ -26,18 +26,15 @@ */ final class MultiTypeDataTransformer implements DataTransformer { - /** @var array */ - private $transformers; - /** - * @param array $transformers + * @param array $transformers */ - public function __construct(array $transformers) - { - $this->transformers = $transformers; + public function __construct( + private readonly array $transformers, + ) { } - public function transform($value) + public function transform(mixed $value): mixed { if ($value === null) { return ''; @@ -52,7 +49,7 @@ public function transform($value) return $this->transformers[$type]->transform($value); } - public function reverseTransform($value) + public function reverseTransform(mixed $value): mixed { $finalException = null; diff --git a/lib/Core/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php b/lib/Core/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php index 23281521..983db9bf 100644 --- a/lib/Core/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/NumberToLocalizedStringTransformer.php @@ -74,27 +74,26 @@ class NumberToLocalizedStringTransformer implements DataTransformer */ public const ROUND_HALF_DOWN = \NumberFormatter::ROUND_HALFDOWN; - protected $grouping; - - protected $roundingMode; - - private $scale; - private $locale; - - public function __construct(?int $scale = null, ?bool $grouping = false, ?int $roundingMode = self::ROUND_HALF_UP, ?string $locale = null) - { + /** + * @param self::ROUND_* $roundingMode + */ + public function __construct( + private readonly ?int $scale = null, + protected ?bool $grouping = false, + protected ?int $roundingMode = self::ROUND_HALF_UP, + protected readonly ?string $locale = null, + ) { if ($grouping === null) { - $grouping = false; + trigger_deprecation('rollerworks/search', '2.0-BETA13', 'Passing null as 2nd argument to "%s()" is deprecated, pass false instead. This will fail 3.0', __CLASS__); + + $this->grouping = false; } if ($roundingMode === null) { - $roundingMode = self::ROUND_HALF_UP; - } + trigger_deprecation('rollerworks/search', '2.0-BETA13', 'Passing null as 3nd argument to "%s()" is deprecated, pass NumberToLocalizedStringTransformer::ROUND_HALF_UP instead. This will fail 3.0', __CLASS__); - $this->scale = $scale; - $this->grouping = $grouping; - $this->roundingMode = $roundingMode; - $this->locale = $locale; + $this->roundingMode = self::ROUND_HALF_UP; + } } /** @@ -107,7 +106,7 @@ public function __construct(?int $scale = null, ?bool $grouping = false, ?int $r * @throws TransformationFailedException if the given value is not numeric * or if the value can not be transformed */ - public function transform($value): ?string + public function transform(mixed $value): ?string { if ($value === null) { return ''; @@ -138,7 +137,7 @@ public function transform($value): ?string * @throws TransformationFailedException if the given value is not a string * or if the value can not be transformed */ - public function reverseTransform($value) + public function reverseTransform(mixed $value): mixed { if (! \is_string($value)) { throw new TransformationFailedException('Expected a string.'); @@ -185,7 +184,9 @@ public function reverseTransform($value) $result = $this->castParsedValue($result); - if (false !== $encoding = mb_detect_encoding($value, null, true)) { + $encoding = mb_detect_encoding($value, null, true); + + if ($encoding !== false) { $length = mb_strlen($value, $encoding); $remainder = mb_substr($value, $position, $length, $encoding); } else { @@ -198,7 +199,7 @@ public function reverseTransform($value) if ($position < $length) { // Check if there are unrecognized characters at the end of the // number (excluding whitespace characters) - $remainder = trim($remainder, " \t\n\r\0\x0b\xc2\xa0"); + $remainder = mb_trim($remainder, " \t\n\r\0\x0b\xc2\xa0"); if ($remainder !== '') { throw new TransformationFailedException(\sprintf('The number contains unrecognized characters: "%s"', $remainder)); @@ -229,9 +230,11 @@ protected function getNumberFormatter(): \NumberFormatter /** * @internal */ - protected function castParsedValue($value) + protected function castParsedValue(float | int $value): float | int { - if (\is_int($value) && (($float = (float) $value) < \PHP_INT_MAX) && $value === (int) $float) { + $float = (float) $value; + + if (\is_int($value) && $float < \PHP_INT_MAX && $value === (int) $float) { return $float; } @@ -240,59 +243,58 @@ protected function castParsedValue($value) /** * Rounds a number according to the configured scale and rounding mode. - * - * @param float|int|string $number A number - * - * @return float|int The rounded number */ - protected function round($number) + protected function round(float | int | string $number): float | int | string { - if ($this->scale !== null && $this->roundingMode !== null) { - // shift number to maintain the correct scale during rounding - $roundingCoef = 10 ** $this->scale; - // string representation to avoid rounding errors, similar to bcmul() - $number = (float) (string) ($number * $roundingCoef); + if ($this->scale === null || $this->roundingMode === null) { + return $number; + } - switch ($this->roundingMode) { - case self::ROUND_CEILING: - $number = ceil($number); + // shift number to maintain the correct scale during rounding + $roundingCoef = 10 ** $this->scale; + // string representation to avoid rounding errors, similar to bcmul() + $number = (float) (string) ($number * $roundingCoef); - break; + switch ($this->roundingMode) { + case self::ROUND_CEILING: + $number = ceil($number); - case self::ROUND_FLOOR: - $number = floor($number); + break; - break; + case self::ROUND_FLOOR: + $number = floor($number); - case self::ROUND_UP: - $number = $number > 0 ? ceil($number) : floor($number); + break; - break; + case self::ROUND_UP: + $number = $number > 0 ? ceil($number) : floor($number); - case self::ROUND_DOWN: - $number = $number > 0 ? floor($number) : ceil($number); + break; - break; + case self::ROUND_DOWN: + $number = $number > 0 ? floor($number) : ceil($number); - case self::ROUND_HALF_EVEN: - $number = round($number, 0, \PHP_ROUND_HALF_EVEN); + break; - break; + case self::ROUND_HALF_EVEN: + $number = round($number, 0, \PHP_ROUND_HALF_EVEN); - case self::ROUND_HALF_UP: - $number = round($number, 0, \PHP_ROUND_HALF_UP); + break; - break; + case self::ROUND_HALF_UP: + $number = round($number, 0, \PHP_ROUND_HALF_UP); - case self::ROUND_HALF_DOWN: - $number = round($number, 0, \PHP_ROUND_HALF_DOWN); + break; - break; - } + case self::ROUND_HALF_DOWN: + $number = round($number, 0, \PHP_ROUND_HALF_DOWN); + + break; - $number /= $roundingCoef; + default: + throw new \Exception('Unknown rounding mode:' . $this->roundingMode); } - return $number; + return $number / $roundingCoef; } } diff --git a/lib/Core/Extension/Core/DataTransformer/NumberToStringTransformer.php b/lib/Core/Extension/Core/DataTransformer/NumberToStringTransformer.php index aa2e583f..a24369d5 100644 --- a/lib/Core/Extension/Core/DataTransformer/NumberToStringTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/NumberToStringTransformer.php @@ -57,7 +57,7 @@ public function transform($value): string * @throws TransformationFailedException If the given value is not a string * or if the value can not be transformed */ - public function reverseTransform($value) + public function reverseTransform(mixed $value): int | float | null { if (! \is_scalar($value)) { throw new TransformationFailedException('Expected a scalar.'); diff --git a/lib/Core/Extension/Core/DataTransformer/OrderToLocalizedTransformer.php b/lib/Core/Extension/Core/DataTransformer/OrderToLocalizedTransformer.php index 428f40d9..92c5b118 100644 --- a/lib/Core/Extension/Core/DataTransformer/OrderToLocalizedTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/OrderToLocalizedTransformer.php @@ -18,15 +18,15 @@ final class OrderToLocalizedTransformer implements DataTransformer { - private array $alias; - private array $viewLabel; - private string $case; - - public function __construct(array $alias, array $viewLabel, string $case = OrderTransformer::CASE_UPPERCASE) - { - $this->case = $case; - $this->alias = $alias; - $this->viewLabel = $viewLabel; + /** + * @param array $alias + * @param array $viewLabel + */ + public function __construct( + private array $alias, + private array $viewLabel, + private readonly string $case = OrderTransformer::CASE_UPPERCASE, + ) { } public function transform($value) @@ -64,7 +64,7 @@ public function reverseTransform($value) throw new TransformationFailedException('Expected a string or null.'); } - if ($value === '') { + if ($value === '' || $value === null) { return null; } @@ -90,7 +90,7 @@ public function reverseTransform($value) 0, null, 'This value is not a valid sorting direction. Accepted directions are: {{ directions }}.', - ['{{ directions }}' => array_unique(array_map('mb_strtolower', array_keys($this->alias)))] + ['{{ directions }}' => array_unique(array_map(mb_strtolower(...), array_keys($this->alias)))] ); } diff --git a/lib/Core/Extension/Core/DataTransformer/OrderTransformer.php b/lib/Core/Extension/Core/DataTransformer/OrderTransformer.php index 7eff41a7..ac6be766 100644 --- a/lib/Core/Extension/Core/DataTransformer/OrderTransformer.php +++ b/lib/Core/Extension/Core/DataTransformer/OrderTransformer.php @@ -25,19 +25,12 @@ final class OrderTransformer implements DataTransformer public const CASE_UPPERCASE = 'UPPERCASE'; /** - * @var array + * @param array $alias */ - private $alias; - - /** - * @var string - */ - private $case; - - public function __construct(array $alias, string $case = self::CASE_UPPERCASE) - { - $this->alias = $alias; - $this->case = $case; + public function __construct( + private array $alias, + private readonly string $case = self::CASE_UPPERCASE, + ) { } public function transform($value) @@ -50,11 +43,7 @@ public function transform($value) return ''; } - if (isset($this->alias[$value])) { - return $this->alias[$value]; - } - - return $value; + return $this->alias[$value] ?? $value; } public function reverseTransform($value) @@ -63,7 +52,7 @@ public function reverseTransform($value) throw new TransformationFailedException('Expected a string or null.'); } - if ($value === '') { + if ($value === '' || $value === null) { return null; } @@ -89,7 +78,7 @@ public function reverseTransform($value) 0, null, 'This value is not a valid sorting direction. Accepted directions are: {{ directions }}.', - ['{{ directions }}' => array_unique(array_map('mb_strtolower', array_keys($this->alias)))] + ['{{ directions }}' => array_unique(array_map(mb_strtolower(...), array_keys($this->alias)))] ); } diff --git a/lib/Core/Extension/Core/Model/MoneyValue.php b/lib/Core/Extension/Core/Model/MoneyValue.php index 48effa3b..a5d262ce 100644 --- a/lib/Core/Extension/Core/Model/MoneyValue.php +++ b/lib/Core/Extension/Core/Model/MoneyValue.php @@ -20,23 +20,13 @@ */ class MoneyValue { - /** - * @var Money - */ - public $value; - - /** - * @var bool - */ - public $withCurrency; - /** * @param bool $withCurrency indicate the input was provided with a currency. * This is only used for exporting */ - public function __construct(Money $value, bool $withCurrency = true) - { - $this->withCurrency = $withCurrency; - $this->value = $value; + public function __construct( + public readonly Money $value, + public readonly bool $withCurrency = true, + ) { } } diff --git a/lib/Core/Extension/Core/Type/BirthdayType.php b/lib/Core/Extension/Core/Type/BirthdayType.php index b5c6ee1c..0b262c4a 100644 --- a/lib/Core/Extension/Core/Type/BirthdayType.php +++ b/lib/Core/Extension/Core/Type/BirthdayType.php @@ -21,6 +21,7 @@ use Rollerworks\Component\Search\Field\SearchFieldView; use Rollerworks\Component\Search\Value\Compare; use Rollerworks\Component\Search\Value\Range; +use Rollerworks\Component\Search\ValueComparator; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -29,7 +30,7 @@ */ final class BirthdayType extends AbstractFieldType { - private $valueComparator; + private ValueComparator $valueComparator; public function __construct() { diff --git a/lib/Core/Extension/Core/Type/BooleanType.php b/lib/Core/Extension/Core/Type/BooleanType.php index 9354caf2..3f5ff94a 100644 --- a/lib/Core/Extension/Core/Type/BooleanType.php +++ b/lib/Core/Extension/Core/Type/BooleanType.php @@ -26,7 +26,7 @@ public function configureOptions(OptionsResolver $resolver): void { $resolver->setDefault('invalid_message', 'This value is not a valid boolean.'); - // Symfony 6.4 compatiblity + // Symfony 6.4 compatibility if (! method_exists($resolver, 'setOptions')) { $resolver->setDefault('view_label', static function (OptionsResolver $options): void { $options->setDefaults([ @@ -90,8 +90,6 @@ public function configureOptions(OptionsResolver $resolver): void $options->setAllowedTypes('true', ['scalar[]']); $options->setAllowedTypes('false', ['scalar[]']); }); - - } public function buildType(FieldConfig $config, array $options): void diff --git a/lib/Core/Extension/Core/Type/ChoiceType.php b/lib/Core/Extension/Core/Type/ChoiceType.php index 2cee7771..b2d09394 100644 --- a/lib/Core/Extension/Core/Type/ChoiceType.php +++ b/lib/Core/Extension/Core/Type/ChoiceType.php @@ -34,11 +34,10 @@ */ final class ChoiceType extends AbstractFieldType { - private $choiceListFactory; - - public function __construct(?ChoiceListFactory $choiceListFactory = null) - { - $this->choiceListFactory = $choiceListFactory ?? new CachingFactoryDecorator( + public function __construct( + private ?ChoiceListFactory $choiceListFactory = null, + ) { + $this->choiceListFactory ??= new CachingFactoryDecorator( new PropertyAccessDecorator( new DefaultChoiceListFactory() ) diff --git a/lib/Core/Extension/Core/Type/DateTimeType.php b/lib/Core/Extension/Core/Type/DateTimeType.php index 04390809..5ee8dd48 100644 --- a/lib/Core/Extension/Core/Type/DateTimeType.php +++ b/lib/Core/Extension/Core/Type/DateTimeType.php @@ -35,11 +35,8 @@ final class DateTimeType extends BaseDateTimeType public const DEFAULT_DATE_FORMAT = \IntlDateFormatter::MEDIUM; public const DEFAULT_TIME_FORMAT = \IntlDateFormatter::MEDIUM; - /** @var DateTimeValueComparator */ - private $valueComparator; - - /** @var DateTimeIntervalValueComparator */ - private $valueComparatorInterval; + private readonly DateTimeValueComparator $valueComparator; + private readonly DateTimeIntervalValueComparator $valueComparatorInterval; public function __construct() { diff --git a/lib/Core/Extension/Core/Type/DateType.php b/lib/Core/Extension/Core/Type/DateType.php index 42efc427..185f44c0 100644 --- a/lib/Core/Extension/Core/Type/DateType.php +++ b/lib/Core/Extension/Core/Type/DateType.php @@ -28,10 +28,9 @@ final class DateType extends BaseDateTimeType { public const DEFAULT_FORMAT = \IntlDateFormatter::MEDIUM; - public const HTML5_FORMAT = 'yyyy-MM-dd'; - private $valueComparator; + private DateValueComparator $valueComparator; public function __construct() { diff --git a/lib/Core/Extension/Core/Type/IntegerType.php b/lib/Core/Extension/Core/Type/IntegerType.php index 4d43cfc1..4f6c086b 100644 --- a/lib/Core/Extension/Core/Type/IntegerType.php +++ b/lib/Core/Extension/Core/Type/IntegerType.php @@ -28,7 +28,7 @@ */ final class IntegerType extends AbstractFieldType { - private $valueComparator; + private NumberValueComparator $valueComparator; public function __construct() { diff --git a/lib/Core/Extension/Core/Type/MoneyType.php b/lib/Core/Extension/Core/Type/MoneyType.php index b7b6e0f5..3f6fd711 100644 --- a/lib/Core/Extension/Core/Type/MoneyType.php +++ b/lib/Core/Extension/Core/Type/MoneyType.php @@ -32,7 +32,7 @@ */ final class MoneyType extends AbstractFieldType { - private $valueComparator; + private MoneyValueComparator $valueComparator; public function __construct() { diff --git a/lib/Core/Extension/Core/Type/NumberType.php b/lib/Core/Extension/Core/Type/NumberType.php index 865b15b0..c2faa765 100644 --- a/lib/Core/Extension/Core/Type/NumberType.php +++ b/lib/Core/Extension/Core/Type/NumberType.php @@ -29,7 +29,7 @@ */ final class NumberType extends AbstractFieldType { - private $valueComparator; + private NumberValueComparator $valueComparator; public function __construct() { diff --git a/lib/Core/Extension/Core/Type/SearchFieldType.php b/lib/Core/Extension/Core/Type/SearchFieldType.php index fa661493..c26c6459 100644 --- a/lib/Core/Extension/Core/Type/SearchFieldType.php +++ b/lib/Core/Extension/Core/Type/SearchFieldType.php @@ -25,7 +25,7 @@ */ final class SearchFieldType extends AbstractFieldType { - private $valueComparator; + private SimpleValueComparator $valueComparator; public function __construct() { diff --git a/lib/Core/Extension/Core/Type/TimeType.php b/lib/Core/Extension/Core/Type/TimeType.php index 3e9f0b36..1d3755d5 100644 --- a/lib/Core/Extension/Core/Type/TimeType.php +++ b/lib/Core/Extension/Core/Type/TimeType.php @@ -28,7 +28,7 @@ */ final class TimeType extends AbstractFieldType { - private $valueComparator; + private DateTimeValueComparator $valueComparator; public function __construct() { diff --git a/lib/Core/Extension/Core/Type/TimestampType.php b/lib/Core/Extension/Core/Type/TimestampType.php index 4db4366c..1bfac197 100644 --- a/lib/Core/Extension/Core/Type/TimestampType.php +++ b/lib/Core/Extension/Core/Type/TimestampType.php @@ -27,7 +27,7 @@ */ final class TimestampType extends AbstractFieldType { - private $valueComparator; + private DateTimeValueComparator $valueComparator; public function __construct() { diff --git a/lib/Core/Extension/Core/ValueComparator/BirthdayValueComparator.php b/lib/Core/Extension/Core/ValueComparator/BirthdayValueComparator.php index e40dbc0b..5b394f95 100644 --- a/lib/Core/Extension/Core/ValueComparator/BirthdayValueComparator.php +++ b/lib/Core/Extension/Core/ValueComparator/BirthdayValueComparator.php @@ -24,7 +24,7 @@ final class BirthdayValueComparator implements ValueComparator * @param \DateTimeImmutable|int $higher * @param \DateTimeImmutable|int $lower */ - public function isHigher($higher, $lower, array $options): bool + public function isHigher(mixed $higher, mixed $lower, array $options): bool { if (! \is_object($higher) xor ! \is_object($lower)) { return false; @@ -37,7 +37,7 @@ public function isHigher($higher, $lower, array $options): bool * @param \DateTimeImmutable|int $lower * @param \DateTimeImmutable|int $higher */ - public function isLower($lower, $higher, array $options): bool + public function isLower(mixed $lower, mixed $higher, array $options): bool { if (! \is_object($higher) xor ! \is_object($lower)) { return false; @@ -50,7 +50,7 @@ public function isLower($lower, $higher, array $options): bool * @param \DateTimeImmutable|int $value * @param \DateTimeImmutable|int $nextValue */ - public function isEqual($value, $nextValue, array $options): bool + public function isEqual(mixed $value, mixed $nextValue, array $options): bool { if (! \is_object($value) xor ! \is_object($nextValue)) { return false; diff --git a/lib/Core/Extension/LazyExtension.php b/lib/Core/Extension/LazyExtension.php index d840c03c..10836baa 100644 --- a/lib/Core/Extension/LazyExtension.php +++ b/lib/Core/Extension/LazyExtension.php @@ -16,6 +16,7 @@ use Psr\Container\ContainerInterface; use Rollerworks\Component\Search\Exception\InvalidArgumentException; use Rollerworks\Component\Search\Field\FieldType; +use Rollerworks\Component\Search\Field\FieldTypeExtension; use Rollerworks\Component\Search\Loader\ClosureContainer; use Rollerworks\Component\Search\SearchExtension; @@ -26,27 +27,20 @@ */ final class LazyExtension implements SearchExtension { - private $typeContainer; - /** - * @var array[] + * @param array> $typeExtensionServices */ - private $typeExtensionServices = []; - - /** - * @param array[] $typeExtensions - */ - public function __construct(ContainerInterface $typeContainer, array $typeExtensions) - { - $this->typeContainer = $typeContainer; - $this->typeExtensionServices = $typeExtensions; + public function __construct( + private readonly ContainerInterface $typeContainer, + private array $typeExtensionServices, + ) { } /** * Creates a new LazyExtension with easy factories for lazy loading. * - * @param array $types FQCN => \Closure factory - * @param array[] $typeExtensions + * @param array $types FQCN => \Closure factory + * @param array> $typeExtensions */ public static function create(array $types, array $typeExtensions = []): self { @@ -73,20 +67,19 @@ public function getTypeExtensions(string $name): array { $extensions = []; - if (isset($this->typeExtensionServices[$name])) { - foreach ($this->typeExtensionServices[$name] as $extensionId => $extension) { - $extensions[] = $extension; + foreach ($this->typeExtensionServices[$name] ?? [] as $extensionId => $extension) { + $extensions[] = $extension; - // validate result of getExtendedType() to ensure it is consistent with the service definition - if ($extension->getExtendedType() !== $name) { - throw new InvalidArgumentException( - \sprintf('The extended type specified for the service "%s" does not match the actual extended type. Expected "%s", given "%s".', - $extensionId, - $name, - $extension->getExtendedType() - ) - ); - } + // validate result of getExtendedType() to ensure it is consistent with the service definition + if ($extension->getExtendedType() !== $name) { + throw new InvalidArgumentException( + \sprintf( + 'The extended type specified for the service "%s" does not match the actual extended type. Expected "%s", given "%s".', + $extensionId, + $name, + $extension->getExtendedType() + ) + ); } } diff --git a/lib/Core/Field/FieldConfig.php b/lib/Core/Field/FieldConfig.php index 1cbd600f..f66b610a 100644 --- a/lib/Core/Field/FieldConfig.php +++ b/lib/Core/Field/FieldConfig.php @@ -15,11 +15,10 @@ use Rollerworks\Component\Search\DataTransformer; use Rollerworks\Component\Search\FieldSetView; +use Rollerworks\Component\Search\Value\ValueHolder; use Rollerworks\Component\Search\ValueComparator; /** - * The configuration of a SearchField. - * * @author Sebastiaan Stok * * @method void finalizeConfig() @@ -36,38 +35,39 @@ public function getType(): ResolvedFieldType; /** * Returns whether value-type $type is accepted by the field. * - * $type must be a FQCN of a class implementing - * {@link \Rollerworks\Component\Search\Value\ValueHolder}. + * @param class-string $type */ public function supportValueType(string $type): bool; /** * Sets whether value-type $type is accepted by the field. * - * $type must be a FQCN of a class implementing - * {@link \Rollerworks\Component\Search\Value\ValueHolder}. + * @param class-string $type */ public function setValueTypeSupport(string $type, bool $enabled); /** - * Set the {@link ValueComparator} instance for validation. + * Set a {@link ValueComparator} used for validating specific value types. + * + * The range and compare value-types require a ValueComparator to be set. */ public function setValueComparator(ValueComparator $comparator); - /** - * Returns the configured {@link ValueComparator} instance. - */ public function getValueComparator(): ?ValueComparator; /** * Sets a view transformer for the field. * * * The reverseTransform method of the transformer is used to convert - * data from the model to the view format. + * data from the model format to the view format. * * * The transform method of the transformer is used to convert from the * view to the model format. * + * The view format is a user-friendly representation, mostly localized + * depending on the configuration of the field. For a date field, + * the view format is for example either 'd-m-Y' or 'm-d-Y', depending on the locale. + * * @param DataTransformer|null $viewTransformer Use null to remove the transformer */ public function setViewTransformer(?DataTransformer $viewTransformer = null); @@ -81,30 +81,38 @@ public function getViewTransformer(): ?DataTransformer; * Sets a normalize transformer for the field. * * * The transform method of the transformer is used to convert data from the - * normalized to the model format. + * normalized format to the model format. * * * The reverseTransform method of the transformer is used to convert from the - * model to the normalized format. + * model format to the normalized format. + * + * The normalized format is a normalized representation of the data, + * used for input provided from a query-string or API. This format is always + * the same regardless of the locale. + * + * For a date field, the normalized format is for example always in + * the 'Y-m-D' format. * * @param DataTransformer|null $viewTransformer Use null to remove the transformer */ public function setNormTransformer(?DataTransformer $viewTransformer = null); /** - * Returns the normalize transformer of the field. + * Returns the normalized-value transformer of the field. */ public function getNormTransformer(): ?DataTransformer; /** * Returns whether the field's data is locked. * - * A field with locked data is restricted to the data passed in - * this configuration. + * A locked field is not allowed to be modified. */ public function isConfigLocked(): bool; /** * Returns all options passed during the construction of the field. + * + * @return array */ public function getOptions(): array; @@ -113,7 +121,7 @@ public function hasOption(string $name): bool; /** * Returns the value of a specific option. * - * @param mixed|null $default + * @param mixed $default */ public function getOption(string $name, $default = null); @@ -124,30 +132,29 @@ public function createView(FieldSetView $fieldSet): SearchFieldView; /** * Sets the value for an attribute. + * + * @param mixed $value */ public function setAttribute(string $name, $value); /** - * Sets the attributes. + * @param array $attributes */ public function setAttributes(array $attributes); /** * Returns additional attributes of the field. * - * @return array An array of key-value combinations + * @return array */ public function getAttributes(): array; - /** - * Returns whether the attribute with the given name exists. - */ public function hasAttribute(string $name): bool; /** - * Returns the value of the given attribute. - * * @param mixed $default The value returned if the attribute does not exist + * + * @return mixed */ public function getAttribute(string $name, $default = null); } diff --git a/lib/Core/Field/GenericResolvedFieldType.php b/lib/Core/Field/GenericResolvedFieldType.php index 7674cac3..2d90be48 100644 --- a/lib/Core/Field/GenericResolvedFieldType.php +++ b/lib/Core/Field/GenericResolvedFieldType.php @@ -26,32 +26,23 @@ */ class GenericResolvedFieldType implements ResolvedFieldType { - /** @var FieldType */ - private $innerType; - - /** @var FieldTypeExtension[] */ - private $typeExtensions; - - /** @var ResolvedFieldType|null */ - private $parent; - - /** @var OptionsResolver */ - private $optionsResolver; + private ?OptionsResolver $optionsResolver = null; /** + * @param FieldTypeExtension[] $typeExtensions + * * @throws UnexpectedTypeException When at least one of the given extensions is not an FieldTypeExtension */ - public function __construct(FieldType $innerType, array $typeExtensions = [], ?ResolvedFieldType $parent = null) - { + public function __construct( + private readonly FieldType $innerType, + private readonly array $typeExtensions = [], + private readonly ?ResolvedFieldType $parent = null, + ) { foreach ($typeExtensions as $extension) { if (! $extension instanceof FieldTypeExtension) { throw new UnexpectedTypeException($extension, FieldTypeExtension::class); } } - - $this->innerType = $innerType; - $this->typeExtensions = $typeExtensions; - $this->parent = $parent; } public function getParent(): ?ResolvedFieldType @@ -126,18 +117,15 @@ public function getBlockPrefix(): string public function getOptionsResolver(): OptionsResolver { - if ($this->optionsResolver === null) { - if ($this->parent !== null) { - $this->optionsResolver = clone $this->parent->getOptionsResolver(); - } else { - $this->optionsResolver = new OptionsResolver(); - } + if ($this->optionsResolver !== null) { + return $this->optionsResolver; + } - $this->innerType->configureOptions($this->optionsResolver); + $this->optionsResolver = $this->parent !== null ? clone $this->parent->getOptionsResolver() : new OptionsResolver(); + $this->innerType->configureOptions($this->optionsResolver); - foreach ($this->typeExtensions as $extension) { - $extension->configureOptions($this->optionsResolver); - } + foreach ($this->typeExtensions as $extension) { + $extension->configureOptions($this->optionsResolver); } return $this->optionsResolver; @@ -150,11 +138,7 @@ public function getOptionsResolver(): OptionsResolver */ protected function newField($name, array $options): FieldConfig { - if (OrderField::isOrder($name)) { - return new OrderField($name, $this, $options); - } - - return new SearchField($name, $this, $options); + return OrderField::isOrder($name) ? new OrderField($name, $this, $options) : new SearchField($name, $this, $options); } /** diff --git a/lib/Core/Field/GenericTypeRegistry.php b/lib/Core/Field/GenericTypeRegistry.php index ffaeea69..4b440c0e 100644 --- a/lib/Core/Field/GenericTypeRegistry.php +++ b/lib/Core/Field/GenericTypeRegistry.php @@ -23,61 +23,51 @@ */ final class GenericTypeRegistry implements TypeRegistry { - /** - * @var SearchExtension[] - */ - private array $extensions = []; - - /** - * @var ResolvedFieldType[] - */ + /** @var array */ private array $types = []; - private ResolvedFieldTypeFactory $resolvedTypeFactory; - /** * @param SearchExtension[] $extensions * * @throws UnexpectedTypeException if an extension does not implement SearchExtension */ - public function __construct(array $extensions, ResolvedFieldTypeFactory $resolvedTypeFactory) - { + public function __construct( + private readonly array $extensions, + private readonly ResolvedFieldTypeFactory $resolvedTypeFactory, + ) { foreach ($extensions as $extension) { if (! $extension instanceof SearchExtension) { throw new UnexpectedTypeException($extension, SearchExtension::class); } } - - $this->extensions = $extensions; - $this->resolvedTypeFactory = $resolvedTypeFactory; } public function getType(string $name): ResolvedFieldType { - if (! isset($this->types[$name])) { - $type = null; + if (isset($this->types[$name])) { + return $this->types[$name]; + } - foreach ($this->extensions as $extension) { - if ($extension->hasType($name)) { - $type = $extension->getType($name); + $type = null; - break; - } - } + foreach ($this->extensions as $extension) { + if ($extension->hasType($name)) { + $type = $extension->getType($name); - if (! $type) { - // Support fully-qualified class names. - if (! class_exists($name) || ! \in_array(FieldType::class, class_implements($name), true)) { - throw new InvalidArgumentException(\sprintf('Could not load type "%s"', $name)); - } + break; + } + } - $type = new $name(); + if (! $type) { + // Support fully-qualified class names. + if (! class_exists($name) || ! \in_array(FieldType::class, class_implements($name), true)) { + throw new InvalidArgumentException(\sprintf('Could not load type "%s".', $name)); } - $this->types[$name] = $this->resolveType($type); + $type = new $name(); } - return $this->types[$name]; + return $this->types[$name] = $this->resolveType($type); } public function hasType(string $name): bool @@ -103,12 +93,10 @@ public function getExtensions(): array private function resolveType(FieldType $type): ResolvedFieldType { $parentType = $type->getParent(); - $fqcn = $type::class; - $typeExtensions = []; foreach ($this->extensions as $extension) { - $typeExtensions[] = $extension->getTypeExtensions($fqcn); + $typeExtensions[] = $extension->getTypeExtensions($type::class); } return $this->resolvedTypeFactory->createResolvedType( diff --git a/lib/Core/Field/OrderField.php b/lib/Core/Field/OrderField.php index b317589d..3856994a 100644 --- a/lib/Core/Field/OrderField.php +++ b/lib/Core/Field/OrderField.php @@ -24,41 +24,22 @@ */ final class OrderField implements FieldConfig { - /** - * @var string - */ - private $name; - - /** - * @var ResolvedFieldType - */ - private $type; - - /** - * @var array - */ - private $options; - - /** - * @var DataTransformer|null - */ - private $viewTransformer; + private ?DataTransformer $normTransformer = null; + private ?DataTransformer $viewTransformer = null; - /** - * @var DataTransformer|null - */ - private $normTransformer; - - /** - * @var array - */ - private $attributes = []; + /** @var array */ + private array $attributes = []; /** + * @param array $options + * * @throws \InvalidArgumentException When the name is invalid */ - public function __construct(string $name, ResolvedFieldType $type, array $options = []) - { + public function __construct( + private readonly string $name, + private readonly ResolvedFieldType $type, + private readonly array $options = [], + ) { if (! preg_match('/^@_?[a-zA-Z][a-zA-Z0-9_\-]*$/D', $name)) { throw new InvalidArgumentException( \sprintf( @@ -68,10 +49,6 @@ public function __construct(string $name, ResolvedFieldType $type, array $option ) ); } - - $this->name = $name; - $this->type = $type; - $this->options = $options; } public static function isOrder(string $name): bool @@ -94,14 +71,14 @@ public function supportValueType(string $type): bool return false; } - public function setValueTypeSupport(string $type, bool $enabled): void + public function setValueTypeSupport(string $type, bool $enabled): never { throw new BadMethodCallException( 'OrderField does not support supporting custom value types' ); } - public function setValueComparator(ValueComparator $comparator): void + public function setValueComparator(ValueComparator $comparator): never { throw new BadMethodCallException( 'OrderField does not support supporting custom value comparator' @@ -113,9 +90,9 @@ public function getValueComparator(): ?ValueComparator return null; } - public function setViewTransformer(?DataTransformer $transformer = null): void + public function setViewTransformer(?DataTransformer $viewTransformer = null): void { - $this->viewTransformer = $transformer; + $this->viewTransformer = $viewTransformer; } public function getViewTransformer(): ?DataTransformer @@ -123,9 +100,9 @@ public function getViewTransformer(): ?DataTransformer return $this->viewTransformer; } - public function setNormTransformer(?DataTransformer $transformer = null): void + public function setNormTransformer(?DataTransformer $viewTransformer = null): void { - $this->normTransformer = $transformer; + $this->normTransformer = $viewTransformer; } public function getNormTransformer(): ?DataTransformer @@ -166,7 +143,7 @@ public function createView(FieldSetView $fieldSet): SearchFieldView return $view; } - public function setAttribute(string $name, $value) + public function setAttribute(string $name, mixed $value) { $this->attributes[$name] = $value; @@ -190,7 +167,7 @@ public function hasAttribute(string $name): bool return \array_key_exists($name, $this->attributes); } - public function getAttribute(string $name, $default = null) + public function getAttribute(string $name, mixed $default = null): mixed { return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; } diff --git a/lib/Core/Field/OrderFieldType.php b/lib/Core/Field/OrderFieldType.php index ca9aff51..8fe26a92 100644 --- a/lib/Core/Field/OrderFieldType.php +++ b/lib/Core/Field/OrderFieldType.php @@ -54,7 +54,8 @@ public function configureOptions(OptionsResolver $resolver): void // Ensure view-labels are part of the alias list. $resolver->addNormalizer('alias', static function (Options $options, array $value): mixed { // Must always exist for interoperability, but it's still possible to overwrite. - $value = array_merge($value, + $value = array_merge( + $value, $options['case'] === OrderTransformer::CASE_LOWERCASE ? ['asc' => 'ASC', 'desc' => 'DESC'] : ['ASC' => 'ASC', 'DESC' => 'DESC'] ); diff --git a/lib/Core/Field/ResolvedFieldType.php b/lib/Core/Field/ResolvedFieldType.php index 53f196e0..d57c1610 100644 --- a/lib/Core/Field/ResolvedFieldType.php +++ b/lib/Core/Field/ResolvedFieldType.php @@ -35,6 +35,9 @@ public function getInnerType(): FieldType; */ public function getTypeExtensions(): array; + /** + * @param array $options + */ public function createField(string $name, array $options = []): FieldConfig; /** @@ -52,6 +55,8 @@ public function createFieldView(FieldConfig $config, FieldSetView $view): Search /** * Configures a SearchFieldView for the type hierarchy. + * + * @param array $options */ public function buildFieldView(SearchFieldView $view, FieldConfig $config, array $options): void; diff --git a/lib/Core/Field/SearchField.php b/lib/Core/Field/SearchField.php index 83ea8602..a29f953a 100644 --- a/lib/Core/Field/SearchField.php +++ b/lib/Core/Field/SearchField.php @@ -19,65 +19,35 @@ use Rollerworks\Component\Search\Exception\InvalidConfigurationException; use Rollerworks\Component\Search\FieldSetView; use Rollerworks\Component\Search\Value\RequiresComparatorValueHolder; +use Rollerworks\Component\Search\Value\ValueHolder; use Rollerworks\Component\Search\ValueComparator; /** - * SearchField. - * * @author Sebastiaan Stok */ class SearchField implements FieldConfig { - /** - * @var string - */ - private $name; - - /** - * @var ResolvedFieldType - */ - private $type; - - /** - * @var array - */ - private $options; - - /** - * @var array - */ - private $attributes = []; - - /** - * @var bool[] - */ - private $supportedValueTypes = []; - - /** - * @var ValueComparator - */ - private $valueComparator; - - /** - * @var bool - */ - private $locked = false; + /** @var array */ + private array $attributes = []; - /** - * @var DataTransformer|null - */ - private $viewTransformer; + /** @var array, bool> */ + private array $supportedValueTypes = []; - /** - * @var DataTransformer|null - */ - private $normTransformer; + private ?ValueComparator $valueComparator = null; + private ?DataTransformer $viewTransformer = null; + private ?DataTransformer $normTransformer = null; + private bool $locked = false; /** - * @throws \InvalidArgumentException When the name is invalid + * @param array $options + * + * @throws \InvalidArgumentException When the name contains illegal characters */ - public function __construct(string $name, ResolvedFieldType $type, array $options = []) - { + public function __construct( + private readonly string $name, + private readonly ResolvedFieldType $type, + private readonly array $options = [], + ) { if (! preg_match('/^_?[a-zA-Z][a-zA-Z0-9_\-]*$/D', $name)) { throw new InvalidArgumentException( \sprintf( @@ -87,10 +57,6 @@ public function __construct(string $name, ResolvedFieldType $type, array $option ) ); } - - $this->name = $name; - $this->type = $type; - $this->options = $options; } public function supportValueType(string $type): bool @@ -99,7 +65,7 @@ public function supportValueType(string $type): bool } /** - * @throws BadMethodCallException + * @throws BadMethodCallException when the setter is called after the config is locked */ public function setValueTypeSupport(string $type, bool $enabled) { @@ -124,6 +90,9 @@ public function getType(): ResolvedFieldType return $this->type; } + /** + * @throws BadMethodCallException when the setter is called after the config is locked + */ public function setValueComparator(ValueComparator $comparator) { if ($this->locked) { @@ -142,6 +111,9 @@ public function getValueComparator(): ?ValueComparator return $this->valueComparator; } + /** + * @throws BadMethodCallException when the setter is called after the config is locked + */ public function setViewTransformer(?DataTransformer $viewTransformer = null) { if ($this->locked) { @@ -160,6 +132,9 @@ public function getViewTransformer(): ?DataTransformer return $this->viewTransformer; } + /** + * @throws BadMethodCallException when the setter is called after the config is locked + */ public function setNormTransformer(?DataTransformer $viewTransformer = null) { if ($this->locked) { @@ -200,7 +175,7 @@ public function finalizeConfig(): void 'Supported value-type "%s" requires a value comparator but none is set for field "%s" with type "%s".', $type, $this->getName(), - \get_class($this->getType()->getInnerType()) + $this->getType()->getInnerType()::class ) ); } @@ -234,6 +209,9 @@ public function getOption(string $name, $default = null) return $default; } + /** + * @throws BadMethodCallException when the configuration is not locked + */ public function createView(FieldSetView $fieldSet): SearchFieldView { if (! $this->locked) { @@ -249,7 +227,10 @@ public function createView(FieldSetView $fieldSet): SearchFieldView return $view; } - public function setAttribute(string $name, $value) + /** + * @throws BadMethodCallException when the setter is called after the config is locked + */ + public function setAttribute(string $name, mixed $value) { if ($this->locked) { throw new BadMethodCallException( @@ -262,6 +243,9 @@ public function setAttribute(string $name, $value) return $this; } + /** + * @throws BadMethodCallException when the setter is called after the config is locked + */ public function setAttributes(array $attributes) { if ($this->locked) { @@ -285,7 +269,7 @@ public function hasAttribute(string $name): bool return \array_key_exists($name, $this->attributes); } - public function getAttribute(string $name, $default = null) + public function getAttribute(string $name, mixed $default = null): mixed { return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; } diff --git a/lib/Core/Field/SearchFieldView.php b/lib/Core/Field/SearchFieldView.php index 407513da..5fb1ae2a 100644 --- a/lib/Core/Field/SearchFieldView.php +++ b/lib/Core/Field/SearchFieldView.php @@ -23,19 +23,14 @@ class SearchFieldView /** * The variables assigned to this view. * - * @var array + * @var array */ - public $vars = [ + public array $vars = [ 'attr' => [], ]; - /** - * @var FieldSetView - */ - public $fieldSet; - - public function __construct(FieldSetView $fieldSet) - { - $this->fieldSet = $fieldSet; + public function __construct( + public FieldSetView $fieldSet, + ) { } } diff --git a/lib/Core/FieldSet.php b/lib/Core/FieldSet.php index 78bc9d99..86fc6f05 100644 --- a/lib/Core/FieldSet.php +++ b/lib/Core/FieldSet.php @@ -24,21 +24,21 @@ interface FieldSet { /** - * Returns the name of the set. + * Returns the name of the set (can be empty). */ public function getSetName(): ?string; /** * Returns the field as a {@link FieldConfig} instance. * - * @throws UnknownFieldException When the field is not registered at this Fieldset + * @throws UnknownFieldException When the field is not registered at this FieldSet */ public function get(string $name): FieldConfig; /** * Returns all the registered fields in the set. * - * @return FieldConfig[] [name] => {FieldConfig instance}) + * @return array */ public function all(): array; diff --git a/lib/Core/FieldSetBuilder.php b/lib/Core/FieldSetBuilder.php index f4d2ca41..fa50741e 100644 --- a/lib/Core/FieldSetBuilder.php +++ b/lib/Core/FieldSetBuilder.php @@ -22,21 +22,21 @@ interface FieldSetBuilder { /** - * @param string $name Name of search field - * @param string $type The FQCN of the type - * @param array $options Array of options for building the field + * @param string $name Name of search field + * @param string $type The FQCN of the type + * @param array $options Array of options for building the field * - * @return static The builder + * @return $this */ public function add(string $name, string $type, array $options = []); /** - * @return static The builder + * @return $this */ public function set(FieldConfig $field); /** - * @return static The builder + * @return $this * * @throws BadMethodCallException When the FieldSet has been already turned into a FieldSet instance */ diff --git a/lib/Core/FieldSetView.php b/lib/Core/FieldSetView.php index 90085e26..6b0dc3b8 100644 --- a/lib/Core/FieldSetView.php +++ b/lib/Core/FieldSetView.php @@ -20,17 +20,11 @@ */ class FieldSetView { - /** - * The variables assigned to this view. - * - * @var array - */ - public $vars = [ + /** The variables assigned to this view. */ + public array $vars = [ 'attr' => [], ]; - /** - * @var SearchFieldView[] - */ - public $fields; + /** @var SearchFieldView[] */ + public array $fields; } diff --git a/lib/Core/GenericFieldSet.php b/lib/Core/GenericFieldSet.php index 694188cc..3c3dc86e 100644 --- a/lib/Core/GenericFieldSet.php +++ b/lib/Core/GenericFieldSet.php @@ -24,21 +24,19 @@ */ final class GenericFieldSet implements FieldSetWithView { - private $fields = []; - private $name; - private $viewBuilder; + /** @var callable|null */ + private mixed $viewBuilder; /** - * Constructor. - * - * @param FieldConfig[] $fields - * @param string|null $name FQCN of the FieldSet configurator - * @param callable $viewBuilder A callable to finalize the FieldSetView + * @param array $fields + * @param string|null $name FQCN of the FieldSet configurator + * @param callable $viewBuilder A callable to finalize the FieldSetView */ - public function __construct(array $fields, ?string $name = null, ?callable $viewBuilder = null) - { - $this->fields = $fields; - $this->name = $name; + public function __construct( + private readonly array $fields, + private readonly ?string $name = null, + ?callable $viewBuilder = null, + ) { $this->viewBuilder = $viewBuilder; } @@ -85,7 +83,7 @@ public function createView(): FieldSetView } if ($this->viewBuilder !== null) { - \call_user_func($this->viewBuilder, $view); + ($this->viewBuilder)($view); } return $view; diff --git a/lib/Core/GenericFieldSetBuilder.php b/lib/Core/GenericFieldSetBuilder.php index cd077441..d9e0fc3f 100644 --- a/lib/Core/GenericFieldSetBuilder.php +++ b/lib/Core/GenericFieldSetBuilder.php @@ -23,34 +23,25 @@ */ final class GenericFieldSetBuilder implements FieldSetBuilder { - /** - * @var FieldConfig[] - */ - private $fields = []; - - /** - * @var array[] - */ - private $unresolvedFields = []; - - /** - * @var SearchFactory - */ - private $searchFactory; - - public function __construct(SearchFactory $searchFactory) - { - $this->searchFactory = $searchFactory; + /** @var array */ + private array $fields = []; + + /** @var array */ + private array $unresolvedFields = []; + + public function __construct( + private readonly SearchFactory $searchFactory, + ) { } - public function set(FieldConfig $field) + public function set(FieldConfig $field): self { $this->fields[$field->getName()] = $field; return $this; } - public function add(string $name, string $type, array $options = []) + public function add(string $name, string $type, array $options = []): self { $this->unresolvedFields[$name] = [ 'type' => $type, @@ -60,7 +51,7 @@ public function add(string $name, string $type, array $options = []) return $this; } - public function remove(string $name) + public function remove(string $name): self { unset($this->fields[$name], $this->unresolvedFields[$name]); @@ -69,15 +60,7 @@ public function remove(string $name) public function has(string $name): bool { - if (isset($this->unresolvedFields[$name])) { - return true; - } - - if (isset($this->fields[$name])) { - return true; - } - - return false; + return isset($this->unresolvedFields[$name]) || isset($this->fields[$name]); } public function get(string $name): FieldConfig @@ -99,18 +82,18 @@ public function get(string $name): FieldConfig throw new InvalidArgumentException(\sprintf('The field with the name "%s" does not exist.', $name)); } - public function getFieldSet(?string $setName = null): FieldSet + public function getFieldSet(?string $name = null): FieldSet { - foreach ($this->unresolvedFields as $name => $field) { - $this->fields[$name] = $this->searchFactory->createField( - $name, - $field['type'], - $field['options'] + foreach ($this->unresolvedFields as $fieldName => $config) { + $this->fields[$fieldName] = $this->searchFactory->createField( + $fieldName, + $config['type'], + $config['options'] ); - unset($this->unresolvedFields[$name]); + unset($this->unresolvedFields[$fieldName]); } - return new GenericFieldSet($this->fields, $setName); + return new GenericFieldSet($this->fields, $name); } } diff --git a/lib/Core/GenericSearchFactory.php b/lib/Core/GenericSearchFactory.php index 79617718..8369a6b5 100644 --- a/lib/Core/GenericSearchFactory.php +++ b/lib/Core/GenericSearchFactory.php @@ -23,15 +23,12 @@ */ final class GenericSearchFactory implements SearchFactory { - private $registry; - private $fieldSetRegistry; - private $serializer; - - public function __construct(TypeRegistry $registry, FieldSetRegistry $fieldSetRegistry) - { - $this->registry = $registry; - $this->fieldSetRegistry = $fieldSetRegistry; - $this->serializer = new SearchConditionSerializer($this); + public function __construct( + private readonly TypeRegistry $registry, + private readonly FieldSetRegistry $fieldSetRegistry, + private ?SearchConditionSerializer $serializer = null, + ) { + $this->serializer ??= new SearchConditionSerializer($this); } public function createFieldSet($configurator): FieldSet @@ -62,6 +59,11 @@ public function createField(string $name, string $type, array $options = []): Fi return $field; } + /** + * @param array{type: string, type_options?: array} $options + * + * @return array + */ private function createOptionsForOrderField(string $name, array $options): array { $type = $this->registry->getType($options['type']); diff --git a/lib/Core/Input/AbstractInput.php b/lib/Core/Input/AbstractInput.php index 5f0998bd..8dfc78d6 100644 --- a/lib/Core/Input/AbstractInput.php +++ b/lib/Core/Input/AbstractInput.php @@ -13,10 +13,7 @@ namespace Rollerworks\Component\Search\Input; -use Rollerworks\Component\Search\ConditionErrorMessage; use Rollerworks\Component\Search\ErrorList; -use Rollerworks\Component\Search\Exception\GroupsNestingException; -use Rollerworks\Component\Search\Exception\GroupsOverflowException; use Rollerworks\Component\Search\InputProcessor; /** @@ -26,54 +23,18 @@ */ abstract class AbstractInput implements InputProcessor { - /** - * @var ProcessorConfig - */ - protected $config; - - /** - * Error messages. - * - * Must be an ErrorList to allow passing by reference - * in the ConditionStructure(ByView)Builder. - * - * @var ConditionErrorMessage[]|ErrorList - */ - protected $errors; + protected ProcessorConfig $config; + protected Validator $validator; + protected ErrorList $errors; - /** - * Current nesting level. - * - * @var int - */ - protected $level = 0; - - /** - * @var Validator - */ - protected $validator; + /** Current nesting level. */ + protected int $level = 0; public function __construct(?Validator $validator = null) { $this->validator = $validator ?? new NullValidator(); } - protected function validateGroupNesting(string $path): void - { - if ($this->level > $this->config->getMaxNestingLevel()) { - throw new GroupsNestingException( - $this->config->getMaxNestingLevel(), $path - ); - } - } - - protected function validateGroupsCount(int $count, string $path): void - { - if ($count > $this->config->getMaxGroups()) { - throw new GroupsOverflowException($this->config->getMaxGroups(), $path); - } - } - /** * This method is called after processing and helps with finding bugs. */ diff --git a/lib/Core/Input/CachingInputProcessor.php b/lib/Core/Input/CachingInputProcessor.php index 74ed083b..25f69249 100644 --- a/lib/Core/Input/CachingInputProcessor.php +++ b/lib/Core/Input/CachingInputProcessor.php @@ -22,7 +22,7 @@ /** * Caches the SearchCondition in a PSR-16 (SimpleCache) storage. * - * Caching a processed input provides the most benefit for a really big + * Caching a processed input provides the most benefit for a large * search condition. Most conditions can be processed with easy, and the * overhead of caching is not worth it. * @@ -33,53 +33,54 @@ */ final class CachingInputProcessor implements InputProcessor { - private $conditionSerializer; - private $inputProcessor; - private $cache; - private $ttl; + private \DateInterval | int | null $ttl; /** * @param \DateInterval|string|int|null $ttl The default Time to life for caches. If no value is sent and * the driver supports TTL then the library may set a default value - * for it or let the driver take care of that. Null means no expiration + * for it or let the driver take care of that. Null means no expiration. + * A string is interpreted as a DateInterval. */ - public function __construct(CacheInterface $cache, SearchConditionSerializer $conditionSerializer, InputProcessor $inputProcessor, \DateInterval|string|int|null $ttl = null) - { + public function __construct( + private readonly CacheInterface $cache, + private readonly SearchConditionSerializer $conditionSerializer, + private readonly InputProcessor $inputProcessor, + \DateInterval | string | int | null $ttl = null, + ) { if (\is_string($ttl)) { $ttl = new \DateInterval($ttl); } - $this->conditionSerializer = $conditionSerializer; - $this->inputProcessor = $inputProcessor; - $this->cache = $cache; $this->ttl = $ttl; } public function process(ProcessorConfig $config, $input): SearchCondition { - if (\is_string($input)) { - $cacheKey = $this->getConditionCacheKey($config, $input); + $ttl = $config->getCacheTTL() ?? $this->ttl; + + if ($ttl === null || $ttl === 0 || ! \is_string($input)) { + return $this->inputProcessor->process($config, $input); + } - try { - return $this->conditionSerializer->unserialize($this->cache->get($cacheKey, [])); - } catch (InvalidArgumentException $e) { - // No-op - } + $cacheKey = $this->getConditionCacheKey($config, $input); - $result = $this->inputProcessor->process($config, $input); + try { + return $this->conditionSerializer->unserialize($this->cache->get($cacheKey, [])); + } catch (InvalidArgumentException) { + // No-op + } - if (! $result->isEmpty()) { - $this->cache->set($cacheKey, $this->conditionSerializer->serialize($result), $config->getCacheTTL() ?? $this->ttl); - } + $result = $this->inputProcessor->process($config, $input); - return $result; + if (! $result->isEmpty()) { + $this->cache->set($cacheKey, $this->conditionSerializer->serialize($result), $ttl); } - return $this->inputProcessor->process($config, $input); + return $result; } private function getConditionCacheKey(ProcessorConfig $config, string $input): string { - return hash('sha256', $config->getFieldSet()->getSetName() . '~' . $input . '~' . \get_class($this->inputProcessor)); + return hash('sha256', $config->getFieldSet()->getSetName() . '~' . $input . '~' . $this->inputProcessor::class); } } diff --git a/lib/Core/Input/ConditionStructureBuilder.php b/lib/Core/Input/ConditionStructureBuilder.php index ed4be96b..99e89577 100644 --- a/lib/Core/Input/ConditionStructureBuilder.php +++ b/lib/Core/Input/ConditionStructureBuilder.php @@ -40,68 +40,45 @@ */ class ConditionStructureBuilder implements StructureBuilder { - /** @var ErrorList */ - private $errorList; + private readonly FieldSet $fieldSet; + private readonly int $maxCount; + private readonly int $maxNesting; + private readonly int $maxGroups; - /** @var Validator */ - private $validator; + /** @var array */ + private array $checkedValueType = []; - /** @var FieldSet */ - private $fieldSet; + private int $valuesCount = 0; + private int $nestingLevel = 0; - /** @var int */ - private $maxCount; - - /** @var int */ - private $maxNesting; - - /** @var int */ - private $maxGroups; - - /** @var array */ - private $checkedValueType = []; - - /** @var int */ - private $valuesCount = 0; - - /** @var int */ - private $nestingLevel = 0; - - /** @var array */ - private $path = []; + /** @var string[] */ + private array $path = []; /** * Group count per nesting level. * - * @var array + * @var array [group-level => group-count] */ - private $groupsCount = []; + private array $groupsCount = []; - /** @var FieldConfig|null */ - protected $fieldConfig; + protected ?FieldConfig $fieldConfig = null; /** @var ValuesGroup[] */ - private $valuesGroupLevels = []; + private array $valuesGroupLevels = []; - /** @var ValuesBag|null */ - private $valuesBag; + private ?ValuesBag $valuesBag = null; + protected DataTransformer | null $inputTransformer; - /** - * False when not set, null when undetected (lazy loaded). - * - * @var bool|DataTransformer|null - */ - protected $inputTransformer; - - public function __construct(ProcessorConfig $config, Validator $validator, ErrorList $errorList, string $path = '') - { - $this->validator = $validator; + public function __construct( + ProcessorConfig $config, + private readonly Validator $validator, + private ErrorList $errorList, + string $path = '', + ) { $this->fieldSet = $config->getFieldSet(); $this->maxCount = $config->getMaxValues(); $this->maxNesting = $config->getMaxNestingLevel(); $this->maxGroups = $config->getMaxGroups(); - - $this->errorList = $errorList; $this->valuesGroupLevels[0] = new ValuesGroup(); $this->path[] = $path; } @@ -111,7 +88,7 @@ public function getErrors(): ErrorList return $this->errorList; } - public function getCurrentPath() + public function getCurrentPath(): array { return $this->path; } @@ -167,13 +144,15 @@ public function field(string $name, string $path): void $this->fieldConfig = $this->fieldSet->get($name); $this->valuesBag = new ValuesBag(); + $this->initializeInputTransformer(); + $this->valuesGroupLevels[$this->nestingLevel]->addField($name, $this->valuesBag); $this->path[] = \sprintf($path, $name); $this->validator->initializeContext($this->fieldConfig, $this->errorList); } - public function simpleValue($value, string $path): void + public function simpleValue(mixed $value, string $path): void { $path = $this->createValuePath($path, 'simpleValue'); $this->increaseValuesCount($path); @@ -184,7 +163,7 @@ public function simpleValue($value, string $path): void $this->valuesBag->addSimpleValue($modelVal); } - public function excludedSimpleValue($value, string $path): void + public function excludedSimpleValue(mixed $value, string $path): void { $path = $this->createValuePath($path, 'excludedSimpleValue'); $this->increaseValuesCount($path); @@ -195,10 +174,7 @@ public function excludedSimpleValue($value, string $path): void $this->valuesBag->addExcludedSimpleValue($modelVal); } - /** - * @param array $path [path, lower-path-pattern, upper-path-pattern] - */ - public function rangeValue($lower, $upper, bool $lowerInclusive, bool $upperInclusive, array $path): void + public function rangeValue(mixed $lower, mixed $upper, bool $lowerInclusive, bool $upperInclusive, array $path): void { $path[0] = $this->createValuePath($path[0], Range::class); @@ -216,10 +192,7 @@ public function rangeValue($lower, $upper, bool $lowerInclusive, bool $upperIncl $this->valuesBag->add($range); } - /** - * @param array $path [path, lower-path-pattern, upper-path-pattern] - */ - public function excludedRangeValue($lower, $upper, bool $lowerInclusive, bool $upperInclusive, array $path): void + public function excludedRangeValue(mixed $lower, mixed $upper, bool $lowerInclusive, bool $upperInclusive, array $path): void { $path[0] = $this->createValuePath($path[0], ExcludedRange::class); @@ -237,11 +210,7 @@ public function excludedRangeValue($lower, $upper, bool $lowerInclusive, bool $u $this->valuesBag->add($range); } - /** - * @param mixed|string $operator - * @param array $path [base-path, operator-path, value-path] - */ - public function comparisonValue($operator, $value, array $path): void + public function comparisonValue(mixed $operator, mixed $value, array $path): void { $path[0] = $this->createValuePath($path[0], Compare::class); @@ -266,12 +235,7 @@ public function comparisonValue($operator, $value, array $path): void $this->valuesBag->add(new Compare($modelVal, $operator)); } - /** - * @param string $type - * @param string $value - * @param array $path [base-path, value-path, type-path] - */ - public function patterMatchValue($type, $value, bool $caseInsensitive, array $path): void + public function patterMatchValue(mixed $type, mixed $value, bool $caseInsensitive, array $path): void { $path[0] = $this->createValuePath($path[0], PatternMatch::class); $valid = true; @@ -325,57 +289,58 @@ public function endValues(): void array_pop($this->path); } + protected function initializeInputTransformer(): void + { + $this->inputTransformer = $this->fieldConfig->getNormTransformer(); + } + /** * Reverse transforms a value if a value transformer is set. * * @return mixed returns null when the value is empty or invalid. * Note: When the value is invalid an error is registered */ - protected function inputToNorm($value, string $path) + protected function inputToNorm(mixed $value, string $path): mixed { - if ($this->inputTransformer === null) { - $this->inputTransformer = $this->fieldConfig->getNormTransformer() ?? false; - } - - if ($this->inputTransformer === false) { - if ($value !== null && ! \is_scalar($value)) { - $e = new \RuntimeException( - \sprintf( - 'Norm value of type %s is not a scalar value or null and not cannot be ' . - 'converted to a string. You must set a NormTransformer for field "%s" with type "%s".', - \gettype($value), - $this->fieldConfig->getName(), - \get_class($this->fieldConfig->getType()->getInnerType()) - ) - ); - - $error = new ConditionErrorMessage( - $path, - $this->fieldConfig->getOption('invalid_message', $e->getMessage()), - $this->fieldConfig->getOption('invalid_message', $e->getMessage()), - $this->fieldConfig->getOption('invalid_message_parameters', []), - null, - $e - ); - - $this->addError($error); + if ($this->inputTransformer !== null) { + try { + return $this->inputTransformer->reverseTransform($value); + } catch (TransformationFailedException $e) { + $this->addError($this->transformationExceptionToError($e, $path)); return null; } - - return $value === '' ? null : $value; } - try { - return $this->inputTransformer->reverseTransform($value); - } catch (TransformationFailedException $e) { - $this->addError($this->transformationExceptionToError($e, $path)); + if ($value !== null && ! \is_scalar($value)) { + $e = new \RuntimeException( + \sprintf( + 'Norm value of type %s is not a scalar value or null and not cannot be ' . + 'converted to a string. You must set a NormTransformer for field "%s" with type "%s".', + \gettype($value), + $this->fieldConfig->getName(), + $this->fieldConfig->getType()->getInnerType()::class + ) + ); + + $error = new ConditionErrorMessage( + $path, + $this->fieldConfig->getOption('invalid_message', $e->getMessage()), + $this->fieldConfig->getOption('invalid_message', $e->getMessage()), + $this->fieldConfig->getOption('invalid_message_parameters', []), + null, + $e + ); + + $this->addError($error); return null; } + + return $value === '' ? null : $value; } - protected function transformationExceptionToError($e, string $path): ConditionErrorMessage + protected function transformationExceptionToError(TransformationFailedException $e, string $path): ConditionErrorMessage { $invalidMessage = $e->getInvalidMessage(); diff --git a/lib/Core/Input/ConditionStructureByViewBuilder.php b/lib/Core/Input/ConditionStructureByViewBuilder.php index 626e0fe3..91937467 100644 --- a/lib/Core/Input/ConditionStructureByViewBuilder.php +++ b/lib/Core/Input/ConditionStructureByViewBuilder.php @@ -13,55 +13,13 @@ namespace Rollerworks\Component\Search\Input; -use Rollerworks\Component\Search\ConditionErrorMessage; -use Rollerworks\Component\Search\Exception\TransformationFailedException; - /** * @author Sebastiaan Stok */ final class ConditionStructureByViewBuilder extends ConditionStructureBuilder { - protected function inputToNorm($value, string $path) + protected function initializeInputTransformer(): void { - if ($this->inputTransformer === null) { - $this->inputTransformer = $this->fieldConfig->getViewTransformer() ?? false; - } - - if (! $this->inputTransformer) { - if ($value !== null && ! \is_scalar($value)) { - $e = new \RuntimeException( - \sprintf( - 'View value of type %s is not a scalar value or null and not cannot be ' . - 'converted to a string. You must set a ViewTransformer for field "%s" with type "%s".', - \gettype($value), - $this->fieldConfig->getName(), - \get_class($this->fieldConfig->getType()->getInnerType()) - ) - ); - - $error = new ConditionErrorMessage( - $path, - $this->fieldConfig->getOption('invalid_message', $e->getMessage()), - $this->fieldConfig->getOption('invalid_message', $e->getMessage()), - $this->fieldConfig->getOption('invalid_message_parameters', []), - null, - $e - ); - - $this->addError($error); - - return null; - } - - return $value === '' ? null : $value; - } - - try { - return $this->inputTransformer->reverseTransform($value); - } catch (TransformationFailedException $e) { - $this->addError($this->transformationExceptionToError($e, $path)); - - return null; - } + $this->inputTransformer = $this->fieldConfig->getViewTransformer(); } } diff --git a/lib/Core/Input/ErrorPathTranslator.php b/lib/Core/Input/ErrorPathTranslator.php index 0a9b16e2..ad80ad64 100644 --- a/lib/Core/Input/ErrorPathTranslator.php +++ b/lib/Core/Input/ErrorPathTranslator.php @@ -34,8 +34,9 @@ */ class ErrorPathTranslator { - public function __construct(protected TranslatorInterface $translator) - { + public function __construct( + protected TranslatorInterface $translator, + ) { } public function translateFromQueryString(string $path): string @@ -152,25 +153,33 @@ protected static function getPos(array $part): int return ((int) self::getChunk($part)) + 1; } - /** @param array{0: string, 1: string} $part */ + /** + * @param array{0: string, 1: string} $part + */ protected static function getChunk(array $part): string { return $part[1]; } - /** @param array{0: string, 1: string} $part */ + /** + * @param array{0: string, 1: string} $part + */ protected static function isGroup(array $part): bool { return ctype_digit(self::getChunk($part)); } - /** @param array{0: string, 1: string} $part */ + /** + * @param array{0: string, 1: string} $part + */ protected static function isField(array $part): bool { return ! ctype_digit(self::getChunk($part)); } - /** @param array{0: string, 1: string} $part */ + /** + * @param array{0: string, 1: string} $part + */ protected static function getFieldName(array $part): string { return '"' . self::getChunk($part) . '"'; diff --git a/lib/Core/Input/JsonInput.php b/lib/Core/Input/JsonInput.php index cd270dd0..78342d3d 100644 --- a/lib/Core/Input/JsonInput.php +++ b/lib/Core/Input/JsonInput.php @@ -76,15 +76,8 @@ */ final class JsonInput extends AbstractInput { - /** - * @var StructureBuilder|null - */ - private $structureBuilder; - - /** - * @var StructureBuilder|null - */ - private $orderStructureBuilder; + private ?StructureBuilder $structureBuilder = null; + private ?StructureBuilder $orderStructureBuilder = null; public function process(ProcessorConfig $config, $input): SearchCondition { @@ -92,7 +85,7 @@ public function process(ProcessorConfig $config, $input): SearchCondition throw new UnexpectedTypeException($input, 'string'); } - $input = trim($input); + $input = mb_trim($input); $fieldSet = $config->getFieldSet(); @@ -150,7 +143,7 @@ private function processGroup(array $group): void { $this->processFields($group['fields'] ?? []); - foreach ($group['groups'] ?? [] as $index => $sub) { + foreach ($group['groups'] ?? [] as $sub) { $this->structureBuilder->enterGroup($sub['logical-case'] ?? ValuesGroup::GROUP_LOGICAL_AND, '[groups][%d]'); $this->processGroup($sub); $this->structureBuilder->leaveGroup(); @@ -228,7 +221,9 @@ private function processOrder(SearchCondition $condition, array $array, FieldSet if ($order === []) { /** @var FieldConfig $field */ foreach ($fieldSet->all() as $name => $field) { - if (OrderField::isOrder($name) && null !== $direction = $field->getOption('default')) { + $direction = $field->getOption('default'); + + if (OrderField::isOrder($name) && $direction !== null) { $this->orderStructureBuilder->field($name, '[order][%s]'); $this->orderStructureBuilder->simpleValue($direction, ''); $this->orderStructureBuilder->endValues(); @@ -257,9 +252,7 @@ private function processOrder(SearchCondition $condition, array $array, FieldSet private function assertValueArrayHasKeys($array, array $requiredKeys, string $path): void { if (! \is_array($array)) { - throw new InputProcessorException(implode('', $this->structureBuilder->getCurrentPath()) . $path, - \sprintf('Expected value-structure to be an array, got %s instead.', \gettype($array)) - ); + throw new InputProcessorException(implode('', $this->structureBuilder->getCurrentPath()) . $path, \sprintf('Expected value-structure to be an array, got %s instead.', \gettype($array))); } $missingKeys = []; @@ -271,7 +264,8 @@ private function assertValueArrayHasKeys($array, array $requiredKeys, string $pa } if ($missingKeys) { - throw new InputProcessorException(implode('', $this->structureBuilder->getCurrentPath()) . $path, + throw new InputProcessorException( + implode('', $this->structureBuilder->getCurrentPath()) . $path, \sprintf( 'Expected value-structure to contain the following keys: %s. ' . 'But the following keys are missing: %s.', diff --git a/lib/Core/Input/OrderStructureBuilder.php b/lib/Core/Input/OrderStructureBuilder.php index 1878209c..24c85086 100644 --- a/lib/Core/Input/OrderStructureBuilder.php +++ b/lib/Core/Input/OrderStructureBuilder.php @@ -29,44 +29,21 @@ */ final class OrderStructureBuilder implements StructureBuilder { - /** @var FieldSet */ - private $fieldSet; - - /** @var ErrorList */ - private $errorList; - - /** @var Validator */ - private $validator; - - /** @var ValuesGroup */ - private $valuesGroup; - - /** @var ValuesBag|null */ - private $valuesBag; - - /** @var FieldConfig|null */ - private $fieldConfig; - - /** @var string */ - private $path; - - /** - * False when not set, null when undetected (lazy loaded). - * - * @var bool|DataTransformer|null - */ - private $inputTransformer; - - private bool $viewFormat; - - public function __construct(ProcessorConfig $config, Validator $validator, ErrorList $errorList, string $path = '', bool $viewFormat = false) - { + private readonly FieldSet $fieldSet; + private readonly ValuesGroup $valuesGroup; + private ?FieldConfig $fieldConfig = null; + private ?ValuesBag $valuesBag = null; + private ?DataTransformer $inputTransformer; + + public function __construct( + ProcessorConfig $config, + private readonly Validator $validator, + private ErrorList $errorList, + private readonly string $path = 'order', + private readonly bool $viewFormat = false, + ) { $this->fieldSet = $config->getFieldSet(); - $this->validator = $validator; - $this->path = $path ?: 'order'; - $this->errorList = $errorList; $this->valuesGroup = new ValuesGroup(); - $this->viewFormat = $viewFormat; } public function getErrors(): ErrorList @@ -101,14 +78,14 @@ public function field(string $name, string $path): void } $this->fieldConfig = $this->fieldSet->get($name); - $this->inputTransformer = ($this->viewFormat ? $this->fieldConfig->getViewTransformer() : $this->fieldConfig->getNormTransformer()) ?? false; + $this->inputTransformer = $this->viewFormat ? $this->fieldConfig->getViewTransformer() : $this->fieldConfig->getNormTransformer(); $this->valuesBag = $this->valuesGroup->getField($name); $this->validator->initializeContext($this->fieldConfig, $this->errorList); } - public function simpleValue($value, string $path): void + public function simpleValue(mixed $value, string $path): void { if ($this->valuesBag === null) { throw new \LogicException('Cannot add value to unknown bag'); @@ -127,42 +104,27 @@ public function simpleValue($value, string $path): void $this->valuesBag->addSimpleValue($modelVal); } - public function excludedSimpleValue($value, string $path): void + public function excludedSimpleValue(mixed $value, string $path): void { throw OrderStructureException::invalidValue($this->fieldConfig->getName()); } - /** - * @param array $path [path, lower-path-pattern, upper-path-pattern] - */ - public function rangeValue($lower, $upper, bool $lowerInclusive, bool $upperInclusive, array $path): void + public function rangeValue(mixed $lower, mixed $upper, bool $lowerInclusive, bool $upperInclusive, array $path): void { throw OrderStructureException::invalidValue($this->fieldConfig->getName()); } - /** - * @param array $path [path, lower-path-pattern, upper-path-pattern] - */ - public function excludedRangeValue($lower, $upper, bool $lowerInclusive, bool $upperInclusive, array $path): void + public function excludedRangeValue(mixed $lower, mixed $upper, bool $lowerInclusive, bool $upperInclusive, array $path): void { throw OrderStructureException::invalidValue($this->fieldConfig->getName()); } - /** - * @param string $operator - * @param array $path [base-path, operator-path, value-path] - */ - public function comparisonValue($operator, $value, array $path): void + public function comparisonValue(mixed $operator, mixed $value, array $path): void { throw OrderStructureException::invalidValue($this->fieldConfig->getName()); } - /** - * @param string $type - * @param string $value - * @param array $path [base-path, value-path, type-path] - */ - public function patterMatchValue($type, $value, bool $caseInsensitive, array $path): void + public function patterMatchValue(mixed $type, mixed $value, bool $caseInsensitive, array $path): void { throw OrderStructureException::invalidValue($this->fieldConfig->getName()); } @@ -184,44 +146,44 @@ private function addError(ConditionErrorMessage $error): void * @return mixed returns null when the value is empty or invalid. * Note: When the value is invalid an error is registered */ - private function inputToNorm($value, string $path) - { - if ($this->inputTransformer === false) { - if ($value !== null && ! \is_scalar($value)) { - $e = new \RuntimeException( - \sprintf( - 'Norm value of type %s is not a scalar value or null and not cannot be ' . - 'converted to a string. You must set a NormTransformer for field "%s" with type "%s".', - \gettype($value), - $this->fieldConfig->getName(), - \get_class($this->fieldConfig->getType()->getInnerType()) - ) - ); - - $error = new ConditionErrorMessage( - $path, - $this->fieldConfig->getOption('invalid_message', $e->getMessage()), - $this->fieldConfig->getOption('invalid_message', $e->getMessage()), - $this->fieldConfig->getOption('invalid_message_parameters', []), - null, - $e - ); - - $this->addError($error); + private function inputToNorm(mixed $value, string $path): mixed + { + if ($this->inputTransformer !== null) { + try { + return $this->inputTransformer->reverseTransform($value); + } catch (TransformationFailedException $e) { + $this->addError($this->transformationExceptionToError($e, $path)); return null; } - - return $value === '' ? null : $value; } - try { - return $this->inputTransformer->reverseTransform($value); - } catch (TransformationFailedException $e) { - $this->addError($this->transformationExceptionToError($e, $path)); + if ($value !== null && ! \is_scalar($value)) { + $e = new \RuntimeException( + \sprintf( + 'Norm value of type %s is not a scalar value or null and not cannot be ' . + 'converted to a string. You must set a NormTransformer for field "%s" with type "%s".', + \gettype($value), + $this->fieldConfig->getName(), + $this->fieldConfig->getType()->getInnerType()::class + ) + ); + + $error = new ConditionErrorMessage( + $path, + $this->fieldConfig->getOption('invalid_message', $e->getMessage()), + $this->fieldConfig->getOption('invalid_message', $e->getMessage()), + $this->fieldConfig->getOption('invalid_message_parameters', []), + null, + $e + ); + + $this->addError($error); return null; } + + return $value === '' ? null : $value; } private function transformationExceptionToError($e, string $path): ConditionErrorMessage diff --git a/lib/Core/Input/ProcessorConfig.php b/lib/Core/Input/ProcessorConfig.php index 40a68ec8..b2de72cd 100644 --- a/lib/Core/Input/ProcessorConfig.php +++ b/lib/Core/Input/ProcessorConfig.php @@ -21,34 +21,15 @@ */ class ProcessorConfig { - /** - * @var int - */ - private $maxNestingLevel = 5; - - /** - * @var int - */ - private $maxValues = 100; - - /** - * @var int - */ - private $maxGroups = 10; - - /** - * @var FieldSet - */ - private $fieldSet; - - private int|\DateInterval|null $cacheTTL = null; - - /** @var string|null */ - private $defaultField; - - public function __construct(FieldSet $fieldSet) - { - $this->fieldSet = $fieldSet; + private int $maxNestingLevel = 5; + private int $maxValues = 100; + private int $maxGroups = 10; + private int | \DateInterval | null $cacheTTL = null; + private ?string $defaultField = null; + + public function __construct( + private readonly FieldSet $fieldSet, + ) { } public function getFieldSet(): FieldSet @@ -120,16 +101,18 @@ public function getMaxGroups(): int } /** + * @param \DateInterval|int|null $cacheTTL use 0 to disable caching + * * @return $this */ - public function setCacheTTL(\DateInterval|int|null $cacheTTL): self + public function setCacheTTL(\DateInterval | int | null $cacheTTL): self { $this->cacheTTL = $cacheTTL; return $this; } - public function getCacheTTL(): \DateInterval|int|null + public function getCacheTTL(): \DateInterval | int | null { return $this->cacheTTL; } @@ -146,7 +129,7 @@ public function getDefaultField(bool $error = false): ?string /** * @return $this */ - public function setDefaultField(string $defaultField): static + public function setDefaultField(?string $defaultField): static { $this->defaultField = $defaultField; diff --git a/lib/Core/Input/StringInput.php b/lib/Core/Input/StringInput.php index 014ad065..bd908928 100644 --- a/lib/Core/Input/StringInput.php +++ b/lib/Core/Input/StringInput.php @@ -136,28 +136,16 @@ */ abstract class StringInput extends AbstractInput { - /** - * @var StructureBuilder|null - */ - protected $structureBuilder; + protected ?StructureBuilder $structureBuilder; + protected ?StructureBuilder $orderStructureBuilder; - /** - * @var StructureBuilder|null - */ - protected $orderStructureBuilder; + /** @var array */ + protected array $fields = []; - /** - * @var string[] - */ - protected $fields = []; - - /** - * @var \Closure[] - */ - protected $valueLexers = []; + /** @var array */ + protected array $valueLexers = []; - /** @var StringLexer */ - private $lexer; + private StringLexer $lexer; public function __construct(?Validator $validator = null) { @@ -176,7 +164,7 @@ public function process(ProcessorConfig $config, $input): SearchCondition throw new UnexpectedTypeException($input, 'string'); } - $input = trim($input); + $input = mb_trim($input); $fieldSet = $config->getFieldSet(); $condition = null; diff --git a/lib/Core/Input/StringLexer.php b/lib/Core/Input/StringLexer.php index 9bc0c8de..ff7be4a7 100644 --- a/lib/Core/Input/StringLexer.php +++ b/lib/Core/Input/StringLexer.php @@ -21,34 +21,37 @@ final class StringLexer { public const FIELD_NAME = '/@?_?(\p{L}[\p{L}\p{N}_-]*)\s*:/Au'; - public const PATTERN_MATCH = 'pattern-match'; public const SIMPLE_VALUE = 'simple-value'; public const COMPARE = 'compare'; public const RANGE = 'range'; - private $valueLexers; - private $data; - private $cursor; - private $char; - private $lineno; - private $col; - private $end; - private $linenoSnapshot; - private $colSnapshot; - private $cursorSnapshot; - private $charSnapshot; + /** + * @var array + */ + private array $valueLexers; + + private string $data; + private int $cursor; + private int $char; + private int $lineno; + private int $col; + private int $end; + private ?int $linenoSnapshot; + private ?int $colSnapshot; + private ?int $cursorSnapshot; + private ?int $charSnapshot; /** * @internal * - * @param \Closure[] $fieldLexers + * @param array $fieldLexers */ public function parse(string $data, array $fieldLexers = []): void { $this->data = str_replace(["\r\n", "\r"], "\n", $data); - $this->valueLexers = $fieldLexers; $this->end = mb_strlen($this->data, '8bit'); + $this->valueLexers = $fieldLexers; $this->lineno = 1; $this->col = 0; @@ -97,7 +100,7 @@ public function moveCursor(string $text): void } } - public function snapshot($force = false): void + public function snapshot(bool $force = false): void { if (! $force && $this->charSnapshot !== null) { return; @@ -167,7 +170,7 @@ public function matchOptional(string $data): ?string * * @throws StringLexerException when there no match or there is no further data */ - public function expects(string $data, $expected = null): string + public function expects(string $data, array | string | null $expected = null): string { $match = $this->regexOrSingleChar($data); @@ -186,7 +189,10 @@ public function isEnd(): bool return $this->cursor >= $this->end; } - public function createSyntaxException($expected): StringLexerException + /** + * @param string|string[] $expected + */ + public function createSyntaxException(string | array $expected): StringLexerException { $expected = (array) $expected; @@ -298,7 +304,7 @@ public function stringValue(string $allowedNext = ',;)'): string $this->moveCursor($c); } - $value = rtrim($value); + $value = mb_rtrim($value); if (preg_match('/\s+/', $value)) { throw $this->createFormatException(StringLexerException::SPACES_REQ_QUOTING); @@ -309,7 +315,7 @@ public function stringValue(string $allowedNext = ',;)'): string public function fieldIdentification(): string { - return mb_substr(trim($this->expects(self::FIELD_NAME, 'FieldIdentification')), 0, -1); + return mb_substr(mb_trim($this->expects(self::FIELD_NAME, 'FieldIdentification')), 0, -1); } // @@ -333,6 +339,8 @@ public function valuePart(string $fieldName, string $allowedNext = ',;)'): strin /** * @internal + * + * @return array{bool, string, string, bool} ['lowerInclusive', 'lowerBound', 'upperBound', 'upperInclusive'] */ public function rangeValue(string $name): array { @@ -356,6 +364,8 @@ public function rangeValue(string $name): array /** * @internal + * + * @return array{string, string} ['operator', 'value'] */ public function comparisonValue(string $name): array { @@ -370,6 +380,8 @@ public function comparisonValue(string $name): array /** * @internal + * + * @return array{bool, string, string} ['caseInsensitive', 'type', 'value'] */ public function patternMatchValue(): array { diff --git a/lib/Core/Input/StringQueryInput.php b/lib/Core/Input/StringQueryInput.php index 0cb96762..001a7914 100644 --- a/lib/Core/Input/StringQueryInput.php +++ b/lib/Core/Input/StringQueryInput.php @@ -23,21 +23,18 @@ final class StringQueryInput extends StringInput public const FIELD_LEXER_OPTION_NAME = 'string_query.value_lexer'; public const VALUE_EXPORTER_OPTION_NAME = 'string_query.value_export'; - /** - * @var callable - */ - private $labelResolver; + /** @var callable */ + private mixed $labelResolver; /** * @param callable|null $labelResolver a callable to resolve the actual label - * of the field, receives a - * FieldConfigInterface instance. - * If null the `label` option value is - * used instead + * of the field, receives a FieldConfig instance. + * When null, the `label` option value is used instead */ public function __construct(?Validator $validator = null, ?callable $labelResolver = null) { parent::__construct($validator); + $this->labelResolver = $labelResolver ?? static fn (FieldConfig $field) => $field->getOption('label') ?? $field->getName(); } diff --git a/lib/Core/LazyFieldSetRegistry.php b/lib/Core/LazyFieldSetRegistry.php index 8fcc957d..40592c73 100644 --- a/lib/Core/LazyFieldSetRegistry.php +++ b/lib/Core/LazyFieldSetRegistry.php @@ -25,17 +25,8 @@ */ final class LazyFieldSetRegistry implements FieldSetRegistry { - private $container; - - /** - * @var FieldSetConfigurator[] - */ - private $configurators = []; - - /** - * @var array - */ - private $serviceIds; + /** @var array */ + private array $configurators = []; /** * Constructor. @@ -44,22 +35,22 @@ final class LazyFieldSetRegistry implements FieldSetRegistry * or setter dependencies. You can simple use the FQCN of the configurator class, * and will be initialized upon first usage. * - * @param ContainerInterface $container A Service locator able to lazily load - * the FieldSet configurators - * @param array $serviceIds Configurator name (FQCN) to service-id mapping + * @param ContainerInterface $container A Service locator able to lazily load + * the FieldSet configurators + * @param array $serviceIds Configurator name (FQCN) to service-id mapping */ - public function __construct(ContainerInterface $container, array $serviceIds) - { - $this->container = $container; - $this->serviceIds = $serviceIds; + public function __construct( + private readonly ContainerInterface $container, + private readonly array $serviceIds, + ) { } /** * Creates a new LazyFieldSetRegistry with easy factories for loading. * - * @param \Closure[] $configurators an array of lazy loading configurators. - * The Closure when called is expected to return - * a FieldSetConfiguratorInterface object + * @param array $configurators an array of lazy loading configurators. + * The Closure when called is expected to return + * a {@see FieldSetConfigurator} object */ public static function create(array $configurators = []): self { @@ -69,32 +60,30 @@ public static function create(array $configurators = []): self } /** - * Returns a FieldSetConfigurator by name. - * * @param string $name The name of the FieldSet configurator * * @throws InvalidArgumentException if the configurator can not be retrieved */ public function getConfigurator(string $name): FieldSetConfigurator { - if (! isset($this->configurators[$name])) { - if (isset($this->serviceIds[$name])) { - $configurator = $this->container->get($this->serviceIds[$name]); - } elseif (class_exists($name)) { - // Support fully-qualified class names. - $configurator = new $name(); - } else { - throw new InvalidArgumentException(\sprintf('Could not load FieldSet configurator "%s".', $name)); - } + if (isset($this->configurators[$name])) { + return $this->configurators[$name]; + } - if (! $configurator instanceof FieldSetConfigurator) { - throw new InvalidArgumentException(\sprintf('Configurator class "%s" is expected to be an instance of ' . FieldSetConfigurator::class, $name)); - } + if (isset($this->serviceIds[$name])) { + $configurator = $this->container->get($this->serviceIds[$name]); + } elseif (class_exists($name)) { + // Support fully-qualified class names. + $configurator = new $name(); + } else { + throw new InvalidArgumentException(\sprintf('Could not load FieldSet configurator "%s".', $name)); + } - $this->configurators[$name] = $configurator; + if (! $configurator instanceof FieldSetConfigurator) { + throw new InvalidArgumentException(\sprintf('Configurator class "%s" is expected to be an instance of ' . FieldSetConfigurator::class, $name)); } - return $this->configurators[$name]; + return $this->configurators[$name] = $configurator; } /** @@ -104,11 +93,7 @@ public function getConfigurator(string $name): FieldSetConfigurator */ public function hasConfigurator(string $name): bool { - if (isset($this->configurators[$name])) { - return true; - } - - if (isset($this->serviceIds[$name])) { + if (isset($this->configurators[$name]) || isset($this->serviceIds[$name])) { return true; } diff --git a/lib/Core/Loader/ClosureContainer.php b/lib/Core/Loader/ClosureContainer.php index 574bee70..e5414e3c 100644 --- a/lib/Core/Loader/ClosureContainer.php +++ b/lib/Core/Loader/ClosureContainer.php @@ -25,15 +25,15 @@ */ final class ClosureContainer implements ContainerInterface { - private $factories; - private $values = []; + /** @var array */ + private array $values = []; /** - * @param array|\Closure[] $factories + * @param array $factories */ - public function __construct(array $factories) - { - $this->factories = $factories; + public function __construct( + private array $factories, + ) { } public function has(string $id): bool @@ -41,15 +41,14 @@ public function has(string $id): bool return isset($this->factories[$id]); } - public function get(string $id): mixed + public function get(string $id): object { if (! isset($this->factories[$id])) { throw new ServiceNotFoundException($id); } - if (true !== $factory = $this->factories[$id]) { - $this->factories[$id] = true; - $this->values[$id] = $factory(); + if (! isset($this->values[$id])) { + $this->values[$id] = $this->factories[$id](); } return $this->values[$id]; diff --git a/lib/Core/Loader/ConditionExporterLoader.php b/lib/Core/Loader/ConditionExporterLoader.php index 65a0cf8c..98894d78 100644 --- a/lib/Core/Loader/ConditionExporterLoader.php +++ b/lib/Core/Loader/ConditionExporterLoader.php @@ -16,7 +16,9 @@ use Psr\Container\ContainerInterface; use Rollerworks\Component\Search\ConditionExporter; use Rollerworks\Component\Search\Exception\InvalidArgumentException; -use Rollerworks\Component\Search\Exporter; +use Rollerworks\Component\Search\Exporter\JsonExporter; +use Rollerworks\Component\Search\Exporter\NormStringQueryExporter; +use Rollerworks\Component\Search\Exporter\StringQueryExporter; /** * ConditionExporterLoader provides lazy loading of ConditionExporters. @@ -25,32 +27,29 @@ */ final class ConditionExporterLoader { - private $container; - private $serviceIds = []; - /** - * @param ContainerInterface $container A PSR-11 compatible Service locator/container - * @param array $serviceIds Format alias to service-id mapping, - * eg. 'json' => 'JsonExporter-ClassName' + * @param ContainerInterface $container A PSR-11 compatible Service locator/container + * @param array $serviceIds Format alias to service-id mapping, + * eg. 'json' => 'service-id' */ - public function __construct(ContainerInterface $container, array $serviceIds) - { - $this->container = $container; - $this->serviceIds = $serviceIds; + public function __construct( + private readonly ContainerInterface $container, + private readonly array $serviceIds, + ) { } /** - * Create a new ConditionExporterLoader with the build-in ConditionExporters - * loadable. + * Create a new ConditionExporterLoader with the build-in + * ConditionExporter loadable. */ public static function create(): self { return new self( new ClosureContainer( [ - 'rollerworks_search.condition_exporter.json' => static fn () => new Exporter\JsonExporter(), - 'rollerworks_search.condition_exporter.string_query' => static fn () => new Exporter\StringQueryExporter(), - 'rollerworks_search.condition_exporter.norm_string_query' => static fn () => new Exporter\NormStringQueryExporter(), + 'rollerworks_search.condition_exporter.json' => static fn (): JsonExporter => new JsonExporter(), + 'rollerworks_search.condition_exporter.string_query' => static fn (): StringQueryExporter => new StringQueryExporter(), + 'rollerworks_search.condition_exporter.norm_string_query' => static fn (): NormStringQueryExporter => new NormStringQueryExporter(), ] ), [ diff --git a/lib/Core/Loader/InputProcessorLoader.php b/lib/Core/Loader/InputProcessorLoader.php index 3743b16b..7ad0c048 100644 --- a/lib/Core/Loader/InputProcessorLoader.php +++ b/lib/Core/Loader/InputProcessorLoader.php @@ -15,7 +15,9 @@ use Psr\Container\ContainerInterface; use Rollerworks\Component\Search\Exception\InvalidArgumentException; -use Rollerworks\Component\Search\Input; +use Rollerworks\Component\Search\Input\JsonInput; +use Rollerworks\Component\Search\Input\NormStringQueryInput; +use Rollerworks\Component\Search\Input\StringQueryInput; use Rollerworks\Component\Search\Input\Validator; use Rollerworks\Component\Search\InputProcessor; @@ -26,18 +28,15 @@ */ final class InputProcessorLoader { - private $container; - private $serviceIds; - /** - * @param ContainerInterface $container A PSR-11 compatible Service locator/container - * @param string[] $serviceIds Format alias to service-id mapping, - * eg. 'json' => 'JsonInput-ClassName' + * @param ContainerInterface $container A PSR-11 compatible Service container + * @param array $serviceIds Format alias to service-id mapping, + * eg. 'json' => 'ServiceId of the container' */ - public function __construct(ContainerInterface $container, array $serviceIds) - { - $this->container = $container; - $this->serviceIds = $serviceIds; + public function __construct( + private readonly ContainerInterface $container, + private readonly array $serviceIds, + ) { } /** @@ -48,9 +47,9 @@ public static function create(?Validator $validator = null): self return new self( new ClosureContainer( [ - 'rollerworks_search.input.json' => static fn () => new Input\JsonInput($validator), - 'rollerworks_search.input.string_query' => static fn () => new Input\StringQueryInput($validator), - 'rollerworks_search.input.norm_string_query' => static fn () => new Input\NormStringQueryInput($validator), + 'rollerworks_search.input.json' => static fn (): JsonInput => new JsonInput($validator), + 'rollerworks_search.input.string_query' => static fn (): StringQueryInput => new StringQueryInput($validator), + 'rollerworks_search.input.norm_string_query' => static fn (): NormStringQueryInput => new NormStringQueryInput($validator), ] ), [ diff --git a/lib/Core/ParameterBag.php b/lib/Core/ParameterBag.php index f42a88d0..9386cf59 100644 --- a/lib/Core/ParameterBag.php +++ b/lib/Core/ParameterBag.php @@ -15,8 +15,12 @@ class ParameterBag { - private $parameters = []; + /** @var array */ + private array $parameters = []; + /** + * @param array $parameters + */ public function __construct(array $parameters = []) { foreach ($parameters as $name => $value) { @@ -24,7 +28,7 @@ public function __construct(array $parameters = []) } } - public function setParameter(string $name, $value): void + public function setParameter(string $name, mixed $value): void { $this->parameters['{' . $name . '}'] = $value; } diff --git a/lib/Core/PreloadedExtension.php b/lib/Core/PreloadedExtension.php index f443b062..d2ef7843 100644 --- a/lib/Core/PreloadedExtension.php +++ b/lib/Core/PreloadedExtension.php @@ -15,25 +15,21 @@ use Rollerworks\Component\Search\Exception\InvalidArgumentException; use Rollerworks\Component\Search\Field\FieldType; +use Rollerworks\Component\Search\Field\FieldTypeExtension; /** * @author Sebastiaan Stok */ -final class PreloadedExtension implements SearchExtension +final readonly class PreloadedExtension implements SearchExtension { - private $types = []; - private $typeExtensions = []; - /** - * Constructor. - * - * @param FieldType[] $types The types that the extension should support - * @param array[] $typeExtensions The type extensions that the extension should support + * @param array $types + * @param array $typeExtensions ['typeName' => [new MyTypeExtension()] */ - public function __construct(array $types, array $typeExtensions = []) - { - $this->types = $types; - $this->typeExtensions = $typeExtensions; + public function __construct( + private array $types, + private array $typeExtensions = [], + ) { } public function getType(string $name): FieldType diff --git a/lib/Core/SearchCondition.php b/lib/Core/SearchCondition.php index 3f2c1ad6..e341fe84 100644 --- a/lib/Core/SearchCondition.php +++ b/lib/Core/SearchCondition.php @@ -23,15 +23,13 @@ */ class SearchCondition { - private $fieldSet; - private $values; - private $primaryCondition; - private $order; + private ?SearchPrimaryCondition $primaryCondition = null; + private ?SearchOrder $order = null; - public function __construct(FieldSet $fieldSet, ValuesGroup $valuesGroup) - { - $this->fieldSet = $fieldSet; - $this->values = $valuesGroup; + public function __construct( + private readonly FieldSet $fieldSet, + private readonly ValuesGroup $values, + ) { } public function getFieldSet(): FieldSet @@ -44,9 +42,14 @@ public function getValuesGroup(): ValuesGroup return $this->values; } - public function setPrimaryCondition(?SearchPrimaryCondition $condition): void + /** + * @return $this + */ + public function setPrimaryCondition(?SearchPrimaryCondition $condition): self { $this->primaryCondition = $condition; + + return $this; } public function getPrimaryCondition(): ?SearchPrimaryCondition @@ -54,9 +57,14 @@ public function getPrimaryCondition(): ?SearchPrimaryCondition return $this->primaryCondition; } - public function setOrder(?SearchOrder $order): void + /** + * @return $this + */ + public function setOrder(?SearchOrder $order): self { $this->order = $order; + + return $this; } public function getOrder(): ?SearchOrder @@ -66,7 +74,7 @@ public function getOrder(): ?SearchOrder public function isEmpty(): bool { - return \count($this->values->getGroups()) === 0 && \count($this->values->getFields()) === 0; + return $this->order === null && \count($this->values->getGroups()) === 0 && \count($this->values->getFields()) === 0; } /** diff --git a/lib/Core/SearchConditionBuilder.php b/lib/Core/SearchConditionBuilder.php index d63f5715..3bbc88ef 100644 --- a/lib/Core/SearchConditionBuilder.php +++ b/lib/Core/SearchConditionBuilder.php @@ -21,35 +21,9 @@ final class SearchConditionBuilder { - /** - * @var ValuesGroup - */ - private $valuesGroup; - - /** - * @var SearchConditionBuilder|null - */ - private $parent; - - /** - * @var FieldSet - */ - private $fieldSet; - - /** - * @var ValuesGroup|null - */ - private $order; - - /** - * The primary-condition of the search-condition. - * - * If set to true current level is the primary-condition and prevents - * from called the primaryCondition() method at this level. - * - * @var self|true|null - */ - private $primaryCondition; + private ValuesGroup $valuesGroup; + private ?ValuesGroup $order = null; + private ?self $primaryCondition = null; /** * @param string $logical eg. one of the following ValuesGroup class constants value: @@ -60,16 +34,16 @@ public static function create(FieldSet $fieldSet, string $logical = ValuesGroup: return new self($logical, $fieldSet); } - private function __construct(string $logical, FieldSet $fieldSet, ?self $parent = null) - { + private function __construct( + string $logical, + private readonly FieldSet $fieldSet, + private readonly ?self $parent = null, + ) { $this->valuesGroup = new ValuesGroup($logical); - $this->parent = $parent; - $this->fieldSet = $fieldSet; } /** - * @param string $logical eg. one of the following ValuesGroup class constants value: - * GROUP_LOGICAL_OR or GROUP_LOGICAL_AND + * @param ValuesGroup::GROUP_LOGICAL_* $logical * * @return $this */ @@ -94,7 +68,7 @@ public function setGroupLogical(string $logical): self * ->field('name') * ->addSimpleValue('my value') * ->addSimpleValue('my value 2') - * ->end() // return back to the ValuesGroupBuilder or SearchConditionBuilder + * ->end() // return to the ValuesGroupBuilder or SearchConditionBuilder * ``` * * Or (with primary condition) @@ -103,17 +77,17 @@ public function setGroupLogical(string $logical): self * ->order('@name', 'ASC') * ->primaryCondition() * ->order('@id', 'DESC') - * ->end() // Returns back to the main condition + * ->end() // Returns to the main condition * ``` * - * @param string $name The field-name (must be valid known ordering field like @id) - * @param string $direction ASC or DESC + * @param string $name The field-name (must be a valid known ordering field like "@id") + * @param 'asc'|'desc'|'ASC'|'DESC' $direction * * @return $this */ public function order(string $name, string $direction = 'ASC'): self { - if ($this->parent && $this->primaryCondition !== true) { + if ($this->parent && $this->parent->primaryCondition !== $this) { throw new BadMethodCallException('Cannot add ordering at nested levels.'); } @@ -124,8 +98,8 @@ public function order(string $name, string $direction = 'ASC'): self $this->fieldSet->get($name); $direction = mb_strtoupper($direction); - if ($direction !== 'ASC' && $direction !== 'DESC') { - throw new InvalidArgumentException(\sprintf('Invalid direction provided "%s" for field "%s", must be either "ASC" or "DESC" (case insensitive).', $direction, $name)); + if (! \in_array($direction, ['DESC', 'ASC'], true)) { + throw new InvalidArgumentException(\sprintf('Invalid direction provided "%s" for field "%s", must be either "ASC" OR "DESC" (case insensitive).', $direction, $name)); } if ($this->order === null) { @@ -161,8 +135,8 @@ public function clearOrder(): self * ->group() * ->field('name') * ->... - * ->end() // return back to the ValuesGroup. - * ->end() // return back to the parent ValuesGroup level + * ->end() // return to the ValuesGroup. + * ->end() // return to the parent ValuesGroup level * ``` * * Note: Groups cannot be altered or removed with the builder after creation. @@ -182,7 +156,7 @@ public function group(string $logical = ValuesGroup::GROUP_LOGICAL_AND): self * Add/expend a field's ValuesBag on 'this' ValuesGroup and returns * a ValuesBagBuilder for adding values to the field. * - * Note. Values must be in the model format, they are not transformed! + * Note: Values must be in the model format, they are not transformed! * * The ValuesBagBuilder is subset of ValuesBag, which provides a developer * friendly interface to construct a ValuesBag structure for the field. @@ -191,7 +165,7 @@ public function group(string $logical = ValuesGroup::GROUP_LOGICAL_AND): self * ->field('name') * ->addSimpleValue('my value') * ->addSimpleValue('my value 2') - * ->end() // return back to the ValuesGroup level + * ->end() // return to the ValuesGroup level * ``` */ public function field(string $name): ValuesBagBuilder @@ -217,7 +191,7 @@ public function field(string $name): ValuesBagBuilder * Add/overwrites a field's ValuesBag on this ValuesGroup and returns * a ValuesBagBuilder for adding values to the field. * - * Note. Values must be in the model format, they are not transformed! + * Note: Values must be in the model format, they are not transformed! * * The ValuesBagBuilder is subset of ValuesBag, which provides a developer * friendly interface to construct a ValuesBag structure for the field. @@ -226,7 +200,7 @@ public function field(string $name): ValuesBagBuilder * ->overwriteField('name') * ->addSimpleValue('my value') * ->addSimpleValue('my value 2') - * ->end() // return back to the ValuesGroup level + * ->end() // return to the ValuesGroup level * ``` */ public function overwriteField(string $name): ValuesBagBuilder @@ -244,7 +218,7 @@ public function overwriteField(string $name): ValuesBagBuilder } /** - * Close the condition building level (field or group) and return back + * Close the condition building level (field or group) and return * to the previous builder level. */ public function end(): self @@ -258,6 +232,8 @@ public function end(): self * Note: This will overwrite any existing primary-condition of this builder. * * @return self A SearchConditionBuilder for the primary-condition structure + * + * @throws BadMethodCallException when called at a nested level */ public function primaryCondition(): self { @@ -266,8 +242,6 @@ public function primaryCondition(): self } $builder = new self(ValuesGroup::GROUP_LOGICAL_AND, $this->fieldSet, $this); - $builder->primaryCondition = true; - $this->primaryCondition = $builder; return $builder; @@ -300,6 +274,12 @@ public function getSearchCondition(): SearchCondition return $searchCondition; } + /** + * Normalize the SearchConditionBuilder structure to a ValuesGroup structure + * that can be used by the SearchCondition. + * + * Otherwise the final SearchCondition would contain a SearchConditionBuilder instance. + */ private function normalizeValueGroup(ValuesGroup $currentValuesGroup, ValuesGroup $rootValuesGroup): void { foreach ($currentValuesGroup->getGroups() as $group) { diff --git a/lib/Core/SearchConditionSerializer.php b/lib/Core/SearchConditionSerializer.php index 6fc7703f..ccf4daa4 100644 --- a/lib/Core/SearchConditionSerializer.php +++ b/lib/Core/SearchConditionSerializer.php @@ -19,17 +19,15 @@ * SearchConditionSerializer, serializes a search condition for persistent storage. * * In practice this serializes the root ValuesGroup and of the condition - * and bundles it with the FieldSet-name for future unserializing. + * and bundles it with the FieldSet-name for further unserializing. * * @author Sebastiaan Stok */ class SearchConditionSerializer { - private $searchFactory; - - public function __construct(SearchFactory $searchFactory) - { - $this->searchFactory = $searchFactory; + public function __construct( + private readonly SearchFactory $searchFactory, + ) { } /** @@ -39,9 +37,9 @@ public function __construct(SearchFactory $searchFactory) * This is not done already because storing a serialized SearchCondition * in a php session would serialize the serialized result again. * - * Caution: The FieldSet must be loadable from the factory. + * Caution: The FieldSet must be loadable from the SearchFactory. * - * @return array [FieldSet-name, serialized ValuesGroup object] + * @return array{0: string, 1: string} ['FieldSet-name', 'serialized ValuesGroup object'] */ public function serialize(SearchCondition $searchCondition): array { @@ -53,7 +51,7 @@ public function serialize(SearchCondition $searchCondition): array /** * Unserialize a serialized SearchCondition. * - * @param array $searchCondition [FieldSet-name, serialized ValuesGroup object] + * @param array{0: string, 1: string} $searchCondition [FieldSet-name, serialized ValuesGroup object] * * @throws InvalidArgumentException when serialized SearchCondition is invalid * (invalid structure or failed to unserialize) @@ -62,17 +60,20 @@ public function unserialize(array $searchCondition): SearchCondition { if (\count($searchCondition) !== 2 || ! isset($searchCondition[0], $searchCondition[1])) { throw new InvalidArgumentException( - 'Serialized search condition must be exactly two values [FieldSet-name, serialized ValuesGroup].' + 'Serialized search condition must be exactly two values ["FieldSet-name", "serialized ValuesGroup"].' ); } $fieldSet = $this->searchFactory->createFieldSet($searchCondition[0]); - // FIXME This needs safe serialzing with error handling - if (false === $group = unserialize($searchCondition[1])) { - throw new InvalidArgumentException('Unable to unserialize invalid value.'); - } + set_error_handler(static function (int $errNo, string $errstr, string $errFile, int $errLine): void { + throw new InvalidArgumentException('Unable to unserialize invalid value.', $errNo, new \ErrorException($errstr, $errNo, $errNo, $errFile, $errLine)); + }); - return new SearchCondition($fieldSet, $group); + try { + return new SearchCondition($fieldSet, unserialize($searchCondition[1], ['allowed_classes' => true])); + } finally { + restore_error_handler(); + } } } diff --git a/lib/Core/SearchFactoryBuilder.php b/lib/Core/SearchFactoryBuilder.php index 28d1c603..b1cba795 100644 --- a/lib/Core/SearchFactoryBuilder.php +++ b/lib/Core/SearchFactoryBuilder.php @@ -24,35 +24,23 @@ */ final class SearchFactoryBuilder { - /** - * @var ResolvedFieldTypeFactory|null - */ - private $resolvedTypeFactory; + private ?ResolvedFieldTypeFactory $resolvedTypeFactory = null; - /** - * @var SearchExtension[] - */ - private $extensions = []; + /** @var SearchExtension[] */ + private array $extensions = []; - /** - * @var FieldType[] - */ - private $types = []; + /** @var FieldType[] */ + private array $types = []; - /** - * @var array - */ - private $typeExtensions = []; + /** @var array */ + private array $typeExtensions = []; - /** - * @var FieldSetRegistry|null - */ - private $fieldSetRegistry; + private ?FieldSetRegistry $fieldSetRegistry = null; /** - * Sets the factory for creating ResolvedFieldTypeInterface instances. + * Sets the factory for creating {@see ResolvedFieldType} instances. * - * @return static The builder + * @return $this The builder */ public function setResolvedTypeFactory(ResolvedFieldTypeFactory $resolvedTypeFactory) { @@ -74,9 +62,9 @@ public function setFieldSetRegistry(FieldSetRegistry $fieldSetRegistry): self /** * Adds an extension to be loaded by the factory. * - * @return static The builder + * @return $this The builder */ - public function addExtension(SearchExtension $extension) + public function addExtension(SearchExtension $extension): self { $this->extensions[] = $extension; @@ -88,9 +76,9 @@ public function addExtension(SearchExtension $extension) * * @param SearchExtension[] $extensions * - * @return static The builder + * @return $this The builder */ - public function addExtensions(array $extensions) + public function addExtensions(array $extensions): self { $this->extensions = array_merge($this->extensions, $extensions); @@ -100,9 +88,9 @@ public function addExtensions(array $extensions) /** * Adds a field type to the factory. * - * @return static The builder + * @return $this The builder */ - public function addType(FieldType $type) + public function addType(FieldType $type): self { $this->types[$type::class] = $type; @@ -114,9 +102,9 @@ public function addType(FieldType $type) * * @param FieldType[] $types * - * @return static The builder + * @return $this The builder */ - public function addTypes(array $types) + public function addTypes(array $types): self { foreach ($types as $type) { $this->types[$type::class] = $type; @@ -128,9 +116,9 @@ public function addTypes(array $types) /** * Adds a field type extension to the factory. * - * @return static The builder + * @return $this The builder */ - public function addTypeExtension(FieldTypeExtension $typeExtension) + public function addTypeExtension(FieldTypeExtension $typeExtension): self { $this->typeExtensions[$typeExtension->getExtendedType()][] = $typeExtension; @@ -142,9 +130,9 @@ public function addTypeExtension(FieldTypeExtension $typeExtension) * * @param FieldTypeExtension[] $typeExtensions * - * @return static The builder + * @return $this The builder */ - public function addTypeExtensions(array $typeExtensions) + public function addTypeExtensions(array $typeExtensions): self { foreach ($typeExtensions as $typeExtension) { $this->typeExtensions[$typeExtension->getExtendedType()][] = $typeExtension; diff --git a/lib/Core/SearchOrder.php b/lib/Core/SearchOrder.php index c0b3b46e..c858b29e 100644 --- a/lib/Core/SearchOrder.php +++ b/lib/Core/SearchOrder.php @@ -21,31 +21,28 @@ */ final class SearchOrder { - private $values; - - public function __construct(ValuesGroup $valuesGroup) - { + public function __construct( + private ValuesGroup $valuesGroup, + ) { if ($valuesGroup->hasGroups()) { throw new InvalidArgumentException('A SearchOrder must have a single-level structure. Only fields with single values are accepted.'); } - - $this->values = $valuesGroup; } public function getValuesGroup(): ValuesGroup { - return $this->values; + return $this->valuesGroup; } /** - * @return array + * @return array */ public function getFields(): array { $fields = []; - foreach ($this->values->getFields() as $fieldName => $valuesBag) { - $direction = mb_strtolower(current($valuesBag->getSimpleValues())); + foreach ($this->valuesGroup->getFields() as $fieldName => $valuesBag) { + $direction = mb_strtolower((string) current($valuesBag->getSimpleValues())); \assert($direction === 'desc' || $direction === 'asc'); $fields[$fieldName] = $direction; diff --git a/lib/Core/SearchPrimaryCondition.php b/lib/Core/SearchPrimaryCondition.php index 39c5785a..a55c506a 100644 --- a/lib/Core/SearchPrimaryCondition.php +++ b/lib/Core/SearchPrimaryCondition.php @@ -27,12 +27,11 @@ */ final class SearchPrimaryCondition { - private $values; - private $order; + private ?SearchOrder $order = null; - public function __construct(ValuesGroup $valuesGroup) - { - $this->values = $valuesGroup; + public function __construct( + private readonly ValuesGroup $values, + ) { } public function getValuesGroup(): ValuesGroup diff --git a/lib/Core/StructureBuilder.php b/lib/Core/StructureBuilder.php index e3611ecd..0403b762 100644 --- a/lib/Core/StructureBuilder.php +++ b/lib/Core/StructureBuilder.php @@ -19,6 +19,8 @@ * Works as a wrapper around the ValuesGroup, and ValuesBag transforming * input while ensuring restrictions are honored. * + * @psalm-type ErrorPath = array{0: string, 1: string, 2: string} + * * @internal * * @author Sebastiaan Stok @@ -27,7 +29,10 @@ interface StructureBuilder { public function getErrors(): ErrorList; - public function getCurrentPath(); + /** + * @return string|string[] + */ + public function getCurrentPath(): string | array; public function getRootGroup(): ValuesGroup; @@ -37,32 +42,29 @@ public function leaveGroup(): void; public function field(string $name, string $path): void; - public function simpleValue($value, string $path): void; + public function simpleValue(mixed $value, string $path): void; - public function excludedSimpleValue($value, string $path): void; + public function excludedSimpleValue(mixed $value, string $path): void; /** - * @param array $path [path, lower-path-pattern, upper-path-pattern] + * @param ErrorPath $path [path, lower-path-pattern, upper-path-pattern] */ - public function rangeValue($lower, $upper, bool $lowerInclusive, bool $upperInclusive, array $path): void; + public function rangeValue(mixed $lower, mixed $upper, bool $lowerInclusive, bool $upperInclusive, array $path): void; /** - * @param array $path [path, lower-path-pattern, upper-path-pattern] + * @param ErrorPath $path [path, lower-path-pattern, upper-path-pattern] */ - public function excludedRangeValue($lower, $upper, bool $lowerInclusive, bool $upperInclusive, array $path): void; + public function excludedRangeValue(mixed $lower, mixed $upper, bool $lowerInclusive, bool $upperInclusive, array $path): void; /** - * @param string $operator - * @param array $path [base-path, operator-path, value-path] + * @param ErrorPath $path [base-path, operator-path, value-path] */ - public function comparisonValue($operator, $value, array $path): void; + public function comparisonValue(mixed $operator, mixed $value, array $path): void; /** - * @param string $type - * @param string $value - * @param array $path [base-path, value-path, type-path] + * @param ErrorPath $path [base-path, value-path, type-path] */ - public function patterMatchValue($type, $value, bool $caseInsensitive, array $path): void; + public function patterMatchValue(mixed $type, mixed $value, bool $caseInsensitive, array $path): void; public function endValues(); } diff --git a/lib/Core/Test/FieldTransformationAssertion.php b/lib/Core/Test/FieldTransformationAssertion.php index 0b75b26d..7154ebd2 100644 --- a/lib/Core/Test/FieldTransformationAssertion.php +++ b/lib/Core/Test/FieldTransformationAssertion.php @@ -19,19 +19,38 @@ use Rollerworks\Component\Search\Field\FieldConfig; /** + * The FieldTransformationAssertion class provides a fluent interface for + * testing field transformations. + * + * Both inputView and inputNorm are expected be strings, when no + * inputNorm is provided the inputView is used as the norm value. + * + * Example: + * + * ``` + * FieldTransformationAssertion::assertThat($field) + * ->withInput('value') + * ->successfullyTransformsTo('transformed-value') + * + * // Should be equal to the input (but may vary), and should still transform to the same value. + * ->andReverseTransformsTo('value') + * + * // Or to expect a transformation failure: + * ->failsToTransforms(new TransformationFailedException('message')) + * ``` + * * @author Sebastiaan Stok */ final class FieldTransformationAssertion { - private $field; - private $inputView; - private $inputNorm; - private $transformed = false; - private $model; - - private function __construct(FieldConfig $field) - { - $this->field = $field; + private string $inputView; + private string $inputNorm; + private bool $transformed = false; + private mixed $model; + + private function __construct( + private readonly FieldConfig $field, + ) { } public static function assertThat(FieldConfig $field): self @@ -39,23 +58,23 @@ public static function assertThat(FieldConfig $field): self return new self($field); } - public function withInput($inputView, $inputNorm = null): self + public function withInput(string $inputView, ?string $inputNorm = null): self { if ($this->transformed) { throw new \LogicException('Cannot change input after transformation.'); } - $this->inputView = (string) $inputView; - $this->inputNorm = $inputNorm === null ? $this->inputView : (string) $inputNorm; + $this->inputView = $inputView; + $this->inputNorm = $inputNorm ?? $this->inputView; return $this; } - public function successfullyTransformsTo($model): self + public function successfullyTransformsTo(mixed $model): self { $normValue = $viewValue = null; - if ($this->inputView === null) { + if (! isset($this->inputView)) { throw new \LogicException('withInput() must be called first.'); } @@ -82,7 +101,7 @@ public function successfullyTransformsTo($model): self public function failsToTransforms(?TransformationFailedException $exceptionForView = null, ?TransformationFailedException $exceptionForModel = null): void { - if ($this->inputView === null) { + if (! isset($this->inputView)) { throw new \LogicException('withInput() must be called first.'); } @@ -115,7 +134,7 @@ public function failsToTransforms(?TransformationFailedException $exceptionForVi } } - public function andReverseTransformsTo($expectedView = null, $expectedNorm = null): void + public function andReverseTransformsTo(?string $expectedView = null, ?string $expectedNorm = null): void { $normValue = $viewValue = null; @@ -139,16 +158,14 @@ public function andReverseTransformsTo($expectedView = null, $expectedNorm = nul Assert::assertEquals($expectedNorm ?? $expectedView, $normValue, 'Norm value does not equal'); } - private function viewToModel($value) + private function viewToModel(string $value) { - if (! $transformer = $this->field->getViewTransformer()) { - return $value === '' ? null : $value; - } + $transformer = $this->field->getViewTransformer(); - return $transformer->reverseTransform($value); + return $transformer ? $transformer->reverseTransform($value) : ($value === '' ? null : $value); } - private function modelToView($value): string + private function modelToView(mixed $value): string { $transformer = $this->field->getViewTransformer(); @@ -161,20 +178,14 @@ private function modelToView($value): string return (string) $transformer->transform($value); } - private function normToModel($value) + private function normToModel(string $value): mixed { $transformer = $this->field->getNormTransformer() ?? $this->field->getViewTransformer(); - // Scalar values should be converted to strings to - // facilitate differentiation between empty ("") and zero (0). - if ($value === null || ! $transformer) { - return (string) $value; - } - - return $transformer->reverseTransform($value); + return ! $transformer ? $value : $transformer->reverseTransform($value); } - private function modelToNorm($value): string + private function modelToNorm(mixed $value): string { $transformer = $this->field->getNormTransformer() ?? $this->field->getViewTransformer(); diff --git a/lib/Core/Test/SearchIntegrationTestCase.php b/lib/Core/Test/SearchIntegrationTestCase.php index a890f04b..f7214821 100644 --- a/lib/Core/Test/SearchIntegrationTestCase.php +++ b/lib/Core/Test/SearchIntegrationTestCase.php @@ -38,15 +38,8 @@ abstract class SearchIntegrationTestCase extends TestCase { use ProphecyTrait; - /** - * @var SearchFactoryBuilder|null - */ - protected $factoryBuilder; - - /** - * @var SearchFactory|null - */ - private $searchFactory; + protected SearchFactoryBuilder $factoryBuilder; + private ?SearchFactory $searchFactory = null; protected function setUp(): void { @@ -101,7 +94,7 @@ protected static function assertConditionsEquals(SearchCondition $expectedCondit try { // First try the "simple" method, it's possible this fails due to index mismatches. self::assertEquals($expectedCondition, $actualCondition); - } catch (\Exception $e) { + } catch (\Exception) { // No need for custom implementations here. // The reindexValuesGroup can be used for custom implementations (when needed). $actualCondition = new SearchCondition( @@ -188,7 +181,7 @@ protected static function reindexValuesGroup(ValuesGroup $valuesGroup): ValuesGr } // use array_merge to renumber indexes and prevent mismatches. - foreach ($valuesBag->all() as $type => $values) { + foreach ($valuesBag->all() as $values) { foreach (array_merge([], $values) as $value) { $newValuesBag->add($value); } @@ -223,6 +216,8 @@ protected function assertConditionEquals($input, SearchCondition $condition, Inp */ protected static function detectSystemException(\Exception $exception): void { + trigger_deprecation('rollerworks/search', '2.0-BETA13', 'The "%s()" method is deprecated, use a proper catch type instead.', __METHOD__); + if (! $exception instanceof SearchException) { throw $exception; } diff --git a/lib/Core/Tests/Exporter/NormStringQueryExporterTest.php b/lib/Core/Tests/Exporter/NormStringQueryExporterTest.php index c1b67649..6afce136 100644 --- a/lib/Core/Tests/Exporter/NormStringQueryExporterTest.php +++ b/lib/Core/Tests/Exporter/NormStringQueryExporterTest.php @@ -59,62 +59,62 @@ public function it_exporters_values(): void $this->assertConditionEquals($this->provideSingleValuePairTest(), $condition, $processor, $config); } - public function provideSingleValuePairTest() + public function provideSingleValuePairTest(): string { return 'name: "value ", -value2, value2-, 10.00, "10,00", hÌ, ٤٤٤٦٥٤٦٠٠, "doctor""who""""", !value3; price: EUR 12.00, 12;'; } - public function provideMultipleValuesTest() + public function provideMultipleValuesTest(): string { return 'name: value, value2; date: 2014-12-16;'; } - public function providePrivateFieldsTest() + public function providePrivateFieldsTest(): string { return 'name: value, value2; date: 2014-12-16;'; } - public function provideRangeValuesTest() + public function provideRangeValuesTest(): string { return 'id: 1 ~ 10, 15 ~ 30, ]100 ~ 200, 310 ~ 400[, !50 ~ 70; date: 2014-12-16 ~ 2014-12-20;'; } - public function provideComparisonValuesTest() + public function provideComparisonValuesTest(): string { return 'id: > 1, < 2, <= 5, >= 8; date: >= 2014-12-16;'; } - public function provideMatcherValuesTest() + public function provideMatcherValuesTest(): string { return 'name: ~* value, ~i> value2, ~< value3, ~!* value4, ~i!* value5, ~= value9, ~!= value10, ~i= value11, ~i!= value12;'; } - public function provideGroupTest() + public function provideGroupTest(): string { return 'name: value, value2; ( name: value3, value4 ); *( name: value8, value10 );'; } - public function provideMultipleSubGroupTest() + public function provideMultipleSubGroupTest(): string { return '( name: value, value2 ); ( name: value3, value4 );'; } - public function provideNestedGroupTest() + public function provideNestedGroupTest(): string { return '( ( name: value, value2 ) );'; } - public function provideEmptyValuesTest() + public function provideEmptyValuesTest(): string { return ''; } - public function provideEmptyGroupTest() + public function provideEmptyGroupTest(): string { return '( );'; } - public function provideOrderTest() + public function provideOrderTest(): string { return '@id: desc; @status: asc;'; } diff --git a/lib/Core/Tests/Exporter/StringQueryExporterTest.php b/lib/Core/Tests/Exporter/StringQueryExporterTest.php index ed6ff811..5774102c 100644 --- a/lib/Core/Tests/Exporter/StringQueryExporterTest.php +++ b/lib/Core/Tests/Exporter/StringQueryExporterTest.php @@ -150,62 +150,62 @@ public function it_exports_with_ordering_and_root_group_logical(): void $this->assertConditionEquals('* @id: desc; @status: asc; name: "value ";', $condition, $this->getInputProcessor(), $config); } - public function provideSingleValuePairTest() + public function provideSingleValuePairTest(): string { return 'name: "value ", -value2, value2-, 10.00, "10,00", hÌ, ٤٤٤٦٥٤٦٠٠, "doctor""who""""", !value3; price: € 12.00, "12,00 $", $ 12.00;'; } - public function provideMultipleValuesTest() + public function provideMultipleValuesTest(): string { return 'name: value, value2; date: 12-16-2014;'; } - public function providePrivateFieldsTest() + public function providePrivateFieldsTest(): string { return 'name: value, value2; date: 12-16-2014;'; } - public function provideRangeValuesTest() + public function provideRangeValuesTest(): string { return 'id: 1 ~ 10, 15 ~ 30, ]100 ~ 200, 310 ~ 400[, !50 ~ 70; date: 12-16-2014 ~ 12-20-2014;'; } - public function provideComparisonValuesTest() + public function provideComparisonValuesTest(): string { return 'id: > 1, < 2, <= 5, >= 8; date: >= 12-16-2014;'; } - public function provideMatcherValuesTest() + public function provideMatcherValuesTest(): string { return 'name: ~* value, ~i> value2, ~< value3, ~!* value4, ~i!* value5, ~= value9, ~!= value10, ~i= value11, ~i!= value12;'; } - public function provideGroupTest() + public function provideGroupTest(): string { return 'name: value, value2; ( name: value3, value4 ); *( name: value8, value10 );'; } - public function provideMultipleSubGroupTest() + public function provideMultipleSubGroupTest(): string { return '( name: value, value2 ); ( name: value3, value4 );'; } - public function provideNestedGroupTest() + public function provideNestedGroupTest(): string { return '( ( name: value, value2 ) );'; } - public function provideEmptyValuesTest() + public function provideEmptyValuesTest(): string { return ''; } - public function provideEmptyGroupTest() + public function provideEmptyGroupTest(): string { return '( );'; } - public function provideOrderTest() + public function provideOrderTest(): string { return '@id: desc; @status: asc;'; } diff --git a/lib/Core/Tests/Extension/Core/ChoiceList/AbstractChoiceListTestCase.php b/lib/Core/Tests/Extension/Core/ChoiceList/AbstractChoiceListTestCase.php index 40eb5da2..e2ff7d01 100644 --- a/lib/Core/Tests/Extension/Core/ChoiceList/AbstractChoiceListTestCase.php +++ b/lib/Core/Tests/Extension/Core/ChoiceList/AbstractChoiceListTestCase.php @@ -23,78 +23,32 @@ */ abstract class AbstractChoiceListTestCase extends TestCase { - /** - * @var ChoiceList - */ - protected $list; - - /** - * @var array - */ - protected $choices; - - /** - * @var array - */ - protected $values; - - /** - * @var array - */ - protected $structuredValues; - - /** - * @var array - */ - protected $keys; - - protected $choice1; - - protected $choice2; - - protected $choice3; - - protected $choice4; - - /** - * @var string - */ - protected $value1; - - /** - * @var string - */ - protected $value2; - - /** - * @var string - */ - protected $value3; - - /** - * @var string - */ - protected $value4; - - /** - * @var string - */ - protected $key1; - - /** - * @var string - */ - protected $key2; - - /** - * @var string - */ - protected $key3; - - /** - * @var string - */ - protected $key4; + protected ChoiceList $list; + + /** @var array */ + protected array $choices; + + /** @var array */ + protected array $values; + + /** @var array */ + protected array $structuredValues; + + /** @var array */ + protected array $keys; + + protected mixed $choice1; + protected mixed $choice2; + protected mixed $choice3; + protected mixed $choice4; + protected string $value1; + protected string $value2; + protected string $value3; + protected string $value4; + protected string $key1; + protected string $key2; + protected string $key3; + protected string $key4; protected function setUp(): void { diff --git a/lib/Core/Tests/Extension/Core/ChoiceList/ArrayChoiceListTestCase.php b/lib/Core/Tests/Extension/Core/ChoiceList/ArrayChoiceListTestCase.php index 2fa4d389..30e58418 100644 --- a/lib/Core/Tests/Extension/Core/ChoiceList/ArrayChoiceListTestCase.php +++ b/lib/Core/Tests/Extension/Core/ChoiceList/ArrayChoiceListTestCase.php @@ -23,7 +23,7 @@ */ final class ArrayChoiceListTestCase extends AbstractChoiceListTestCase { - private $object; + private \stdClass $object; protected function setUp(): void { @@ -37,11 +37,17 @@ protected function createChoiceList(): ChoiceList return new ArrayChoiceList($this->getChoices()); } - protected function getChoices() + /** + * @return mixed[] + */ + protected function getChoices(): array { return [0, 1, 1.5, '1', 'a', false, true, $this->object, null]; } + /** + * @return string[] + */ protected function getValues() { return ['0', '1', '2', '3', '4', '5', '6', '7', '8']; @@ -50,7 +56,7 @@ protected function getValues() /** @test */ public function create_choice_list_with_value_callback(): void { - $callback = static fn ($choice) => ':' . $choice; + $callback = static fn ($choice): string => ':' . $choice; $choiceList = new ArrayChoiceList([2 => 'foo', 7 => 'bar', 10 => 'baz'], $callback); diff --git a/lib/Core/Tests/Extension/Core/ChoiceList/Factory/DefaultChoiceListFactoryTest.php b/lib/Core/Tests/Extension/Core/ChoiceList/Factory/DefaultChoiceListFactoryTest.php index b2250299..bb6a2bd3 100644 --- a/lib/Core/Tests/Extension/Core/ChoiceList/Factory/DefaultChoiceListFactoryTest.php +++ b/lib/Core/Tests/Extension/Core/ChoiceList/Factory/DefaultChoiceListFactoryTest.php @@ -30,23 +30,19 @@ */ final class DefaultChoiceListFactoryTest extends TestCase { - private $obj1; - private $obj2; - private $obj3; - private $obj4; - private $list; + private object $obj1; + private object $obj2; + private object $obj3; + private object $obj4; + private ArrayChoiceList $list; + private DefaultChoiceListFactory $factory; - /** - * @var DefaultChoiceListFactory|null - */ - private $factory; - - public function getValue($object) + public function getValue(object $object): mixed { return $object->value; } - public function getScalarValue($choice) + public function getScalarValue(string $choice): string { switch ($choice) { case 'a': @@ -60,35 +56,38 @@ public function getScalarValue($choice) case 'd': return '2'; + + default: + throw new \InvalidArgumentException(sprintf('Unexpected choice "%s".', $choice)); } } - public function getLabel($object): string + public function getLabel(object $object): string { return $object->label; } - public function getFormIndex($object) + public function getFormIndex(object $object) { return $object->index; } - public function isPreferred($object): bool + public function isPreferred(object $object): bool { return $this->obj2 === $object || $this->obj3 === $object; } - public function getAttr($object) + public function getAttr(object $object) { return $object->attr; } - public function getGroup($object): string + public function getGroup(object $object): string { return $this->obj1 === $object || $this->obj2 === $object ? 'Group 1' : 'Group 2'; } - public function getGroupAsObject($object): DefaultChoiceListFactoryTest_Castable + public function getGroupAsObject(object $object): DefaultChoiceListFactoryTest_Castable { return $this->obj1 === $object || $this->obj2 === $object ? new DefaultChoiceListFactoryTest_Castable('Group 1') @@ -178,7 +177,7 @@ public function create_from_choices_flat_values_as_callable(): void { $list = $this->factory->createListFromChoices( ['A' => $this->obj1, 'B' => $this->obj2, 'C' => $this->obj3, 'D' => $this->obj4], - [$this, 'getValue'] + $this->getValue(...) ); $this->assertObjectListWithCustomValues($list); @@ -229,7 +228,7 @@ public function create_from_choices_grouped_values_as_callable(): void 'Group 1' => ['A' => $this->obj1, 'B' => $this->obj2], 'Group 2' => ['C' => $this->obj3, 'D' => $this->obj4], ], - [$this, 'getValue'] + $this->getValue(...) ); $this->assertObjectListWithCustomValues($list); @@ -325,7 +324,7 @@ public function create_view_flat_preferred_choices_as_callable(): void { $view = $this->factory->createView( $this->list, - [$this, 'isPreferred'] + $this->isPreferred(...) ); $this->assertFlatView($view); @@ -339,7 +338,7 @@ public function create_view_flat_preferred_choices_as_closure(): void $view = $this->factory->createView( $this->list, - static fn ($object) => $obj2 === $object || $obj3 === $object + static fn ($object): bool => $obj2 === $object || $obj3 === $object ); $this->assertFlatView($view); @@ -350,7 +349,7 @@ public function create_view_flat_preferred_choices_closure_receives_key(): void { $view = $this->factory->createView( $this->list, - static fn ($object, $key) => $key === 'B' || $key === 'C' + static fn ($object, $key): bool => $key === 'B' || $key === 'C' ); $this->assertFlatView($view); @@ -361,7 +360,7 @@ public function create_view_flat_preferred_choices_closure_receives_value(): voi { $view = $this->factory->createView( $this->list, - static fn ($object, $key, $value) => $value === '1' || $value === '2' + static fn ($object, $key, $value): bool => $value === '1' || $value === '2' ); $this->assertFlatView($view); @@ -373,7 +372,7 @@ public function create_view_flat_label_as_callable(): void $view = $this->factory->createView( $this->list, [$this->obj2, $this->obj3], - [$this, 'getLabel'] + $this->getLabel(...) ); $this->assertFlatView($view); @@ -432,7 +431,7 @@ public function create_view_flat_index_as_callable(): void $this->list, [$this->obj2, $this->obj3], null, // label - [$this, 'getFormIndex'] + $this->getFormIndex(...) ); $this->assertFlatViewWithCustomIndices($view); @@ -536,7 +535,7 @@ public function create_view_flat_group_by_as_callable(): void [$this->obj2, $this->obj3], null, // label null, // index - [$this, 'getGroup'] + $this->getGroup(...) ); $this->assertGroupedView($view); @@ -550,7 +549,7 @@ public function create_view_flat_group_by_object_that_can_be_cast_to_string(): v [$this->obj2, $this->obj3], null, // label null, // index - [$this, 'getGroupAsObject'] + $this->getGroupAsObject(...) ); $this->assertGroupedView($view); @@ -567,7 +566,7 @@ public function create_view_flat_group_by_as_closure(): void [$this->obj2, $this->obj3], null, // label null, // index - static fn ($object) => $obj1 === $object || $obj2 === $object ? 'Group 1' : 'Group 2' + static fn ($object): string => $obj1 === $object || $obj2 === $object ? 'Group 1' : 'Group 2' ); $this->assertGroupedView($view); @@ -581,7 +580,7 @@ public function create_view_flat_group_by_closure_receives_key(): void [$this->obj2, $this->obj3], null, // label null, // index - static fn ($object, $key) => $key === 'A' || $key === 'B' ? 'Group 1' : 'Group 2' + static fn ($object, $key): string => $key === 'A' || $key === 'B' ? 'Group 1' : 'Group 2' ); $this->assertGroupedView($view); @@ -595,7 +594,7 @@ public function create_view_flat_group_by_closure_receives_value(): void [$this->obj2, $this->obj3], null, // label null, // index - static fn ($object, $key, $value) => $value === '0' || $value === '1' ? 'Group 1' : 'Group 2' + static fn ($object, $key, $value): string => $value === '0' || $value === '1' ? 'Group 1' : 'Group 2' ); $this->assertGroupedView($view); @@ -643,7 +642,7 @@ public function create_view_flat_attr_as_callable(): void null, // label null, // index null, // group - [$this, 'getAttr'] + $this->getAttr(...) ); $this->assertFlatViewWithAttr($view); @@ -673,14 +672,10 @@ public function create_view_flat_attr_closure_receives_key(): void null, // label null, // index null, // group - static function ($object, $key) { - switch ($key) { - case 'B': return ['attr1' => 'value1']; - - case 'C': return ['attr2' => 'value2']; - - default: return []; - } + static fn ($object, $key): array => match ($key) { + 'B' => ['attr1' => 'value1'], + 'C' => ['attr2' => 'value2'], + default => [], } ); @@ -696,14 +691,10 @@ public function create_view_flat_attr_closure_receives_value(): void null, // label null, // index null, // group - static function ($object, $key, $value) { - switch ($value) { - case '1': return ['attr1' => 'value1']; - - case '2': return ['attr2' => 'value2']; - - default: return []; - } + static fn ($object, $key, $value): array => match ($value) { + '1' => ['attr1' => 'value1'], + '2' => ['attr2' => 'value2'], + default => [], } ); @@ -870,18 +861,18 @@ private function assertGroupedView($view): void } } -/** @ignore */ -final class DefaultChoiceListFactoryTest_Castable +/** + * @ignore + */ +final class DefaultChoiceListFactoryTest_Castable implements \Stringable { - private $property; - - public function __construct($property) - { - $this->property = $property; + public function __construct( + private mixed $property, + ) { } - public function __toString() + public function __toString(): string { - return $this->property; + return (string) $this->property; } } diff --git a/lib/Core/Tests/Extension/Core/ChoiceList/Factory/PropertyAccessDecoratorTest.php b/lib/Core/Tests/Extension/Core/ChoiceList/Factory/PropertyAccessDecoratorTest.php index c344ff27..3264a981 100644 --- a/lib/Core/Tests/Extension/Core/ChoiceList/Factory/PropertyAccessDecoratorTest.php +++ b/lib/Core/Tests/Extension/Core/ChoiceList/Factory/PropertyAccessDecoratorTest.php @@ -30,15 +30,10 @@ */ final class PropertyAccessDecoratorTest extends TestCase { - /** - * @var MockObject|null - */ - private $decoratedFactory; + /** @var MockObject&ChoiceListFactory */ + private MockObject $decoratedFactory; - /** - * @var PropertyAccessDecorator|null - */ - private $factory; + private PropertyAccessDecorator $factory; protected function setUp(): void { @@ -61,7 +56,7 @@ public function create_from_choices_property_path(): void $this->decoratedFactory->expects(self::once()) ->method('createListFromChoices') ->with($choices, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($choices, $callback) => new ArrayChoiceList(array_map($callback, $choices))) + ->willReturnCallback(static fn ($choices, $callback): ArrayChoiceList => new ArrayChoiceList(array_map($callback, $choices))) ; self::assertChoiceListEquals($this->factory->createListFromChoices($choices, 'property')); @@ -75,7 +70,7 @@ public function create_from_choices_property_path_instance(): void $this->decoratedFactory->expects(self::once()) ->method('createListFromChoices') ->with($choices, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($choices, $callback) => new ArrayChoiceList(array_map($callback, $choices))) + ->willReturnCallback(static fn ($choices, $callback): ArrayChoiceList => new ArrayChoiceList(array_map($callback, $choices))) ; self::assertChoiceListEquals($this->factory->createListFromChoices($choices, new PropertyPath('property'))); @@ -89,7 +84,7 @@ public function create_from_loader_property_path(): void $this->decoratedFactory->expects(self::once()) ->method('createListFromLoader') ->with($loader, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($loader, $callback) => new ArrayChoiceList([$callback((object) ['property' => 'value'])])) + ->willReturnCallback(static fn ($loader, $callback): ArrayChoiceList => new ArrayChoiceList([$callback((object) ['property' => 'value'])])) ; self::assertChoiceListEquals($this->factory->createListFromLoader($loader, 'property')); @@ -105,7 +100,7 @@ public function create_from_choices_assume_null_if_value_property_path_unreadabl $this->decoratedFactory->expects(self::once()) ->method('createListFromChoices') ->with($choices, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($choices, $callback) => new ArrayChoiceList(array_map($callback, $choices))) + ->willReturnCallback(static fn ($choices, $callback): ArrayChoiceList => new ArrayChoiceList(array_map($callback, $choices))) ; self::assertChoiceListEquals($this->factory->createListFromChoices($choices, 'property'), null); @@ -117,12 +112,11 @@ public function create_from_choices_assume_null_if_value_property_path_unreadabl public function create_from_choice_loader_assume_null_if_value_property_path_unreadable(): void { $loader = $this->createMock(ChoiceLoader::class); - $list = null; $this->decoratedFactory->expects(self::once()) ->method('createListFromLoader') ->with($loader, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($loader, $callback) => new ArrayChoiceList([$callback(null)])) + ->willReturnCallback(static fn ($loader, $callback): ArrayChoiceList => new ArrayChoiceList([$callback(null)])) ; self::assertChoiceListEquals($this->factory->createListFromLoader($loader, 'property'), null); @@ -136,7 +130,7 @@ public function create_from_loader_property_path_instance(): void $this->decoratedFactory->expects(self::once()) ->method('createListFromLoader') ->with($loader, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($loader, $callback) => new ArrayChoiceList([$callback((object) ['property' => 'value'])])) + ->willReturnCallback(static fn ($loader, $callback): ArrayChoiceList => new ArrayChoiceList([$callback((object) ['property' => 'value'])])) ; self::assertChoiceListEquals($this->factory->createListFromLoader($loader, new PropertyPath('property'))); @@ -154,7 +148,7 @@ public function create_view_preferred_choices_as_property_path(): void $this->decoratedFactory->expects(self::once()) ->method('createView') ->with($list, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($list, $preferred) => new ChoiceListView([], [$preferred((object) ['property' => true])])) + ->willReturnCallback(static fn ($list, $preferred): ChoiceListView => new ChoiceListView([], [$preferred((object) ['property' => true])])) ; self::assertEquals( @@ -174,7 +168,7 @@ public function create_view_preferred_choices_as_property_path_instance(): void $this->decoratedFactory->expects(self::once()) ->method('createView') ->with($list, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($list, $preferred) => new ChoiceListView([], [$preferred((object) ['property' => true])])) + ->willReturnCallback(static fn ($list, $preferred): ChoiceListView => new ChoiceListView([], [$preferred((object) ['property' => true])])) ; self::assertEquals( @@ -196,7 +190,7 @@ public function create_view_assume_null_if_preferred_choices_property_path_unrea $this->decoratedFactory->expects(self::once()) ->method('createView') ->with($list, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($list, $preferred) => new ChoiceListView([], [$preferred((object) ['category' => null])])) + ->willReturnCallback(static fn ($list, $preferred): ChoiceListView => new ChoiceListView([], [$preferred((object) ['category' => null])])) ; self::assertEquals( @@ -216,7 +210,7 @@ public function create_view_labels_as_property_path(): void $this->decoratedFactory->expects(self::once()) ->method('createView') ->with($list, null, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($list, $preferred, $label) => new ChoiceListView([$label((object) ['property' => 'label'])])) + ->willReturnCallback(static fn ($list, $preferred, $label): ChoiceListView => new ChoiceListView([$label((object) ['property' => 'label'])])) ; self::assertEquals( @@ -237,7 +231,7 @@ public function create_view_labels_as_property_path_instance(): void $this->decoratedFactory->expects(self::once()) ->method('createView') ->with($list, null, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($list, $preferred, $label) => new ChoiceListView([$label((object) ['property' => 'label'])])) + ->willReturnCallback(static fn ($list, $preferred, $label): ChoiceListView => new ChoiceListView([$label((object) ['property' => 'label'])])) ; self::assertEquals( @@ -258,7 +252,7 @@ public function create_view_indices_as_property_path(): void $this->decoratedFactory->expects(self::once()) ->method('createView') ->with($list, null, null, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($list, $preferred, $label, $index) => new ChoiceListView([$index((object) ['property' => 'index'])])) + ->willReturnCallback(static fn ($list, $preferred, $label, $index): ChoiceListView => new ChoiceListView([$index((object) ['property' => 'index'])])) ; self::assertEquals( @@ -280,7 +274,7 @@ public function create_view_indices_as_property_path_instance(): void $this->decoratedFactory->expects(self::once()) ->method('createView') ->with($list, null, null, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($list, $preferred, $label, $index) => new ChoiceListView([$index((object) ['property' => 'index'])])) + ->willReturnCallback(static fn ($list, $preferred, $label, $index): ChoiceListView => new ChoiceListView([$index((object) ['property' => 'index'])])) ; self::assertEquals( @@ -302,7 +296,7 @@ public function create_view_groups_as_property_path(): void $this->decoratedFactory->expects(self::once()) ->method('createView') ->with($list, null, null, null, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($list, $preferred, $label, $index, $groupBy) => new ChoiceListView([$groupBy((object) ['property' => 'group'])])) + ->willReturnCallback(static fn ($list, $preferred, $label, $index, $groupBy): ChoiceListView => new ChoiceListView([$groupBy((object) ['property' => 'group'])])) ; self::assertEquals( @@ -325,7 +319,7 @@ public function create_view_groups_as_property_path_instance(): void $this->decoratedFactory->expects(self::once()) ->method('createView') ->with($list, null, null, null, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($list, $preferred, $label, $index, $groupBy) => new ChoiceListView([$groupBy((object) ['property' => 'group'])])) + ->willReturnCallback(static fn ($list, $preferred, $label, $index, $groupBy): ChoiceListView => new ChoiceListView([$groupBy((object) ['property' => 'group'])])) ; self::assertEquals( @@ -350,7 +344,7 @@ public function create_view_assume_null_if_groups_property_path_unreadable(): vo $this->decoratedFactory->expects(self::once()) ->method('createView') ->with($list, null, null, null, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($list, $preferred, $label, $index, $groupBy) => new ChoiceListView([$groupBy((object) ['group' => null])])) + ->willReturnCallback(static fn ($list, $preferred, $label, $index, $groupBy): ChoiceListView => new ChoiceListView([$groupBy((object) ['group' => null])])) ; self::assertEquals( @@ -373,7 +367,7 @@ public function create_view_attr_as_property_path(): void $this->decoratedFactory->expects(self::once()) ->method('createView') ->with($list, null, null, null, null, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($list, $preferred, $label, $index, $groupBy, $attr) => new ChoiceListView([$attr((object) ['property' => 'attr'])])) + ->willReturnCallback(static fn ($list, $preferred, $label, $index, $groupBy, $attr): ChoiceListView => new ChoiceListView([$attr((object) ['property' => 'attr'])])) ; self::assertEquals( @@ -397,7 +391,7 @@ public function create_view_attr_as_property_path_instance(): void $this->decoratedFactory->expects(self::once()) ->method('createView') ->with($list, null, null, null, null, self::isInstanceOf('\Closure')) - ->willReturnCallback(static fn ($list, $preferred, $label, $index, $groupBy, $attr) => new ChoiceListView([$attr((object) ['property' => 'attr'])])) + ->willReturnCallback(static fn ($list, $preferred, $label, $index, $groupBy, $attr): ChoiceListView => new ChoiceListView([$attr((object) ['property' => 'attr'])])) ; self::assertEquals( diff --git a/lib/Core/Tests/Extension/Core/ChoiceList/LazyChoiceListTest.php b/lib/Core/Tests/Extension/Core/ChoiceList/LazyChoiceListTest.php index e5ded613..34f5f776 100644 --- a/lib/Core/Tests/Extension/Core/ChoiceList/LazyChoiceListTest.php +++ b/lib/Core/Tests/Extension/Core/ChoiceList/LazyChoiceListTest.php @@ -26,22 +26,15 @@ */ final class LazyChoiceListTest extends TestCase { - /** - * @var LazyChoiceList|null - */ - private $list; - - /** - * @var MockObject|null - */ - private $loadedList; - - /** - * @var MockObject|null - */ - private $loader; - - private $value; + private LazyChoiceList $list; + + /** @var MockObject&ChoiceList */ + private MockObject $loadedList; + + /** @var MockObject&ChoiceLoader */ + private MockObject $loader; + + private \Closure $value; protected function setUp(): void { diff --git a/lib/Core/Tests/Extension/Core/ChoiceList/Loader/CallbackChoiceLoaderTest.php b/lib/Core/Tests/Extension/Core/ChoiceList/Loader/CallbackChoiceLoaderTest.php index 4ed24901..61a08ea5 100644 --- a/lib/Core/Tests/Extension/Core/ChoiceList/Loader/CallbackChoiceLoaderTest.php +++ b/lib/Core/Tests/Extension/Core/ChoiceList/Loader/CallbackChoiceLoaderTest.php @@ -24,30 +24,17 @@ */ final class CallbackChoiceLoaderTest extends TestCase { - /** - * @var CallbackChoiceLoader|null - */ - private static $loader; - - /** - * @var callable|null - */ - private static $value; + private static ?CallbackChoiceLoader $loader; + private static ?LazyChoiceList $lazyChoiceList; - /** - * @var array|null - */ - private static $choices; + /** @var callable|null */ + private static $value; - /** - * @var string[]|null - */ - private static $choiceValues; + /** @var object[]|null */ + private static ?array $choices; - /** - * @var LazyChoiceList|null - */ - private static $lazyChoiceList; + /** @var string[]|null */ + private static ?array $choiceValues; public static function setUpBeforeClass(): void { diff --git a/lib/Core/Tests/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php b/lib/Core/Tests/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php index d67f669e..e7c9bd14 100644 --- a/lib/Core/Tests/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php +++ b/lib/Core/Tests/Extension/Core/DataTransformer/ChoiceToValueTransformerTest.php @@ -25,15 +25,8 @@ */ final class ChoiceToValueTransformerTest extends TestCase { - /** - * @var ChoiceToValueTransformer|null - */ - private $transformer; - - /** - * @var ChoiceToValueTransformer|null - */ - private $transformerWithNull; + private ?ChoiceToValueTransformer $transformer; + private ?ChoiceToValueTransformer $transformerWithNull; protected function setUp(): void { @@ -50,6 +43,17 @@ protected function tearDown(): void $this->transformerWithNull = null; } + /** + * @dataProvider transformProvider + * + * @test + */ + public function transform($in, $out, $inWithNull, $outWithNull): void + { + self::assertSame($out, $this->transformer->transform($in)); + self::assertSame($outWithNull, $this->transformerWithNull->transform($inWithNull)); + } + public static function transformProvider(): iterable { return [ @@ -62,14 +66,14 @@ public static function transformProvider(): iterable } /** - * @dataProvider transformProvider + * @dataProvider reverseTransformProvider * * @test */ - public function transform($in, $out, $inWithNull, $outWithNull): void + public function reverse_transform($in, $out, $inWithNull, $outWithNull): void { - self::assertSame($out, $this->transformer->transform($in)); - self::assertSame($outWithNull, $this->transformerWithNull->transform($inWithNull)); + self::assertSame($out, $this->transformer->reverseTransform($in)); + self::assertSame($outWithNull, $this->transformerWithNull->reverseTransform($inWithNull)); } public static function reverseTransformProvider(): iterable @@ -85,14 +89,15 @@ public static function reverseTransformProvider(): iterable } /** - * @dataProvider reverseTransformProvider + * @dataProvider reverseTransformExpectsStringOrNullProvider * * @test */ - public function reverse_transform($in, $out, $inWithNull, $outWithNull): void + public function reverse_transform_expects_string_or_null($value): void { - self::assertSame($out, $this->transformer->reverseTransform($in)); - self::assertSame($outWithNull, $this->transformerWithNull->reverseTransform($inWithNull)); + $this->expectException(TransformationFailedException::class); + + $this->transformer->reverseTransform($value); } public static function reverseTransformExpectsStringOrNullProvider(): iterable @@ -104,16 +109,4 @@ public static function reverseTransformExpectsStringOrNullProvider(): iterable [[]], ]; } - - /** - * @dataProvider reverseTransformExpectsStringOrNullProvider - * - * @test - */ - public function reverse_transform_expects_string_or_null($value): void - { - $this->expectException(TransformationFailedException::class); - - $this->transformer->reverseTransform($value); - } } diff --git a/lib/Core/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php b/lib/Core/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php index 75e76ce2..1348ffda 100644 --- a/lib/Core/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php +++ b/lib/Core/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php @@ -27,8 +27,8 @@ final class DateTimeToLocalizedStringTransformerTest extends TestCase { use assertDateTimeEqualsTrait; - protected $dateTime; - protected $dateTimeWithoutSeconds; + protected ?\DateTimeImmutable $dateTime; + protected ?\DateTimeImmutable $dateTimeWithoutSeconds; protected function setUp(): void { @@ -49,38 +49,6 @@ protected function tearDown(): void $this->dateTimeWithoutSeconds = null; } - public static function dataProvider(): iterable - { - return [ - [\IntlDateFormatter::SHORT, null, null, '03.02.10, 04:05', '2010-02-03 04:05:00 UTC'], - [\IntlDateFormatter::MEDIUM, null, null, '03.02.2010, 04:05', '2010-02-03 04:05:00 UTC'], - [\IntlDateFormatter::LONG, null, null, '3. Februar 2010 um 04:05', '2010-02-03 04:05:00 UTC'], - [\IntlDateFormatter::FULL, null, null, 'Mittwoch, 3. Februar 2010 um 04:05', '2010-02-03 04:05:00 UTC'], - [\IntlDateFormatter::SHORT, \IntlDateFormatter::NONE, null, '03.02.10', '2010-02-03 00:00:00 UTC'], - [\IntlDateFormatter::MEDIUM, \IntlDateFormatter::NONE, null, '03.02.2010', '2010-02-03 00:00:00 UTC'], - [\IntlDateFormatter::LONG, \IntlDateFormatter::NONE, null, '3. Februar 2010', '2010-02-03 00:00:00 UTC'], - [\IntlDateFormatter::FULL, \IntlDateFormatter::NONE, null, 'Mittwoch, 3. Februar 2010', '2010-02-03 00:00:00 UTC'], - [null, \IntlDateFormatter::SHORT, null, '03.02.2010, 04:05', '2010-02-03 04:05:00 UTC'], - [null, \IntlDateFormatter::MEDIUM, null, '03.02.2010, 04:05:06', '2010-02-03 04:05:06 UTC'], - [null, \IntlDateFormatter::LONG, null, '03.02.2010, 04:05:06 UTC', '2010-02-03 04:05:06 UTC'], - // see below for extra test case for time format FULL - [\IntlDateFormatter::NONE, \IntlDateFormatter::SHORT, null, '04:05', '1970-01-01 04:05:00 UTC'], - [\IntlDateFormatter::NONE, \IntlDateFormatter::MEDIUM, null, '04:05:06', '1970-01-01 04:05:06 UTC'], - [\IntlDateFormatter::NONE, \IntlDateFormatter::LONG, null, '04:05:06 UTC', '1970-01-01 04:05:06 UTC'], - [null, null, 'yyyy-MM-dd HH:mm:00', '2010-02-03 04:05:00', '2010-02-03 04:05:00 UTC'], - [null, null, 'yyyy-MM-dd HH:mm', '2010-02-03 04:05', '2010-02-03 04:05:00 UTC'], - [null, null, 'yyyy-MM-dd HH', '2010-02-03 04', '2010-02-03 04:00:00 UTC'], - [null, null, 'yyyy-MM-dd', '2010-02-03', '2010-02-03 00:00:00 UTC'], - [null, null, 'yyyy-MM', '2010-02', '2010-02-01 00:00:00 UTC'], - [null, null, 'yyyy', '2010', '2010-01-01 00:00:00 UTC'], - [null, null, 'dd-MM-yyyy', '03-02-2010', '2010-02-03 00:00:00 UTC'], - [null, null, 'HH:mm:ss', '04:05:06', '1970-01-01 04:05:06 UTC'], - [null, null, 'HH:mm:00', '04:05:00', '1970-01-01 04:05:00 UTC'], - [null, null, 'HH:mm', '04:05', '1970-01-01 04:05:00 UTC'], - [null, null, 'HH', '04', '1970-01-01 04:00:00 UTC'], - ]; - } - /** * @dataProvider dataProvider * @@ -219,7 +187,41 @@ public function reverse_transform($dateFormat, $timeFormat, $pattern, $input, $o self::assertEquals($output, $transformer->reverseTransform($input)); } - /** @test */ + public static function dataProvider(): iterable + { + return [ + [\IntlDateFormatter::SHORT, null, null, '03.02.10, 04:05', '2010-02-03 04:05:00 UTC'], + [\IntlDateFormatter::MEDIUM, null, null, '03.02.2010, 04:05', '2010-02-03 04:05:00 UTC'], + [\IntlDateFormatter::LONG, null, null, '3. Februar 2010 um 04:05', '2010-02-03 04:05:00 UTC'], + [\IntlDateFormatter::FULL, null, null, 'Mittwoch, 3. Februar 2010 um 04:05', '2010-02-03 04:05:00 UTC'], + [\IntlDateFormatter::SHORT, \IntlDateFormatter::NONE, null, '03.02.10', '2010-02-03 00:00:00 UTC'], + [\IntlDateFormatter::MEDIUM, \IntlDateFormatter::NONE, null, '03.02.2010', '2010-02-03 00:00:00 UTC'], + [\IntlDateFormatter::LONG, \IntlDateFormatter::NONE, null, '3. Februar 2010', '2010-02-03 00:00:00 UTC'], + [\IntlDateFormatter::FULL, \IntlDateFormatter::NONE, null, 'Mittwoch, 3. Februar 2010', '2010-02-03 00:00:00 UTC'], + [null, \IntlDateFormatter::SHORT, null, '03.02.2010, 04:05', '2010-02-03 04:05:00 UTC'], + [null, \IntlDateFormatter::MEDIUM, null, '03.02.2010, 04:05:06', '2010-02-03 04:05:06 UTC'], + [null, \IntlDateFormatter::LONG, null, '03.02.2010, 04:05:06 UTC', '2010-02-03 04:05:06 UTC'], + // see below for extra test case for time format FULL + [\IntlDateFormatter::NONE, \IntlDateFormatter::SHORT, null, '04:05', '1970-01-01 04:05:00 UTC'], + [\IntlDateFormatter::NONE, \IntlDateFormatter::MEDIUM, null, '04:05:06', '1970-01-01 04:05:06 UTC'], + [\IntlDateFormatter::NONE, \IntlDateFormatter::LONG, null, '04:05:06 UTC', '1970-01-01 04:05:06 UTC'], + [null, null, 'yyyy-MM-dd HH:mm:00', '2010-02-03 04:05:00', '2010-02-03 04:05:00 UTC'], + [null, null, 'yyyy-MM-dd HH:mm', '2010-02-03 04:05', '2010-02-03 04:05:00 UTC'], + [null, null, 'yyyy-MM-dd HH', '2010-02-03 04', '2010-02-03 04:00:00 UTC'], + [null, null, 'yyyy-MM-dd', '2010-02-03', '2010-02-03 00:00:00 UTC'], + [null, null, 'yyyy-MM', '2010-02', '2010-02-01 00:00:00 UTC'], + [null, null, 'yyyy', '2010', '2010-01-01 00:00:00 UTC'], + [null, null, 'dd-MM-yyyy', '03-02-2010', '2010-02-03 00:00:00 UTC'], + [null, null, 'HH:mm:ss', '04:05:06', '1970-01-01 04:05:06 UTC'], + [null, null, 'HH:mm:00', '04:05:00', '1970-01-01 04:05:00 UTC'], + [null, null, 'HH:mm', '04:05', '1970-01-01 04:05:00 UTC'], + [null, null, 'HH', '04', '1970-01-01 04:00:00 UTC'], + ]; + } + + /** + * @test + */ public function reverse_transform_full_time(): void { $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', null, \IntlDateFormatter::FULL); diff --git a/lib/Core/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php b/lib/Core/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php index c667d44d..d7967d2b 100644 --- a/lib/Core/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php +++ b/lib/Core/Tests/Extension/Core/DataTransformer/DateTimeToRfc3339TransformerTest.php @@ -22,8 +22,8 @@ */ final class DateTimeToRfc3339TransformerTest extends TestCase { - protected $dateTime; - protected $dateTimeWithoutSeconds; + protected ?\DateTimeImmutable $dateTime; + protected ?\DateTimeImmutable $dateTimeWithoutSeconds; protected function setUp(): void { @@ -39,28 +39,6 @@ protected function tearDown(): void $this->dateTimeWithoutSeconds = null; } - public static function allProvider(): iterable - { - return [ - ['UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06Z'], - ['UTC', 'UTC', null, ''], - ['America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:06 America/New_York', '2010-02-03T17:05:06+08:00'], - ['America/New_York', 'Asia/Hong_Kong', null, ''], - ['UTC', 'Asia/Hong_Kong', '2010-02-03 04:05:06 UTC', '2010-02-03T12:05:06+08:00'], - ['America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05:06Z'], - ]; - } - - public static function reverseTransformProvider(): array - { - return array_merge((array) self::allProvider(), [ - // format without seconds, as appears in some browsers - ['UTC', 'UTC', '2010-02-03 04:05:00 UTC', '2010-02-03T04:05Z'], - ['America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:00 America/New_York', '2010-02-03T17:05+08:00'], - ['Europe/Amsterdam', 'Europe/Amsterdam', '2013-08-21 10:30:00 Europe/Amsterdam', '2013-08-21T08:30:00Z'], - ]); - } - /** * @dataProvider allProvider * @@ -85,7 +63,21 @@ public function transform_date_time_immutable($fromTz, $toTz, $from, $to): void self::assertSame($to, $transformer->transform($from !== null ? new \DateTimeImmutable($from) : null)); } - /** @test */ + public static function allProvider(): iterable + { + return [ + ['UTC', 'UTC', '2010-02-03 04:05:06 UTC', '2010-02-03T04:05:06Z'], + ['UTC', 'UTC', null, ''], + ['America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:06 America/New_York', '2010-02-03T17:05:06+08:00'], + ['America/New_York', 'Asia/Hong_Kong', null, ''], + ['UTC', 'Asia/Hong_Kong', '2010-02-03 04:05:06 UTC', '2010-02-03T12:05:06+08:00'], + ['America/New_York', 'UTC', '2010-02-03 04:05:06 America/New_York', '2010-02-03T09:05:06Z'], + ]; + } + + /** + * @test + */ public function transform_requires_valid_date_time(): void { $transformer = new DateTimeToRfc3339Transformer(); @@ -111,7 +103,19 @@ public function reverse_transform($toTz, $fromTz, $to, $from): void } } - /** @test */ + public static function reverseTransformProvider(): array + { + return array_merge((array) self::allProvider(), [ + // format without seconds, as appears in some browsers + ['UTC', 'UTC', '2010-02-03 04:05:00 UTC', '2010-02-03T04:05Z'], + ['America/New_York', 'Asia/Hong_Kong', '2010-02-03 04:05:00 America/New_York', '2010-02-03T17:05+08:00'], + ['Europe/Amsterdam', 'Europe/Amsterdam', '2013-08-21 10:30:00 Europe/Amsterdam', '2013-08-21T08:30:00Z'], + ]); + } + + /** + * @test + */ public function reverse_transform_requires_string(): void { $transformer = new DateTimeToRfc3339Transformer(); diff --git a/lib/Core/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php b/lib/Core/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php index 5ac8a206..b9ef8872 100644 --- a/lib/Core/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php +++ b/lib/Core/Tests/Extension/Core/DataTransformer/DateTimeToStringTransformerTest.php @@ -25,6 +25,20 @@ final class DateTimeToStringTransformerTest extends TestCase { use assertDateTimeEqualsTrait; + /** + * @dataProvider dataProvider + * + * @test + */ + public function transform($format, $output, $input): void + { + $transformer = new DateTimeToStringTransformer('UTC', 'UTC', $format); + + $input = new \DateTimeImmutable($input); + + self::assertEquals($output, $transformer->transform($input)); + } + public static function dataProvider(): iterable { return [ @@ -74,20 +88,8 @@ public static function dataProvider(): iterable } /** - * @dataProvider dataProvider - * * @test */ - public function transform($format, $output, $input): void - { - $transformer = new DateTimeToStringTransformer('UTC', 'UTC', $format); - - $input = new \DateTimeImmutable($input); - - self::assertEquals($output, $transformer->transform($input)); - } - - /** @test */ public function transform_empty(): void { $transformer = new DateTimeToStringTransformer(); diff --git a/lib/Core/Tests/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformerTest.php b/lib/Core/Tests/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformerTest.php index 826b702d..ff010cea 100644 --- a/lib/Core/Tests/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformerTest.php +++ b/lib/Core/Tests/Extension/Core/DataTransformer/IntegerToLocalizedStringTransformerTest.php @@ -82,13 +82,15 @@ public static function transformWithRoundingProvider(): iterable } /** + * @param self::ROUND_* $roundingMode + * * @dataProvider transformWithRoundingProvider * * @test */ - public function transform_with_rounding($input, $output, $roundingMode): void + public function transform_with_rounding(float $input, string $output, int $roundingMode): void { - $transformer = new IntegerToLocalizedStringTransformer(null, $roundingMode); + $transformer = new IntegerToLocalizedStringTransformer(false, $roundingMode); self::assertEquals($output, $transformer->transform($input)); } @@ -183,13 +185,15 @@ public static function reverseTransformWithRoundingProvider(): iterable } /** + * @param self::ROUND_* $roundingMode + * * @dataProvider reverseTransformWithRoundingProvider * * @test */ - public function reverse_transform_with_rounding($input, $output, $roundingMode): void + public function reverse_transform_with_rounding(string $input, int $output, int $roundingMode): void { - $transformer = new IntegerToLocalizedStringTransformer(null, $roundingMode); + $transformer = new IntegerToLocalizedStringTransformer(false, $roundingMode); self::assertEquals($output, $transformer->reverseTransform($input)); } @@ -219,7 +223,7 @@ public function reverse_transform_expects_valid_number(): void * * @test */ - public function reverse_transform_expects_integer($number, $locale): void + public function reverse_transform_expects_integer(string $number, string $locale): void { IntlTestHelper::requireFullIntl($this, '70.1'); diff --git a/lib/Core/Tests/Extension/Core/DataTransformer/IntegerToStringTransformerTest.php b/lib/Core/Tests/Extension/Core/DataTransformer/IntegerToStringTransformerTest.php index a08c6b00..d4c419a1 100644 --- a/lib/Core/Tests/Extension/Core/DataTransformer/IntegerToStringTransformerTest.php +++ b/lib/Core/Tests/Extension/Core/DataTransformer/IntegerToStringTransformerTest.php @@ -29,6 +29,20 @@ protected function setUp(): void \Locale::setDefault('en'); } + /** + * @param IntegerToStringTransformer::ROUND_* $roundingMode + * + * @dataProvider transformWithRoundingProvider + * + * @test + */ + public function transform_with_rounding(float $input, string $output, int $roundingMode): void + { + $transformer = new IntegerToStringTransformer($roundingMode); + + self::assertEquals($output, $transformer->transform($input)); + } + public static function transformWithRoundingProvider(): iterable { return [ @@ -81,18 +95,8 @@ public static function transformWithRoundingProvider(): iterable } /** - * @dataProvider transformWithRoundingProvider - * * @test */ - public function transform_with_rounding(float $input, string $output, $roundingMode): void - { - $transformer = new IntegerToStringTransformer($roundingMode); - - self::assertEquals($output, $transformer->transform($input)); - } - - /** @test */ public function reverse_transform(): void { $transformer = new IntegerToStringTransformer(); @@ -111,6 +115,20 @@ public function reverse_transform_empty(): void self::assertNull($transformer->reverseTransform('')); } + /** + * @param IntegerToStringTransformer::ROUND_* $roundingMode + * + * @dataProvider reverseTransformWithRoundingProvider + * + * @test + */ + public function reverse_transform_with_rounding(string $input, int $output, int $roundingMode): void + { + $transformer = new IntegerToStringTransformer($roundingMode); + + self::assertEquals($output, $transformer->reverseTransform($input)); + } + public static function reverseTransformWithRoundingProvider(): iterable { return [ @@ -167,14 +185,6 @@ public static function reverseTransformWithRoundingProvider(): iterable * * @test */ - public function reverse_transform_with_rounding(string $input, $output, int $roundingMode): void - { - $transformer = new IntegerToStringTransformer($roundingMode); - - self::assertEquals($output, $transformer->reverseTransform($input)); - } - - /** @test */ public function reverse_transform_expects_scalar(): void { $transformer = new IntegerToStringTransformer(); diff --git a/lib/Core/Tests/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformerTest.php b/lib/Core/Tests/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformerTest.php index c2eee3b9..97bf9370 100644 --- a/lib/Core/Tests/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformerTest.php +++ b/lib/Core/Tests/Extension/Core/DataTransformer/MoneyToLocalizedStringTransformerTest.php @@ -46,19 +46,6 @@ private function parseMoneyAsDecimal($input, string $currency = 'EUR') return $moneyParser->parse((string) $input, new Currency($currency)); } - public static function provideTransformations(): iterable - { - return [ - [null, '', 'de_AT'], - [1, '€ 1,00', 'de_AT'], - [1.5, '€ 1,50', 'de_AT'], - [1234.5, '€ 1234,50', 'de_AT'], - [12345.912, '€ 12345,91', 'de_AT'], - [1234.5, '1234,50 €', 'ru'], - [1234.5, '1234,50 €', 'fi'], - ]; - } - /** * @dataProvider provideTransformations * @@ -98,7 +85,7 @@ public function transform_without_currency($from, $to, $locale): void $from = new MoneyValue($this->parseMoneyAsDecimal($from), false); } - $to = preg_replace('#(\s?\p{Sc}\s?)#u', '', $to); + $to = preg_replace('#(\s?\p{Sc}\s?)#u', '', (string) $to); self::assertEquals($to, $transformer->transform($from)); } @@ -118,17 +105,6 @@ public function transform_without_currency_and_different_default_currency(): voi self::assertEquals('1234,50', $transformer->transform($from)); } - public static function provideTransformationsWithGrouping(): iterable - { - return [ - [1234.5, '1.234,50 €', 'de_DE'], - [12345.912, '12.345,91 €', 'de_DE'], - [1234.5, '1 234,50 €', 'fr'], - [1234.5, '1 234,50 €', 'ru'], - [1234.5, '1 234,50 €', 'fi'], - ]; - } - /** * @dataProvider provideTransformationsWithGrouping * @@ -180,7 +156,7 @@ public function reverse_transform_without_currency($to, $from, $locale): void \Locale::setDefault($locale); - $from = preg_replace('#(\s?\p{Sc}\s?)#u', '', $from); + $from = preg_replace('#(\s?\p{Sc}\s?)#u', '', (string) $from); $transformer = new MoneyToLocalizedStringTransformer('USD'); if ($to !== null) { @@ -190,6 +166,19 @@ public function reverse_transform_without_currency($to, $from, $locale): void self::assertEquals($to, $transformer->reverseTransform($from)); } + public static function provideTransformations(): iterable + { + return [ + [null, '', 'de_AT'], + [1, '€ 1,00', 'de_AT'], + [1.5, '€ 1,50', 'de_AT'], + [1234.5, '€ 1234,50', 'de_AT'], + [12345.912, '€ 12345,91', 'de_AT'], + [1234.5, '1234,50 €', 'ru'], + [1234.5, '1234,50 €', 'fi'], + ]; + } + /** * @dataProvider provideTransformationsWithGrouping * @@ -208,6 +197,17 @@ public function reverse_transform_with_grouping($to, $from, $locale): void self::assertEquals($to, $transformer->reverseTransform($from)); } + public static function provideTransformationsWithGrouping(): iterable + { + return [ + [1234.5, '1.234,50 €', 'de_DE'], + [12345.912, '12.345,91 €', 'de_DE'], + [1234.5, '1 234,50 €', 'fr'], + [1234.5, '1 234,50 €', 'ru'], + [1234.5, '1 234,50 €', 'fi'], + ]; + } + /** * @see https://github.com/symfony/symfony/issues/7609 * diff --git a/lib/Core/Tests/Extension/Core/DataTransformer/MoneyToStringTransformerTest.php b/lib/Core/Tests/Extension/Core/DataTransformer/MoneyToStringTransformerTest.php index 434fda30..d727b7af 100644 --- a/lib/Core/Tests/Extension/Core/DataTransformer/MoneyToStringTransformerTest.php +++ b/lib/Core/Tests/Extension/Core/DataTransformer/MoneyToStringTransformerTest.php @@ -38,18 +38,6 @@ private function parseMoneyAsDecimal($input, string $currency = 'EUR') return $moneyParser->parse((string) $input, new Currency($currency)); } - public static function provideTransformations(): iterable - { - return [ - [null, ''], - [1, 'EUR 1.00'], - [1.5, 'EUR 1.50'], - [1234.5, 'EUR 1234.50'], - [12345.912, 'EUR 12345.91'], - [1234.5, 'EUR 1234.50'], - ]; - } - /** * @dataProvider provideTransformations * @@ -79,7 +67,7 @@ public function transform_without_currency($from, $to): void $from = new MoneyValue($this->parseMoneyAsDecimal($from), false); } - $to = mb_substr($to, 4); + $to = mb_substr((string) $to, 4); self::assertEquals($to, $transformer->transform($from)); } @@ -111,13 +99,27 @@ public function reverse_transform_without_currency($to, $from): void if ($to !== null) { $to = new MoneyValue($this->parseMoneyAsDecimal($to, 'USD'), false); - $from = mb_substr($from, 4); + $from = mb_substr((string) $from, 4); } self::assertEquals($to, $transformer->reverseTransform($from)); } - /** @test */ + public static function provideTransformations(): iterable + { + return [ + [null, ''], + [1, 'EUR 1.00'], + [1.5, 'EUR 1.50'], + [1234.5, 'EUR 1234.50'], + [12345.912, 'EUR 12345.91'], + [1234.5, 'EUR 1234.50'], + ]; + } + + /** + * @test + */ public function transform_expects_money_value(): void { $transformer = new MoneyToStringTransformer('EUR'); diff --git a/lib/Core/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php b/lib/Core/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php index 486c4d88..671f1f46 100644 --- a/lib/Core/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php +++ b/lib/Core/Tests/Extension/Core/DataTransformer/NumberToLocalizedStringTransformerTest.php @@ -30,25 +30,12 @@ protected function setUp(): void \Locale::setDefault('en'); } - public static function provideTransformations(): iterable - { - return [ - [null, '', 'de_AT'], - [1, '1', 'de_AT'], - [1.5, '1,5', 'de_AT'], - [1234.5, '1234,5', 'de_AT'], - [12345.912, '12345,912', 'de_AT'], - [1234.5, '1234,5', 'ru'], - [1234.5, '1234,5', 'fi'], - ]; - } - /** * @dataProvider provideTransformations * * @test */ - public function transform($from, $to, $locale): void + public function transform(float|int|null $from, string $to, string $locale): void { // Since we test against other locales, we need the full implementation IntlTestHelper::requireFullIntl($this, '70.1'); @@ -60,23 +47,12 @@ public function transform($from, $to, $locale): void self::assertSame($to, $transformer->transform($from)); } - public static function provideTransformationsWithGrouping(): iterable - { - return [ - [1234.5, '1.234,5', 'de_DE'], - [12345.912, '12.345,912', 'de_DE'], - [1234.5, '1 234,5', 'fr'], - [1234.5, '1 234,5', 'ru'], - [1234.5, '1 234,5', 'fi'], - ]; - } - /** * @dataProvider provideTransformationsWithGrouping * * @test */ - public function transform_with_grouping($from, $to, $locale): void + public function transform_with_grouping(float $from, string $to, string $locale): void { // Since we test against other locales, we need the full implementation IntlTestHelper::requireFullIntl($this, '70.1'); @@ -102,6 +78,23 @@ public function transform_with_scale(): void self::assertEquals('678,92', $transformer->transform(678.916)); } + /** + * @dataProvider transformWithRoundingProvider + * + * @test + */ + public function transform_with_rounding($scale, $input, $output, $roundingMode): void + { + // Since we test against "de_AT", we need the full implementation + IntlTestHelper::requireFullIntl($this, '70.1'); + + \Locale::setDefault('de_AT'); + + $transformer = new NumberToLocalizedStringTransformer($scale, null, $roundingMode); + + self::assertEquals($output, $transformer->transform($input)); + } + public static function transformWithRoundingProvider(): iterable { return [ @@ -192,23 +185,8 @@ public static function transformWithRoundingProvider(): iterable } /** - * @dataProvider transformWithRoundingProvider - * * @test */ - public function transform_with_rounding($scale, $input, $output, $roundingMode): void - { - // Since we test against "de_AT", we need the full implementation - IntlTestHelper::requireFullIntl($this, '70.1'); - - \Locale::setDefault('de_AT'); - - $transformer = new NumberToLocalizedStringTransformer($scale, null, $roundingMode); - - self::assertEquals($output, $transformer->transform($input)); - } - - /** @test */ public function transform_does_not_round_if_no_scale(): void { // Since we test against "de_AT", we need the full implementation @@ -216,7 +194,7 @@ public function transform_does_not_round_if_no_scale(): void \Locale::setDefault('de_AT'); - $transformer = new NumberToLocalizedStringTransformer(null, null, NumberToLocalizedStringTransformer::ROUND_DOWN); + $transformer = new NumberToLocalizedStringTransformer(null, false, NumberToLocalizedStringTransformer::ROUND_DOWN); self::assertEquals('1234,547', $transformer->transform(1234.547)); } @@ -238,6 +216,19 @@ public function reverse_transform($to, $from, $locale): void self::assertEquals($to, $transformer->reverseTransform($from)); } + public static function provideTransformations(): iterable + { + return [ + [null, '', 'de_AT'], + [1, '1', 'de_AT'], + [1.5, '1,5', 'de_AT'], + [1234.5, '1234,5', 'de_AT'], + [12345.912, '12345,912', 'de_AT'], + [1234.5, '1234,5', 'ru'], + [1234.5, '1234,5', 'fi'], + ]; + } + /** * @dataProvider provideTransformationsWithGrouping * @@ -255,6 +246,17 @@ public function reverse_transform_with_grouping($to, $from, $locale): void self::assertEquals($to, $transformer->reverseTransform($from)); } + public static function provideTransformationsWithGrouping(): iterable + { + return [ + [1234.5, '1.234,5', 'de_DE'], + [12345.912, '12.345,912', 'de_DE'], + [1234.5, '1 234,5', 'fr'], + [1234.5, '1 234,5', 'ru'], + [1234.5, '1 234,5', 'fi'], + ]; + } + /** * @see https://github.com/symfony/symfony/issues/7609 * @@ -287,6 +289,18 @@ public function reverse_transform_with_grouping_but_without_group_separator(): v self::assertEquals(12345.912, $transformer->reverseTransform('12345,912')); } + /** + * @dataProvider reverseTransformWithRoundingProvider + * + * @test + */ + public function reverse_transform_with_rounding($scale, $input, $output, $roundingMode): void + { + $transformer = new NumberToLocalizedStringTransformer($scale, null, $roundingMode); + + self::assertEquals($output, $transformer->reverseTransform($input)); + } + public static function reverseTransformWithRoundingProvider(): iterable { return [ @@ -377,21 +391,11 @@ public static function reverseTransformWithRoundingProvider(): iterable } /** - * @dataProvider reverseTransformWithRoundingProvider - * * @test */ - public function reverse_transform_with_rounding($scale, $input, $output, $roundingMode): void - { - $transformer = new NumberToLocalizedStringTransformer($scale, null, $roundingMode); - - self::assertEquals($output, $transformer->reverseTransform($input)); - } - - /** @test */ public function reverse_transform_does_not_round_if_no_scale(): void { - $transformer = new NumberToLocalizedStringTransformer(null, null, NumberToLocalizedStringTransformer::ROUND_DOWN); + $transformer = new NumberToLocalizedStringTransformer(null, false, NumberToLocalizedStringTransformer::ROUND_DOWN); self::assertEquals(1234.547, $transformer->reverseTransform('1234,547')); } diff --git a/lib/Core/Tests/Extension/Core/DataTransformer/NumberToStringTransformerTest.php b/lib/Core/Tests/Extension/Core/DataTransformer/NumberToStringTransformerTest.php index 2d1c7ea4..4560cacd 100644 --- a/lib/Core/Tests/Extension/Core/DataTransformer/NumberToStringTransformerTest.php +++ b/lib/Core/Tests/Extension/Core/DataTransformer/NumberToStringTransformerTest.php @@ -29,23 +29,12 @@ protected function setUp(): void \Locale::setDefault('en'); } - public static function provideTransformations(): iterable - { - return [ - [null, ''], - [1, '1'], - [1.5, '1.5'], - [1234.5, '1234.5'], - [12345.912, '12345.912'], - ]; - } - /** * @dataProvider provideTransformations * * @test */ - public function transform($from, string $to): void + public function transform(mixed $from, string $to): void { $transformer = new NumberToStringTransformer(); @@ -61,6 +50,20 @@ public function transform_with_scale(): void self::assertSame('678.92', $transformer->transform(678.916)); } + /** + * @param NumberToStringTransformer::ROUND_* $roundingMode + * + * @dataProvider transformWithRoundingProvider + * + * @test + */ + public function transform_with_rounding(int $scale, $input, string $output, int $roundingMode): void + { + $transformer = new NumberToStringTransformer($scale, false, $roundingMode); + + self::assertEquals($output, $transformer->transform($input)); + } + public static function transformWithRoundingProvider(): iterable { return [ @@ -155,14 +158,6 @@ public static function transformWithRoundingProvider(): iterable * * @test */ - public function transform_with_rounding(int $scale, $input, string $output, int $roundingMode): void - { - $transformer = new NumberToStringTransformer($scale, false, $roundingMode); - - self::assertEquals($output, $transformer->transform($input)); - } - - /** @test */ public function transform_does_not_round_if_no_scale(): void { $transformer = new NumberToStringTransformer(null, false, NumberToStringTransformer::ROUND_DOWN); @@ -175,13 +170,38 @@ public function transform_does_not_round_if_no_scale(): void * * @test */ - public function reverse_transform($to, $from): void + public function reverse_transform(mixed $to, mixed $from): void { $transformer = new NumberToStringTransformer(); self::assertEquals($to, $transformer->reverseTransform($from)); } + public static function provideTransformations(): iterable + { + return [ + [null, ''], + [1, '1'], + [1.5, '1.5'], + [1234.5, '1234.5'], + [12345.912, '12345.912'], + ]; + } + + /** + * @param NumberToStringTransformer::ROUND_* $roundingMode + * + * @dataProvider reverseTransformWithRoundingProvider + * + * @test + */ + public function reverse_transform_with_rounding(int $scale, string $input, mixed $output, int $roundingMode): void + { + $transformer = new NumberToStringTransformer($scale, false, $roundingMode); + + self::assertEquals($output, $transformer->reverseTransform($input)); + } + public static function reverseTransformWithRoundingProvider(): iterable { return [ @@ -276,14 +296,6 @@ public static function reverseTransformWithRoundingProvider(): iterable * * @test */ - public function reverse_transform_with_rounding(int $scale, string $input, $output, int $roundingMode): void - { - $transformer = new NumberToStringTransformer($scale, false, $roundingMode); - - self::assertEquals($output, $transformer->reverseTransform($input)); - } - - /** @test */ public function reverse_transform_does_not_round_if_no_scale(): void { $transformer = new NumberToStringTransformer(null, false, NumberToStringTransformer::ROUND_DOWN); diff --git a/lib/Core/Tests/Extension/Core/Type/BooleanTypeTest.php b/lib/Core/Tests/Extension/Core/Type/BooleanTypeTest.php index ed541959..935d60fd 100644 --- a/lib/Core/Tests/Extension/Core/Type/BooleanTypeTest.php +++ b/lib/Core/Tests/Extension/Core/Type/BooleanTypeTest.php @@ -28,7 +28,7 @@ public function transform_to_boolean(): void $field = $this->getFactory()->createField('active', BooleanType::class); FieldTransformationAssertion::assertThat($field) - ->withInput(null) + ->withInput('') ->successfullyTransformsTo(null) ->andReverseTransformsTo('') ; @@ -50,12 +50,6 @@ public function transform_to_boolean(): void ->successfullyTransformsTo(false) ->andReverseTransformsTo('no', 'false') ; - - FieldTransformationAssertion::assertThat($field) - ->withInput('false', false) - ->successfullyTransformsTo(false) - ->andReverseTransformsTo('no', 'false') - ; } /** @test */ @@ -66,12 +60,6 @@ public function transform_to_boolean_with_custom_label_and_norm(): void 'norm_label' => ['true' => 'yes', 'false' => 'no'], ]); - FieldTransformationAssertion::assertThat($field) - ->withInput(null) - ->successfullyTransformsTo(null) - ->andReverseTransformsTo('') - ; - FieldTransformationAssertion::assertThat($field) ->withInput('ja', 'yes') ->successfullyTransformsTo(true) diff --git a/lib/Core/Tests/Extension/Core/Type/ChoiceTypeTest.php b/lib/Core/Tests/Extension/Core/Type/ChoiceTypeTest.php index 9ee7fd47..90d4cc2b 100644 --- a/lib/Core/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/lib/Core/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -27,7 +27,8 @@ */ final class ChoiceTypeTest extends SearchIntegrationTestCase { - private $choices = [ + /** @var array */ + private array $choices = [ 'Bernhard' => 'a', 'Fabien' => 'b', 'Kris' => 'c', @@ -35,15 +36,18 @@ final class ChoiceTypeTest extends SearchIntegrationTestCase 'Roman' => 'e', ]; - private $scalarChoices = [ + /** @var array */ + private array $scalarChoices = [ 'Yes' => true, 'No' => false, 'n/a' => '', ]; - private $objectChoices; + /** @var array */ + private ?array $objectChoices; - protected $groupedChoices = [ + /** @var array> */ + protected array $groupedChoices = [ 'Symfony' => [ 'Bernhard' => 'a', 'Fabien' => 'b', @@ -197,9 +201,9 @@ public function object_choices(): void ]); FieldTransformationAssertion::assertThat($field) - ->withInput($this->objectChoices[2]->name, $this->objectChoices[2]->id) + ->withInput($this->objectChoices[2]->name, (string) $this->objectChoices[2]->id) ->successfullyTransformsTo($this->objectChoices[2]) - ->andReverseTransformsTo($this->objectChoices[2]->name, $this->objectChoices[2]->id) + ->andReverseTransformsTo($this->objectChoices[2]->name, (string) $this->objectChoices[2]->id) ; } @@ -215,9 +219,9 @@ public function value_view_format_is_value(): void ]); FieldTransformationAssertion::assertThat($field) - ->withInput($this->objectChoices[2]->id) + ->withInput((string) $this->objectChoices[2]->id) ->successfullyTransformsTo($this->objectChoices[2]) - ->andReverseTransformsTo($this->objectChoices[2]->id) + ->andReverseTransformsTo((string) $this->objectChoices[2]->id) ; $field->finalizeConfig(); diff --git a/lib/Core/Tests/Extension/Core/Type/IntegerTypeTest.php b/lib/Core/Tests/Extension/Core/Type/IntegerTypeTest.php index e2ab99d3..e4989693 100644 --- a/lib/Core/Tests/Extension/Core/Type/IntegerTypeTest.php +++ b/lib/Core/Tests/Extension/Core/Type/IntegerTypeTest.php @@ -17,8 +17,6 @@ use Rollerworks\Component\Search\FieldSetView; use Rollerworks\Component\Search\Test\FieldTransformationAssertion; use Rollerworks\Component\Search\Test\SearchIntegrationTestCase; -use Symfony\Component\Intl\Intl; -use Symfony\Component\Intl\Util\IcuVersion; use Symfony\Component\Intl\Util\IntlTestHelper; /** diff --git a/lib/Core/Tests/Extension/Core/Type/TimeTypeTest.php b/lib/Core/Tests/Extension/Core/Type/TimeTypeTest.php index 31e5007a..0da4b53e 100644 --- a/lib/Core/Tests/Extension/Core/Type/TimeTypeTest.php +++ b/lib/Core/Tests/Extension/Core/Type/TimeTypeTest.php @@ -25,7 +25,7 @@ */ final class TimeTypeTest extends SearchIntegrationTestCase { - private $defaultTimezone; + private string $defaultTimezone; protected function setUp(): void { diff --git a/lib/Core/Tests/Extension/Core/ValueComparison/DateValueComparisonTest.php b/lib/Core/Tests/Extension/Core/ValueComparison/DateValueComparisonTest.php index 68d97133..dd0283b2 100644 --- a/lib/Core/Tests/Extension/Core/ValueComparison/DateValueComparisonTest.php +++ b/lib/Core/Tests/Extension/Core/ValueComparison/DateValueComparisonTest.php @@ -21,8 +21,7 @@ */ final class DateValueComparisonTest extends TestCase { - /** @var DateValueComparator */ - private $comparison; + private DateValueComparator $comparison; protected function setUp(): void { diff --git a/lib/Core/Tests/Extension/Core/ValueComparison/MoneyValueComparisonTest.php b/lib/Core/Tests/Extension/Core/ValueComparison/MoneyValueComparisonTest.php index a077ca4b..592d8288 100644 --- a/lib/Core/Tests/Extension/Core/ValueComparison/MoneyValueComparisonTest.php +++ b/lib/Core/Tests/Extension/Core/ValueComparison/MoneyValueComparisonTest.php @@ -24,8 +24,7 @@ */ final class MoneyValueComparisonTest extends TestCase { - /** @var MoneyValueComparator */ - private $comparison; + private MoneyValueComparator $comparison; protected function setUp(): void { diff --git a/lib/Core/Tests/Extension/LazyExtensionTest.php b/lib/Core/Tests/Extension/LazyExtensionTest.php index 03509d54..3e708ab3 100644 --- a/lib/Core/Tests/Extension/LazyExtensionTest.php +++ b/lib/Core/Tests/Extension/LazyExtensionTest.php @@ -13,6 +13,7 @@ namespace Rollerworks\Component\Search\Tests\Extension; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Rollerworks\Component\Search\Exception\InvalidArgumentException; use Rollerworks\Component\Search\Extension\Core\Type\IntegerType; @@ -41,7 +42,7 @@ public function it_loads_registered_type(): void { $extension = LazyExtension::create( [ - TextType::class => static fn () => new TextType(), + TextType::class => static fn (): TextType => new TextType(), ] ); @@ -72,11 +73,11 @@ public function it_loads_type_extensions(): void $extension = LazyExtension::create( [ - TextType::class => static fn () => new TextType(), + TextType::class => static fn (): TextType => new TextType(), ], [ - TextType::class => [$typeExtension1, $typeExtension2], - IntegerType::class => [$typeExtension3], + TextType::class => ['ext1' => $typeExtension1, 'ext' => $typeExtension2], + IntegerType::class => ['ext3' => $typeExtension3], ] ); @@ -96,7 +97,7 @@ public function it_checks_type_extension_parent_equality(): void $extension = LazyExtension::create( [ - TextType::class => static fn () => new TextType(), + TextType::class => static fn (): TextType => new TextType(), ], [ IntegerType::class => ['extension_1' => $typeExtension1], @@ -116,7 +117,7 @@ public function it_checks_type_extension_parent_equality(): void $extension->getTypeExtensions(IntegerType::class); } - public function createTypeExtension(string $type) + public function createTypeExtension(string $type): FieldTypeExtension { $typeExtension = $this->createMock(FieldTypeExtension::class); $typeExtension diff --git a/lib/Core/Tests/Field/OrderFieldTest.php b/lib/Core/Tests/Field/OrderFieldTest.php index 01de7afc..256d3c44 100644 --- a/lib/Core/Tests/Field/OrderFieldTest.php +++ b/lib/Core/Tests/Field/OrderFieldTest.php @@ -13,6 +13,7 @@ namespace Rollerworks\Component\Search\Tests\Field; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Rollerworks\Component\Search\DataTransformer; use Rollerworks\Component\Search\Exception\BadMethodCallException; @@ -26,15 +27,10 @@ */ final class OrderFieldTest extends TestCase { - /** - * @var ResolvedFieldType - */ - private $resolvedType; + /** @var MockObject&ResolvedFieldType */ + private MockObject $resolvedType; - /** - * @var OrderField - */ - private $field; + private OrderField $field; protected function setUp(): void { @@ -97,9 +93,8 @@ public function it_does_not_allow_configuring_value_support(): void $this->expectException(BadMethodCallException::class); $this->expectExceptionMessage('does not support supporting custom value types'); - $this->field->setValueTypeSupport(Range::class, true); - self::assertFalse($this->field->supportValueType(Range::class)); + $this->field->setValueTypeSupport(Range::class, true); } /** @test */ @@ -116,8 +111,8 @@ public function it_does_not_allow_setting_a_comparison_class(): void $comparisonObj = $this->getMockBuilder(ValueComparator::class)->getMock(); - $this->field->setValueComparator($comparisonObj); self::assertNull($this->field->getValueComparator()); + $this->field->setValueComparator($comparisonObj); } /** @test */ diff --git a/lib/Core/Tests/Field/ResolvedFieldTypeTest.php b/lib/Core/Tests/Field/ResolvedFieldTypeTest.php index c3f1c441..5d9dd39d 100644 --- a/lib/Core/Tests/Field/ResolvedFieldTypeTest.php +++ b/lib/Core/Tests/Field/ResolvedFieldTypeTest.php @@ -13,7 +13,6 @@ namespace Rollerworks\Component\Search\Tests\Field; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Rollerworks\Component\Search\Extension\Core\Type\SearchFieldType; use Rollerworks\Component\Search\Field\AbstractFieldType; @@ -168,10 +167,7 @@ public static function provideTypeClassBlockPrefixTuples(): iterable yield [FBooType::class, 'f_boo']; } - /** - * @return FieldConfig&MockObject - */ - private function createFieldMock(string $name = 'name') + private function createFieldMock(string $name = 'name'): FieldConfig { $mock = $this->getMockBuilder(FieldConfig::class)->getMock(); $mock->expects(self::any())->method('getName')->willReturn($name); @@ -181,10 +177,7 @@ private function createFieldMock(string $name = 'name') return $mock; } - /** - * @return MockObject&SearchFieldView - */ - private function createSearchFieldViewMock() + private function createSearchFieldViewMock(): SearchFieldView { return $this->createMock(SearchFieldView::class); } @@ -241,8 +234,10 @@ final class UsageTrackingFieldTypeExtension extends AbstractFieldTypeExtension * @param mixed[] $calls * @param array $defaultOptions */ - public function __construct(array &$calls, private array $defaultOptions) - { + public function __construct( + array &$calls, + private array $defaultOptions, + ) { $this->calls = &$calls; } diff --git a/lib/Core/Tests/Field/SearchFieldTest.php b/lib/Core/Tests/Field/SearchFieldTest.php index d4bb43ca..edf0c41d 100644 --- a/lib/Core/Tests/Field/SearchFieldTest.php +++ b/lib/Core/Tests/Field/SearchFieldTest.php @@ -13,7 +13,6 @@ namespace Rollerworks\Component\Search\Tests\Field; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Rollerworks\Component\Search\DataTransformer; use Rollerworks\Component\Search\Exception\BadMethodCallException; @@ -30,19 +29,13 @@ */ final class SearchFieldTest extends TestCase { - /** - * @var ResolvedFieldType - */ - private $resolvedType; + private ResolvedFieldType $resolvedType; - /** - * @var SearchField - */ - private $field; + private SearchField $field; protected function setUp(): void { - $this->resolvedType = $this->getMockBuilder(ResolvedFieldType::class)->getMock(); + $this->resolvedType = $this->createMock(ResolvedFieldType::class); $this->field = new SearchField('foobar', $this->resolvedType, ['name' => 'value']); } @@ -187,10 +180,7 @@ public function its_data_is_not_changeable_when_locked(): void $this->field->setViewTransformer(null); } - /** - * @return DataTransformer|MockObject - */ - private function createTransformerMock() + private function createTransformerMock(): DataTransformer { return $this->getMockBuilder(DataTransformer::class)->getMock(); } diff --git a/lib/Core/Tests/GenericFieldSetBuilderTest.php b/lib/Core/Tests/GenericFieldSetBuilderTest.php index f1fe849b..9529d6ea 100644 --- a/lib/Core/Tests/GenericFieldSetBuilderTest.php +++ b/lib/Core/Tests/GenericFieldSetBuilderTest.php @@ -31,10 +31,7 @@ final class GenericFieldSetBuilderTest extends TestCase { use ProphecyTrait; - /** - * @var GenericFieldSetBuilder - */ - private $builder; + private GenericFieldSetBuilder $builder; protected function setUp(): void { @@ -113,7 +110,7 @@ public function get_build_field_set(): void private function assertBuilderFieldConfigurationEquals(string $name, string $type, array $options = []): void { - self::assertInstanceOf(FieldConfig::class, $field = $this->builder->get($name)); + $field = $this->builder->get($name); self::assertEquals($name, $field->getName()); self::assertInstanceOf($type, $field->getType()->getInnerType()); self::assertEquals($options, $field->getOptions()); diff --git a/lib/Core/Tests/GenericSearchFactoryTest.php b/lib/Core/Tests/GenericSearchFactoryTest.php index abf28d70..cd3eb163 100644 --- a/lib/Core/Tests/GenericSearchFactoryTest.php +++ b/lib/Core/Tests/GenericSearchFactoryTest.php @@ -27,33 +27,20 @@ */ final class GenericSearchFactoryTest extends TestCase { - /** - * @var MockObject - */ - private $registry; - - /** - * @var MockObject - */ - private $fieldSetRegistry; - - /** - * @var GenericSearchFactory - */ - private $factory; - - /** - * @var MockObject - */ - private $fieldConfig; + /** @var MockObject&TypeRegistry */ + private MockObject $registry; + + /** @var MockObject&FieldConfig */ + private MockObject $fieldConfig; + + private GenericSearchFactory $factory; protected function setUp(): void { - $this->fieldSetRegistry = $this->createMock(FieldSetRegistry::class); $this->registry = $this->createMock(TypeRegistry::class); $this->fieldConfig = $this->createMock(FieldConfig::class); - $this->factory = new GenericSearchFactory($this->registry, $this->fieldSetRegistry); + $this->factory = new GenericSearchFactory($this->registry, $this->createMock(FieldSetRegistry::class)); } /** @test */ @@ -88,7 +75,7 @@ public function create_field_with_type_name(): void self::assertSame($this->fieldConfig, $this->factory->createField('name', TextType::class, $options)); } - private function getMockResolvedType() + private function getMockResolvedType(): MockObject { return $this->getMockBuilder(ResolvedFieldType::class)->getMock(); } diff --git a/lib/Core/Tests/Input/CachingInputProcessorTest.php b/lib/Core/Tests/Input/CachingInputProcessorTest.php index 0bea99ea..35b7a142 100644 --- a/lib/Core/Tests/Input/CachingInputProcessorTest.php +++ b/lib/Core/Tests/Input/CachingInputProcessorTest.php @@ -48,6 +48,22 @@ public function it_ignores_caching_for_non_string_input(): void self::assertEquals($input, $inputProcessor->getInput()); } + /** @test */ + public function it_ignores_caching_when_ttl_is_0(): void + { + $cache = $this->createMock(CacheInterface::class); + $cache->expects(self::never())->method('get'); + + $serializer = $this->createMock(SearchConditionSerializer::class); + $inputProcessor = new SpyingInputProcessor(); + $processor = new CachingInputProcessor($cache, $serializer, $inputProcessor); + + $processor->process($config = (new ProcessorConfig(new FieldSetStub()))->setCacheTTL(0), $input = ['Hello']); + + self::assertEquals($config, $inputProcessor->getConfig()); + self::assertEquals($input, $inputProcessor->getInput()); + } + /** @test */ public function it_processes_with_no_existing_cache(): void { @@ -62,7 +78,7 @@ public function it_processes_with_no_existing_cache(): void $cache = new Psr16Cache($arrayCache = new ArrayAdapter()); $processor = new CachingInputProcessor($cache, $serializer, $inputProcessor); - $processor->process($config = new ProcessorConfig(new FieldSetStub()), $input = 'Hello'); + $processor->process($config = (new ProcessorConfig(new FieldSetStub()))->setCacheTTL(10), $input = 'Hello'); self::assertCount(1, $arrayCache->getValues()); self::assertEquals($config, $inputProcessor->getConfig()); @@ -91,7 +107,7 @@ public function it_uses_cached_version_when_cache_is_valid(): void $processor = new CachingInputProcessor($cache, $serializer, $inputProcessor); - self::assertSame($condition, $processor->process(new ProcessorConfig(new FieldSetStub()), 'Hello')); + self::assertSame($condition, $processor->process((new ProcessorConfig(new FieldSetStub()))->setCacheTTL(10), 'Hello')); self::assertNull($inputProcessor->getConfig()); self::assertNull($inputProcessor->getInput()); } @@ -129,7 +145,7 @@ public function it_stores_processed_result_in_cache(): void $inputProcessor = new SpyingInputProcessor(); $processor = new CachingInputProcessor($cache, $serializer, $inputProcessor); - $processor->process($config = new ProcessorConfig(new FieldSetStub()), $input = 'Hello'); + $processor->process($config = (new ProcessorConfig(new FieldSetStub()))->setCacheTTL(10), $input = 'Hello'); self::assertEquals($config, $inputProcessor->getConfig()); self::assertEquals($input, $inputProcessor->getInput()); @@ -248,7 +264,7 @@ public function it_does_not_store_empty_condition(): void $inputProcessor = new EmptyInputProcessorStub(); $processor = new CachingInputProcessor($cache, $serializer, $inputProcessor); - $processor->process(new ProcessorConfig(new FieldSetStub()), 'Hello'); + $processor->process((new ProcessorConfig(new FieldSetStub()))->setCacheTTL(10), 'Hello'); } /** @test */ @@ -264,10 +280,10 @@ public function it_separates_caches_by_processor_class(): void $cache = new Psr16Cache($arrayCache = new ArrayAdapter()); $processor = new CachingInputProcessor($cache, $serializer, new SpyingInputProcessor()); - $processor->process($config = new ProcessorConfig(new FieldSetStub()), $input = 'Hello'); + $processor->process((new ProcessorConfig(new FieldSetStub()))->setCacheTTL(10), 'Hello'); $processor2 = new CachingInputProcessor($cache, $serializer, new StubInputProcessor()); - $processor2->process($config2 = new ProcessorConfig(new FieldSetStub()), $input2 = 'Hello'); + $processor2->process((new ProcessorConfig(new FieldSetStub()))->setCacheTTL(10), 'Hello'); self::assertCount(2, $arrayCache->getValues()); } diff --git a/lib/Core/Tests/Input/InputProcessorTestCase.php b/lib/Core/Tests/Input/InputProcessorTestCase.php index 32f05d21..59254c2d 100644 --- a/lib/Core/Tests/Input/InputProcessorTestCase.php +++ b/lib/Core/Tests/Input/InputProcessorTestCase.php @@ -610,7 +610,7 @@ public function transform($value): void // No-op } - public function reverseTransform($value): void + public function reverseTransform($value): never { throw new TransformationFailedException('Error.', 0, null, 'I explicitly refuse the accept this value.', ['value' => $value]); } @@ -618,11 +618,9 @@ public function reverseTransform($value): void $this->factoryBuilder->addTypeExtensions([ new class($alwaysFailTransformer) extends AbstractFieldTypeExtension { - private DataTransformer $transformer; - - public function __construct(DataTransformer $transformer) - { - $this->transformer = $transformer; + public function __construct( + private readonly DataTransformer $transformer, + ) { } public function buildType(FieldConfig $builder, array $options): void diff --git a/lib/Core/Tests/Input/NormStringQueryInputTest.php b/lib/Core/Tests/Input/NormStringQueryInputTest.php index d51cedd7..a2e074b0 100644 --- a/lib/Core/Tests/Input/NormStringQueryInputTest.php +++ b/lib/Core/Tests/Input/NormStringQueryInputTest.php @@ -202,6 +202,12 @@ public function it_errors_when_order_clause_is_nested(string $input): void $this->assertConditionContainsErrors($input, $config, [$error]); } + public static function provideNestedOrderClauseTests(): iterable + { + yield ['(@id: asc;)']; + yield ['((@id: asc;))']; + } + /** * @test * @@ -224,10 +230,4 @@ public static function provideInvalidOrderClauseValueTests(): iterable yield 'comparison' => ['@id: >1;']; yield 'pattern' => ['@id: ~> desc;']; } - - public static function provideNestedOrderClauseTests(): iterable - { - yield ['(@id: asc;)']; - yield ['((@id: asc;))']; - } } diff --git a/lib/Core/Tests/Input/StringLexerTest.php b/lib/Core/Tests/Input/StringLexerTest.php index 74d38d71..96d15e9c 100644 --- a/lib/Core/Tests/Input/StringLexerTest.php +++ b/lib/Core/Tests/Input/StringLexerTest.php @@ -22,8 +22,7 @@ */ final class StringLexerTest extends TestCase { - /** @var StringLexer */ - private $lexer; + private StringLexer $lexer; /** @before */ public function setUpLexer(): void diff --git a/lib/Core/Tests/Input/StringQueryInputTest.php b/lib/Core/Tests/Input/StringQueryInputTest.php index c5f0d082..8faf6683 100644 --- a/lib/Core/Tests/Input/StringQueryInputTest.php +++ b/lib/Core/Tests/Input/StringQueryInputTest.php @@ -118,6 +118,15 @@ public function it_processes_aliased_fields($input): void $this->assertConditionEquals($input, $condition, $processor, $config); } + public static function provideAliasedFieldsTests(): iterable + { + return [ + ['first-name: value1; first-name: value, value2;'], + ['first-name: value, value2;'], + ['value, value2;'], + ]; + } + /** @test */ public function it_expects_a_string_input(): void { @@ -329,6 +338,12 @@ public function it_errors_when_order_clause_is_nested(string $input): void $this->assertConditionContainsErrors($input, $config, [$error]); } + public static function provideNestedOrderClauseTests(): iterable + { + yield ['(@id: asc;)']; + yield ['((@id: asc;))']; + } + /** * @test * @@ -342,10 +357,14 @@ public function it_errors_when_order_clause_is_has_unsupported_values($input): v $this->assertConditionContainsErrors($input, $config, [$error]); } - public static function provideNestedOrderClauseTests(): iterable + public static function provideInvalidOrderClauseValueTests(): iterable { - yield ['(@id: asc;)']; - yield ['((@id: asc;))']; + yield 'multiple values' => ['@id: desc, asc;']; + yield 'negated value' => ['@id: !desc;']; + yield 'range' => ['@id: 1 ~ 12;']; + yield 'negated range' => ['@id: !1 ~ 12;']; + yield 'comparison' => ['@id: >1;']; + yield 'pattern' => ['@id: ~> desc;']; } public static function provideEmptyInputTests(): iterable @@ -437,15 +456,6 @@ public static function provideNestedGroupTests(): iterable ]; } - public static function provideAliasedFieldsTests(): iterable - { - return [ - ['first-name: value1; first-name: value, value2;'], - ['first-name: value, value2;'], - ['value, value2;'], - ]; - } - public static function provideValueOverflowTests(): iterable { return [ @@ -545,14 +555,4 @@ public static function provideNestedErrorsTests(): iterable ['((((((date: 1;))))))', [new ConditionErrorMessage('[0][0][0][0][0][0][date][0]', 'This value is not valid.')]], ]; } - - public static function provideInvalidOrderClauseValueTests(): iterable - { - yield 'multiple values' => ['@id: desc, asc;']; - yield 'negated value' => ['@id: !desc;']; - yield 'range' => ['@id: 1 ~ 12;']; - yield 'negated range' => ['@id: !1 ~ 12;']; - yield 'comparison' => ['@id: >1;']; - yield 'pattern' => ['@id: ~> desc;']; - } } diff --git a/lib/Core/Tests/LazyFieldSetRegistryTest.php b/lib/Core/Tests/LazyFieldSetRegistryTest.php index d40b4180..f178ae6e 100644 --- a/lib/Core/Tests/LazyFieldSetRegistryTest.php +++ b/lib/Core/Tests/LazyFieldSetRegistryTest.php @@ -29,12 +29,10 @@ public function it_loads_configurator_lazily(): void $configurator = $this->createMock(FieldSetConfigurator::class); $configurator2 = $this->createMock(FieldSetConfigurator::class); - $registry = LazyFieldSetRegistry::create( - [ - 'set' => static fn () => $configurator, - 'set2' => static fn () => $configurator2, - ] - ); + $registry = LazyFieldSetRegistry::create([ + 'set' => static fn (): FieldSetConfigurator => $configurator, + 'set2' => static fn (): FieldSetConfigurator => $configurator2, + ]); self::assertTrue($registry->hasConfigurator('set')); self::assertTrue($registry->hasConfigurator('set2')); @@ -56,11 +54,9 @@ public function it_loads_configurator_by_fqcn(): void $configurator = $this->createMock(FieldSetConfigurator::class); $configurator2 = $this->createMock(FieldSetConfigurator::class); - $registry = LazyFieldSetRegistry::create( - [ - 'set' => static fn () => $configurator, - ] - ); + $registry = LazyFieldSetRegistry::create([ + 'set' => static fn (): FieldSetConfigurator => $configurator, + ]); $name = $configurator2::class; @@ -69,7 +65,7 @@ public function it_loads_configurator_by_fqcn(): void self::assertFalse($registry->hasConfigurator('set2')); self::assertSame($configurator, $registry->getConfigurator('set')); - self::assertSame($name, \get_class($registry->getConfigurator($name))); + self::assertSame($name, $registry->getConfigurator($name)::class); } /** @test */ @@ -79,12 +75,10 @@ public function it_checks_registered_before_class_name(): void $configurator2 = $this->createMock(FieldSetConfigurator::class); $name = $configurator2::class; - $registry = LazyFieldSetRegistry::create( - [ - 'set' => static fn () => $configurator, - $name => static fn () => $configurator2, - ] - ); + $registry = LazyFieldSetRegistry::create([ + 'set' => static fn (): FieldSetConfigurator => $configurator, + $name => static fn (): FieldSetConfigurator => $configurator2, + ]); $name = $configurator2::class; @@ -102,11 +96,9 @@ public function it_errors_when_configurator_is_not_registered_and_class_is_a_con $configurator = $this->createMock(FieldSetConfigurator::class); $configurator2 = \stdClass::class; - $registry = LazyFieldSetRegistry::create( - [ - 'set' => static fn () => $configurator, - ] - ); + $registry = LazyFieldSetRegistry::create([ + 'set' => static fn (): FieldSetConfigurator => $configurator, + ]); self::assertTrue($registry->hasConfigurator('set')); self::assertFalse($registry->hasConfigurator('set2')); @@ -126,11 +118,9 @@ public function it_errors_when_configurator_is_not_registered_class_does_not_exi $configurator = $this->createMock(FieldSetConfigurator::class); $configurator2 = 'f4394832948_foobar_cow'; - $registry = LazyFieldSetRegistry::create( - [ - 'set' => static fn () => $configurator, - ] - ); + $registry = LazyFieldSetRegistry::create([ + 'set' => static fn (): FieldSetConfigurator => $configurator, + ]); self::assertTrue($registry->hasConfigurator('set')); self::assertFalse($registry->hasConfigurator('set2')); diff --git a/lib/Symfony/SearchBundle/Tests/SearchConditionBuilderTest.php b/lib/Core/Tests/SearchConditionBuilderTest.php similarity index 99% rename from lib/Symfony/SearchBundle/Tests/SearchConditionBuilderTest.php rename to lib/Core/Tests/SearchConditionBuilderTest.php index 225a2253..b8d3d22a 100644 --- a/lib/Symfony/SearchBundle/Tests/SearchConditionBuilderTest.php +++ b/lib/Core/Tests/SearchConditionBuilderTest.php @@ -11,7 +11,7 @@ * with this source code in the file LICENSE. */ -namespace Rollerworks\Bundle\SearchBundle\Tests; +namespace Rollerworks\Component\Search\Tests; use PHPUnit\Framework\TestCase; use Rollerworks\Component\Search\Extension\Core\Type\IntegerType; @@ -32,7 +32,9 @@ */ final class SearchConditionBuilderTest extends TestCase { - /** @test */ + /** + * @test + */ public function it_produces_an_empty_condition(): void { $fieldSet = new FieldSetStub(); @@ -44,7 +46,9 @@ public function it_produces_an_empty_condition(): void self::assertEquals(new SearchCondition($fieldSet, new ValuesGroup(ValuesGroup::GROUP_LOGICAL_OR)), $builder->getSearchCondition()); } - /** @test */ + /** + * @test + */ public function it_disallows_serialization(): void { $fieldSet = new FieldSetStub(); @@ -56,7 +60,9 @@ public function it_disallows_serialization(): void serialize($builder); } - /** @test */ + /** + * @test + */ public function it_sorting_at_nested_levels(): void { $searchFactory = Searches::createSearchFactory(); diff --git a/lib/Core/Tests/SearchConditionSerializerTest.php b/lib/Core/Tests/SearchConditionSerializerTest.php index 864092b6..282ecd2c 100644 --- a/lib/Core/Tests/SearchConditionSerializerTest.php +++ b/lib/Core/Tests/SearchConditionSerializerTest.php @@ -14,7 +14,6 @@ namespace Rollerworks\Component\Search\Tests; use PHPUnit\Framework\TestCase; -use Prophecy\PhpUnit\ProphecyTrait; use Rollerworks\Component\Search\Exception\InvalidArgumentException; use Rollerworks\Component\Search\Field\FieldConfig; use Rollerworks\Component\Search\GenericFieldSet; @@ -29,27 +28,24 @@ */ final class SearchConditionSerializerTest extends TestCase { - use ProphecyTrait; - - /** - * @var SearchConditionSerializer - */ - private $serializer; - - /** @var GenericFieldSet */ - private $fieldSet; + private SearchConditionSerializer $serializer; + private GenericFieldSet $fieldSet; protected function setUp(): void { $field = $this->createMock(FieldConfig::class); - $field->expects(self::any())->method('getName')->willReturn('id'); + $field->method('getName')->willReturn('id'); $this->fieldSet = new GenericFieldSet(['id' => $field], 'foobar'); - $factory = $this->prophesize(SearchFactory::class); - $factory->createFieldSet('foobar')->willReturn($this->fieldSet); + $factory = $this->createMock(SearchFactory::class); + $factory + ->method('createFieldSet') + ->with('foobar') + ->willReturn($this->fieldSet) + ; - $this->serializer = new SearchConditionSerializer($factory->reveal()); + $this->serializer = new SearchConditionSerializer($factory); } /** @test */ @@ -86,7 +82,7 @@ public function un_serialize_missing_fields(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - 'Serialized search condition must be exactly two values [FieldSet-name, serialized ValuesGroup].' + 'Serialized search condition must be exactly two values ["FieldSet-name", "serialized ValuesGroup"].' ); $this->serializer->unserialize(['foobar']); @@ -97,7 +93,7 @@ public function un_serialize_wrong_field(): void { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( - 'Serialized search condition must be exactly two values [FieldSet-name, serialized ValuesGroup].' + 'Serialized search condition must be exactly two values ["FieldSet-name", "serialized ValuesGroup"].' ); $this->serializer->unserialize(['foobar', 'foo' => 'bar']); @@ -106,16 +102,9 @@ public function un_serialize_wrong_field(): void /** @test */ public function un_serialize_invalid_data(): void { - try { - // Disable errors to get the exception - error_reporting(0); - - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Unable to unserialize invalid value.'); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Unable to unserialize invalid value.'); - $this->serializer->unserialize(['foobar', '{i-am-invalid}']); - } finally { - error_reporting(1); - } + $this->serializer->unserialize(['foobar', '{i-am-invalid}']); } } diff --git a/lib/Core/Tests/SearchConditionTest.php b/lib/Core/Tests/SearchConditionTest.php index a3f21168..dfb523bd 100644 --- a/lib/Core/Tests/SearchConditionTest.php +++ b/lib/Core/Tests/SearchConditionTest.php @@ -17,6 +17,7 @@ use Rollerworks\Component\Search\Exception\UnsupportedFieldSetException; use Rollerworks\Component\Search\FieldSet; use Rollerworks\Component\Search\SearchCondition; +use Rollerworks\Component\Search\SearchOrder; use Rollerworks\Component\Search\SearchPrimaryCondition; use Rollerworks\Component\Search\Value\ValuesBag; use Rollerworks\Component\Search\Value\ValuesGroup; @@ -27,7 +28,7 @@ final class SearchConditionTest extends TestCase { /** @test */ - public function it_can_check_if__field_set_is_supported(): void + public function it_can_check_if_field_set_is_supported(): void { $fieldSet = $this->createMock(FieldSet::class); $fieldSet->expects(self::any())->method('getSetName')->willReturn('test'); @@ -41,7 +42,7 @@ public function it_can_check_if__field_set_is_supported(): void } /** @test */ - public function it_gives_an_exception_when_checked__field_set_is_not_supported(): void + public function it_gives_an_exception_when_checked_field_set_is_not_supported(): void { $fieldSet = $this->createMock(FieldSet::class); $fieldSet->expects(self::any())->method('getSetName')->willReturn('test'); @@ -82,4 +83,42 @@ public function it_allows_unsetting_the_primary_condition(): void self::assertNull($condition->getPrimaryCondition()); } + + /** @test */ + public function it_gives_whether_condition_is_empty(): void + { + $fieldSet = $this->createMock(FieldSet::class); + $fieldSet->expects(self::any())->method('getSetName')->willReturn('test'); + + // Empty condition + self::assertTrue((new SearchCondition($fieldSet, new ValuesGroup()))->isEmpty()); + + // With primary condition (not part of the cached condition) + self::assertTrue( + (new SearchCondition($fieldSet, new ValuesGroup())) + ->setPrimaryCondition( + new SearchPrimaryCondition((new ValuesGroup())->addField('id', new ValuesBag()) + ), + )->isEmpty(), + ); + + // -- None empty -- + + // Field + $condition = new SearchCondition($fieldSet, new ValuesGroup()); + $condition->getValuesGroup()->addField('id', new ValuesBag()); + self::assertFalse($condition->isEmpty()); + + // Nested group + $condition = new SearchCondition($fieldSet, new ValuesGroup()); + $condition->getValuesGroup()->addGroup((new ValuesGroup())); + self::assertFalse($condition->isEmpty()); + + // Ordering + self::assertFalse( + (new SearchCondition($fieldSet, new ValuesGroup())) + ->setOrder(new SearchOrder(((new ValuesGroup())->addField('id', (new ValuesBag())->addSimpleValue('desc')))), + )->isEmpty(), + ); + } } diff --git a/lib/Core/Tests/SearchFactoryBuilderTest.php b/lib/Core/Tests/SearchFactoryBuilderTest.php index e8c1fa40..737b68bc 100644 --- a/lib/Core/Tests/SearchFactoryBuilderTest.php +++ b/lib/Core/Tests/SearchFactoryBuilderTest.php @@ -23,9 +23,8 @@ */ final class SearchFactoryBuilderTest extends TestCase { - /** @var \ReflectionProperty */ - private $registry; - private $type; + private \ReflectionProperty $registry; + private FooType $type; protected function setUp(): void { diff --git a/lib/Core/Tests/Value/CompareTest.php b/lib/Core/Tests/Value/CompareTest.php index 47f79852..77b81de9 100644 --- a/lib/Core/Tests/Value/CompareTest.php +++ b/lib/Core/Tests/Value/CompareTest.php @@ -21,8 +21,7 @@ */ final class CompareTest extends TestCase { - /** @var Compare */ - private $value; + private Compare $value; protected function setUp(): void { diff --git a/lib/Core/Tests/Value/PatternMatchTest.php b/lib/Core/Tests/Value/PatternMatchTest.php index 8b471fec..cdbd0460 100644 --- a/lib/Core/Tests/Value/PatternMatchTest.php +++ b/lib/Core/Tests/Value/PatternMatchTest.php @@ -21,8 +21,7 @@ */ final class PatternMatchTest extends TestCase { - /** @var PatternMatch */ - private $value; + private PatternMatch $value; protected function setUp(): void { diff --git a/lib/Core/Tests/Value/RangeTest.php b/lib/Core/Tests/Value/RangeTest.php index 7dd7b9a0..35535c2f 100644 --- a/lib/Core/Tests/Value/RangeTest.php +++ b/lib/Core/Tests/Value/RangeTest.php @@ -21,10 +21,7 @@ */ final class RangeTest extends TestCase { - /** - * @var Range - */ - private $value; + private Range $value; /** @test */ public function it_has_a_lower_value(): void diff --git a/lib/Core/Util/StringUtil.php b/lib/Core/Util/StringUtil.php index 1e6e0fd9..112757c1 100644 --- a/lib/Core/Util/StringUtil.php +++ b/lib/Core/Util/StringUtil.php @@ -37,7 +37,7 @@ public static function fqcnToBlockPrefix(string $fqcn): ?string { // Non-greedy ("+?") to match "type" suffix, if present if (preg_match('~([^\\\]+?)(type)?$~i', $fqcn, $matches)) { - return mb_strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\1_\2', '\1_\2'], $matches[1])); + return mb_strtolower((string) preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\1_\2', '\1_\2'], $matches[1])); } return null; diff --git a/lib/Core/Value/Compare.php b/lib/Core/Value/Compare.php index e27aedae..83e7ffb3 100644 --- a/lib/Core/Value/Compare.php +++ b/lib/Core/Value/Compare.php @@ -13,23 +13,19 @@ namespace Rollerworks\Component\Search\Value; -final class Compare implements RequiresComparatorValueHolder +final readonly class Compare implements RequiresComparatorValueHolder { - private string $operator; - private mixed $value; - public const OPERATORS = ['>=', '<=', '<>', '<', '>']; - public function __construct(mixed $value, string $operator) - { + public function __construct( + private readonly mixed $value, + private readonly string $operator, + ) { if (! \in_array($operator, self::OPERATORS, true)) { throw new \InvalidArgumentException( \sprintf('Unknown operator "%s".', $operator) ); } - - $this->value = $value; - $this->operator = $operator; } public function getOperator(): string diff --git a/lib/Core/Value/PatternMatch.php b/lib/Core/Value/PatternMatch.php index b70579ee..f9936926 100644 --- a/lib/Core/Value/PatternMatch.php +++ b/lib/Core/Value/PatternMatch.php @@ -18,7 +18,7 @@ /** * @author Sebastiaan Stok */ -final class PatternMatch implements ValueHolder +final readonly class PatternMatch implements ValueHolder { public const PATTERN_CONTAINS = 'CONTAINS'; public const PATTERN_STARTS_WITH = 'STARTS_WITH'; @@ -29,24 +29,22 @@ final class PatternMatch implements ValueHolder public const PATTERN_EQUALS = 'EQUALS'; public const PATTERN_NOT_EQUALS = 'NOT_EQUALS'; - private string $value; - private string $patternType; - private bool $caseInsensitive; + private readonly string $patternType; /** * @throws \InvalidArgumentException When the pattern-match type is invalid */ - public function __construct(string $value, string $patternType, bool $caseInsensitive = false) - { - $typeConst = __CLASS__ . '::PATTERN_' . mb_strtoupper($patternType); + public function __construct( + private readonly string $value, + string $patternType, + private readonly bool $caseInsensitive = false, + ) { + $typeConst = self::class . '::PATTERN_' . mb_strtoupper($patternType); if (! \defined($typeConst)) { throw new InvalidArgumentException(\sprintf('Unknown PatternMatch type "%s".', $patternType)); } - - $this->value = $value; $this->patternType = mb_strtoupper($patternType); - $this->caseInsensitive = $caseInsensitive; } public function getValue(): string diff --git a/lib/Core/Value/Range.php b/lib/Core/Value/Range.php index 68adacfd..875f7a49 100644 --- a/lib/Core/Value/Range.php +++ b/lib/Core/Value/Range.php @@ -18,17 +18,12 @@ */ class Range implements RequiresComparatorValueHolder { - private mixed $lower; - private mixed $upper; - private bool $inclusiveLower; - private bool $inclusiveUpper; - - public function __construct(mixed $lower, mixed $upper, bool $inclusiveLower = true, bool $inclusiveUpper = true) - { - $this->lower = $lower; - $this->upper = $upper; - $this->inclusiveLower = $inclusiveLower; - $this->inclusiveUpper = $inclusiveUpper; + public function __construct( + private readonly mixed $lower, + private readonly mixed $upper, + private readonly bool $inclusiveLower = true, + private readonly bool $inclusiveUpper = true, + ) { } public function getLower() diff --git a/lib/Core/Value/ValuesBag.php b/lib/Core/Value/ValuesBag.php index a055f466..6a8fd538 100644 --- a/lib/Core/Value/ValuesBag.php +++ b/lib/Core/Value/ValuesBag.php @@ -22,39 +22,29 @@ class ValuesBag implements \Countable { private int $valuesCount = 0; - /** - * @var array - */ + /** @var array */ private array $simpleValues = []; - /** - * @var array - */ + /** @var array */ private array $simpleExcludedValues = []; - /** - * @var array, ValueHolder[]> - */ + /** @var array, ValueHolder[]> */ private array $values = []; + /** + * @param class-string|'simpleValues'|'simpleValue'|'simpleExcludedValue'|'simpleExcludedValues'|null $type + */ public function count(?string $type = null): int { if ($type === null) { return $this->valuesCount; } - switch ($type) { - case 'simpleValues': - case 'simpleValue': - return \count($this->simpleValues); - - case 'simpleExcludedValues': - case 'simpleExcludedValue': - return \count($this->simpleExcludedValues); - - default: - return \count($this->values[$type] ?? []); - } + return match ($type) { + 'simpleValues', 'simpleValue' => \count($this->simpleValues), + 'simpleExcludedValues', 'simpleExcludedValue' => \count($this->simpleExcludedValues), + default => \count($this->values[$type] ?? []), + }; } /** @@ -132,7 +122,7 @@ public function getExcludedSimpleValues(): array /** * @return $this */ - public function addExcludedSimpleValue($value): static + public function addExcludedSimpleValue(mixed $value): static { $this->simpleExcludedValues[] = $value; ++$this->valuesCount; @@ -188,6 +178,8 @@ public function has(string $type): bool /** * Remove a value by type and index. * + * @param class-string $type + * * @return $this */ public function remove(string $type, int $index): static diff --git a/lib/Core/Value/ValuesGroup.php b/lib/Core/Value/ValuesGroup.php index 68b348ac..2c04bc8d 100644 --- a/lib/Core/Value/ValuesGroup.php +++ b/lib/Core/Value/ValuesGroup.php @@ -25,33 +25,27 @@ class ValuesGroup public const GROUP_LOGICAL_OR = 'OR'; public const GROUP_LOGICAL_AND = 'AND'; - /** - * @var ValuesGroup[] - */ + /** @var ValuesGroup[] */ private array $groups = []; - /** - * @var array - */ + /** @var array */ private array $fields = []; /** - * @var string - */ - private $groupLogical; - - /** + * @param self::GROUP_LOGICAL_* $groupLogical + * * @throws InvalidArgumentException When no an unsupported group logical is provided */ - public function __construct(string $groupLogical = self::GROUP_LOGICAL_AND) - { + public function __construct( + private string $groupLogical = self::GROUP_LOGICAL_AND, + ) { $this->setGroupLogical($groupLogical); } /** * @return $this */ - public function addGroup(self $group) + public function addGroup(self $group): self { $this->groups[] = $group; @@ -86,7 +80,7 @@ public function getGroups(): array /** * @return $this */ - public function removeGroup(int $index) + public function removeGroup(int $index): self { if (isset($this->groups[$index])) { unset($this->groups[$index]); @@ -133,7 +127,7 @@ public function getField(string $name): ValuesBag /** * @return $this */ - public function removeField(string $name) + public function removeField(string $name): self { if (isset($this->fields[$name])) { unset($this->fields[$name]); @@ -166,14 +160,13 @@ public function getGroupLogical(): string } /** - * This is either one of the following class constants: - * GROUP_LOGICAL_OR or GROUP_LOGICAL_AND. + * @param self::GROUP_LOGICAL_* $groupLogical * * @return $this * * @throws InvalidArgumentException When an unsupported group-logical is provided */ - public function setGroupLogical(string $groupLogical) + public function setGroupLogical(string $groupLogical): self { if (! \in_array($groupLogical, [self::GROUP_LOGICAL_OR, self::GROUP_LOGICAL_AND], true)) { throw new InvalidArgumentException(\sprintf('Unsupported group logical "%s".', $groupLogical)); diff --git a/lib/Core/ValuesBagBuilder.php b/lib/Core/ValuesBagBuilder.php index 054b3724..cf30dac2 100644 --- a/lib/Core/ValuesBagBuilder.php +++ b/lib/Core/ValuesBagBuilder.php @@ -20,11 +20,9 @@ */ class ValuesBagBuilder extends ValuesBag { - private $parent; - - public function __construct(SearchConditionBuilder $parent) - { - $this->parent = $parent; + public function __construct( + private readonly SearchConditionBuilder $parent, + ) { } public function end(): SearchConditionBuilder diff --git a/lib/Core/composer.json b/lib/Core/composer.json index 28c89dca..f7521b87 100644 --- a/lib/Core/composer.json +++ b/lib/Core/composer.json @@ -20,11 +20,12 @@ ], "homepage": "https://rollerworks.github.io/", "require": { - "php": ">=8.1", + "php": "^8.1", "nesbot/carbon": "^2.38 || ^3.0", "psr/container": "^1.1 || ^2.0", "symfony/intl": "^6.4 || ^7.4 || ^8.0", "symfony/options-resolver": "^6.4 || ^7.4 || ^8.0", + "symfony/polyfill-php84": "^1.33", "symfony/property-access": "^6.4 || ^7.4 || ^8.0", "symfony/string": "^6.4 || ^7.4 || ^8.0", "symfony/translation-contracts": "^3.4" diff --git a/lib/Doctrine/Dbal/AbstractCachedConditionGenerator.php b/lib/Doctrine/Dbal/AbstractCachedConditionGenerator.php index 7bae2b3c..e67fa234 100644 --- a/lib/Doctrine/Dbal/AbstractCachedConditionGenerator.php +++ b/lib/Doctrine/Dbal/AbstractCachedConditionGenerator.php @@ -19,24 +19,21 @@ abstract class AbstractCachedConditionGenerator { - protected Cache $cacheDriver; - protected \DateInterval|int|null $cacheLifeTime; - protected SearchCondition $searchCondition; protected ?string $cacheKey = null; protected bool $isApplied = false; /** - * @param Cache $cacheDriver PSR-16 SimpleCache instance. Use a custom pool to ease - * purging invalidated items - * @param \DateInterval|int|null $ttl Optional. The TTL value of this item. If no value is sent and - * the driver supports TTL then the library may set a default - * value for it or let the driver take care of that. + * @param Cache $cacheDriver PSR-16 SimpleCache instance. Use a custom pool to ease + * purging invalidated items + * @param \DateInterval|int|null $cacheLifeTime Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default + * value for it or let the driver take care of that. */ - protected function __construct(Cache $cacheDriver, SearchCondition $searchCondition, \DateInterval|int|null $ttl = null) - { - $this->cacheDriver = $cacheDriver; - $this->cacheLifeTime = $ttl; - $this->searchCondition = $searchCondition; + protected function __construct( + protected Cache $cacheDriver, + protected SearchCondition $searchCondition, + protected \DateInterval | int | null $cacheLifeTime = null, + ) { } public function getSearchCondition(): SearchCondition diff --git a/lib/Doctrine/Dbal/CachedConditionGenerator.php b/lib/Doctrine/Dbal/CachedConditionGenerator.php index 4d59fac0..bdaef087 100644 --- a/lib/Doctrine/Dbal/CachedConditionGenerator.php +++ b/lib/Doctrine/Dbal/CachedConditionGenerator.php @@ -32,14 +32,15 @@ */ final class CachedConditionGenerator extends AbstractCachedConditionGenerator implements ConditionGenerator { - private QueryBuilder $qb; - private FieldConfigurationSet $fieldsConfig; - - public function __construct(QueryBuilder $queryBuilder, SearchCondition $searchCondition, Cache $cacheDriver, \DateInterval|int|null $ttl = null) - { + private readonly FieldConfigurationSet $fieldsConfig; + + public function __construct( + private readonly QueryBuilder $qb, + SearchCondition $searchCondition, + Cache $cacheDriver, + \DateInterval | int | null $ttl = null, + ) { parent::__construct($cacheDriver, $searchCondition, $ttl); - - $this->qb = $queryBuilder; $this->fieldsConfig = new FieldConfigurationSet($searchCondition->getFieldSet()); } diff --git a/lib/Doctrine/Dbal/ConversionHints.php b/lib/Doctrine/Dbal/ConversionHints.php index b93c440b..2c63f0bc 100644 --- a/lib/Doctrine/Dbal/ConversionHints.php +++ b/lib/Doctrine/Dbal/ConversionHints.php @@ -26,45 +26,25 @@ class ConversionHints public const CONTEXT_SIMPLE_VALUE = 'simple_value'; public const CONTEXT_COMPARISON = 'comparison'; - /** - * @var QueryField - */ - public $field; + public QueryField $field; + public Connection $connection; + public string $column; - /** - * @var Connection - */ - public $connection; + /** @var self::CONTEXT_* */ + public string $context; - /** - * @var string - */ - public $column; - - /** - * @var string - */ - public $context; + /** @var mixed|ValueHolder */ + public mixed $originalValue; - /** - * @var mixed|ValueHolder - */ - public $originalValue; - - /** - * @var AbstractQueryPlatform - */ - private $queryPlatform; - - public function __construct(AbstractQueryPlatform $queryPlatform) - { - $this->queryPlatform = $queryPlatform; + public function __construct( + private readonly AbstractQueryPlatform $queryPlatform, + ) { } /** * Returns a parameter-name to reference a value. */ - public function createParamReferenceFor($value, string|Type|null $type = 'string'): string + public function createParamReferenceFor($value, string | Type | null $type = 'string'): string { if (\is_object($type)) { $type = Type::lookupName($type); @@ -97,7 +77,7 @@ public function createParamReferenceFor($value, string|Type|null $type = 'string * The $this->originalValue might return a value-holder or actual * processing value depending on the context. */ - public function getProcessingValue() + public function getProcessingValue(): mixed { switch ($this->context) { case self::CONTEXT_SIMPLE_VALUE: @@ -111,6 +91,9 @@ public function getProcessingValue() case self::CONTEXT_RANGE_UPPER_BOUND: return $this->originalValue->getUpper(); + + default: + throw new \LogicException(\sprintf('Unknown context "%s".', $this->context)); } } diff --git a/lib/Doctrine/Dbal/DoctrineDbalFactory.php b/lib/Doctrine/Dbal/DoctrineDbalFactory.php index 7dea10bd..d331afe7 100644 --- a/lib/Doctrine/Dbal/DoctrineDbalFactory.php +++ b/lib/Doctrine/Dbal/DoctrineDbalFactory.php @@ -22,14 +22,9 @@ */ final class DoctrineDbalFactory { - /** - * @var Cache - */ - private $cacheDriver; - - public function __construct(?Cache $cacheDriver = null) - { - $this->cacheDriver = $cacheDriver; + public function __construct( + private readonly ?Cache $cacheDriver = null, + ) { } /** @@ -52,7 +47,7 @@ public function createConditionGenerator(QueryBuilder $queryBuilder, SearchCondi * the driver supports TTL then the library may set a default value * for it or let the driver take care of that. */ - public function createCachedConditionGenerator(QueryBuilder $queryBuilder, SearchCondition $searchCondition, $ttl = 0): ConditionGenerator + public function createCachedConditionGenerator(QueryBuilder $queryBuilder, SearchCondition $searchCondition, \DateInterval | int | null $ttl = 0): ConditionGenerator { if ($this->cacheDriver === null) { return new SqlConditionGenerator($queryBuilder, $searchCondition); diff --git a/lib/Doctrine/Dbal/Extension/Conversion/DateIntervalConversion.php b/lib/Doctrine/Dbal/Extension/Conversion/DateIntervalConversion.php index 41c63cfe..a29579c4 100644 --- a/lib/Doctrine/Dbal/Extension/Conversion/DateIntervalConversion.php +++ b/lib/Doctrine/Dbal/Extension/Conversion/DateIntervalConversion.php @@ -58,7 +58,7 @@ public static function convertForMysql(CarbonInterval $value): string $handler = static function (array $units) use ($negative) { foreach ($units as &$value) { // MySQL doesn't support plural names. - $value = mb_strtoupper(rtrim($value, 's')); + $value = mb_strtoupper(mb_rtrim((string) $value, 's')); } return implode($negative ? ' - INTERVAL ' : ' + INTERVAL ', $units); diff --git a/lib/Doctrine/Dbal/Extension/Conversion/MoneyValueConversion.php b/lib/Doctrine/Dbal/Extension/Conversion/MoneyValueConversion.php index a82cc89a..ba3f0134 100644 --- a/lib/Doctrine/Dbal/Extension/Conversion/MoneyValueConversion.php +++ b/lib/Doctrine/Dbal/Extension/Conversion/MoneyValueConversion.php @@ -23,11 +23,8 @@ final class MoneyValueConversion implements ValueConversion, ColumnConversion { - /** @var DecimalMoneyFormatter */ - private $formatter; - - /** @var ISOCurrencies */ - private $currencies; + private DecimalMoneyFormatter $formatter; + private ISOCurrencies $currencies; public function __construct() { diff --git a/lib/Doctrine/Dbal/Extension/DoctrineDbalExtension.php b/lib/Doctrine/Dbal/Extension/DoctrineDbalExtension.php index b0fdfb30..4e82f58f 100644 --- a/lib/Doctrine/Dbal/Extension/DoctrineDbalExtension.php +++ b/lib/Doctrine/Dbal/Extension/DoctrineDbalExtension.php @@ -14,25 +14,28 @@ namespace Rollerworks\Component\Search\Extension\Doctrine\Dbal; use Rollerworks\Component\Search\AbstractExtension; -use Rollerworks\Component\Search\Extension\Doctrine\Dbal\Conversion\AgeDateConversion; -use Rollerworks\Component\Search\Extension\Doctrine\Dbal\Conversion\MoneyValueConversion; +use Rollerworks\Component\Search\Extension\Doctrine\Dbal\Type\BirthdayTypeExtension; +use Rollerworks\Component\Search\Extension\Doctrine\Dbal\Type\ChildCountType; +use Rollerworks\Component\Search\Extension\Doctrine\Dbal\Type\DateTimeTypeExtension; +use Rollerworks\Component\Search\Extension\Doctrine\Dbal\Type\FieldTypeExtension; +use Rollerworks\Component\Search\Extension\Doctrine\Dbal\Type\MoneyTypeExtension; class DoctrineDbalExtension extends AbstractExtension { protected function loadTypesExtensions(): array { return [ - new Type\FieldTypeExtension(), - new Type\DateTimeTypeExtension(), - new Type\BirthdayTypeExtension(new AgeDateConversion()), - new Type\MoneyTypeExtension(new MoneyValueConversion()), + new FieldTypeExtension(), + new DateTimeTypeExtension(), + new BirthdayTypeExtension(), + new MoneyTypeExtension(), ]; } protected function loadTypes(): array { return [ - new Type\ChildCountType(), + new ChildCountType(), ]; } } diff --git a/lib/Doctrine/Dbal/Extension/Type/BirthdayTypeExtension.php b/lib/Doctrine/Dbal/Extension/Type/BirthdayTypeExtension.php index 3ace39fe..fb7f2dac 100644 --- a/lib/Doctrine/Dbal/Extension/Type/BirthdayTypeExtension.php +++ b/lib/Doctrine/Dbal/Extension/Type/BirthdayTypeExtension.php @@ -23,16 +23,11 @@ * * @author Sebastiaan Stok */ -class BirthdayTypeExtension extends AbstractFieldTypeExtension +final class BirthdayTypeExtension extends AbstractFieldTypeExtension { - /** - * @var AgeDateConversion - */ - private $conversion; - - public function __construct(AgeDateConversion $conversion) - { - $this->conversion = $conversion; + public function __construct( + private readonly AgeDateConversion $conversion = new AgeDateConversion(), + ) { } public function configureOptions(OptionsResolver $resolver): void diff --git a/lib/Doctrine/Dbal/Extension/Type/ChildCountType.php b/lib/Doctrine/Dbal/Extension/Type/ChildCountType.php index b33be372..71fa8041 100644 --- a/lib/Doctrine/Dbal/Extension/Type/ChildCountType.php +++ b/lib/Doctrine/Dbal/Extension/Type/ChildCountType.php @@ -25,7 +25,7 @@ */ class ChildCountType extends AbstractFieldType { - protected $conversion; + protected ChildCountConversion $conversion; public function __construct() { diff --git a/lib/Doctrine/Dbal/Extension/Type/DateTimeTypeExtension.php b/lib/Doctrine/Dbal/Extension/Type/DateTimeTypeExtension.php index 181809c3..8427bda8 100644 --- a/lib/Doctrine/Dbal/Extension/Type/DateTimeTypeExtension.php +++ b/lib/Doctrine/Dbal/Extension/Type/DateTimeTypeExtension.php @@ -19,12 +19,9 @@ use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; -class DateTimeTypeExtension extends AbstractFieldTypeExtension +final class DateTimeTypeExtension extends AbstractFieldTypeExtension { - /** - * @var DateIntervalConversion - */ - private $conversion; + private DateIntervalConversion $conversion; public function __construct() { @@ -33,15 +30,9 @@ public function __construct() public function configureOptions(OptionsResolver $resolver): void { - $resolver->setDefaults( - ['doctrine_dbal_conversion' => function (Options $options) { - if ($options['allow_relative']) { - return $this->conversion; - } - - return null; - }] - ); + $resolver->setDefaults([ + 'doctrine_dbal_conversion' => fn (Options $options): ?DateIntervalConversion => $options['allow_relative'] ? $this->conversion : null, + ]); } public function getExtendedType(): string diff --git a/lib/Doctrine/Dbal/Extension/Type/MoneyTypeExtension.php b/lib/Doctrine/Dbal/Extension/Type/MoneyTypeExtension.php index 13ae3dd4..4405c266 100644 --- a/lib/Doctrine/Dbal/Extension/Type/MoneyTypeExtension.php +++ b/lib/Doctrine/Dbal/Extension/Type/MoneyTypeExtension.php @@ -27,16 +27,11 @@ * * @author Sebastiaan Stok */ -class MoneyTypeExtension extends AbstractFieldTypeExtension +final class MoneyTypeExtension extends AbstractFieldTypeExtension { - /** - * @var MoneyValueConversion - */ - private $conversion; - - public function __construct(MoneyValueConversion $conversion) - { - $this->conversion = $conversion; + public function __construct( + private readonly MoneyValueConversion $conversion = new MoneyValueConversion(), + ) { } public function configureOptions(OptionsResolver $resolver): void diff --git a/lib/Doctrine/Dbal/FieldConfigurationSet.php b/lib/Doctrine/Dbal/FieldConfigurationSet.php index 30a3699f..586ba986 100644 --- a/lib/Doctrine/Dbal/FieldConfigurationSet.php +++ b/lib/Doctrine/Dbal/FieldConfigurationSet.php @@ -21,13 +21,12 @@ */ final class FieldConfigurationSet { - private FieldSet $fieldSet; /** @var array> */ - public $fields = []; + public array $fields = []; - public function __construct(FieldSet $fieldSet) - { - $this->fieldSet = $fieldSet; + public function __construct( + private readonly FieldSet $fieldSet, + ) { } public function setField(string $fieldName, string $column, ?string $alias = null, string $type = 'string'): void diff --git a/lib/Doctrine/Dbal/Query/QueryField.php b/lib/Doctrine/Dbal/Query/QueryField.php index fb7325ea..764364ee 100644 --- a/lib/Doctrine/Dbal/Query/QueryField.php +++ b/lib/Doctrine/Dbal/Query/QueryField.php @@ -28,57 +28,20 @@ */ class QueryField { - /** - * @var string - */ - public $mappingName; - - /** - * @var FieldConfig - */ - public $fieldConfig; - - /** - * @var DbType - */ - public $dbType; - - /** - * @var string - */ - public $dbTypeName; - - /** - * @var string - */ - public $column; - - /** - * @var ColumnConversion|null - */ - public $columnConversion; - - /** - * @var ValueConversion|null - */ - public $valueConversion; - - /** - * @var string - */ - public $alias; - - /** - * @var string - */ - public $tableColumn; - - public function __construct(string $mappingName, FieldConfig $fieldConfig, string $dbType, string $column, ?string $alias = null) - { - $this->mappingName = $mappingName; - $this->fieldConfig = $fieldConfig; - - $this->alias = $alias; + public readonly DbType $dbType; + public readonly string $dbTypeName; + public readonly string $column; + public ?object $columnConversion; + public ?object $valueConversion; + public readonly string $tableColumn; + + public function __construct( + public string $mappingName, + public FieldConfig $fieldConfig, + string $dbType, + string $column, + public ?string $alias = null, + ) { $this->tableColumn = $column; $this->column = ($alias ? $alias . '.' : '') . $column; $this->dbType = DbType::getType($dbType); @@ -87,6 +50,9 @@ public function __construct(string $mappingName, FieldConfig $fieldConfig, strin $this->initConversions($fieldConfig); } + /** + * @return array{mapping_name: string, field: string, db_type: string} + */ public function __serialize(): array { return [ @@ -96,6 +62,9 @@ public function __serialize(): array ]; } + /** + * @param mixed[] $data + */ public function __unserialize(array $data): void { // noop @@ -109,12 +78,7 @@ protected function initConversions(FieldConfig $fieldConfig): void $converter = $converter(); } - if ($converter instanceof ColumnConversion) { - $this->columnConversion = $converter; - } - - if ($converter instanceof ValueConversion) { - $this->valueConversion = $converter; - } + $this->columnConversion = $converter instanceof ColumnConversion ? $converter : null; + $this->valueConversion = $converter instanceof ValueConversion ? $converter : null; } } diff --git a/lib/Doctrine/Dbal/Query/QueryGenerator.php b/lib/Doctrine/Dbal/Query/QueryGenerator.php index 077f7f25..f05fa6dc 100644 --- a/lib/Doctrine/Dbal/Query/QueryGenerator.php +++ b/lib/Doctrine/Dbal/Query/QueryGenerator.php @@ -39,25 +39,13 @@ final class QueryGenerator { /** - * @var array> [field-name][mapping-index] => {QueryField} + * @param array> $fields [field-name][mapping-index] => {QueryField} */ - private array $fields; - - /** - * @var Connection - */ - private $connection; - - /** - * @var AbstractQueryPlatform - */ - private $queryPlatform; - - public function __construct(Connection $connection, AbstractQueryPlatform $queryPlatform, array $fields) - { - $this->connection = $connection; - $this->queryPlatform = $queryPlatform; - $this->fields = $fields; + public function __construct( + private readonly Connection $connection, + private readonly AbstractQueryPlatform $queryPlatform, + private readonly array $fields, + ) { } /** diff --git a/lib/Doctrine/Dbal/QueryPlatform/AbstractQueryPlatform.php b/lib/Doctrine/Dbal/QueryPlatform/AbstractQueryPlatform.php index 9d399006..8903ca5a 100644 --- a/lib/Doctrine/Dbal/QueryPlatform/AbstractQueryPlatform.php +++ b/lib/Doctrine/Dbal/QueryPlatform/AbstractQueryPlatform.php @@ -28,30 +28,19 @@ */ abstract class AbstractQueryPlatform { - /** - * @var Connection - */ - protected $connection; - - /** @var ArrayCollection */ - private $parameters; + /** @var ArrayCollection */ + private readonly ArrayCollection $parameters; - /** @var int */ - private $parameterIdx = -1; + private int $parameterIdx = -1; - /** - * @var 'mysql'|'sqlite'|'pgsql'|'oci'|'sqlsrv'|'mock'|string - */ - public string $platformName; - - public function __construct(Connection $connection, string $platformName) - { - $this->connection = $connection; + public function __construct( + protected Connection $connection, + public readonly string $platformName, + ) { $this->parameters = new ArrayCollection(); - $this->platformName = $platformName; } - public function getValueAsSql($value, QueryField $mappingConfig, ConversionHints $hints): string + public function getValueAsSql(mixed $value, QueryField $mappingConfig, ConversionHints $hints): string { if ($mappingConfig->valueConversion !== null) { return $mappingConfig->valueConversion->convertValue( @@ -64,7 +53,7 @@ public function getValueAsSql($value, QueryField $mappingConfig, ConversionHints return $this->createParamReferenceFor($value, $mappingConfig->dbTypeName); } - public function createParamReferenceFor($value, ?string $type = null): string + public function createParamReferenceFor(mixed $value, ?string $type = null): string { $name = 'search_' . (++$this->parameterIdx); $this->parameters->set($name, [$value, $type]); @@ -126,6 +115,9 @@ protected function getLikeEscapeChars(): string return '%_'; } + /** + * @return ArrayCollection + */ public function getParameters(): ArrayCollection { return $this->parameters; diff --git a/lib/Doctrine/Dbal/SqlConditionGenerator.php b/lib/Doctrine/Dbal/SqlConditionGenerator.php index 2bb4c5ce..4b5ef6a4 100644 --- a/lib/Doctrine/Dbal/SqlConditionGenerator.php +++ b/lib/Doctrine/Dbal/SqlConditionGenerator.php @@ -14,12 +14,18 @@ namespace Rollerworks\Component\Search\Doctrine\Dbal; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\OraclePlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; +use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\DBAL\Query\QueryBuilder; use Rollerworks\Component\Search\Doctrine\Dbal\Query\QueryGenerator; use Rollerworks\Component\Search\Doctrine\Dbal\QueryPlatform\AbstractQueryPlatform; use Rollerworks\Component\Search\Doctrine\Dbal\QueryPlatform\SqlQueryPlatform; use Rollerworks\Component\Search\Exception\BadMethodCallException; use Rollerworks\Component\Search\SearchCondition; +use Rollerworks\Component\Search\Tests\Doctrine\Dbal\Mocks\DatabasePlatformMock; /** * SearchCondition Doctrine DBAL ConditionGenerator. @@ -39,16 +45,14 @@ */ final class SqlConditionGenerator implements ConditionGenerator { - private QueryBuilder $qb; - private SearchCondition $searchCondition; - private FieldConfigurationSet $fieldsConfig; + private readonly FieldConfigurationSet $fieldsConfig; private bool $isApplied = false; - public function __construct(QueryBuilder $queryBuilder, SearchCondition $searchCondition) - { - $this->qb = $queryBuilder; - $this->searchCondition = $searchCondition; - $this->fieldsConfig = new FieldConfigurationSet($searchCondition->getFieldSet()); + public function __construct( + private readonly QueryBuilder $qb, + private readonly SearchCondition $searchCondition, + ) { + $this->fieldsConfig = new FieldConfigurationSet($this->searchCondition->getFieldSet()); } public function setField(string $fieldName, string $column, ?string $alias = null, string $type = 'string'): self @@ -130,12 +134,12 @@ private static function getPlatformName(Connection $connection): string $platform = $connection->getDatabasePlatform(); return match (true) { - $platform instanceof \Doctrine\DBAL\Platforms\AbstractMySQLPlatform => 'mysql', - $platform instanceof \Doctrine\DBAL\Platforms\SQLitePlatform => 'sqlite', - $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform => 'pgsql', - $platform instanceof \Doctrine\DBAL\Platforms\OraclePlatform => 'oci', - $platform instanceof \Doctrine\DBAL\Platforms\SQLServerPlatform => 'sqlsrv', - $platform instanceof \Rollerworks\Component\Search\Tests\Doctrine\Dbal\Mocks\DatabasePlatformMock => 'mock', + $platform instanceof AbstractMySQLPlatform => 'mysql', + $platform instanceof SQLitePlatform => 'sqlite', + $platform instanceof PostgreSQLPlatform => 'pgsql', + $platform instanceof OraclePlatform => 'oci', + $platform instanceof SQLServerPlatform => 'sqlsrv', + $platform instanceof DatabasePlatformMock => 'mock', default => $platform::class, }; } diff --git a/lib/Doctrine/Dbal/Tests/CachedConditionGeneratorTest.php b/lib/Doctrine/Dbal/Tests/CachedConditionGeneratorTest.php index 56fdeca4..4f057464 100644 --- a/lib/Doctrine/Dbal/Tests/CachedConditionGeneratorTest.php +++ b/lib/Doctrine/Dbal/Tests/CachedConditionGeneratorTest.php @@ -29,10 +29,8 @@ final class CachedConditionGeneratorTest extends DbalTestCase private QueryBuilder $query; private CachedConditionGenerator $conditionGenerator; - /** - * @var Cache&MockObject - */ - protected $cacheDriver; + /** @var Cache&MockObject */ + protected MockObject $cacheDriver; public const CACHE_KEY = 'da80730b87c4750f8c619ac64b679586ac2f9d86c53508d1a53a7c0341b4e363'; diff --git a/lib/Doctrine/Dbal/Tests/DbalExtensions/Connection.php b/lib/Doctrine/Dbal/Tests/DbalExtensions/Connection.php index ee78fc39..31c61b9c 100644 --- a/lib/Doctrine/Dbal/Tests/DbalExtensions/Connection.php +++ b/lib/Doctrine/Dbal/Tests/DbalExtensions/Connection.php @@ -26,8 +26,7 @@ */ final class Connection extends BaseConnection { - /** @var QueryLog */ - public $queryLog; + public QueryLog $queryLog; public function __construct(array $params, Driver $driver, ?Configuration $config = null, ?EventManager $eventManager = null) { diff --git a/lib/Doctrine/Dbal/Tests/DbalExtensions/LegacySqlLogger.php b/lib/Doctrine/Dbal/Tests/DbalExtensions/LegacySqlLogger.php index e2993520..317d04e0 100644 --- a/lib/Doctrine/Dbal/Tests/DbalExtensions/LegacySqlLogger.php +++ b/lib/Doctrine/Dbal/Tests/DbalExtensions/LegacySqlLogger.php @@ -22,12 +22,9 @@ */ final class LegacySqlLogger implements SQLLogger { - /** @var QueryLog */ - private $queryLog; - - public function __construct(QueryLog $queryLog) - { - $this->queryLog = $queryLog; + public function __construct( + private readonly QueryLog $queryLog, + ) { } public function startQuery($sql, ?array $params = null, ?array $types = null): void diff --git a/lib/Doctrine/Dbal/Tests/DbalExtensions/QueryLog.php b/lib/Doctrine/Dbal/Tests/DbalExtensions/QueryLog.php index c89414a8..5a3fb2ef 100644 --- a/lib/Doctrine/Dbal/Tests/DbalExtensions/QueryLog.php +++ b/lib/Doctrine/Dbal/Tests/DbalExtensions/QueryLog.php @@ -21,10 +21,9 @@ final class QueryLog { /** @var list */ - public $queries = []; + public array $queries = []; - /** @var bool */ - private $enabled = false; + private bool $enabled = false; public function logQuery(string $sql, ?array $params = null, ?array $types = null): void { @@ -39,7 +38,9 @@ public function logQuery(string $sql, ?array $params = null, ?array $types = nul ]; } - /** @return $this */ + /** + * @return $this + */ public function reset(): self { $this->enabled = false; @@ -48,7 +49,9 @@ public function reset(): self return $this; } - /** @return $this */ + /** + * @return $this + */ public function enable(): self { $this->enabled = true; @@ -56,7 +59,9 @@ public function enable(): self return $this; } - /** @return $this */ + /** + * @return $this + */ public function disable(): self { $this->enabled = false; diff --git a/lib/Doctrine/Dbal/Tests/DbalExtensions/SqlLogger.php b/lib/Doctrine/Dbal/Tests/DbalExtensions/SqlLogger.php index 11d06d13..dff85848 100644 --- a/lib/Doctrine/Dbal/Tests/DbalExtensions/SqlLogger.php +++ b/lib/Doctrine/Dbal/Tests/DbalExtensions/SqlLogger.php @@ -22,12 +22,9 @@ */ final class SqlLogger extends AbstractLogger { - /** @var QueryLog */ - private $queryLog; - - public function __construct(QueryLog $queryLog) - { - $this->queryLog = $queryLog; + public function __construct( + private readonly QueryLog $queryLog, + ) { } public function log($level, $message, array $context = []): void diff --git a/lib/Doctrine/Dbal/Tests/DoctrineDbalFactoryTest.php b/lib/Doctrine/Dbal/Tests/DoctrineDbalFactoryTest.php index 94320119..aceafc6b 100644 --- a/lib/Doctrine/Dbal/Tests/DoctrineDbalFactoryTest.php +++ b/lib/Doctrine/Dbal/Tests/DoctrineDbalFactoryTest.php @@ -26,10 +26,7 @@ */ final class DoctrineDbalFactoryTest extends DbalTestCase { - /** - * @var DoctrineDbalFactory - */ - protected $factory; + protected DoctrineDbalFactory $factory; /** @test */ public function create_condition_generator(): void diff --git a/lib/Doctrine/Dbal/Tests/Functional/Extension/Conversion/AgeConversionTest.php b/lib/Doctrine/Dbal/Tests/Functional/Extension/Conversion/AgeConversionTest.php index 9169679f..73aa13d4 100644 --- a/lib/Doctrine/Dbal/Tests/Functional/Extension/Conversion/AgeConversionTest.php +++ b/lib/Doctrine/Dbal/Tests/Functional/Extension/Conversion/AgeConversionTest.php @@ -39,7 +39,7 @@ protected function setUpDbSchema(DbSchema $schema): void /** * @return SchemaRecord[] */ - protected function getDbRecords() + protected function getDbRecords(): array { return [ SchemaRecord::create('site_user', ['id' => 'integer', 'birthday' => 'date_immutable']) diff --git a/lib/Doctrine/Dbal/Tests/Functional/Extension/Conversion/ChildCountTypeTest.php b/lib/Doctrine/Dbal/Tests/Functional/Extension/Conversion/ChildCountTypeTest.php index 7932026c..6d0d8755 100644 --- a/lib/Doctrine/Dbal/Tests/Functional/Extension/Conversion/ChildCountTypeTest.php +++ b/lib/Doctrine/Dbal/Tests/Functional/Extension/Conversion/ChildCountTypeTest.php @@ -41,10 +41,7 @@ protected function setUpDbSchema(DbSchema $schema): void $userTable->addColumn('name', 'string', ['length' => 255]); } - /** - * @return SchemaRecord[] - */ - protected function getDbRecords() + protected function getDbRecords(): array { return [ SchemaRecord::create('site_user', ['id' => 'integer', 'birthday' => 'date_immutable']) diff --git a/lib/Doctrine/Dbal/Tests/Functional/Extension/Conversion/MoneyValueConversionTest.php b/lib/Doctrine/Dbal/Tests/Functional/Extension/Conversion/MoneyValueConversionTest.php index 2d562d9e..e6640570 100644 --- a/lib/Doctrine/Dbal/Tests/Functional/Extension/Conversion/MoneyValueConversionTest.php +++ b/lib/Doctrine/Dbal/Tests/Functional/Extension/Conversion/MoneyValueConversionTest.php @@ -40,10 +40,7 @@ protected function setUpDbSchema(DbSchema $schema): void $invoiceTable->addColumn('total', 'decimal', ['scale' => 2, 'precision' => 10]); } - /** - * @return SchemaRecord[] - */ - protected function getDbRecords() + protected function getDbRecords(): array { return [ SchemaRecord::create('product', ['id' => 'integer', 'price' => 'string', 'total' => 'decimal']) diff --git a/lib/Doctrine/Dbal/Tests/Functional/FunctionalDbalTestCase.php b/lib/Doctrine/Dbal/Tests/Functional/FunctionalDbalTestCase.php index fa3fa596..1090cae0 100644 --- a/lib/Doctrine/Dbal/Tests/Functional/FunctionalDbalTestCase.php +++ b/lib/Doctrine/Dbal/Tests/Functional/FunctionalDbalTestCase.php @@ -22,7 +22,6 @@ use Doctrine\DBAL\Types\Type; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\Exception; -use PHPUnit\Framework\Warning; use Rollerworks\Component\Search\Doctrine\Dbal\ConditionGenerator; use Rollerworks\Component\Search\Doctrine\Dbal\Test\QueryBuilderAssertion; use Rollerworks\Component\Search\Doctrine\Dbal\Tests\TestUtil; @@ -33,21 +32,12 @@ abstract class FunctionalDbalTestCase extends DbalTestCase { /** - * Shared connection when a TestCase is run alone (outside of it's functional suite). - * - * @var Connection|null + * Shared connection for setting up the database in a single place + * during class setup and tear down. */ - private static $sharedConn; + private static ?Connection $sharedConn; - /** - * @var Connection|null - */ - protected $conn; - - /** - * @var string|null - */ - protected $query; + protected Connection $conn; protected static function resetSharedConn(): void { @@ -82,6 +72,7 @@ protected function setUp(): void try { $this->conn->executeStatement($s); } catch (\Throwable) { + // Noop - tables might not exist yet } } @@ -133,7 +124,7 @@ private function updateSchema(Connection $connection, DbSchema $providedSchema): public static function tearDownAfterClass(): void { - // There are errors recorded so don't reset the connection. + // There were errors recorded so don't reset the connection. if (\count(self::$sharedConn->queryLog->queries ?? []) > 0) { return; } @@ -163,13 +154,13 @@ protected function setUpDbSchema(DbSchema $schema): void /** * @return SchemaRecord[] */ - protected function getDbRecords() + protected function getDbRecords(): array { return []; } /** - * Returns the string for the ConditionGenerator. + * Returns the QueryBuilder for the ConditionGenerator. */ abstract protected function getQuery(): QueryBuilder; @@ -181,6 +172,8 @@ protected function configureConditionGenerator(ConditionGenerator $conditionGene } /** + * @param (int|string)[] $ids + * * @throws \Doctrine\DBAL\Exception */ protected function assertRecordsAreFound(SearchCondition $condition, array $ids): void @@ -263,9 +256,11 @@ protected function assertRecordsAreFound(SearchCondition $condition, array $ids) } /** - * @param SearchCondition|ConditionGenerator $conditionOrWhere + * @param ConditionGenerator|SearchCondition $conditionOrWhere Either a ConditionGenerator or a SearchCondition + * @param string $expectedSql Expected generated SQL for the query + * @param array|null $parameters Expected parameters for the query */ - protected function assertQueryIsExecutable($conditionOrWhere, string $expectedSql = '', ?array $parameters = null): void + protected function assertQueryIsExecutable(ConditionGenerator | SearchCondition $conditionOrWhere, string $expectedSql = '', ?array $parameters = null): void { if ($conditionOrWhere instanceof SearchCondition) { $conditionGenerator = $this->getDbalFactory()->createConditionGenerator($this->getQuery(), $conditionOrWhere); diff --git a/lib/Doctrine/Dbal/Tests/Functional/SqlConditionGeneratorResultsTest.php b/lib/Doctrine/Dbal/Tests/Functional/SqlConditionGeneratorResultsTest.php index feae807b..71d8e831 100644 --- a/lib/Doctrine/Dbal/Tests/Functional/SqlConditionGeneratorResultsTest.php +++ b/lib/Doctrine/Dbal/Tests/Functional/SqlConditionGeneratorResultsTest.php @@ -49,10 +49,7 @@ */ final class SqlConditionGeneratorResultsTest extends FunctionalDbalTestCase { - /** - * @var StringQueryInput - */ - private $inputProcessor; + private StringQueryInput $inputProcessor; protected function setUp(): void { @@ -94,12 +91,9 @@ protected function setUpDbSchema(DbSchema $schema): void $invoiceDetailsTable->setPrimaryKey(['id']); } - /** - * @return SchemaRecord[] - */ - protected function getDbRecords() + protected function getDbRecords(): array { - $date = static fn (string $input) => new \DateTimeImmutable($input, new \DateTimeZone('UTC')); + $date = static fn (string $input): \DateTimeImmutable => new \DateTimeImmutable($input, new \DateTimeZone('UTC')); return [ SchemaRecord::create( diff --git a/lib/Doctrine/Dbal/Tests/Functional/SqlConditionGeneratorTest.php b/lib/Doctrine/Dbal/Tests/Functional/SqlConditionGeneratorTest.php index 04424715..3d9b9131 100644 --- a/lib/Doctrine/Dbal/Tests/Functional/SqlConditionGeneratorTest.php +++ b/lib/Doctrine/Dbal/Tests/Functional/SqlConditionGeneratorTest.php @@ -340,7 +340,7 @@ public function column_conversion(): void $converter ->expects(self::atLeastOnce()) ->method('convertColumn') - ->willReturnCallback(static fn ($column) => "CAST({$column} AS {$type})") + ->willReturnCallback(static fn ($column): string => "CAST({$column} AS {$type})") ; $fieldSetBuilder = $this->getFieldSet(false); @@ -365,7 +365,7 @@ public function value_conversion(): void $converter ->expects(self::atLeastOnce()) ->method('convertValue') - ->willReturnCallback(static fn ($input) => "CAST({$input} AS {$type})") + ->willReturnCallback(static fn ($input): string => "CAST({$input} AS {$type})") ; $fieldSetBuilder = $this->getFieldSet(false); diff --git a/lib/Doctrine/Dbal/Tests/Mocks/ConnectionMock.php b/lib/Doctrine/Dbal/Tests/Mocks/ConnectionMock.php index b056af19..69d12131 100644 --- a/lib/Doctrine/Dbal/Tests/Mocks/ConnectionMock.php +++ b/lib/Doctrine/Dbal/Tests/Mocks/ConnectionMock.php @@ -45,7 +45,7 @@ public function getDatabasePlatform(): AbstractPlatform /** * @override */ - public function insert(string $table, array $data, array $types = []): int|string + public function insert(string $table, array $data, array $types = []): int | string { $this->_inserts[$table][] = $data; @@ -57,7 +57,7 @@ public function insert(string $table, array $data, array $types = []): int|strin * * @param mixed|null $seqName */ - public function lastInsertId($seqName = null): int|string + public function lastInsertId($seqName = null): int | string { return $this->_lastInsertId; } diff --git a/lib/Doctrine/Dbal/Tests/Mocks/DatabasePlatformMock.php b/lib/Doctrine/Dbal/Tests/Mocks/DatabasePlatformMock.php index 87e9107a..9481f17a 100644 --- a/lib/Doctrine/Dbal/Tests/Mocks/DatabasePlatformMock.php +++ b/lib/Doctrine/Dbal/Tests/Mocks/DatabasePlatformMock.php @@ -14,7 +14,6 @@ namespace Rollerworks\Component\Search\Tests\Doctrine\Dbal\Mocks; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\DateIntervalUnit; use Doctrine\DBAL\Platforms\Keywords\KeywordList; diff --git a/lib/Doctrine/Dbal/Tests/SchemaRecord.php b/lib/Doctrine/Dbal/Tests/SchemaRecord.php index 04f6dd1b..46198079 100644 --- a/lib/Doctrine/Dbal/Tests/SchemaRecord.php +++ b/lib/Doctrine/Dbal/Tests/SchemaRecord.php @@ -17,35 +17,40 @@ final class SchemaRecord { - private $table; - private $records = []; - private $columns = []; + /** @var array */ + private array $records = []; - public function __construct($tableName, array $columns) - { - $this->table = $tableName; - $this->columns = $columns; + /** + * @param array $columns ['column1' => 'type'] + */ + public function __construct( + private readonly string $table, + private readonly array $columns, + ) { } /** - * @param string $tableName Fully qualified table-name - * @param array $columns [column1, column2] (must contain "id") + * @param string $tableName Fully qualified table-name + * @param array $columns ['column1' => 'type'] * * @return SchemaRecord */ - public static function create($tableName, array $columns) + public static function create(string $tableName, array $columns) { return new self($tableName, $columns); } - public function getTable() + public function getTable(): string { return $this->table; } + /** + * @param mixed[] $values Values are expected in the same order as the columns + */ public function add(array $values) { - if (\count($values) != \count($this->columns)) { + if (\count($values) !== \count($this->columns)) { throw new \InvalidArgumentException( \sprintf( 'Values count mismatch, expected %d got %d on table "%s" with record: %s', @@ -63,26 +68,25 @@ public function add(array $values) } /** - * Semantic method for chaining. - * - * @return static + * @return $this */ - public function end() + public function end(): self { return $this; } /** - * Semantic method for chaining. - * - * @return static + * @return $this */ - public function records() + public function records(): self { return $this; } - public function getColumns() + /** + * @return array ['column1' => 'type'] + */ + public function getColumns(): array { return $this->columns; } diff --git a/lib/Doctrine/Dbal/Tests/SqlConditionGeneratorTest.php b/lib/Doctrine/Dbal/Tests/SqlConditionGeneratorTest.php index 867a4374..50343ddc 100644 --- a/lib/Doctrine/Dbal/Tests/SqlConditionGeneratorTest.php +++ b/lib/Doctrine/Dbal/Tests/SqlConditionGeneratorTest.php @@ -818,7 +818,7 @@ public function lazy_conversion_loading(): void $fieldSetBuilder = $this->getFieldSet(false); $fieldSetBuilder->add('customer', IntegerType::class, [ 'grouping' => true, - 'doctrine_dbal_conversion' => static fn () => $converter, + 'doctrine_dbal_conversion' => static fn (): ColumnConversion => $converter, ]); $condition = SearchConditionBuilder::create($fieldSetBuilder->getFieldSet()) diff --git a/lib/Doctrine/Dbal/Tests/TestUtil.php b/lib/Doctrine/Dbal/Tests/TestUtil.php index 14b90ae1..ca0b6027 100644 --- a/lib/Doctrine/Dbal/Tests/TestUtil.php +++ b/lib/Doctrine/Dbal/Tests/TestUtil.php @@ -97,7 +97,7 @@ private static function initializeDatabase(): void try { $schemaManager->dropDatabase($dbname); - } catch (DriverException $e) { + } catch (DriverException) { } $schemaManager->createDatabase($dbname); @@ -122,7 +122,7 @@ private static function addDbEventSubscribers(Connection $conn): void $evm = $conn->getEventManager(); /** @var class-string $subscriberClass */ - foreach (explode(',', $GLOBALS['db_event_subscribers']) as $subscriberClass) { + foreach (explode(',', (string) $GLOBALS['db_event_subscribers']) as $subscriberClass) { $subscriberInstance = new $subscriberClass(); $evm->addEventSubscriber($subscriberInstance); } diff --git a/lib/Doctrine/Dbal/composer.json b/lib/Doctrine/Dbal/composer.json index d7110413..3a13abab 100644 --- a/lib/Doctrine/Dbal/composer.json +++ b/lib/Doctrine/Dbal/composer.json @@ -20,7 +20,7 @@ } ], "require": { - "php": ">=8.1", + "php": "^8.1", "doctrine/dbal": "^3.8.0 || ^4.2", "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "rollerworks/search": ">=2.0.0-BETA9 ^2.0@dev" diff --git a/lib/Doctrine/Orm/CachedDqlConditionGenerator.php b/lib/Doctrine/Orm/CachedDqlConditionGenerator.php index eff57676..39e35abe 100644 --- a/lib/Doctrine/Orm/CachedDqlConditionGenerator.php +++ b/lib/Doctrine/Orm/CachedDqlConditionGenerator.php @@ -38,17 +38,10 @@ */ class CachedDqlConditionGenerator extends AbstractCachedConditionGenerator implements ConditionGenerator { - /** - * @var FieldConfigBuilder - */ - private $fieldsConfig; - - /** - * @var QueryBuilder - */ - private $qb; + private FieldConfigBuilder $fieldsConfig; + private QueryBuilder $qb; - public function __construct(QueryBuilder $query, SearchCondition $searchCondition, Cache $cacheDriver, \DateInterval|int|null $ttl = null) + public function __construct(QueryBuilder $query, SearchCondition $searchCondition, Cache $cacheDriver, \DateInterval | int | null $ttl = null) { parent::__construct($cacheDriver, $searchCondition, $ttl); diff --git a/lib/Doctrine/Orm/DoctrineOrmFactory.php b/lib/Doctrine/Orm/DoctrineOrmFactory.php index 16fa3ce7..df00da16 100644 --- a/lib/Doctrine/Orm/DoctrineOrmFactory.php +++ b/lib/Doctrine/Orm/DoctrineOrmFactory.php @@ -22,14 +22,9 @@ */ class DoctrineOrmFactory { - /** - * @var Cache - */ - private $cacheDriver; - - public function __construct(?Cache $cacheDriver = null) - { - $this->cacheDriver = $cacheDriver; + public function __construct( + private readonly ?Cache $cacheDriver = null, + ) { } /** @@ -49,7 +44,7 @@ public function createConditionGenerator(QueryBuilder $query, SearchCondition $s * the driver supports TTL then the library may set a default value * for it or let the driver take care of that. */ - public function createCachedConditionGenerator(QueryBuilder $query, SearchCondition $searchCondition, $ttl = null): ConditionGenerator + public function createCachedConditionGenerator(QueryBuilder $query, SearchCondition $searchCondition, \DateInterval | int | null $ttl = null): ConditionGenerator { if ($this->cacheDriver === null) { return $this->createConditionGenerator($query, $searchCondition); diff --git a/lib/Doctrine/Orm/DqlConditionGenerator.php b/lib/Doctrine/Orm/DqlConditionGenerator.php index 9a0777a2..0bacb668 100644 --- a/lib/Doctrine/Orm/DqlConditionGenerator.php +++ b/lib/Doctrine/Orm/DqlConditionGenerator.php @@ -15,6 +15,11 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\OraclePlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; +use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\QueryBuilder; use Rollerworks\Component\Search\Doctrine\Dbal\Query\QueryGenerator; @@ -22,6 +27,7 @@ use Rollerworks\Component\Search\Exception\BadMethodCallException; use Rollerworks\Component\Search\SearchCondition; use Rollerworks\Component\Search\SearchOrder; +use Rollerworks\Component\Search\Tests\Doctrine\Dbal\Mocks\DatabasePlatformMock; /** * SearchCondition Doctrine ORM DQL ConditionGenerator. @@ -33,36 +39,16 @@ */ final class DqlConditionGenerator { - /** - * @var SearchCondition - */ - private $searchCondition; - - /** - * @var EntityManagerInterface - */ - private $entityManager; - - /** - * @var string - */ - private $whereClause; + private string $whereClause; - /** - * @var FieldConfigBuilder - */ - private $fieldsConfig; - - /** - * @var ArrayCollection|null - */ - private $parameters; + /** @var ArrayCollection */ + private ArrayCollection $parameters; - public function __construct(EntityManagerInterface $entityManager, SearchCondition $searchCondition, FieldConfigBuilder $configBuilder) - { - $this->entityManager = $entityManager; - $this->searchCondition = $searchCondition; - $this->fieldsConfig = $configBuilder; + public function __construct( + private readonly EntityManagerInterface $entityManager, + private readonly SearchCondition $searchCondition, + private readonly FieldConfigBuilder $fieldsConfig, + ) { } public function getWhereClause(): string @@ -78,6 +64,9 @@ public function getWhereClause(): string return $this->whereClause; } + /** + * @return ArrayCollection + */ public function getParameters(): ArrayCollection { if (! isset($this->parameters)) { @@ -113,12 +102,12 @@ private static function getPlatformName(Connection $connection): string $platform = $connection->getDatabasePlatform(); return match (true) { - $platform instanceof \Doctrine\DBAL\Platforms\AbstractMySQLPlatform => 'mysql', - $platform instanceof \Doctrine\DBAL\Platforms\SQLitePlatform => 'sqlite', - $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform => 'pgsql', - $platform instanceof \Doctrine\DBAL\Platforms\OraclePlatform => 'oci', - $platform instanceof \Doctrine\DBAL\Platforms\SQLServerPlatform => 'sqlsrv', - $platform instanceof \Rollerworks\Component\Search\Tests\Doctrine\Dbal\Mocks\DatabasePlatformMock => 'mock', + $platform instanceof AbstractMySQLPlatform => 'mysql', + $platform instanceof SQLitePlatform => 'sqlite', + $platform instanceof PostgreSQLPlatform => 'pgsql', + $platform instanceof OraclePlatform => 'oci', + $platform instanceof SQLServerPlatform => 'sqlsrv', + $platform instanceof DatabasePlatformMock => 'mock', default => $platform::class, }; } diff --git a/lib/Doctrine/Orm/Extension/Conversion/MoneyValueConversion.php b/lib/Doctrine/Orm/Extension/Conversion/MoneyValueConversion.php index 3a15d732..e24de663 100644 --- a/lib/Doctrine/Orm/Extension/Conversion/MoneyValueConversion.php +++ b/lib/Doctrine/Orm/Extension/Conversion/MoneyValueConversion.php @@ -23,11 +23,8 @@ final class MoneyValueConversion implements ValueConversion, ColumnConversion { - /** @var DecimalMoneyFormatter */ - private $formatter; - - /** @var ISOCurrencies */ - private $currencies; + private DecimalMoneyFormatter $formatter; + private ISOCurrencies $currencies; public function __construct() { @@ -38,7 +35,7 @@ public function __construct() /** * @param MoneyValue $value */ - public function convertValue($value, array $options, ConversionHints $hints): string + public function convertValue(mixed $value, array $options, ConversionHints $hints): string { $sqlValue = $hints->createParamReferenceFor($this->formatter->format($value->value)); $scale = $this->currencies->subunitFor($value->value->getCurrency()); diff --git a/lib/Doctrine/Orm/Extension/Functions/AgeFunction.php b/lib/Doctrine/Orm/Extension/Functions/AgeFunction.php index f34d030b..c0a5d78d 100644 --- a/lib/Doctrine/Orm/Extension/Functions/AgeFunction.php +++ b/lib/Doctrine/Orm/Extension/Functions/AgeFunction.php @@ -13,6 +13,7 @@ namespace Rollerworks\Component\Search\Doctrine\Orm\Extension\Functions; +use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\TokenType; @@ -22,7 +23,7 @@ */ final class AgeFunction extends PlatformSpecificFunction { - public $stringPrimary; + public Node $stringPrimary; public function getSql(SqlWalker $sqlWalker): string { diff --git a/lib/Doctrine/Orm/Extension/Functions/CastFunction.php b/lib/Doctrine/Orm/Extension/Functions/CastFunction.php index 1c06c668..41cf4c18 100644 --- a/lib/Doctrine/Orm/Extension/Functions/CastFunction.php +++ b/lib/Doctrine/Orm/Extension/Functions/CastFunction.php @@ -14,6 +14,7 @@ namespace Rollerworks\Component\Search\Doctrine\Orm\Extension\Functions; use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\TokenType; @@ -23,12 +24,8 @@ */ final class CastFunction extends FunctionNode { - public $stringPrimary; - - /** - * @var string - */ - public $type; + public Node $stringPrimary; + public string $type; public function getSql(SqlWalker $sqlWalker): string { diff --git a/lib/Doctrine/Orm/Extension/Functions/CastIntervalFunction.php b/lib/Doctrine/Orm/Extension/Functions/CastIntervalFunction.php index 6920b8e5..c373aa63 100644 --- a/lib/Doctrine/Orm/Extension/Functions/CastIntervalFunction.php +++ b/lib/Doctrine/Orm/Extension/Functions/CastIntervalFunction.php @@ -24,8 +24,8 @@ */ final class CastIntervalFunction extends PlatformSpecificFunction { - public $intervalExpression; - public $inverted; + public string $intervalExpression; + public bool $inverted; public function getSql(SqlWalker $sqlWalker): string { diff --git a/lib/Doctrine/Orm/Extension/Functions/CountChildrenFunction.php b/lib/Doctrine/Orm/Extension/Functions/CountChildrenFunction.php index c519054b..ca311272 100644 --- a/lib/Doctrine/Orm/Extension/Functions/CountChildrenFunction.php +++ b/lib/Doctrine/Orm/Extension/Functions/CountChildrenFunction.php @@ -14,6 +14,7 @@ namespace Rollerworks\Component\Search\Doctrine\Orm\Extension\Functions; use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\TokenType; @@ -23,9 +24,9 @@ */ final class CountChildrenFunction extends FunctionNode { - public $stringPrimary; - public $field; - public $column; + public Node $stringPrimary; + public Node $field; + public Node $column; public function getSql(SqlWalker $sqlWalker): string { diff --git a/lib/Doctrine/Orm/Extension/Functions/MoneyCastFunction.php b/lib/Doctrine/Orm/Extension/Functions/MoneyCastFunction.php index 31a937b9..6b3a02fa 100644 --- a/lib/Doctrine/Orm/Extension/Functions/MoneyCastFunction.php +++ b/lib/Doctrine/Orm/Extension/Functions/MoneyCastFunction.php @@ -13,6 +13,7 @@ namespace Rollerworks\Component\Search\Doctrine\Orm\Extension\Functions; +use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\TokenType; @@ -22,12 +23,8 @@ */ final class MoneyCastFunction extends PlatformSpecificFunction { - public $stringPrimary; - - /** - * @var int - */ - public $scale; + public Node $stringPrimary; + public int $scale; public function getSql(SqlWalker $sqlWalker): string { diff --git a/lib/Doctrine/Orm/Extension/Functions/PlatformSpecificFunction.php b/lib/Doctrine/Orm/Extension/Functions/PlatformSpecificFunction.php index 5c3d2b85..5e12ed22 100644 --- a/lib/Doctrine/Orm/Extension/Functions/PlatformSpecificFunction.php +++ b/lib/Doctrine/Orm/Extension/Functions/PlatformSpecificFunction.php @@ -14,7 +14,13 @@ namespace Rollerworks\Component\Search\Doctrine\Orm\Extension\Functions; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Platforms\AbstractMySQLPlatform; +use Doctrine\DBAL\Platforms\OraclePlatform; +use Doctrine\DBAL\Platforms\PostgreSQLPlatform; +use Doctrine\DBAL\Platforms\SQLitePlatform; +use Doctrine\DBAL\Platforms\SQLServerPlatform; use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Rollerworks\Component\Search\Tests\Doctrine\Dbal\Mocks\DatabasePlatformMock; /** * Extend this class class for platform specific functionality. @@ -31,12 +37,12 @@ protected function getPlatformName(Connection $connection): string $platform = $connection->getDatabasePlatform(); return match (true) { - $platform instanceof \Doctrine\DBAL\Platforms\AbstractMySQLPlatform => 'mysql', - $platform instanceof \Doctrine\DBAL\Platforms\SQLitePlatform => 'sqlite', - $platform instanceof \Doctrine\DBAL\Platforms\PostgreSQLPlatform => 'pgsql', - $platform instanceof \Doctrine\DBAL\Platforms\OraclePlatform => 'oci', - $platform instanceof \Doctrine\DBAL\Platforms\SQLServerPlatform => 'sqlsrv', - $platform instanceof \Rollerworks\Component\Search\Tests\Doctrine\Dbal\Mocks\DatabasePlatformMock => 'mock', + $platform instanceof AbstractMySQLPlatform => 'mysql', + $platform instanceof SQLitePlatform => 'sqlite', + $platform instanceof PostgreSQLPlatform => 'pgsql', + $platform instanceof OraclePlatform => 'oci', + $platform instanceof SQLServerPlatform => 'sqlsrv', + $platform instanceof DatabasePlatformMock => 'mock', default => $platform::class, }; } diff --git a/lib/Doctrine/Orm/Extension/Type/BirthdayTypeExtension.php b/lib/Doctrine/Orm/Extension/Type/BirthdayTypeExtension.php index 4bedccf2..0defb8e2 100644 --- a/lib/Doctrine/Orm/Extension/Type/BirthdayTypeExtension.php +++ b/lib/Doctrine/Orm/Extension/Type/BirthdayTypeExtension.php @@ -20,8 +20,7 @@ final class BirthdayTypeExtension extends AbstractFieldTypeExtension { - /** @var AgeDateConversion */ - private $conversion; + private AgeDateConversion $conversion; public function __construct() { diff --git a/lib/Doctrine/Orm/Extension/Type/ChildCountType.php b/lib/Doctrine/Orm/Extension/Type/ChildCountType.php index ec43df91..eff89fd2 100644 --- a/lib/Doctrine/Orm/Extension/Type/ChildCountType.php +++ b/lib/Doctrine/Orm/Extension/Type/ChildCountType.php @@ -18,9 +18,9 @@ use Rollerworks\Component\Search\Field\AbstractFieldTypeExtension; use Symfony\Component\OptionsResolver\OptionsResolver; -class ChildCountType extends AbstractFieldTypeExtension +final class ChildCountType extends AbstractFieldTypeExtension { - private $conversion; + private ChildCountConversion $conversion; public function __construct() { diff --git a/lib/Doctrine/Orm/Extension/Type/DateTimeTypeExtension.php b/lib/Doctrine/Orm/Extension/Type/DateTimeTypeExtension.php index 44aa3f01..bf1b0c3e 100644 --- a/lib/Doctrine/Orm/Extension/Type/DateTimeTypeExtension.php +++ b/lib/Doctrine/Orm/Extension/Type/DateTimeTypeExtension.php @@ -20,8 +20,7 @@ final class DateTimeTypeExtension extends AbstractFieldTypeExtension { - /** @var DateIntervalConversion */ - private $conversion; + private DateIntervalConversion $conversion; public function __construct() { diff --git a/lib/Doctrine/Orm/Extension/Type/MoneyTypeExtension.php b/lib/Doctrine/Orm/Extension/Type/MoneyTypeExtension.php index 9e6bea68..7047fdde 100644 --- a/lib/Doctrine/Orm/Extension/Type/MoneyTypeExtension.php +++ b/lib/Doctrine/Orm/Extension/Type/MoneyTypeExtension.php @@ -20,10 +20,7 @@ final class MoneyTypeExtension extends AbstractFieldTypeExtension { - /** - * @var MoneyValueConversion - */ - private $conversion; + private MoneyValueConversion $conversion; public function __construct() { diff --git a/lib/Doctrine/Orm/FieldConfigBuilder.php b/lib/Doctrine/Orm/FieldConfigBuilder.php index 2b9ab1fd..02ecee2a 100644 --- a/lib/Doctrine/Orm/FieldConfigBuilder.php +++ b/lib/Doctrine/Orm/FieldConfigBuilder.php @@ -21,25 +21,16 @@ */ final class FieldConfigBuilder { - /** @var FieldSet */ - private $fieldSet; - - /** @var EntityManagerInterface */ - private $entityManager; - /** @var array> ['fieldName'][mappingIndex] => {OrmQueryField} */ private array $fields = []; - /** @var string */ - private $defaultEntity; - - /** @var string */ - private $defaultAlias; + private ?string $defaultEntity = null; + private ?string $defaultAlias = null; - public function __construct(EntityManagerInterface $entityManager, FieldSet $fieldSet) - { - $this->entityManager = $entityManager; - $this->fieldSet = $fieldSet; + public function __construct( + private readonly EntityManagerInterface $entityManager, + private readonly FieldSet $fieldSet, + ) { } public function setDefaultEntity(string $entity, string $alias): void @@ -53,6 +44,10 @@ public function setField(string $mappingName, string $property, ?string $alias = $mappingIdx = null; $fieldName = $mappingName; + if ($entity === null && $this->defaultEntity === null) { + throw new \RuntimeException('No default entity is set, either provide the entity or set a default entity first.'); + } + if (mb_strpos($mappingName, '#') !== false) { [$fieldName, $mappingIdx] = explode('#', $mappingName, 2); unset($this->fields[$fieldName]['']); diff --git a/lib/Doctrine/Orm/OrmQueryField.php b/lib/Doctrine/Orm/OrmQueryField.php index a1cc478e..9da46148 100644 --- a/lib/Doctrine/Orm/OrmQueryField.php +++ b/lib/Doctrine/Orm/OrmQueryField.php @@ -22,10 +22,14 @@ */ final class OrmQueryField extends QueryField { - public string $entity; - - public function __construct(string $mappingName, FieldConfig $fieldConfig, string $dbType, string $column, string $alias, string $entity) - { + public function __construct( + string $mappingName, + FieldConfig $fieldConfig, + string $dbType, + string $column, + string $alias, + public string $entity, + ) { parent::__construct( $mappingName, $fieldConfig, @@ -33,8 +37,6 @@ public function __construct(string $mappingName, FieldConfig $fieldConfig, strin $column, $alias ); - - $this->entity = $entity; } protected function initConversions(FieldConfig $fieldConfig): void @@ -45,12 +47,7 @@ protected function initConversions(FieldConfig $fieldConfig): void $converter = $converter(); } - if ($converter instanceof ColumnConversion) { - $this->columnConversion = $converter; - } - - if ($converter instanceof ValueConversion) { - $this->valueConversion = $converter; - } + $this->columnConversion = $converter instanceof ColumnConversion ? $converter : null; + $this->valueConversion = $converter instanceof ValueConversion ? $converter : null; } } diff --git a/lib/Doctrine/Orm/QueryBuilderConditionGenerator.php b/lib/Doctrine/Orm/QueryBuilderConditionGenerator.php index 5607a478..afe277b6 100644 --- a/lib/Doctrine/Orm/QueryBuilderConditionGenerator.php +++ b/lib/Doctrine/Orm/QueryBuilderConditionGenerator.php @@ -36,22 +36,10 @@ */ final class QueryBuilderConditionGenerator implements ConditionGenerator { - /** - * @var FieldConfigBuilder - */ - private $fieldsConfig; - - /** - * @var SearchCondition - */ - private $searchCondition; - - /** - * @var QueryBuilder - */ - private $qb; - - private $isApplied = false; + private FieldConfigBuilder $fieldsConfig; + private SearchCondition $searchCondition; + private QueryBuilder $qb; + private bool $isApplied = false; public function __construct(QueryBuilder $query, SearchCondition $searchCondition) { diff --git a/lib/Doctrine/Orm/Tests/CachedDqlConditionGeneratorTest.php b/lib/Doctrine/Orm/Tests/CachedDqlConditionGeneratorTest.php index 3c84f341..2f8e119e 100644 --- a/lib/Doctrine/Orm/Tests/CachedDqlConditionGeneratorTest.php +++ b/lib/Doctrine/Orm/Tests/CachedDqlConditionGeneratorTest.php @@ -21,6 +21,7 @@ use Rollerworks\Component\Search\Doctrine\Orm\ConditionGenerator; use Rollerworks\Component\Search\SearchCondition; use Rollerworks\Component\Search\SearchConditionBuilder; +use Rollerworks\Component\Search\Tests\Doctrine\Orm\Fixtures\Entity\ECommerceCustomer; use Rollerworks\Component\Search\Tests\Doctrine\Orm\Fixtures\Entity\ECommerceInvoice; /** @@ -30,20 +31,11 @@ */ final class CachedDqlConditionGeneratorTest extends OrmTestCase { - /** - * @var QueryBuilder - */ - private $query; + private QueryBuilder $query; + protected CachedDqlConditionGenerator $conditionGenerator; - /** - * @var CachedDqlConditionGenerator - */ - protected $conditionGenerator; - - /** - * @var CacheInterface|MockObject - */ - protected $cacheDriver; + /** @var CacheInterface|MockObject */ + protected MockObject $cacheDriver; public const CACHE_KEY = 'df58623bc959d1cf34d360c31f4d57b21ee51ffb5179059507b90c47035075dd'; @@ -315,11 +307,11 @@ protected function getQuery(): QueryBuilder private function createCachedConditionGenerator(CacheInterface $cacheDriver, SearchCondition $searchCondition, ?QueryBuilder $qb = null): CachedDqlConditionGenerator { $conditionGenerator = new CachedDqlConditionGenerator($qb ?? $this->query, $searchCondition, $cacheDriver, 60); - $conditionGenerator->setDefaultEntity(self::INVOICE_CLASS, 'I'); + $conditionGenerator->setDefaultEntity(ECommerceInvoice::class, 'I'); $conditionGenerator->setField('id', 'id', null, null, 'smallint'); $conditionGenerator->setField('@id', 'id'); - $conditionGenerator->setDefaultEntity(self::CUSTOMER_CLASS, 'C'); + $conditionGenerator->setDefaultEntity(ECommerceCustomer::class, 'C'); $conditionGenerator->setField('customer', 'id', null, null, 'integer'); $conditionGenerator->setField('@customer', 'id'); $conditionGenerator->setField('customer_name#first_name', 'firstName'); diff --git a/lib/Doctrine/Orm/Tests/ConditionGeneratorResultsTestCase.php b/lib/Doctrine/Orm/Tests/ConditionGeneratorResultsTestCase.php index 984a602e..da04b930 100644 --- a/lib/Doctrine/Orm/Tests/ConditionGeneratorResultsTestCase.php +++ b/lib/Doctrine/Orm/Tests/ConditionGeneratorResultsTestCase.php @@ -26,6 +26,9 @@ use Rollerworks\Component\Search\Input\StringQueryInput; use Rollerworks\Component\Search\SearchPrimaryCondition; use Rollerworks\Component\Search\Tests\Doctrine\Dbal\SchemaRecord; +use Rollerworks\Component\Search\Tests\Doctrine\Orm\Fixtures\Entity\ECommerceCustomer; +use Rollerworks\Component\Search\Tests\Doctrine\Orm\Fixtures\Entity\ECommerceInvoice; +use Rollerworks\Component\Search\Tests\Doctrine\Orm\Fixtures\Entity\ECommerceInvoiceRow; /** * Ensures the expected results are actually found. @@ -47,10 +50,7 @@ */ abstract class ConditionGeneratorResultsTestCase extends OrmTestCase { - /** - * @var StringQueryInput - */ - private $inputProcessor; + private StringQueryInput $inputProcessor; protected function setUp(): void { @@ -64,7 +64,7 @@ protected function setUp(): void */ protected function getDbRecords() { - $date = static fn (string $input) => new \DateTimeImmutable($input, new \DateTimeZone('UTC')); + $date = static fn (string $input): \DateTimeImmutable => new \DateTimeImmutable($input, new \DateTimeZone('UTC')); return [ SchemaRecord::create( @@ -152,7 +152,7 @@ protected function getDbRecords() protected function configureConditionGenerator(FieldConfigBuilder $conditionGenerator): void { - $conditionGenerator->setDefaultEntity(self::INVOICE_CLASS, 'I'); + $conditionGenerator->setDefaultEntity(ECommerceInvoice::class, 'I'); $conditionGenerator->setField('id', 'id'); $conditionGenerator->setField('@id', 'id'); $conditionGenerator->setField('label', 'label'); @@ -160,13 +160,13 @@ protected function configureConditionGenerator(FieldConfigBuilder $conditionGene $conditionGenerator->setField('status', 'status'); $conditionGenerator->setField('total', 'total'); - $conditionGenerator->setDefaultEntity(Fixtures\Entity\ECommerceInvoiceRow::class, 'R'); + $conditionGenerator->setDefaultEntity(ECommerceInvoiceRow::class, 'R'); $conditionGenerator->setField('row-label', 'label'); $conditionGenerator->setField('row-price', 'price'); $conditionGenerator->setField('row-quantity', 'quantity'); $conditionGenerator->setField('row-total', 'total'); - $conditionGenerator->setDefaultEntity(self::CUSTOMER_CLASS, 'C'); + $conditionGenerator->setDefaultEntity(ECommerceCustomer::class, 'C'); $conditionGenerator->setField('customer', 'id'); $conditionGenerator->setField('customer-first-name', 'firstName'); $conditionGenerator->setField('customer-name#first_name', 'firstName'); diff --git a/lib/Doctrine/Orm/Tests/DoctrineOrmFactoryTest.php b/lib/Doctrine/Orm/Tests/DoctrineOrmFactoryTest.php index c279bf7c..9b9b685f 100644 --- a/lib/Doctrine/Orm/Tests/DoctrineOrmFactoryTest.php +++ b/lib/Doctrine/Orm/Tests/DoctrineOrmFactoryTest.php @@ -17,7 +17,6 @@ use Psr\SimpleCache\CacheInterface; use Rollerworks\Component\Search\Doctrine\Orm\CachedDqlConditionGenerator; use Rollerworks\Component\Search\Doctrine\Orm\DoctrineOrmFactory; -use Rollerworks\Component\Search\Doctrine\Orm\QueryBuilderConditionGenerator; use Rollerworks\Component\Search\GenericFieldSet; use Rollerworks\Component\Search\SearchCondition; use Rollerworks\Component\Search\Tests\Doctrine\Orm\Fixtures\Entity\ECommerceInvoice; @@ -30,10 +29,7 @@ */ final class DoctrineOrmFactoryTest extends OrmTestCase { - /** - * @var DoctrineOrmFactory - */ - protected $factory; + protected DoctrineOrmFactory $factory; /** @test */ public function create_condition_generator(): void @@ -42,7 +38,7 @@ public function create_condition_generator(): void $condition = new SearchCondition(new GenericFieldSet([]), new ValuesGroup()); $conditionGenerator = $this->factory->createConditionGenerator($qb, $condition); - self::assertInstanceOf(QueryBuilderConditionGenerator::class, $conditionGenerator); + self::assertSame($qb, $conditionGenerator->getQueryBuilder()); } /** @test */ diff --git a/lib/Doctrine/Orm/Tests/DqlConditionGeneratorTest.php b/lib/Doctrine/Orm/Tests/DqlConditionGeneratorTest.php index 0df7913b..497aa887 100644 --- a/lib/Doctrine/Orm/Tests/DqlConditionGeneratorTest.php +++ b/lib/Doctrine/Orm/Tests/DqlConditionGeneratorTest.php @@ -27,6 +27,7 @@ use Rollerworks\Component\Search\SearchCondition; use Rollerworks\Component\Search\SearchConditionBuilder; use Rollerworks\Component\Search\SearchPrimaryCondition; +use Rollerworks\Component\Search\Tests\Doctrine\Orm\Fixtures\Entity\ECommerceCustomer; use Rollerworks\Component\Search\Tests\Doctrine\Orm\Fixtures\Entity\ECommerceInvoice; use Rollerworks\Component\Search\Value\Compare; use Rollerworks\Component\Search\Value\ExcludedRange; @@ -64,12 +65,12 @@ private function getConditionGenerator(SearchCondition $condition, ?QueryBuilder $conditionGenerator = $this->getOrmFactory()->createConditionGenerator($qb, $condition); if (! $noMapping) { - $conditionGenerator->setDefaultEntity(self::INVOICE_CLASS, 'I'); + $conditionGenerator->setDefaultEntity(ECommerceInvoice::class, 'I'); $conditionGenerator->setField('id', 'id', null, null, 'smallint'); $conditionGenerator->setField('@id', 'id'); $conditionGenerator->setField('status', 'status'); - $conditionGenerator->setDefaultEntity(self::CUSTOMER_CLASS, 'C'); + $conditionGenerator->setDefaultEntity(ECommerceCustomer::class, 'C'); $conditionGenerator->setField('customer', 'id'); $conditionGenerator->setField('@customer', 'id'); $conditionGenerator->setField('customer_name#first_name', 'firstName'); @@ -906,7 +907,7 @@ public function apply_to_query_builder(): void ; $qb = $this->em->createQueryBuilder(); - $qb->select('C')->from(self::CUSTOMER_CLASS, 'C'); + $qb->select('C')->from(ECommerceCustomer::class, 'C'); $conditionGenerator = $this->getConditionGenerator($condition, $qb); @@ -1005,8 +1006,8 @@ private function assertDqlCompiles(ConditionGenerator $conditionGenerator, strin $conditionGenerator->apply(); $expectedDql = $mainDql . ($expectedDql ? ' ' : '') . $expectedDql; - $expectedDql = preg_replace('/\s+/', ' ', trim($expectedDql)); - $actualDql = preg_replace('/\s+/', ' ', trim($qb->getDQL())); + $expectedDql = preg_replace('/\s+/', ' ', mb_trim($expectedDql)); + $actualDql = preg_replace('/\s+/', ' ', mb_trim($qb->getDQL())); self::assertEquals($expectedDql, $actualDql); self::assertQueryParametersEquals($parameters, $qb); @@ -1015,8 +1016,8 @@ private function assertDqlCompiles(ConditionGenerator $conditionGenerator, strin $sql = $qb->getQuery()->getSQL(); if ($expectedSql !== '') { - $expectedSql = preg_replace('/\s+/', ' ', trim($expectedSql)); - $sql = preg_replace('/\s+/', ' ', trim($sql)); + $expectedSql = preg_replace('/\s+/', ' ', mb_trim($expectedSql)); + $sql = preg_replace('/\s+/', ' ', mb_trim($sql)); self::assertEquals($expectedSql, $sql); } diff --git a/lib/Doctrine/Orm/Tests/FieldConfigBuilderTest.php b/lib/Doctrine/Orm/Tests/FieldConfigBuilderTest.php index f3dbaf08..9a4ab50d 100644 --- a/lib/Doctrine/Orm/Tests/FieldConfigBuilderTest.php +++ b/lib/Doctrine/Orm/Tests/FieldConfigBuilderTest.php @@ -13,6 +13,8 @@ namespace Rollerworks\Component\Search\Tests\Doctrine\Orm; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\ClassMetadata; use PHPUnit\Framework\TestCase; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; @@ -22,6 +24,8 @@ use Rollerworks\Component\Search\Extension\Core\Type\TextType; use Rollerworks\Component\Search\Searches; use Rollerworks\Component\Search\SearchFactory; +use Rollerworks\Component\Search\Tests\Doctrine\Orm\Fixtures\Entity\ECommerceCustomer; +use Rollerworks\Component\Search\Tests\Doctrine\Orm\Fixtures\Entity\ECommerceInvoice; /** * @internal @@ -30,25 +34,15 @@ final class FieldConfigBuilderTest extends TestCase { use ProphecyTrait; - public const CUSTOMER_CLASS = Fixtures\Entity\ECommerceCustomer::class; - public const INVOICE_CLASS = Fixtures\Entity\ECommerceInvoice::class; - - /** - * @var ObjectProphecy - */ - private $em; - - /** - * @var SearchFactory - */ - private $searchFactory; + private SearchFactory $searchFactory; + private ObjectProphecy $em; protected function setUp(): void { parent::setUp(); $this->searchFactory = Searches::createSearchFactoryBuilder()->getSearchFactory(); - $this->em = $this->prophesize('Doctrine\ORM\EntityManagerInterface'); + $this->em = $this->prophesize(EntityManagerInterface::class); } private function getFieldSet($build = true) @@ -64,9 +58,12 @@ private function getFieldSet($build = true) return $build ? $fieldSet->getFieldSet('invoice') : $fieldSet; } - private function getClassMetadata(string $entityClass, array $fields): void + /** + * @param array $fields + */ + private function expectClassMetadata(string $entityClass, array $fields): void { - $classMetadata = $this->prophesize('Doctrine\ORM\Mapping\ClassMetadata'); + $classMetadata = $this->prophesize(ClassMetadata::class); $classMetadata->getName()->willReturn($entityClass); foreach ($fields as $property => $field) { @@ -75,11 +72,9 @@ private function getClassMetadata(string $entityClass, array $fields): void $classMetadata->hasAssociation($property)->willReturn(true); $classMetadata->getTypeOfField($property)->willReturn(null); $classMetadata->getAssociationTargetClass($property)->willReturn($field['join']['class']); - $classMetadata->getSingleAssociationReferencedJoinColumnName($property)->willReturn( - $field['join']['column'] - ); + $classMetadata->getSingleAssociationReferencedJoinColumnName($property)->willReturn($field['join']['column']); - $joinClassMetadata = $this->prophesize('Doctrine\ORM\Mapping\ClassMetadata'); + $joinClassMetadata = $this->prophesize(ClassMetadata::class); $joinClassMetadata->getName()->willReturn($field['join']['class']); $joinClassMetadata->getFieldForColumn($field['join']['column'])->willReturn($field['join']['property']); $joinClassMetadata->getTypeOfField($field['join']['property'])->willReturn($field['join']['type']); @@ -102,12 +97,12 @@ private function getClassMetadata(string $entityClass, array $fields): void /** @test */ public function resolve_with_default_entity(): void { - $this->getClassMetadata(self::INVOICE_CLASS, [ + $this->expectClassMetadata(ECommerceInvoice::class, [ 'id' => ['type' => 'integer'], 'parent' => ['type' => 'integer'], ]); - $this->getClassMetadata(self::CUSTOMER_CLASS, [ + $this->expectClassMetadata(ECommerceCustomer::class, [ 'id' => ['type' => 'integer'], 'first_name' => ['type' => 'string'], 'last_name' => ['type' => 'string'], @@ -115,11 +110,11 @@ public function resolve_with_default_entity(): void $fieldConfigBuilder = new FieldConfigBuilder($this->em->reveal(), $fieldSet = $this->getFieldSet()); - $fieldConfigBuilder->setDefaultEntity(self::INVOICE_CLASS, 'I'); + $fieldConfigBuilder->setDefaultEntity(ECommerceInvoice::class, 'I'); $fieldConfigBuilder->setField('id', 'id', null, null, 'smallint'); $fieldConfigBuilder->setField('credit_parent#0', 'parent'); - $fieldConfigBuilder->setDefaultEntity(self::CUSTOMER_CLASS, 'C'); + $fieldConfigBuilder->setDefaultEntity(ECommerceCustomer::class, 'C'); $fieldConfigBuilder->setField('customer', 'id'); $fieldConfigBuilder->setField('customer_name#first_name', 'first_name'); $fieldConfigBuilder->setField('customer_name#last_name', 'last_name'); @@ -127,52 +122,52 @@ public function resolve_with_default_entity(): void $fields = $fieldConfigBuilder->getFields(); // Invoice - self::assertEquals(new QueryField('id', $fieldSet->get('id'), 'smallint', 'id', 'I', self::INVOICE_CLASS), $fields['id']['']); - self::assertEquals(new QueryField('credit_parent#0', $fieldSet->get('credit_parent'), 'integer', 'parent', 'I', self::INVOICE_CLASS), $fields['credit_parent']['0']); + self::assertEquals(new QueryField('id', $fieldSet->get('id'), 'smallint', 'id', 'I', ECommerceInvoice::class), $fields['id']['']); + self::assertEquals(new QueryField('credit_parent#0', $fieldSet->get('credit_parent'), 'integer', 'parent', 'I', ECommerceInvoice::class), $fields['credit_parent']['0']); // Customer - self::assertEquals(new QueryField('customer', $fieldSet->get('customer'), 'integer', 'id', 'C', self::CUSTOMER_CLASS), $fields['customer']['']); - self::assertEquals(new QueryField('customer_name#first_name', $fieldSet->get('customer_name'), 'string', 'first_name', 'C', self::CUSTOMER_CLASS), $fields['customer_name']['first_name']); - self::assertEquals(new QueryField('customer_name#last_name', $fieldSet->get('customer_name'), 'string', 'last_name', 'C', self::CUSTOMER_CLASS), $fields['customer_name']['last_name']); + self::assertEquals(new QueryField('customer', $fieldSet->get('customer'), 'integer', 'id', 'C', ECommerceCustomer::class), $fields['customer']['']); + self::assertEquals(new QueryField('customer_name#first_name', $fieldSet->get('customer_name'), 'string', 'first_name', 'C', ECommerceCustomer::class), $fields['customer_name']['first_name']); + self::assertEquals(new QueryField('customer_name#last_name', $fieldSet->get('customer_name'), 'string', 'last_name', 'C', ECommerceCustomer::class), $fields['customer_name']['last_name']); } /** @test */ public function resolve_with_full_field_mapping(): void { - $this->getClassMetadata(self::INVOICE_CLASS, [ + $this->expectClassMetadata(ECommerceInvoice::class, [ 'id' => ['type' => 'integer'], 'parent' => ['type' => 'integer'], 'invoice_id' => ['type' => 'integer'], 'parent_id' => ['type' => 'integer'], ]); - $this->getClassMetadata(self::CUSTOMER_CLASS, [ + $this->expectClassMetadata(ECommerceCustomer::class, [ 'id' => ['type' => 'integer'], 'first_name' => ['type' => 'string'], 'last_name' => ['type' => 'string'], ]); $fieldConfigBuilder = new FieldConfigBuilder($this->em->reveal(), $fieldSet = $this->getFieldSet()); - $fieldConfigBuilder->setField('id', 'id', 'I', self::INVOICE_CLASS, 'smallint'); - $fieldConfigBuilder->setField('credit_parent#0', 'parent', 'I', self::INVOICE_CLASS); + $fieldConfigBuilder->setField('id', 'id', 'I', ECommerceInvoice::class, 'smallint'); + $fieldConfigBuilder->setField('credit_parent#0', 'parent', 'I', ECommerceInvoice::class); $fields = $fieldConfigBuilder->getFields(); // Invoice - self::assertEquals(new QueryField('id', $fieldSet->get('id'), 'smallint', 'id', 'I', self::INVOICE_CLASS), $fields['id']['']); - self::assertEquals(new QueryField('credit_parent#0', $fieldSet->get('credit_parent'), 'integer', 'parent', 'I', self::INVOICE_CLASS), $fields['credit_parent']['0']); + self::assertEquals(new QueryField('id', $fieldSet->get('id'), 'smallint', 'id', 'I', ECommerceInvoice::class), $fields['id']['']); + self::assertEquals(new QueryField('credit_parent#0', $fieldSet->get('credit_parent'), 'integer', 'parent', 'I', ECommerceInvoice::class), $fields['credit_parent']['0']); } /** @test */ public function fails_to_resolve_with_join_association(): void { - $this->getClassMetadata( - self::INVOICE_CLASS, + $this->expectClassMetadata( + ECommerceInvoice::class, [ 'id' => ['type' => 'integer'], 'parent' => [ 'join' => [ - 'class' => self::INVOICE_CLASS, + 'class' => ECommerceInvoice::class, 'property' => 'id', 'type' => 'integer', 'column' => 'invoice_id', @@ -182,27 +177,38 @@ public function fails_to_resolve_with_join_association(): void ] ); - $this->getClassMetadata(self::CUSTOMER_CLASS, [ + $this->expectClassMetadata(ECommerceCustomer::class, [ 'id' => ['type' => 'integer'], 'name' => ['type' => 'string'], ]); $fieldConfigBuilder = new FieldConfigBuilder($this->em->reveal(), $this->getFieldSet()); - $fieldConfigBuilder->setDefaultEntity(self::INVOICE_CLASS, 'I'); + $fieldConfigBuilder->setDefaultEntity(ECommerceInvoice::class, 'I'); $fieldConfigBuilder->setField('id', 'id', null, null, 'smallint'); $this->expectException('RuntimeException'); - $this->expectExceptionMessage('Entity field "' . self::INVOICE_CLASS . '"#parent is a JOIN association'); + $this->expectExceptionMessage('Entity field "' . ECommerceInvoice::class . '"#parent is a JOIN association'); $fieldConfigBuilder->setField('credit_parent', 'parent', 'I', null, 'parent_id'); } + /** @test */ + public function fails_when_no_entity_is_provided(): void + { + $fieldConfigBuilder = new FieldConfigBuilder($this->em->reveal(), $this->getFieldSet()); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('No default entity is set, either provide the entity or set a default entity first.'); + + $fieldConfigBuilder->setField('id', 'id'); + } + /** @test */ public function fails_to_resolve_with_multi_column_join_association(): void { - $this->getClassMetadata( - self::INVOICE_CLASS, + $this->expectClassMetadata( + ECommerceInvoice::class, [ 'id' => ['type' => 'integer'], 'parent' => [ @@ -211,17 +217,17 @@ public function fails_to_resolve_with_multi_column_join_association(): void ] ); - $this->getClassMetadata(self::CUSTOMER_CLASS, [ + $this->expectClassMetadata(ECommerceCustomer::class, [ 'id' => ['type' => 'integer'], 'name' => ['type' => 'string'], ]); $fieldConfigBuilder = new FieldConfigBuilder($this->em->reveal(), $this->getFieldSet()); - $fieldConfigBuilder->setField('id', 'id', 'I', self::INVOICE_CLASS, 'smallint'); + $fieldConfigBuilder->setField('id', 'id', 'I', ECommerceInvoice::class, 'smallint'); $this->expectException('RuntimeException'); - $this->expectExceptionMessage('Entity field "' . self::INVOICE_CLASS . '"#parent is a JOIN association'); + $this->expectExceptionMessage('Entity field "' . ECommerceInvoice::class . '"#parent is a JOIN association'); - $fieldConfigBuilder->setField('credit_parent#0', 'parent', 'I', self::INVOICE_CLASS); + $fieldConfigBuilder->setField('credit_parent#0', 'parent', 'I', ECommerceInvoice::class); } } diff --git a/lib/Doctrine/Orm/Tests/Fixtures/GetCustomerTypeFunction.php b/lib/Doctrine/Orm/Tests/Fixtures/GetCustomerTypeFunction.php index 9c417f2e..41d80b20 100644 --- a/lib/Doctrine/Orm/Tests/Fixtures/GetCustomerTypeFunction.php +++ b/lib/Doctrine/Orm/Tests/Fixtures/GetCustomerTypeFunction.php @@ -14,13 +14,14 @@ namespace Rollerworks\Component\Search\Doctrine\Orm\Tests\Fixtures; use Doctrine\ORM\Query\AST\Functions\FunctionNode; +use Doctrine\ORM\Query\AST\Node; use Doctrine\ORM\Query\Parser; use Doctrine\ORM\Query\SqlWalker; use Doctrine\ORM\Query\TokenType; final class GetCustomerTypeFunction extends FunctionNode { - public $stringPrimary; + public Node $stringPrimary; public function getSql(SqlWalker $sqlWalker): string { diff --git a/lib/Doctrine/Orm/Tests/OrmTestCase.php b/lib/Doctrine/Orm/Tests/OrmTestCase.php index a05b0ad1..a1275fa1 100644 --- a/lib/Doctrine/Orm/Tests/OrmTestCase.php +++ b/lib/Doctrine/Orm/Tests/OrmTestCase.php @@ -13,6 +13,7 @@ namespace Rollerworks\Component\Search\Tests\Doctrine\Orm; +use Doctrine\DBAL\Connection as DoctrineConnection; use Doctrine\DBAL\Types\Type; use Doctrine\ORM\EntityManager; use Doctrine\ORM\ORMSetup; @@ -43,30 +44,10 @@ abstract class OrmTestCase extends DbalTestCase { - protected const CUSTOMER_CLASS = Fixtures\Entity\ECommerceCustomer::class; - protected const INVOICE_CLASS = Fixtures\Entity\ECommerceInvoice::class; - - /** - * @var EntityManager|null - */ - protected $em; - - /** - * @var \Doctrine\DBAL\Connection|null - */ - protected $conn; - - /** - * Shared connection when a TestCase is run alone (outside of it's functional suite). - * - * @var Connection|null - */ - private static $sharedConn; - - /** - * @var EntityManager|null - */ - private static $sharedEm; + protected ?EntityManager $em; + protected Connection $conn; + private static ?Connection $sharedConn; + private static ?EntityManager $sharedEm; protected function setUp(): void { @@ -162,6 +143,8 @@ protected function configureConditionGenerator(FieldConfigBuilder $conditionGene } /** + * @param (int|string)[] $ids + * * @throws \Doctrine\DBAL\Exception */ protected function assertRecordsAreFound(SearchCondition $condition, array $ids, string $input): void @@ -205,7 +188,7 @@ protected function assertRecordsAreFound(SearchCondition $condition, array $ids, $query = $qb->getQuery(); $rows = $query->getArrayResult(); $idRows = array_map( - static fn ($value) => $value['id'], + static fn ($value): mixed => $value['id'], $rows ); @@ -223,6 +206,9 @@ protected function assertRecordsAreFound(SearchCondition $condition, array $ids, ); } + /** + * @param array|null $parameters + */ protected static function assertQueryParametersEquals(?array $parameters, QueryBuilder $qb): void { if ($parameters === null) { diff --git a/lib/Doctrine/Orm/composer.json b/lib/Doctrine/Orm/composer.json index 670653a6..4e9c9b85 100644 --- a/lib/Doctrine/Orm/composer.json +++ b/lib/Doctrine/Orm/composer.json @@ -20,7 +20,7 @@ } ], "require": { - "php": ">=8.1", + "php": "^8.1", "doctrine/orm": "^2.7.3 || ^3.3", "rollerworks/search": ">=2.0.0-BETA1 ^2.0@dev", "rollerworks/search-doctrine-dbal": ">=2.0.0-BETA9 ^2.0@dev" diff --git a/lib/Elasticsearch/CachedConditionGenerator.php b/lib/Elasticsearch/CachedConditionGenerator.php index a73c60eb..ac96bded 100644 --- a/lib/Elasticsearch/CachedConditionGenerator.php +++ b/lib/Elasticsearch/CachedConditionGenerator.php @@ -17,32 +17,13 @@ use Psr\SimpleCache\CacheInterface as Cache; use Rollerworks\Component\Search\SearchCondition; +/** + * @final + */ class CachedConditionGenerator implements ConditionGenerator { - /** - * @var ConditionGenerator - */ - private $conditionGenerator; - - /** - * @var Cache - */ - private $cacheDriver; - - /** - * @var string - */ - private $cacheKey; - - /** - * @var \DateInterval|int|null - */ - private $cacheTtl; - - /** - * @var Query|null - */ - private $query; + private ?string $cacheKey = null; + private ?Query $query = null; /** * @param ConditionGenerator $conditionGenerator The actual ConditionGenerator to use when no cache exists @@ -52,11 +33,11 @@ class CachedConditionGenerator implements ConditionGenerator * the driver supports TTL then the library may set a default value * for it or let the driver take care of that. */ - public function __construct(ConditionGenerator $conditionGenerator, Cache $cacheDriver, $ttl = 0) - { - $this->conditionGenerator = $conditionGenerator; - $this->cacheDriver = $cacheDriver; - $this->cacheTtl = $ttl; + public function __construct( + private readonly ConditionGenerator $conditionGenerator, + private readonly Cache $cacheDriver, + private \DateInterval | int | null $ttl = 0, + ) { } public function registerField(string $fieldName, string $mapping, array $conditions = [], array $options = []) @@ -75,7 +56,7 @@ public function getQuery(): Query $this->query = $this->cacheDriver->get($cacheKey); } else { $this->query = $this->conditionGenerator->getQuery(); - $this->cacheDriver->set($cacheKey, $this->query, $this->cacheTtl); + $this->cacheDriver->set($cacheKey, $this->query, $this->ttl); } } @@ -89,7 +70,6 @@ public function getMappings(): array public function getSearchCondition(): SearchCondition { - /** @noinspection PhpInternalEntityUsedInspection */ return $this->conditionGenerator->getSearchCondition(); } diff --git a/lib/Elasticsearch/ElasticsearchFactory.php b/lib/Elasticsearch/ElasticsearchFactory.php index a04c5100..3d3675f2 100644 --- a/lib/Elasticsearch/ElasticsearchFactory.php +++ b/lib/Elasticsearch/ElasticsearchFactory.php @@ -19,20 +19,10 @@ class ElasticsearchFactory { - /** - * @var Cache|null - */ - private $cacheDriver; - - /** - * @var ParameterBag|null - */ - private $parameterBag; - - public function __construct(?Cache $cacheDriver = null, ?ParameterBag $parameterBag = null) - { - $this->cacheDriver = $cacheDriver; - $this->parameterBag = $parameterBag; + public function __construct( + private readonly ?Cache $cacheDriver = null, + private readonly ?ParameterBag $parameterBag = null, + ) { } /** diff --git a/lib/Elasticsearch/Extension/Conversion/DateConversion.php b/lib/Elasticsearch/Extension/Conversion/DateConversion.php index e761722e..b89c4b7b 100644 --- a/lib/Elasticsearch/Extension/Conversion/DateConversion.php +++ b/lib/Elasticsearch/Extension/Conversion/DateConversion.php @@ -17,16 +17,15 @@ use Rollerworks\Component\Search\Elasticsearch\QueryConversion; use Rollerworks\Component\Search\Elasticsearch\QueryPreparationHints; use Rollerworks\Component\Search\Elasticsearch\ValueConversion; +use Rollerworks\Component\Search\Exception\TransformationFailedException; +use Rollerworks\Component\Search\Extension\Core\DataTransformer\BaseDateTimeTransformer; use Rollerworks\Component\Search\Extension\Core\DataTransformer\DateTimeToStringTransformer; use Rollerworks\Component\Search\Value\Compare; use Rollerworks\Component\Search\Value\Range; class DateConversion implements ValueConversion, QueryConversion { - /** - * @var DateTimeToStringTransformer - */ - protected $transformer; + protected BaseDateTimeTransformer $transformer; public function __construct() { @@ -34,7 +33,7 @@ public function __construct() } /** - * @throws \Rollerworks\Component\Search\Exception\TransformationFailedException + * @throws TransformationFailedException */ public function convertValue($value): string { diff --git a/lib/Elasticsearch/Extension/Type/BirthdayTypeExtension.php b/lib/Elasticsearch/Extension/Type/BirthdayTypeExtension.php index d008599c..c45a4a99 100644 --- a/lib/Elasticsearch/Extension/Type/BirthdayTypeExtension.php +++ b/lib/Elasticsearch/Extension/Type/BirthdayTypeExtension.php @@ -21,14 +21,9 @@ class BirthdayTypeExtension extends AbstractFieldTypeExtension { - /** - * @var DateConversion - */ - private $conversion; - - public function __construct(DateConversion $conversion) - { - $this->conversion = $conversion; + public function __construct( + private readonly DateConversion $conversion, + ) { } public function configureOptions(OptionsResolver $resolver): void diff --git a/lib/Elasticsearch/Extension/Type/DateTimeTypeExtension.php b/lib/Elasticsearch/Extension/Type/DateTimeTypeExtension.php index 81867354..2b9ee368 100644 --- a/lib/Elasticsearch/Extension/Type/DateTimeTypeExtension.php +++ b/lib/Elasticsearch/Extension/Type/DateTimeTypeExtension.php @@ -21,14 +21,10 @@ class DateTimeTypeExtension extends AbstractFieldTypeExtension { - /** - * @var DateTimeConversion - */ - private $conversion; - - public function __construct(DateTimeConversion $conversion) - { - $this->conversion = $conversion; + public function __construct( + private readonly DateTimeConversion $conversion = new DateTimeConversion( + ), + ) { } public function configureOptions(OptionsResolver $resolver): void diff --git a/lib/Elasticsearch/Extension/Type/DateTypeExtension.php b/lib/Elasticsearch/Extension/Type/DateTypeExtension.php index 6fd433b9..2c95da99 100644 --- a/lib/Elasticsearch/Extension/Type/DateTypeExtension.php +++ b/lib/Elasticsearch/Extension/Type/DateTypeExtension.php @@ -21,14 +21,9 @@ class DateTypeExtension extends AbstractFieldTypeExtension { - /** - * @var DateConversion - */ - private $conversion; - - public function __construct(DateConversion $conversion) - { - $this->conversion = $conversion; + public function __construct( + private readonly DateConversion $conversion = new DateConversion(), + ) { } public function configureOptions(OptionsResolver $resolver): void diff --git a/lib/Elasticsearch/Extension/Type/MoneyTypeExtension.php b/lib/Elasticsearch/Extension/Type/MoneyTypeExtension.php index 511285c2..8c81108c 100644 --- a/lib/Elasticsearch/Extension/Type/MoneyTypeExtension.php +++ b/lib/Elasticsearch/Extension/Type/MoneyTypeExtension.php @@ -20,14 +20,10 @@ class MoneyTypeExtension extends AbstractFieldTypeExtension { - /** - * @var CurrencyConversion - */ - private $conversion; - - public function __construct(CurrencyConversion $conversion) - { - $this->conversion = $conversion; + public function __construct( + private readonly CurrencyConversion $conversion = new CurrencyConversion( + ), + ) { } public function configureOptions(OptionsResolver $resolver): void diff --git a/lib/Elasticsearch/FieldMapping.php b/lib/Elasticsearch/FieldMapping.php index a5972c7e..47b59113 100644 --- a/lib/Elasticsearch/FieldMapping.php +++ b/lib/Elasticsearch/FieldMapping.php @@ -19,42 +19,35 @@ /** @internal */ final class FieldMapping { - public $fieldName; - public $indexName; - public $typeName; - public $propertyName; - public $propertyValue; - public $propertyQuery; - public $nested = false; - public $join = false; - public $boost; - - /** - * @var ValueConversion - */ - public $valueConversion; - - /** - * @var QueryConversion - */ - public $queryConversion; - + public ?string $indexName; + public ?string $typeName; + public ?string $propertyName; + public mixed $propertyValue; + + /** @var array */ + public array $propertyQuery = []; + + public mixed $nested = false; // Unknown type + public mixed $join = false; // Unknown type + public float $boost; // Currently unused + public ?ValueConversion $valueConversion = null; + public ?QueryConversion $queryConversion = null; public ?ChildOrderConversion $childOrderConversion = null; - /** - * @var self[] - */ - public $conditions; + /** @var self[] */ + public array $conditions; /** - * @var array + * @param self[] $conditions + * @param array $options */ - public $options; - - public function __construct(string $fieldName, string $property, FieldConfig $fieldConfig, array $conditions = [], array $options = []) - { - $this->fieldName = $fieldName; - + public function __construct( + public string $fieldName, + string $property, + FieldConfig $fieldConfig, + array $conditions = [], + public array $options = [], + ) { $mapping = $this->parseProperty($property); $this->indexName = $mapping['indexName']; $this->typeName = $mapping['typeName']; @@ -78,7 +71,6 @@ public function __construct(string $fieldName, string $property, FieldConfig $fi } $this->conditions = $this->expandConditions($conditions, $fieldConfig); - $this->options = $options; } /** @@ -94,7 +86,9 @@ public function __construct(string $fieldName, string $property, FieldConfig $fi * - /# * - /#child>. * - * @return string[] + * Returns: array{indexName: ?string, typeName: ?string, propertyName: string, nested: array{path: string}|bool, join: array{type: string}|bool} + * + * @return array */ private function parseProperty(string $property): array { @@ -104,10 +98,10 @@ private function parseProperty(string $property): array $nested = false; $join = false; - if (mb_strpos($property, '#') !== false) { + if (str_contains($property, '#')) { [$path, $propertyName] = explode('#', $property); - $path = trim($path, '/'); + $path = mb_trim($path, '/'); $indexName = $path; if (mb_strpos($path, '/') !== false) { @@ -115,27 +109,27 @@ private function parseProperty(string $property): array } } - if (mb_strpos($property, '>') !== false) { + if (str_contains($property, '>')) { $tokens = explode('>', $propertyName); // last token is the property name - $propertyName = trim(array_pop($tokens), '.'); + $propertyName = mb_trim(array_pop($tokens), '.'); foreach ($tokens as $type) { - $type = trim($type, '.'); + $type = mb_trim($type, '.'); $join = compact('type', 'join'); } } - if (mb_strpos($propertyName, '[]') !== false) { + if (str_contains($propertyName, '[]')) { $tokens = explode('[]', $propertyName); // last token is the property name - $propertyName = trim(array_pop($tokens), '.'); - $propertyName = trim(end($tokens), '.') . '.' . $propertyName; + $propertyName = mb_trim(array_pop($tokens), '.'); + $propertyName = mb_trim(end($tokens), '.') . '.' . $propertyName; foreach ($tokens as $path) { - $path = trim($path, '.'); + $path = mb_trim($path, '.'); $nested = compact('path', 'nested'); } } diff --git a/lib/Elasticsearch/QueryConditionGenerator.php b/lib/Elasticsearch/QueryConditionGenerator.php index 1572273c..a4808515 100644 --- a/lib/Elasticsearch/QueryConditionGenerator.php +++ b/lib/Elasticsearch/QueryConditionGenerator.php @@ -16,6 +16,7 @@ use Elastica\Query; use Rollerworks\Component\Search\Exception\BadMethodCallException; use Rollerworks\Component\Search\Exception\UnknownFieldException; +use Rollerworks\Component\Search\FieldSet; use Rollerworks\Component\Search\ParameterBag; use Rollerworks\Component\Search\SearchCondition; use Rollerworks\Component\Search\SearchOrder; @@ -65,7 +66,6 @@ // note: this one is NOT available for Elasticsearch, we use it as a named constant only private const COMPARISON_UNEQUAL = '<>'; - private const COMPARISON_OPERATOR_MAP = [ '<>' => self::COMPARISON_UNEQUAL, '<' => self::COMPARISON_LESS, @@ -74,21 +74,16 @@ '>=' => self::COMPARISON_GREATER_OR_EQUAL, ]; - private $searchCondition; - private $fieldSet; - - /** @var FieldMapping[] $mapping */ - private $mappings; + private FieldSet $fieldSet; - /** @var ParameterBag|null */ - private $parameterBag; + /** @var array $mapping */ + private array $mappings = []; - public function __construct(SearchCondition $searchCondition, ?ParameterBag $parameterBag = null) - { - $this->searchCondition = $searchCondition; - $this->parameterBag = $parameterBag; - - $this->fieldSet = $searchCondition->getFieldSet(); + public function __construct( + private readonly SearchCondition $searchCondition, + private readonly ?ParameterBag $parameterBag = null, + ) { + $this->fieldSet = $this->searchCondition->getFieldSet(); } public function registerField(string $fieldName, string $property, array $conditions = [], array $options = []) @@ -138,6 +133,9 @@ public function getQuery(): Query return new Query(array_filter([self::QUERY => $rootGroupCondition, self::SORT => $orderClause])); } + /** + * @return FieldMapping[] + */ public function getMappings(): array { $mappings = []; @@ -145,16 +143,21 @@ public function getMappings(): array $this->getGroupMappings($group, $mappings); - if (null !== $primaryCondition = $this->searchCondition->getPrimaryCondition()) { + $primaryCondition = $this->searchCondition->getPrimaryCondition(); + + if ($primaryCondition !== null) { $this->getGroupMappings($primaryCondition->getValuesGroup(), $mappings); } if ($mappings === []) { - if (null !== $searchOrder = $this->searchCondition->getOrder()) { + $searchOrder = $this->searchCondition->getOrder(); + $primarySearchOrder = $primaryCondition?->getOrder(); + + if ($searchOrder !== null) { $this->getGroupMappings($searchOrder->getValuesGroup(), $mappings); } - if ($primaryCondition !== null && null !== $primarySearchOrder = $primaryCondition->getOrder()) { + if ($primarySearchOrder !== null) { $this->getGroupMappings($primarySearchOrder->getValuesGroup(), $mappings); } } @@ -188,6 +191,9 @@ public static function translateComparison(string $operator): string return self::COMPARISON_OPERATOR_MAP[$operator]; } + /** + * @param-out array $mappings + */ private function getGroupMappings(ValuesGroup $group, array &$mappings): void { foreach ($group->getFields() as $fieldName => $valuesBag) { @@ -371,7 +377,7 @@ private function processOrder(?SearchOrder $order, array &$clause, array &$condi } } - private function convertValue($value, ?ValueConversion $converter, bool $injectParams) + private function convertValue(mixed $value, ?ValueConversion $converter, bool $injectParams) { $value = $injectParams ? $this->injectParameters($value) : $value; @@ -576,7 +582,7 @@ private function injectParameters($template) } if (\is_array($template)) { - return array_map([$this->parameterBag, 'injectParameters'], $template); + return array_map($this->parameterBag->injectParameters(...), $template); } return $this->parameterBag->injectParameters($template); @@ -652,29 +658,27 @@ private function processMappingConditions(FieldMapping $mapping, QueryPreparatio { $conditions = []; - if ($mapping->conditions !== []) { - foreach ($mapping->conditions as $mappingCondition) { - if ($mappingCondition->propertyQuery) { - $hints->context = QueryPreparationHints::CONTEXT_PRECONDITION_QUERY; - $values = $mappingCondition->propertyQuery; - } else { - $hints->context = QueryPreparationHints::CONTEXT_PRECONDITION_VALUE; - $values = (array) $mappingCondition->propertyValue; - } - - $conditions[] = $this->prepareProcessedValuesQuery( - $mappingCondition->propertyName, - $values, - $hints, - $mappingCondition->queryConversion, - $mappingCondition->valueConversion, - $mappingCondition->nested, - $mappingCondition->join, - $injectParams, - [], - $mappingCondition->options - ); + foreach ($mapping->conditions as $mappingCondition) { + if ($mappingCondition->propertyQuery) { + $hints->context = QueryPreparationHints::CONTEXT_PRECONDITION_QUERY; + $values = $mappingCondition->propertyQuery; + } else { + $hints->context = QueryPreparationHints::CONTEXT_PRECONDITION_VALUE; + $values = (array) $mappingCondition->propertyValue; } + + $conditions[] = $this->prepareProcessedValuesQuery( + $mappingCondition->propertyName, + $values, + $hints, + $mappingCondition->queryConversion, + $mappingCondition->valueConversion, + $mappingCondition->nested, + $mappingCondition->join, + $injectParams, + [], + $mappingCondition->options + ); } return $conditions; diff --git a/lib/Elasticsearch/QueryPreparationHints.php b/lib/Elasticsearch/QueryPreparationHints.php index faac4a26..0de0753e 100644 --- a/lib/Elasticsearch/QueryPreparationHints.php +++ b/lib/Elasticsearch/QueryPreparationHints.php @@ -25,11 +25,8 @@ class QueryPreparationHints public const CONTEXT_PATTERN_MATCH = 'PATTERN_MATCH'; public const CONTEXT_ORDER = 'ORDER'; - /** @var bool */ - public $identifier = false; + public bool $identifier = false; - /** - * @var string Preparation context, one of ConversionHints::CONTEXT_* constants - */ - public $context; + /** @var QueryPreparationHints::CONTEXT_* */ + public string $context; } diff --git a/lib/Elasticsearch/Tests/CachedConditionGeneratorTest.php b/lib/Elasticsearch/Tests/CachedConditionGeneratorTest.php index 0ad537ff..b5e9a354 100644 --- a/lib/Elasticsearch/Tests/CachedConditionGeneratorTest.php +++ b/lib/Elasticsearch/Tests/CachedConditionGeneratorTest.php @@ -31,20 +31,13 @@ */ final class CachedConditionGeneratorTest extends ElasticsearchTestCase { - /** - * @var CachedConditionGenerator - */ - private $cachedConditionGenerator; - - /** - * @var Cache&MockObject - */ - private $cacheDriver; - - /** - * @var ConditionGenerator&MockObject - */ - private $conditionGenerator; + private CachedConditionGenerator $cachedConditionGenerator; + + /** @var Cache&MockObject */ + private MockObject $cacheDriver; + + /** @var ConditionGenerator&MockObject */ + private MockObject $conditionGenerator; /** @test */ public function get_query_no_cache(): void diff --git a/lib/Elasticsearch/Tests/ElasticsearchFactoryTest.php b/lib/Elasticsearch/Tests/ElasticsearchFactoryTest.php index ea0d582b..8cc31e61 100644 --- a/lib/Elasticsearch/Tests/ElasticsearchFactoryTest.php +++ b/lib/Elasticsearch/Tests/ElasticsearchFactoryTest.php @@ -28,10 +28,7 @@ */ final class ElasticsearchFactoryTest extends ElasticsearchTestCase { - /** - * @var ElasticsearchFactory - */ - protected $factory; + protected ElasticsearchFactory $factory; /** @test */ public function create_condition_generator(): void diff --git a/lib/Elasticsearch/Tests/Functional/ConditionGeneratorResultsTest.php b/lib/Elasticsearch/Tests/Functional/ConditionGeneratorResultsTest.php index 6373e06d..57d0c717 100644 --- a/lib/Elasticsearch/Tests/Functional/ConditionGeneratorResultsTest.php +++ b/lib/Elasticsearch/Tests/Functional/ConditionGeneratorResultsTest.php @@ -17,8 +17,6 @@ use Rollerworks\Component\Search\Input\StringQueryInput; /** - * Class ConditionGeneratorResultsTest. - * * @group functional * * Special cases needed to be handled here @@ -30,10 +28,7 @@ */ final class ConditionGeneratorResultsTest extends FunctionalElasticsearchTestCase { - /** - * @var StringQueryInput - */ - private $inputProcessor; + private StringQueryInput $inputProcessor; protected function setUp(): void { diff --git a/lib/Elasticsearch/Tests/Functional/FunctionalElasticsearchTestCase.php b/lib/Elasticsearch/Tests/Functional/FunctionalElasticsearchTestCase.php index add94d97..49c6c03e 100644 --- a/lib/Elasticsearch/Tests/Functional/FunctionalElasticsearchTestCase.php +++ b/lib/Elasticsearch/Tests/Functional/FunctionalElasticsearchTestCase.php @@ -98,7 +98,7 @@ protected function getMappings(): array protected function getDocuments(): array { - $date = static fn (string $input) => (new \DateTimeImmutable($input, new \DateTimeZone('UTC')))->format('Y-m-d\TH:i:sP'); + $date = static fn (string $input): string => (new \DateTimeImmutable($input, new \DateTimeZone('UTC')))->format('Y-m-d\TH:i:sP'); return [ 'customers' => [ @@ -303,7 +303,7 @@ protected function assertDocumentsAreFound(SearchCondition $condition, array $ex $results = $search->search($query); $documents = $results->getDocuments(); $foundIds = array_map( - static fn (Document $document) => (string) $document->getId(), + static fn (Document $document): string => (string) $document->getId(), $documents ); } catch (ResponseException $exception) { @@ -316,7 +316,7 @@ protected function assertDocumentsAreFound(SearchCondition $condition, array $ex } self::assertSame( - array_map('strval', $expectedIds), + array_map(strval(...), $expectedIds), $foundIds, \sprintf( "Found these records instead: \n%s\n" diff --git a/lib/Elasticsearch/composer.json b/lib/Elasticsearch/composer.json index 5016c256..c5828166 100644 --- a/lib/Elasticsearch/composer.json +++ b/lib/Elasticsearch/composer.json @@ -20,7 +20,7 @@ } ], "require": { - "php": ">=8.1", + "php": "^8.1", "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "rollerworks/search": ">=2.0.0-BETA1 ^2.0@dev", "ruflin/elastica": "^5.0 || ^6.0 || ^7.0" diff --git a/lib/Symfony/SearchBundle/DependencyInjection/Compiler/ExtensionPass.php b/lib/Symfony/SearchBundle/DependencyInjection/Compiler/ExtensionPass.php index d3b213bc..978d6fa0 100644 --- a/lib/Symfony/SearchBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/lib/Symfony/SearchBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -34,15 +34,11 @@ final class ExtensionPass implements CompilerPassInterface { use PriorityTaggedServiceTrait; - private $fieldExtensionService; - private $fieldTypeTag; - private $fieldTypeExtensionTag; - - public function __construct(string $fieldExtensionService = 'rollerworks_search.extension', string $fieldTypeTag = 'rollerworks_search.type', string $fieldTypeExtensionTag = 'rollerworks_search.type_extension') - { - $this->fieldExtensionService = $fieldExtensionService; - $this->fieldTypeTag = $fieldTypeTag; - $this->fieldTypeExtensionTag = $fieldTypeExtensionTag; + public function __construct( + private string $fieldExtensionService = 'rollerworks_search.extension', + private string $fieldTypeTag = 'rollerworks_search.type', + private string $fieldTypeExtensionTag = 'rollerworks_search.type_extension', + ) { } public function process(ContainerBuilder $container): void @@ -92,7 +88,7 @@ private function processTypeExtensions(ContainerBuilder $container): array foreach ($typeExtensions as $extendedType => $extensions) { $allExtensions[$extendedType] = new IteratorArgument(array_map( - static fn ($extensionId) => new Reference($extensionId), + static fn ($extensionId): Reference => new Reference($extensionId), $extensions )); } diff --git a/lib/Symfony/SearchBundle/DependencyInjection/Configuration.php b/lib/Symfony/SearchBundle/DependencyInjection/Configuration.php index aeeab447..bd65e1fb 100644 --- a/lib/Symfony/SearchBundle/DependencyInjection/Configuration.php +++ b/lib/Symfony/SearchBundle/DependencyInjection/Configuration.php @@ -42,7 +42,7 @@ private function addDoctrineSection(ArrayNodeDefinition $rootNode): void ->arrayNode('doctrine') ->validate() ->ifTrue( - static fn (array $nodes) => $nodes['orm']['enabled'] && ! $nodes['dbal']['enabled'] + static fn (array $nodes): bool => $nodes['orm']['enabled'] && ! $nodes['dbal']['enabled'] ) ->thenInvalid('rollerworks_search.dbal must be enabled when rollerworks_search.orm is enabled') ->end() diff --git a/lib/Symfony/SearchBundle/Tests/Functional/Application/AppBundle/Controller/SearchController.php b/lib/Symfony/SearchBundle/Tests/Functional/Application/AppBundle/Controller/SearchController.php index 4208c2c7..b37d5463 100644 --- a/lib/Symfony/SearchBundle/Tests/Functional/Application/AppBundle/Controller/SearchController.php +++ b/lib/Symfony/SearchBundle/Tests/Functional/Application/AppBundle/Controller/SearchController.php @@ -26,15 +26,11 @@ final class SearchController { - private $searchFactory; - private $inputProcessorLoader; - private $urlGenerator; - - public function __construct(SearchFactory $searchFactory, InputProcessorLoader $inputProcessorLoader, UrlGeneratorInterface $urlGenerator) - { - $this->searchFactory = $searchFactory; - $this->inputProcessorLoader = $inputProcessorLoader; - $this->urlGenerator = $urlGenerator; + public function __construct( + private readonly SearchFactory $searchFactory, + private readonly InputProcessorLoader $inputProcessorLoader, + private readonly UrlGeneratorInterface $urlGenerator, + ) { } public function __invoke(Request $request) @@ -59,7 +55,7 @@ public function __invoke(Request $request) 'INVALID:
    ' . implode( "\n", array_map( - static fn (ConditionErrorMessage $e) => '
  • ' . $e->message . '
  • ', + static fn (ConditionErrorMessage $e): string => '
  • ' . $e->message . '
  • ', $e->getErrors() ) ) . '
', 500 diff --git a/lib/Symfony/SearchBundle/Tests/Functional/FunctionalTestCase.php b/lib/Symfony/SearchBundle/Tests/Functional/FunctionalTestCase.php index 78f3447e..e52f61f8 100644 --- a/lib/Symfony/SearchBundle/Tests/Functional/FunctionalTestCase.php +++ b/lib/Symfony/SearchBundle/Tests/Functional/FunctionalTestCase.php @@ -13,6 +13,7 @@ namespace Rollerworks\Bundle\SearchBundle\Tests\Functional; +use Rollerworks\Bundle\SearchBundle\Tests\Functional\Application\AppKernel; use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\HttpKernel\KernelInterface; @@ -21,7 +22,7 @@ abstract class FunctionalTestCase extends WebTestCase { protected static function createKernel(array $options = []): KernelInterface { - return new Application\AppKernel( + return new AppKernel( $options['config'] ?? 'default.yml', false ); @@ -29,7 +30,7 @@ protected static function createKernel(array $options = []): KernelInterface protected static function getKernelClass(): string { - return Application\AppKernel::class; + return AppKernel::class; } protected static function newClient(array $options = [], array $server = []): KernelBrowser diff --git a/lib/Symfony/SearchBundle/TranslatorBasedAliasResolver.php b/lib/Symfony/SearchBundle/TranslatorBasedAliasResolver.php index 17ca9fdb..f905034a 100644 --- a/lib/Symfony/SearchBundle/TranslatorBasedAliasResolver.php +++ b/lib/Symfony/SearchBundle/TranslatorBasedAliasResolver.php @@ -20,8 +20,9 @@ final class TranslatorBasedAliasResolver { - public function __construct(private TranslatorInterface $translator) - { + public function __construct( + private readonly TranslatorInterface $translator, + ) { } public function __invoke(FieldConfig $field): string diff --git a/lib/Symfony/SearchBundle/composer.json b/lib/Symfony/SearchBundle/composer.json index b144961b..1cdacec0 100644 --- a/lib/Symfony/SearchBundle/composer.json +++ b/lib/Symfony/SearchBundle/composer.json @@ -17,7 +17,7 @@ } ], "require": { - "php": ">=8.1", + "php": "^8.1", "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "rollerworks/search": ">=2.0.0-BETA3 ^2.0@dev", "symfony/framework-bundle": "^6.4 || ^7.4 || ^8.0", diff --git a/lib/Symfony/Validator/InputValidator.php b/lib/Symfony/Validator/InputValidator.php index 7db344ab..84941c5c 100644 --- a/lib/Symfony/Validator/InputValidator.php +++ b/lib/Symfony/Validator/InputValidator.php @@ -31,17 +31,17 @@ */ final class InputValidator implements Validator { - private ValidatorInterface $validator; - private ErrorList $errorList; + /** @var Constraint[]|null */ private ?array $constraints = []; + /** @var Constraint[]|null */ - private ?array $patternMatchConstraints; + private ?array $patternMatchConstraints = null; - public function __construct(ValidatorInterface $validator) - { - $this->validator = $validator; + public function __construct( + private readonly ValidatorInterface $validator, + ) { } public function initializeContext(FieldConfig $field, ErrorList $errorList): void diff --git a/lib/Symfony/Validator/Tests/InputValidatorTest.php b/lib/Symfony/Validator/Tests/InputValidatorTest.php index 8c1b0793..7e2d1294 100644 --- a/lib/Symfony/Validator/Tests/InputValidatorTest.php +++ b/lib/Symfony/Validator/Tests/InputValidatorTest.php @@ -25,18 +25,15 @@ use Symfony\Component\Validator\Constraints as Assert; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\Validation; +use Symfony\Component\Validator\Validator\ValidatorInterface; /** * @internal */ final class InputValidatorTest extends SearchIntegrationTestCase { - private $sfValidator; - - /** - * @var InputValidator - */ - private $validator; + private ValidatorInterface $sfValidator; + private InputValidator $validator; protected function setUp(): void { diff --git a/lib/Symfony/Validator/Type/FieldTypeValidatorExtension.php b/lib/Symfony/Validator/Type/FieldTypeValidatorExtension.php index 40d45446..fde50b92 100644 --- a/lib/Symfony/Validator/Type/FieldTypeValidatorExtension.php +++ b/lib/Symfony/Validator/Type/FieldTypeValidatorExtension.php @@ -31,7 +31,7 @@ public function getExtendedType(): string public function configureOptions(OptionsResolver $resolver): void { // Constraint should always be converted to an array - $constraintsNormalizer = static fn (Options $options, $constraints) => \is_object($constraints) ? [$constraints] : (array) $constraints; + $constraintsNormalizer = static fn (Options $options, $constraints): array => \is_object($constraints) ? [$constraints] : (array) $constraints; $resolver->setDefault('constraints', []); $resolver->setDefault('pattern_match_constraints', []); diff --git a/lib/Symfony/Validator/composer.json b/lib/Symfony/Validator/composer.json index a6ad4d13..a351e675 100644 --- a/lib/Symfony/Validator/composer.json +++ b/lib/Symfony/Validator/composer.json @@ -20,7 +20,7 @@ } ], "require": { - "php": ">=8.1", + "php": "^8.1", "rollerworks/search": ">=2.0.0-BETA3 ^2.0@dev", "symfony/validator": "^6.4 || ^7.4 || ^8.0" }, diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a2ce483e..feca5249 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -216,12 +216,6 @@ parameters: count: 1 path: lib/Doctrine/Orm/Tests/OrmTestCase.php - - - message: '#^Method Rollerworks\\Component\\Search\\Elasticsearch\\FieldMapping\:\:parseProperty\(\) should return array\ but returns array\\|bool\|string\>\|bool\|string\>\|bool\|string\>\|string\|false\|null\>\.$#' - identifier: return.type - count: 1 - path: lib/Elasticsearch/FieldMapping.php - - message: '#^Variable \$nestedBool might not be defined\.$#' identifier: variable.undefined @@ -246,12 +240,6 @@ parameters: count: 1 path: lib/Symfony/SearchBundle/Tests/Functional/ApiPlatformTest.php - - - message: '#^Call to method Rollerworks\\Component\\Search\\SearchConditionBuilder\:\:end\(\) on a separate line has no effect\.$#' - identifier: method.resultUnused - count: 3 - path: lib/Symfony/SearchBundle/Tests/SearchConditionBuilderTest.php - - message: '#^Property Rollerworks\\Component\\Search\\Extension\\Symfony\\Validator\\InputValidator\:\:\$errorList is never read, only written\.$#' identifier: property.onlyWritten diff --git a/phpstan.neon b/phpstan.neon index ff5445b1..394bd9a2 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -26,4 +26,13 @@ parameters: path: lib/Core/Tests/Extension/Core/DataTransformer/*.php message: '#^Parameter \#1 \$[^\s]+ of method [^:]+\:\:(reverse)?Transform\(\) expects#i' + - '#SearchConditionBuilder\:\:end\(\) on a separate line has no effect#' + + - + # This is expected as part of the actual test + message: '#^Parameter \#1 \$searchCondition of method Rollerworks\\Component\\Search\\SearchConditionSerializer\:\:unserialize\(\) expects array\{string, string\}#' + identifier: argument.type + path: lib/Core/Tests/SearchConditionSerializerTest.php + + - '#Call to an undefined static method Carbon\\Translator\:\:get\(\)#'