diff --git a/src/Symfony/Bridge/Doctrine/CHANGELOG.md b/src/Symfony/Bridge/Doctrine/CHANGELOG.md index 754e6938da402..d4e9c6f6de642 100644 --- a/src/Symfony/Bridge/Doctrine/CHANGELOG.md +++ b/src/Symfony/Bridge/Doctrine/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + +* Allow validating every class against `UniqueEntity` constraint + 7.0 --- diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CreateDoubleNameEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CreateDoubleNameEntity.php new file mode 100644 index 0000000000000..421b67c5c1d77 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/CreateDoubleNameEntity.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +class CreateDoubleNameEntity +{ + public $primaryName; + public $secondaryName; + + public function __construct($primaryName, $secondaryName) + { + $this->primaryName = $primaryName; + $this->secondaryName = $secondaryName; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Dto.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Dto.php new file mode 100644 index 0000000000000..1a9444324496b --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/Dto.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +class Dto +{ + public string $foo; +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/HireAnEmployee.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/HireAnEmployee.php new file mode 100644 index 0000000000000..4ef9d610077a8 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/HireAnEmployee.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +class HireAnEmployee +{ + public $name; + + public function __construct($name) + { + $this->name = $name; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UpdateCompositeIntIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UpdateCompositeIntIdEntity.php new file mode 100644 index 0000000000000..3c134e084bea7 --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UpdateCompositeIntIdEntity.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +class UpdateCompositeIntIdEntity +{ + public $id1; + public $id2; + public $name; + + public function __construct($id1, $id2, $name) + { + $this->id1 = $id1; + $this->id2 = $id2; + $this->name = $name; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UpdateCompositeObjectNoToStringIdEntity.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UpdateCompositeObjectNoToStringIdEntity.php new file mode 100644 index 0000000000000..4b18c54044aee --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UpdateCompositeObjectNoToStringIdEntity.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +class UpdateCompositeObjectNoToStringIdEntity +{ + /** + * @var SingleIntIdNoToStringEntity + */ + protected $object1; + + /** + * @var SingleIntIdNoToStringEntity + */ + protected $object2; + + public $name; + + public function __construct(SingleIntIdNoToStringEntity $object1, SingleIntIdNoToStringEntity $object2, $name) + { + $this->object1 = $object1; + $this->object2 = $object2; + $this->name = $name; + } + + public function getObject1(): SingleIntIdNoToStringEntity + { + return $this->object1; + } + + public function getObject2(): SingleIntIdNoToStringEntity + { + return $this->object2; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UpdateEmployeeProfile.php b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UpdateEmployeeProfile.php new file mode 100644 index 0000000000000..92c1d56a90e8d --- /dev/null +++ b/src/Symfony/Bridge/Doctrine/Tests/Fixtures/UpdateEmployeeProfile.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Doctrine\Tests\Fixtures; + +class UpdateEmployeeProfile +{ + public $id; + public $name; + + public function __construct($id, $name) + { + $this->id = $id; + $this->name = $name; + } +} diff --git a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php index aaae5ede92cf1..abe31d474dfab 100644 --- a/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php +++ b/src/Symfony/Bridge/Doctrine/Tests/Validator/Constraints/UniqueEntityValidatorTest.php @@ -25,9 +25,12 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\AssociationEntity2; use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeIntIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeObjectNoToStringIdEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\CreateDoubleNameEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoubleNameEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\DoubleNullableNameEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\Dto; use Symfony\Bridge\Doctrine\Tests\Fixtures\Employee; +use Symfony\Bridge\Doctrine\Tests\Fixtures\HireAnEmployee; use Symfony\Bridge\Doctrine\Tests\Fixtures\Person; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleIntIdNoToStringEntity; @@ -35,6 +38,9 @@ use Symfony\Bridge\Doctrine\Tests\Fixtures\SingleStringIdEntity; use Symfony\Bridge\Doctrine\Tests\Fixtures\Type\StringWrapper; use Symfony\Bridge\Doctrine\Tests\Fixtures\Type\StringWrapperType; +use Symfony\Bridge\Doctrine\Tests\Fixtures\UpdateCompositeIntIdEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\UpdateCompositeObjectNoToStringIdEntity; +use Symfony\Bridge\Doctrine\Tests\Fixtures\UpdateEmployeeProfile; use Symfony\Bridge\Doctrine\Tests\TestRepositoryFactory; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator; @@ -943,4 +949,243 @@ public function rewind(): void }], ]; } + + public function testValidateDTOUniqueness() + { + $constraint = new UniqueEntity([ + 'message' => 'myMessage', + 'fields' => ['name'], + 'em' => self::EM_NAME, + 'entityClass' => Person::class, + ]); + + $entity = new Person(1, 'Foo'); + $dto = new HireAnEmployee('Foo'); + + $this->validator->validate($entity, $constraint); + + $this->assertNoViolation(); + + $this->em->persist($entity); + $this->em->flush(); + + $this->validator->validate($entity, $constraint); + + $this->assertNoViolation(); + + $this->validator->validate($dto, $constraint); + + $this->buildViolation('myMessage') + ->atPath('property.path.name') + ->setInvalidValue('Foo') + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) + ->setCause([$entity]) + ->setParameters(['{{ value }}' => '"Foo"']) + ->assertRaised(); + } + + public function testValidateMappingOfFieldNames() + { + $constraint = new UniqueEntity([ + 'message' => 'myMessage', + 'fields' => ['primaryName' => 'name', 'secondaryName' => 'name2'], + 'em' => self::EM_NAME, + 'entityClass' => DoubleNameEntity::class, + ]); + + $entity = new DoubleNameEntity(1, 'Foo', 'Bar'); + $dto = new CreateDoubleNameEntity('Foo', 'Bar'); + + $this->em->persist($entity); + $this->em->flush(); + + $this->validator->validate($dto, $constraint); + + $this->buildViolation('myMessage') + ->atPath('property.path.name') + ->setParameter('{{ value }}', '"Foo"') + ->setInvalidValue('Foo') + ->setCause([$entity]) + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) + ->assertRaised(); + } + + public function testInvalidateDTOFieldName() + { + $this->expectException(ConstraintDefinitionException::class); + $this->expectExceptionMessage('The field "primaryName" is not a property of class "Symfony\Bridge\Doctrine\Tests\Fixtures\HireAnEmployee".'); + $constraint = new UniqueEntity([ + 'message' => 'myMessage', + 'fields' => ['primaryName' => 'name'], + 'em' => self::EM_NAME, + 'entityClass' => SingleStringIdEntity::class, + ]); + + $dto = new HireAnEmployee('Foo'); + $this->validator->validate($dto, $constraint); + } + + public function testInvalidateEntityFieldName() + { + $this->expectException(ConstraintDefinitionException::class); + $this->expectExceptionMessage('The field "name2" is not mapped by Doctrine, so it cannot be validated for uniqueness.'); + $constraint = new UniqueEntity([ + 'message' => 'myMessage', + 'fields' => ['name2'], + 'em' => self::EM_NAME, + 'entityClass' => SingleStringIdEntity::class, + ]); + + $dto = new HireAnEmployee('Foo'); + $this->validator->validate($dto, $constraint); + } + + public function testValidateDTOUniquenessWhenUpdatingEntity() + { + $constraint = new UniqueEntity([ + 'message' => 'myMessage', + 'fields' => ['name'], + 'em' => self::EM_NAME, + 'entityClass' => Person::class, + 'identifierFieldNames' => ['id'], + ]); + + $entity1 = new Person(1, 'Foo'); + $entity2 = new Person(2, 'Bar'); + + $this->em->persist($entity1); + $this->em->persist($entity2); + $this->em->flush(); + + $dto = new UpdateEmployeeProfile(2, 'Foo'); + + $this->validator->validate($dto, $constraint); + + $this->buildViolation('myMessage') + ->atPath('property.path.name') + ->setInvalidValue('Foo') + ->setCode(UniqueEntity::NOT_UNIQUE_ERROR) + ->setCause([$entity1]) + ->setParameters(['{{ value }}' => '"Foo"']) + ->assertRaised(); + } + + public function testValidateDTOUniquenessWhenUpdatingEntityWithTheSameValue() + { + $constraint = new UniqueEntity([ + 'message' => 'myMessage', + 'fields' => ['name'], + 'em' => self::EM_NAME, + 'entityClass' => CompositeIntIdEntity::class, + 'identifierFieldNames' => ['id1', 'id2'], + ]); + + $entity = new CompositeIntIdEntity(1, 2, 'Foo'); + + $this->em->persist($entity); + $this->em->flush(); + + $dto = new UpdateCompositeIntIdEntity(1, 2, 'Foo'); + + $this->validator->validate($dto, $constraint); + + $this->assertNoViolation(); + } + + public function testValidateIdentifierMappingOfFieldNames() + { + $constraint = new UniqueEntity([ + 'message' => 'myMessage', + 'fields' => ['object1' => 'objectOne', 'object2' => 'objectTwo'], + 'em' => self::EM_NAME, + 'entityClass' => CompositeObjectNoToStringIdEntity::class, + 'identifierFieldNames' => ['object1' => 'objectOne', 'object2' => 'objectTwo'], + ]); + + $objectOne = new SingleIntIdNoToStringEntity(1, 'foo'); + $objectTwo = new SingleIntIdNoToStringEntity(2, 'bar'); + + $this->em->persist($objectOne); + $this->em->persist($objectTwo); + $this->em->flush(); + + $entity = new CompositeObjectNoToStringIdEntity($objectOne, $objectTwo); + + $this->em->persist($entity); + $this->em->flush(); + + $dto = new UpdateCompositeObjectNoToStringIdEntity($objectOne, $objectTwo, 'Foo'); + + $this->validator->validate($dto, $constraint); + + $this->assertNoViolation(); + } + + public function testInvalidateMissingIdentifierFieldName() + { + $this->expectException(ConstraintDefinitionException::class); + $this->expectExceptionMessage('The "Symfony\Bridge\Doctrine\Tests\Fixtures\CompositeObjectNoToStringIdEntity" entity identifier field names should be "objectOne, objectTwo", not "objectTwo".'); + $constraint = new UniqueEntity([ + 'message' => 'myMessage', + 'fields' => ['object1' => 'objectOne', 'object2' => 'objectTwo'], + 'em' => self::EM_NAME, + 'entityClass' => CompositeObjectNoToStringIdEntity::class, + 'identifierFieldNames' => ['object2' => 'objectTwo'], + ]); + + $objectOne = new SingleIntIdNoToStringEntity(1, 'foo'); + $objectTwo = new SingleIntIdNoToStringEntity(2, 'bar'); + + $this->em->persist($objectOne); + $this->em->persist($objectTwo); + $this->em->flush(); + + $entity = new CompositeObjectNoToStringIdEntity($objectOne, $objectTwo); + + $this->em->persist($entity); + $this->em->flush(); + + $dto = new UpdateCompositeObjectNoToStringIdEntity($objectOne, $objectTwo, 'Foo'); + $this->validator->validate($dto, $constraint); + } + + public function testUninitializedValueThrowException() + { + $this->expectExceptionMessage('Typed property Symfony\Bridge\Doctrine\Tests\Fixtures\Dto::$foo must not be accessed before initialization'); + $constraint = new UniqueEntity([ + 'message' => 'myMessage', + 'fields' => ['foo' => 'name'], + 'em' => self::EM_NAME, + 'entityClass' => DoubleNameEntity::class, + ]); + + $entity = new DoubleNameEntity(1, 'Foo', 'Bar'); + $dto = new Dto(); + + $this->em->persist($entity); + $this->em->flush(); + + $this->validator->validate($dto, $constraint); + } + + public function testEntityManagerNullObjectWhenDTO() + { + $this->expectException(ConstraintDefinitionException::class); + $this->expectExceptionMessage('Unable to find the object manager associated with an entity of class "Symfony\Bridge\Doctrine\Tests\Fixtures\Person"'); + $constraint = new UniqueEntity([ + 'message' => 'myMessage', + 'fields' => ['name'], + 'entityClass' => Person::class, + // no "em" option set + ]); + + $this->em = null; + $this->registry = $this->createRegistryMock($this->em); + $this->validator = $this->createValidator(); + $this->validator->initialize($this->context); + + $dto = new HireAnEmployee('Foo'); + + $this->validator->validate($dto, $constraint); + } } diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php index 02e853016d202..5786d4c224fce 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntity.php @@ -35,6 +35,7 @@ class UniqueEntity extends Constraint public array|string $fields = []; public ?string $errorPath = null; public bool|array|string $ignoreNull = true; + public array $identifierFieldNames = []; /** * @param array|string $fields The combination of fields that must contain unique values or a set of options @@ -54,6 +55,7 @@ public function __construct( ?string $repositoryMethod = null, ?string $errorPath = null, bool|string|array|null $ignoreNull = null, + ?array $identifierFieldNames = null, ?array $groups = null, $payload = null, array $options = [], @@ -73,6 +75,7 @@ public function __construct( $this->repositoryMethod = $repositoryMethod ?? $this->repositoryMethod; $this->errorPath = $errorPath ?? $this->errorPath; $this->ignoreNull = $ignoreNull ?? $this->ignoreNull; + $this->identifierFieldNames = $identifierFieldNames ?? $this->identifierFieldNames; } public function getRequiredOptions(): array diff --git a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php index 4c3216187cb41..25e7bb66ee480 100644 --- a/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php +++ b/src/Symfony/Bridge/Doctrine/Validator/Constraints/UniqueEntityValidator.php @@ -11,8 +11,10 @@ namespace Symfony\Bridge\Doctrine\Validator\Constraints; +use Doctrine\ORM\Mapping\MappingException as ORMMappingException; use Doctrine\Persistence\ManagerRegistry; use Doctrine\Persistence\Mapping\ClassMetadata; +use Doctrine\Persistence\Mapping\MappingException as PersistenceMappingException; use Doctrine\Persistence\ObjectManager; use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\ConstraintValidator; @@ -33,12 +35,10 @@ public function __construct( } /** - * @param object $entity - * * @throws UnexpectedTypeException * @throws ConstraintDefinitionException */ - public function validate(mixed $entity, Constraint $constraint): void + public function validate(mixed $value, Constraint $constraint): void { if (!$constraint instanceof UniqueEntity) { throw new UnexpectedTypeException($constraint, UniqueEntity::class); @@ -58,14 +58,16 @@ public function validate(mixed $entity, Constraint $constraint): void throw new ConstraintDefinitionException('At least one field has to be specified.'); } - if (null === $entity) { + if (null === $value) { return; } - if (!\is_object($entity)) { - throw new UnexpectedValueException($entity, 'object'); + if (!\is_object($value)) { + throw new UnexpectedValueException($value, 'object'); } + $entityClass = $constraint->entityClass ?? $value::class; + if ($constraint->em) { $em = $this->registry->getManager($constraint->em); @@ -73,25 +75,28 @@ public function validate(mixed $entity, Constraint $constraint): void throw new ConstraintDefinitionException(sprintf('Object manager "%s" does not exist.', $constraint->em)); } } else { - $em = $this->registry->getManagerForClass($entity::class); + $em = $this->registry->getManagerForClass($entityClass); if (!$em) { - throw new ConstraintDefinitionException(sprintf('Unable to find the object manager associated with an entity of class "%s".', get_debug_type($entity))); + throw new ConstraintDefinitionException(sprintf('Unable to find the object manager associated with an entity of class "%s".', $entityClass)); } } - $class = $em->getClassMetadata($entity::class); + try { + $em->getRepository($value::class); + $isValueEntity = true; + } catch (ORMMappingException|PersistenceMappingException) { + $isValueEntity = false; + } + + $class = $em->getClassMetadata($entityClass); $criteria = []; $hasIgnorableNullValue = false; - foreach ($fields as $fieldName) { - if (!$class->hasField($fieldName) && !$class->hasAssociation($fieldName)) { - throw new ConstraintDefinitionException(sprintf('The field "%s" is not mapped by Doctrine, so it cannot be validated for uniqueness.', $fieldName)); - } - - $fieldValue = $class->reflFields[$fieldName]->getValue($entity); + $fieldValues = $this->getFieldValues($value, $class, $fields, $isValueEntity); + foreach ($fieldValues as $fieldName => $fieldValue) { if (null === $fieldValue && $this->ignoreNullForField($constraint, $fieldName)) { $hasIgnorableNullValue = true; @@ -128,11 +133,12 @@ public function validate(mixed $entity, Constraint $constraint): void $repository = $em->getRepository($constraint->entityClass); $supportedClass = $repository->getClassName(); - if (!$entity instanceof $supportedClass) { + if ($isValueEntity && !$value instanceof $supportedClass) { + $class = $em->getClassMetadata($value::class); throw new ConstraintDefinitionException(sprintf('The "%s" entity repository does not support the "%s" entity. The entity should be an instance of or extend "%s".', $constraint->entityClass, $class->getName(), $supportedClass)); } } else { - $repository = $em->getRepository($entity::class); + $repository = $em->getRepository($value::class); } $arguments = [$criteria]; @@ -173,12 +179,36 @@ public function validate(mixed $entity, Constraint $constraint): void * which is the same as the entity being validated, the criteria is * unique. */ - if (!$result || (1 === \count($result) && current($result) === $entity)) { + if (!$result || (1 === \count($result) && current($result) === $value)) { return; } - $errorPath = $constraint->errorPath ?? $fields[0]; - $invalidValue = $criteria[$errorPath] ?? $criteria[$fields[0]]; + /* If a single entity matched the query criteria, which is the same as + * the entity being updated by validated object, the criteria is unique. + */ + if (!$isValueEntity && !empty($constraint->identifierFieldNames) && 1 === \count($result)) { + $fieldValues = $this->getFieldValues($value, $class, $constraint->identifierFieldNames); + if (array_values($class->getIdentifierFieldNames()) != array_values($constraint->identifierFieldNames)) { + throw new ConstraintDefinitionException(sprintf('The "%s" entity identifier field names should be "%s", not "%s".', $entityClass, implode(', ', $class->getIdentifierFieldNames()), implode(', ', $constraint->identifierFieldNames))); + } + + $entityMatched = true; + + foreach ($constraint->identifierFieldNames as $identifierFieldName) { + $propertyValue = $this->getPropertyValue($entityClass, $identifierFieldName, current($result)); + if ($fieldValues[$identifierFieldName] !== $propertyValue) { + $entityMatched = false; + break; + } + } + + if ($entityMatched) { + return; + } + } + + $errorPath = $constraint->errorPath ?? current($fields); + $invalidValue = $criteria[$errorPath] ?? $criteria[current($fields)]; $this->context->buildViolation($constraint->message) ->atPath($errorPath) @@ -209,11 +239,11 @@ private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, } if ($class->getName() !== $idClass = $value::class) { - // non unique value might be a composite PK that consists of other entity objects + // non-unique value might be a composite PK that consists of other entity objects if ($em->getMetadataFactory()->hasMetadataFor($idClass)) { $identifiers = $em->getClassMetadata($idClass)->getIdentifierValues($value); } else { - // this case might happen if the non unique column has a custom doctrine type and its value is an object + // this case might happen if the non-unique column has a custom doctrine type and its value is an object // in which case we cannot get any identifiers for it $identifiers = []; } @@ -237,4 +267,36 @@ private function formatWithIdentifiers(ObjectManager $em, ClassMetadata $class, return sprintf('object("%s") identified by (%s)', $idClass, implode(', ', $identifiers)); } + + private function getFieldValues(mixed $object, ClassMetadata $class, array $fields, bool $isValueEntity = false): array + { + if (!$isValueEntity) { + $reflectionObject = new \ReflectionObject($object); + } + + $fieldValues = []; + $objectClass = $object::class; + + foreach ($fields as $objectFieldName => $entityFieldName) { + if (!$class->hasField($entityFieldName) && !$class->hasAssociation($entityFieldName)) { + throw new ConstraintDefinitionException(sprintf('The field "%s" is not mapped by Doctrine, so it cannot be validated for uniqueness.', $entityFieldName)); + } + + $fieldName = \is_int($objectFieldName) ? $entityFieldName : $objectFieldName; + if (!$isValueEntity && !$reflectionObject->hasProperty($fieldName)) { + throw new ConstraintDefinitionException(sprintf('The field "%s" is not a property of class "%s".', $fieldName, $objectClass)); + } + + $fieldValues[$entityFieldName] = $this->getPropertyValue($objectClass, $fieldName, $object); + } + + return $fieldValues; + } + + private function getPropertyValue(string $class, string $name, mixed $object): mixed + { + $property = new \ReflectionProperty($class, $name); + + return $property->getValue($object); + } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy