diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 2733a91..be74a68 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -16,4 +16,4 @@ jobs: name: "PHPUnit" uses: "doctrine/.github/.github/workflows/continuous-integration.yml@3.0.0" with: - php-versions: '["7.4", "8.0", "8.1", "8.2"]' + php-versions: '["8.0", "8.1", "8.2"]' diff --git a/composer.json b/composer.json index 3e42bf6..51c5e6b 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "rss": "https://github.com/doctrine/doctrine-laminas-hydrator/releases.atom" }, "require": { - "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0", + "php": "~8.0.0 || ~8.1.0 || ~8.2.0", "ext-ctype": "*", "doctrine/collections": "^1.6.8", "doctrine/inflector": "^2.0.4", diff --git a/src/DoctrineObject.php b/src/DoctrineObject.php index c335d42..e1d2d5b 100644 --- a/src/DoctrineObject.php +++ b/src/DoctrineObject.php @@ -27,33 +27,30 @@ use function array_intersect_key; use function array_key_exists; use function array_keys; -use function array_merge; use function assert; use function ctype_upper; use function current; -use function get_class; use function get_class_methods; use function gettype; use function in_array; use function is_array; use function is_callable; use function is_int; +use function is_iterable; use function is_object; use function is_string; use function method_exists; use function property_exists; use function sprintf; +use function str_ends_with; +use function str_starts_with; use function strpos; use function substr; class DoctrineObject extends AbstractHydrator { - protected ObjectManager $objectManager; - protected ?ClassMetadata $metadata = null; - protected bool $byValue = true; - /** @var class-string */ protected string $defaultByValueStrategy = AllowRemoveByValue::class; @@ -66,11 +63,9 @@ class DoctrineObject extends AbstractHydrator * @param ObjectManager $objectManager The ObjectManager to use * @param bool $byValue If set to true, hydrator will always use entity's public API */ - public function __construct(ObjectManager $objectManager, bool $byValue = true, ?Inflector $inflector = null) + public function __construct(protected ObjectManager $objectManager, protected bool $byValue = true, ?Inflector $inflector = null) { - $this->objectManager = $objectManager; - $this->byValue = $byValue; - $this->inflector = $inflector ?? InflectorFactory::create()->build(); + $this->inflector = $inflector ?? InflectorFactory::create()->build(); } protected function getClassMetadata(): ClassMetadata @@ -122,10 +117,7 @@ public function setDefaultByReferenceStrategy(string $defaultByReferenceStrategy */ public function getFieldNames(): iterable { - $fields = array_merge( - $this->getClassMetadata()->getFieldNames(), - $this->getClassMetadata()->getAssociationNames() - ); + $fields = [...$this->getClassMetadata()->getFieldNames(), ...$this->getClassMetadata()->getAssociationNames()]; foreach ($fields as $fieldName) { $pos = strpos($fieldName, '.'); @@ -174,7 +166,7 @@ public function hydrate(array $data, object $object): object */ protected function prepare(object $object): void { - $this->metadata = $this->objectManager->getClassMetadata(get_class($object)); + $this->metadata = $this->objectManager->getClassMetadata($object::class); $this->prepareStrategies(); } @@ -210,7 +202,7 @@ protected function prepareStrategies(): void sprintf( 'Strategies used for collections valued associations must inherit from %s, %s given', Strategy\CollectionStrategyInterface::class, - get_class($strategy) + $strategy::class ) ); } @@ -250,7 +242,7 @@ protected function extractByValue(object $object): array } elseif (in_array($isser, $methods)) { $data[$dataFieldName] = $this->extractValue($fieldName, $object->$isser(), $object); } elseif ( - substr($fieldName, 0, 2) === 'is' + str_starts_with($fieldName, 'is') && ctype_upper(substr($fieldName, 2, 1)) && in_array($fieldName, $methods) ) { @@ -283,7 +275,7 @@ protected function extractByReference(object $object): array throw new LogicException( sprintf( 'this class "%s" is readonly, data can\'t be extracted', - get_class($object) + $object::class ) ); } @@ -441,7 +433,7 @@ protected function hydrateByReference(array $data, ?object $object): object throw new LogicException( sprintf( 'Cannot hydrate class "%s" by reference. Property "%s" is readonly. To fix this error, remove readonly.', - get_class($object), + $object::class, $field ) ); @@ -513,9 +505,8 @@ protected function tryConvertArrayToObject(array $data, object $object): ?object * target will be returned. * * @param class-string $target - * @param mixed $value */ - protected function toOne(string $target, $value): ?object + protected function toOne(string $target, mixed $value): ?object { $metadata = $this->objectManager->getClassMetadata($target); @@ -540,11 +531,10 @@ protected function toOne(string $target, $value): ?object * changing the collection of the object * * @param class-string $target - * @param mixed $values * * @throws InvalidArgumentException */ - protected function toMany(object $object, string $collectionName, string $target, $values): void + protected function toMany(object $object, string $collectionName, string $target, mixed $values): void { $metadata = $this->objectManager->getClassMetadata($target); $identifier = $metadata->getIdentifier(); @@ -609,9 +599,7 @@ protected function toMany(object $object, string $collectionName, string $target $collection = array_filter( $collection, - static function ($item) { - return $item !== null; - } + static fn ($item) => $item !== null ); // Set the object so that the strategy can extract the Collection from it @@ -630,11 +618,9 @@ static function ($item) { * * @link http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#doctrine-mapping-types * - * @param mixed $value - * * @return mixed|null */ - protected function handleTypeConversions($value, ?string $typeOfField) + protected function handleTypeConversions(mixed $value, ?string $typeOfField) { if ($value === null) { return null; @@ -667,7 +653,7 @@ protected function handleTypeConversions($value, ?string $typeOfField) return null; } - $isImmutable = substr($typeOfField, -9) === 'immutable'; + $isImmutable = str_ends_with($typeOfField, 'immutable'); // Psalm has troubles with nested conditions, therefore break this into two return statements. // See https://github.com/vimeo/psalm/issues/6683. @@ -712,14 +698,13 @@ protected function handleTypeConversions($value, ?string $typeOfField) /** * Find an object by a given target class and identifier * - * @param mixed $identifiers * @psalm-param class-string $targetClass * * @psalm-return T|null * * @template T of object */ - protected function find($identifiers, string $targetClass): ?object + protected function find(mixed $identifiers, string $targetClass): ?object { if ($identifiers instanceof $targetClass) { return $identifiers; @@ -743,12 +728,10 @@ private function isNullIdentifier($identifier): bool return true; } - if ($identifier instanceof Traversable || is_array($identifier)) { + if (is_iterable($identifier)) { $nonNullIdentifiers = array_filter( ArrayUtils::iteratorToArray($identifier), - static function ($value) { - return $value !== null; - } + static fn ($value) => $value !== null ); return empty($nonNullIdentifiers); diff --git a/src/Filter/PropertyName.php b/src/Filter/PropertyName.php index c3073a5..1c24cc9 100644 --- a/src/Filter/PropertyName.php +++ b/src/Filter/PropertyName.php @@ -18,15 +18,12 @@ final class PropertyName implements FilterInterface /** @var string[] */ private array $properties = []; - private bool $exclude; - /** * @param string|string[] $properties The properties to exclude or include. * @param bool $exclude If the method should be excluded */ - public function __construct($properties, bool $exclude = true) + public function __construct($properties, private bool $exclude = true) { - $this->exclude = $exclude; $this->properties = is_array($properties) ? $properties : [$properties]; diff --git a/src/Strategy/AbstractCollectionStrategy.php b/src/Strategy/AbstractCollectionStrategy.php index 2fd5085..3ee2f65 100644 --- a/src/Strategy/AbstractCollectionStrategy.php +++ b/src/Strategy/AbstractCollectionStrategy.php @@ -13,7 +13,6 @@ use LogicException; use ReflectionException; -use function get_class; use function is_array; use function method_exists; use function spl_object_hash; @@ -115,7 +114,7 @@ protected function getCollectionFromObjectByValue(): Collection 'The getter %s to access collection %s in object %s does not exist', $getter, $this->getCollectionName(), - get_class($object) + $object::class ) ); } diff --git a/src/Strategy/AllowRemoveByValue.php b/src/Strategy/AllowRemoveByValue.php index 79c0473..677251f 100644 --- a/src/Strategy/AllowRemoveByValue.php +++ b/src/Strategy/AllowRemoveByValue.php @@ -8,7 +8,6 @@ use LogicException; use function array_udiff; -use function get_class; use function method_exists; use function sprintf; @@ -43,7 +42,7 @@ public function hydrate($value, ?array $data) entity domain code, but one or both seem to be missing', $adder, $remover, - get_class($object) + $object::class ) ); } diff --git a/src/Strategy/DisallowRemoveByValue.php b/src/Strategy/DisallowRemoveByValue.php index f5417a9..c3103db 100644 --- a/src/Strategy/DisallowRemoveByValue.php +++ b/src/Strategy/DisallowRemoveByValue.php @@ -8,7 +8,6 @@ use LogicException; use function array_udiff; -use function get_class; use function method_exists; use function sprintf; @@ -41,7 +40,7 @@ public function hydrate($value, ?array $data) 'DisallowRemove strategy for DoctrineModule hydrator requires %s to be defined in %s entity domain code, but it seems to be missing', $adder, - get_class($object) + $object::class ) ); } diff --git a/tests/Assets/ByValueDifferentiatorEntity.php b/tests/Assets/ByValueDifferentiatorEntity.php index edbcfa8..d027912 100644 --- a/tests/Assets/ByValueDifferentiatorEntity.php +++ b/tests/Assets/ByValueDifferentiatorEntity.php @@ -13,18 +13,12 @@ class ByValueDifferentiatorEntity protected string $field; - /** - * @param string|int $id - */ - public function setId($id): void + public function setId(string|int $id): void { $this->id = $id; } - /** - * @return string|int - */ - public function getId() + public function getId(): string|int { return $this->id; } diff --git a/tests/Assets/DifferentAllowRemoveByValue.php b/tests/Assets/DifferentAllowRemoveByValue.php index 5c43352..250590c 100644 --- a/tests/Assets/DifferentAllowRemoveByValue.php +++ b/tests/Assets/DifferentAllowRemoveByValue.php @@ -9,7 +9,6 @@ use LogicException; use function array_udiff; -use function get_class; use function method_exists; use function sprintf; @@ -35,7 +34,7 @@ public function hydrate($value, ?array $data) entity domain code, but one or both seem to be missing', $adder, $remover, - get_class($object) + $object::class ) ); } diff --git a/tests/Assets/NamingStrategyEntity.php b/tests/Assets/NamingStrategyEntity.php index 3a0a5a7..2e28472 100644 --- a/tests/Assets/NamingStrategyEntity.php +++ b/tests/Assets/NamingStrategyEntity.php @@ -6,11 +6,8 @@ class NamingStrategyEntity { - protected ?string $camelCase = null; - - public function __construct(?string $camelCase = null) + public function __construct(protected ?string $camelCase = null) { - $this->camelCase = $camelCase; } public function setCamelCase(?string $camelCase): void diff --git a/tests/Assets/OneToOneEntity.php b/tests/Assets/OneToOneEntity.php index e1940fb..3ee5b03 100644 --- a/tests/Assets/OneToOneEntity.php +++ b/tests/Assets/OneToOneEntity.php @@ -10,7 +10,7 @@ class OneToOneEntity { protected int $id; - protected ?ByValueDifferentiatorEntity $toOne; + protected ?ByValueDifferentiatorEntity $toOne = null; protected DateTime $createdAt; diff --git a/tests/Assets/SimpleEntity.php b/tests/Assets/SimpleEntity.php index ce4b037..f8c2471 100644 --- a/tests/Assets/SimpleEntity.php +++ b/tests/Assets/SimpleEntity.php @@ -11,18 +11,12 @@ class SimpleEntity protected string $field; - /** - * @param string|int $id - */ - public function setId($id): void + public function setId(string|int $id): void { $this->id = $id; } - /** - * @return string|int - */ - public function getId() + public function getId(): string|int { return $this->id; } diff --git a/tests/Assets/SimpleEntityReadonlyPhp82.php b/tests/Assets/SimpleEntityReadonlyPhp82.php index 037be2d..2f62666 100644 --- a/tests/Assets/SimpleEntityReadonlyPhp82.php +++ b/tests/Assets/SimpleEntityReadonlyPhp82.php @@ -6,13 +6,7 @@ readonly class SimpleEntityReadonlyPhp82 { - protected ?int $id; - - protected ?string $field; - - public function __construct(?int $id, ?string $field) + public function __construct(protected ?int $id, protected ?string $field) { - $this->id = $id; - $this->field = $field; } } diff --git a/tests/Assets/SimpleEntityWithDateTime.php b/tests/Assets/SimpleEntityWithDateTime.php index 610f538..65f55f8 100644 --- a/tests/Assets/SimpleEntityWithDateTime.php +++ b/tests/Assets/SimpleEntityWithDateTime.php @@ -10,7 +10,7 @@ class SimpleEntityWithDateTime { protected int $id; - protected ?DateTime $date; + protected ?DateTime $date = null; public function setId(int $id): void { diff --git a/tests/Assets/SimpleEntityWithGenericField.php b/tests/Assets/SimpleEntityWithGenericField.php index e4c2acf..af24915 100644 --- a/tests/Assets/SimpleEntityWithGenericField.php +++ b/tests/Assets/SimpleEntityWithGenericField.php @@ -21,10 +21,7 @@ public function getId(): ?int return $this->id; } - /** - * @param mixed $value - */ - public function setGenericField($value): void + public function setGenericField(mixed $value): void { $this->genericField = $value; } diff --git a/tests/Assets/SimpleEntityWithReadonlyPropsPhp81.php b/tests/Assets/SimpleEntityWithReadonlyPropsPhp81.php index dec46b2..dacff49 100644 --- a/tests/Assets/SimpleEntityWithReadonlyPropsPhp81.php +++ b/tests/Assets/SimpleEntityWithReadonlyPropsPhp81.php @@ -6,13 +6,10 @@ class SimpleEntityWithReadonlyPropsPhp81 { - protected readonly ?int $id; - protected ?string $field; - public function __construct(?int $id) + public function __construct(protected readonly ?int $id) { - $this->id = $id; } public function getId(): int diff --git a/tests/Assets/SimplePrivateEntity.php b/tests/Assets/SimplePrivateEntity.php index 5d7f6b5..01adaf5 100644 --- a/tests/Assets/SimplePrivateEntity.php +++ b/tests/Assets/SimplePrivateEntity.php @@ -9,12 +9,10 @@ class SimplePrivateEntity { /** - * @param mixed $value - * * @phpcsSuppress SlevomatCodingStandard.Classes.UnusedPrivateElements * @phpstan-ignore-next-line */ - private function setPrivate($value): void + private function setPrivate(mixed $value): void { throw new Exception('Should never be called'); } @@ -28,10 +26,7 @@ private function getPrivate(): void throw new Exception('Should never be called'); } - /** - * @param mixed $value - */ - protected function setProtected($value): void + protected function setProtected(mixed $value): void { throw new Exception('Should never be called'); } diff --git a/tests/DoctrineObjectTypeConversionsTest.php b/tests/DoctrineObjectTypeConversionsTest.php index c843a9a..7e916f7 100644 --- a/tests/DoctrineObjectTypeConversionsTest.php +++ b/tests/DoctrineObjectTypeConversionsTest.php @@ -196,8 +196,8 @@ public function testHandleTypeConversionsDatetime(): void $entity = new Assets\SimpleEntityWithGenericField(); $now = new DateTime(); - $now->setTimestamp(1522353676); - $data = ['genericField' => 1522353676]; + $now->setTimestamp(1_522_353_676); + $data = ['genericField' => 1_522_353_676]; $entity = $this->hydratorByValue->hydrate($data, $entity); @@ -244,8 +244,8 @@ public function testHandleTypeConversionsDatetimeImmutable(): void $this->configureObjectManagerForSimpleEntityWithGenericField('datetime_immutable'); $entity = new Assets\SimpleEntityWithGenericField(); - $now = (new DateTimeImmutable())->setTimestamp(1522353676); - $data = ['genericField' => 1522353676]; + $now = (new DateTimeImmutable())->setTimestamp(1_522_353_676); + $data = ['genericField' => 1_522_353_676]; $entity = $this->hydratorByValue->hydrate($data, $entity); @@ -293,8 +293,8 @@ public function testHandleTypeConversionsDatetimetz(): void $entity = new Assets\SimpleEntityWithGenericField(); $now = new DateTime(); - $now->setTimestamp(1522353676); - $data = ['genericField' => 1522353676]; + $now->setTimestamp(1_522_353_676); + $data = ['genericField' => 1_522_353_676]; $entity = $this->hydratorByValue->hydrate($data, $entity); @@ -341,8 +341,8 @@ public function testHandleTypeConversionsDatetimetzImmutable(): void $this->configureObjectManagerForSimpleEntityWithGenericField('datetimetz_immutable'); $entity = new Assets\SimpleEntityWithGenericField(); - $now = (new DateTimeImmutable())->setTimestamp(1522353676); - $data = ['genericField' => 1522353676]; + $now = (new DateTimeImmutable())->setTimestamp(1_522_353_676); + $data = ['genericField' => 1_522_353_676]; $entity = $this->hydratorByValue->hydrate($data, $entity); @@ -390,8 +390,8 @@ public function testHandleTypeConversionsTime(): void $entity = new Assets\SimpleEntityWithGenericField(); $now = new DateTime(); - $now->setTimestamp(1522353676); - $data = ['genericField' => 1522353676]; + $now->setTimestamp(1_522_353_676); + $data = ['genericField' => 1_522_353_676]; $entity = $this->hydratorByValue->hydrate($data, $entity); @@ -439,8 +439,8 @@ public function testHandleTypeConversionsDate(): void $entity = new Assets\SimpleEntityWithGenericField(); $now = new DateTime(); - $now->setTimestamp(1522353676); - $data = ['genericField' => 1522353676]; + $now->setTimestamp(1_522_353_676); + $data = ['genericField' => 1_522_353_676]; $entity = $this->hydratorByValue->hydrate($data, $entity);