diff --git a/src/Mongo/MongoPersistenceStrategy.php b/src/Mongo/MongoPersistenceStrategy.php index 591a932c7..f761c5a54 100644 --- a/src/Mongo/MongoPersistenceStrategy.php +++ b/src/Mongo/MongoPersistenceStrategy.php @@ -95,32 +95,4 @@ public function isScheduledForInsert(object $object): bool return $uow->isScheduledForInsert($object) || $uow->isScheduledForUpsert($object); } - - public function findBy(string $class, array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array - { - $qb = $this->objectManagerFor($class) - ->getRepository($class) - ->createQueryBuilder() - ->refresh(); - - foreach ($criteria as $field => $value) { - $qb->field($field)->equals($value); - } - - if ($orderBy) { - foreach ($orderBy as $field => $direction) { - $qb->sort($field, $direction); - } - } - - if ($limit) { - $qb->limit($limit); - } - - if ($offset) { - $qb->skip($offset); - } - - return $qb->getQuery()->execute()->toArray(); // @phpstan-ignore method.nonObject - } } diff --git a/src/ORM/AbstractORMPersistenceStrategy.php b/src/ORM/AbstractORMPersistenceStrategy.php index fc6199cc3..2c1b12e60 100644 --- a/src/ORM/AbstractORMPersistenceStrategy.php +++ b/src/ORM/AbstractORMPersistenceStrategy.php @@ -97,37 +97,4 @@ final public function managedNamespaces(): array return \array_values(\array_merge(...$namespaces)); } - - final public function findBy(string $class, array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array - { - $qb = $this->objectManagerFor($class)->getRepository($class)->createQueryBuilder('o'); - - foreach ($criteria as $field => $value) { - $paramName = \str_replace('.', '_', $field); - if (\is_array($value)) { - $qb->andWhere("o.{$field} IN(:{$paramName})"); - } else { - $qb->andWhere("o.{$field} = :{$paramName}"); - } - $qb->setParameter($paramName, $value); - } - - if ($orderBy) { - foreach ($orderBy as $field => $direction) { - $qb->addOrderBy('o.'.$field, $direction); - } - } - - if ($limit) { - $qb->setMaxResults($limit); - } - - if ($offset) { - $qb->setFirstResult($offset); - } - - return $qb->getQuery() - ->setHint(Query::HINT_REFRESH, true) - ->getResult(); - } } diff --git a/src/Persistence/PersistenceManager.php b/src/Persistence/PersistenceManager.php index bd2d611ff..5f35cde0d 100644 --- a/src/Persistence/PersistenceManager.php +++ b/src/Persistence/PersistenceManager.php @@ -419,23 +419,6 @@ public static function isOrmOnly(): bool })(); } - /** - * @template T of object - * - * @param class-string $class - * @param array $criteria - * @param array|null $orderBy - * @phpstan-param array|null $orderBy - * - * @return list - */ - public function findBy(string $class, array $criteria = [], ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array - { - $class = ProxyGenerator::unwrap($class); - - return $this->strategyFor($class)->findBy($class, $criteria, $orderBy, $limit, $offset); - } - private function flushAllStrategies(): void { foreach ($this->strategies as $strategy) { diff --git a/src/Persistence/PersistenceStrategy.php b/src/Persistence/PersistenceStrategy.php index ea4a54fde..6e49d9e98 100644 --- a/src/Persistence/PersistenceStrategy.php +++ b/src/Persistence/PersistenceStrategy.php @@ -92,20 +92,6 @@ public function getIdentifierValues(object $object): array */ abstract public function managedNamespaces(): array; - /** - * Uses a query builder to be able to pass hints to UoW and to force Doctrine to return fresh objects. - * - * @template T of object - * - * @param class-string $class - * @param array $criteria - * @param array|null $orderBy - * @phpstan-param array|null $orderBy - * - * @return list - */ - abstract public function findBy(string $class, array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): array; - /** * @param class-string $owner * diff --git a/src/Persistence/Proxy/PersistedObjectsTracker.php b/src/Persistence/Proxy/PersistedObjectsTracker.php index 007789b06..50a2ab06a 100644 --- a/src/Persistence/Proxy/PersistedObjectsTracker.php +++ b/src/Persistence/Proxy/PersistedObjectsTracker.php @@ -39,6 +39,8 @@ public function add(object ...$objects): void { foreach ($objects as $object) { if (self::$buffer->offsetExists($object) && self::$buffer[$object]) { + self::proxifyObject($object, self::$buffer[$object]); + continue; } @@ -78,16 +80,21 @@ private static function proxifyObjects(): void continue; } - $reflector = new \ReflectionClass($object); + self::proxifyObject($object, $id); + } + } - if ($reflector->isUninitializedLazyObject($object)) { - continue; - } + private static function proxifyObject(object $object, mixed $id): void + { + $reflector = new \ReflectionClass($object); - $clone = clone $object; - $reflector->resetAsLazyGhost($object, function($object) use ($clone, $id) { - Configuration::instance()->persistence()->autorefresh($object, $id, $clone); - }); + if ($reflector->isUninitializedLazyObject($object)) { + return; } + + $clone = clone $object; + $reflector->resetAsLazyGhost($object, function($object) use ($clone, $id) { + Configuration::instance()->persistence()->autorefresh($object, $id, $clone); + }); } } diff --git a/src/Persistence/RepositoryDecorator.php b/src/Persistence/RepositoryDecorator.php index 2042695a9..b5d2097cd 100644 --- a/src/Persistence/RepositoryDecorator.php +++ b/src/Persistence/RepositoryDecorator.php @@ -133,20 +133,7 @@ public function findAll(): array */ public function findBy(array $criteria, ?array $orderBy = null, $limit = null, $offset = null): array { - if ($this->inMemory) { - $results = $this->inner()->findBy($this->normalize($criteria), $orderBy, $limit, $offset); - } else { - try { - $results = Configuration::instance()->persistence()->findBy($this->class, $this->normalize($criteria), $orderBy, $limit, $offset); - } catch (\LogicException|\Error) { - // prevent entities/documents with readonly properties to create an error - // LogicException is for ORM / Error is for ODM - // @see https://github.com/doctrine/orm/issues/9505 - $results = $this->inner()->findBy($this->normalize($criteria), $orderBy, $limit, $offset); - } - } - - $objects = \array_values($results); + $objects = \array_values($this->inner()->findBy($this->normalize($criteria), $orderBy, $limit, $offset)); if (!$this instanceof ProxyRepositoryDecorator) { Configuration::instance()->persistedObjectsTracker?->add(...$objects); diff --git a/tests/Fixture/Entity/EdgeCases/ManyToOneWithAutoGeneratedUlid/InverseSide.php b/tests/Fixture/Entity/EdgeCases/ManyToOneWithAutoGeneratedUlid/InverseSide.php new file mode 100644 index 000000000..25619c8ea --- /dev/null +++ b/tests/Fixture/Entity/EdgeCases/ManyToOneWithAutoGeneratedUlid/InverseSide.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry\Tests\Fixture\Entity\EdgeCases\ManyToOneWithAutoGeneratedUlid; + +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; +use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator; +use Symfony\Bridge\Doctrine\Types\UlidType; +use Symfony\Component\Uid\Ulid; + +/** + * @author Nicolas PHILIPPE + */ +#[ORM\Entity] +#[ORM\Table('many_to_one_with_auto_generated_ulid_inverse')] +class InverseSide +{ + #[ORM\Id] + #[ORM\Column(type: UlidType::NAME, unique: true)] + #[ORM\GeneratedValue(strategy: 'CUSTOM')] + #[ORM\CustomIdGenerator(class: UlidGenerator::class)] + public ?Ulid $id = null; + + /** @var Collection */ + #[ORM\OneToMany(targetEntity: OwningSide::class, mappedBy: 'inverseSide', cascade: ['persist'])] + public Collection $owningSides; + + public function __construct() { + $this->owningSides = new ArrayCollection(); + } +} diff --git a/tests/Fixture/Entity/EdgeCases/ManyToOneWithAutoGeneratedUlid/OwningSide.php b/tests/Fixture/Entity/EdgeCases/ManyToOneWithAutoGeneratedUlid/OwningSide.php new file mode 100644 index 000000000..8b38482ec --- /dev/null +++ b/tests/Fixture/Entity/EdgeCases/ManyToOneWithAutoGeneratedUlid/OwningSide.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Zenstruck\Foundry\Tests\Fixture\Entity\EdgeCases\ManyToOneWithAutoGeneratedUlid; + +use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator; +use Symfony\Bridge\Doctrine\Types\UlidType; +use Symfony\Component\Uid\Ulid; + +/** + * @author Nicolas PHILIPPE + */ +#[ORM\Entity] +#[ORM\Table('many_to_one_with_auto_generated_ulid_owning')] +class OwningSide +{ + #[ORM\Id] + #[ORM\Column(type: UlidType::NAME, unique: true)] + #[ORM\GeneratedValue(strategy: 'CUSTOM')] + #[ORM\CustomIdGenerator(class: UlidGenerator::class)] + public ?Ulid $id = null; + + #[ORM\ManyToOne(cascade: ['persist'], inversedBy: 'owningSides')] + public ?InverseSide $inverseSide = null; +} diff --git a/tests/Integration/ORM/EdgeCasesRelationshipTest.php b/tests/Integration/ORM/EdgeCasesRelationshipTest.php index b4711f6cb..c7124479b 100644 --- a/tests/Integration/ORM/EdgeCasesRelationshipTest.php +++ b/tests/Integration/ORM/EdgeCasesRelationshipTest.php @@ -31,6 +31,7 @@ use Zenstruck\Foundry\Tests\Fixture\Entity\EdgeCases\InversedOneToOneWithoutNullable; use Zenstruck\Foundry\Tests\Fixture\Entity\EdgeCases\InversedOneToOneWithSetter; use Zenstruck\Foundry\Tests\Fixture\Entity\EdgeCases\ManyToOneToSelfReferencing; +use Zenstruck\Foundry\Tests\Fixture\Entity\EdgeCases\ManyToOneWithAutoGeneratedUlid; use Zenstruck\Foundry\Tests\Fixture\Entity\EdgeCases\OneToManyWithUnionType; use Zenstruck\Foundry\Tests\Fixture\Entity\EdgeCases\RichDomainMandatoryRelationship; use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\EdgeCases\MultipleMandatoryRelationshipToSameEntity; @@ -304,6 +305,20 @@ static function(InversedOneToOneWithNonNullableOwning\OwningSide $o) use ($inver self::assertSame($owningSide, $owningSide->inverseSide->getOwningSide()); } + /** + * @test + */ + #[Test] + public function it_can_find_and_object_with_ulid_as_id(): void + { + $this->expectNotToPerformAssertions(); + + $inverseSide = persistent_factory(ManyToOneWithAutoGeneratedUlid\InverseSide::class)->create(); + + persistent_factory(ManyToOneWithAutoGeneratedUlid\OwningSide::class)->many(2)->create(['inverseSide' => $inverseSide]); + persistent_factory(ManyToOneWithAutoGeneratedUlid\OwningSide::class)::random(['inverseSide' => $inverseSide]); + } + /** * @test */