Skip to content

Commit 4eea1c8

Browse files
committedMar 10, 2025
Merge branch '2.3.x' into merge-2.3.X
* 2.3.x: bot: fix cs [skip ci] fix: bug with factory collectin of persistent factory in unit test (#842) fix: use Doctrine metadata event when persist is disabled (#841) minor: add parameter "withAutoRefresh" to unproxy() function (#840) chore: upgrade also dama when upgrading phpunit version (#839) bot: fix cs [skip ci] changelog: update [skip ci] fix: can call ->create() in after persist callback (#833)
2 parents 48d9249 + 559616c commit 4eea1c8

File tree

7 files changed

+90
-23
lines changed

7 files changed

+90
-23
lines changed
 

‎CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## [v2.3.6](https://github.com/zenstruck/foundry/releases/tag/v2.3.6)
4+
5+
February 25th, 2025 - [v2.3.5...v2.3.6](https://github.com/zenstruck/foundry/compare/v2.3.5...v2.3.6)
6+
7+
* 300645b fix: can call ->create() in after persist callback (#833) by @nikophil
8+
39
## [v2.3.5](https://github.com/zenstruck/foundry/releases/tag/v2.3.5)
410

511
February 24th, 2025 - [v2.3.4...v2.3.5](https://github.com/zenstruck/foundry/compare/v2.3.4...v2.3.5)

‎phpunit

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ SHOULD_UPDATE_PHPUNIT=$(check_phpunit_version "${PHPUNIT_VERSION}")
3535

3636
if [ "${SHOULD_UPDATE_PHPUNIT}" = "0" ]; then
3737
echo "ℹ️ Upgrading PHPUnit to ${PHPUNIT_VERSION}"
38-
composer update brianium/paratest "phpunit/phpunit:^${PHPUNIT_VERSION}" -W
38+
composer update dama/doctrine-test-bundle brianium/paratest "phpunit/phpunit:^${PHPUNIT_VERSION}" -W
3939
fi
4040
### <<
4141

‎src/Persistence/PersistentObjectFactory.php

+20-17
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Doctrine\Persistence\ObjectRepository;
1515
use Symfony\Component\VarExporter\Exception\LogicException as VarExportLogicException;
1616
use Zenstruck\Foundry\Configuration;
17+
use Zenstruck\Foundry\Exception\FoundryNotBooted;
1718
use Zenstruck\Foundry\Exception\PersistenceDisabled;
1819
use Zenstruck\Foundry\Exception\PersistenceNotAvailable;
1920
use Zenstruck\Foundry\Factory;
@@ -304,16 +305,17 @@ protected function normalizeParameter(string $field, mixed $value): mixed
304305
if ($inversedRelationshipMetadata && !$inversedRelationshipMetadata->isCollection) {
305306
$inverseField = $inversedRelationshipMetadata->inverseField;
306307

307-
// we need to handle the circular dependency involved by inversed one-to-one relationship:
308-
// a placeholder object is used, which will be replaced by the real object, after its instantiation
309-
$inversedObject = $value->withPersistMode(PersistMode::NO_PERSIST_BUT_SCHEDULE_FOR_INSERT)
310-
->create([$inverseField => $placeholder = (new \ReflectionClass(static::class()))->newInstanceWithoutConstructor()]);
308+
$inversedObject = $value->withPersistMode(
309+
$this->isPersisting() ? PersistMode::NO_PERSIST_BUT_SCHEDULE_FOR_INSERT : PersistMode::WITHOUT_PERSISTING
310+
)
311311

312-
// auto-refresh computes changeset and prevents the placeholder object to be cleanly
313-
// forgotten fom the persistence manager
314-
if ($inversedObject instanceof Proxy) {
315-
$inversedObject = $inversedObject->_real(withAutoRefresh: false);
316-
}
312+
// we need to handle the circular dependency involved by inversed one-to-one relationship:
313+
// a placeholder object is used, which will be replaced by the real object, after its instantiation
314+
->create([
315+
$inverseField => $placeholder = (new \ReflectionClass(static::class()))->newInstanceWithoutConstructor(),
316+
]);
317+
318+
$inversedObject = unproxy($inversedObject, withAutoRefresh: false);
317319

318320
$this->tempAfterInstantiate[] = static function(object $object) use ($inversedObject, $inverseField, $pm, $placeholder) {
319321
$pm->forget($placeholder);
@@ -324,12 +326,12 @@ protected function normalizeParameter(string $field, mixed $value): mixed
324326
}
325327
}
326328

327-
return unproxy(parent::normalizeParameter($field, $value));
329+
return unproxy(parent::normalizeParameter($field, $value), withAutoRefresh: false);
328330
}
329331

330332
protected function normalizeCollection(string $field, FactoryCollection $collection): array
331333
{
332-
if (!$this->isPersisting() || !$collection->factory instanceof self) {
334+
if (!Configuration::instance()->isPersistenceAvailable() || !$collection->factory instanceof self) {
333335
return parent::normalizeCollection($field, $collection);
334336
}
335337

@@ -338,12 +340,15 @@ protected function normalizeCollection(string $field, FactoryCollection $collect
338340
$inverseRelationshipMetadata = $pm->inverseRelationshipMetadata(static::class(), $collection->factory::class(), $field);
339341

340342
if ($inverseRelationshipMetadata && $inverseRelationshipMetadata->isCollection) {
341-
$this->tempAfterInstantiate[] = static function(object $object) use ($collection, $inverseRelationshipMetadata, $field) {
343+
$this->tempAfterInstantiate[] = function(object $object) use ($collection, $inverseRelationshipMetadata, $field) {
342344
$inverseField = $inverseRelationshipMetadata->inverseField;
343345

344-
$inverseObjects = $collection->withPersistMode(PersistMode::NO_PERSIST_BUT_SCHEDULE_FOR_INSERT)->create([$inverseField => $object]);
346+
$inverseObjects = $collection->withPersistMode(
347+
$this->isPersisting() ? PersistMode::NO_PERSIST_BUT_SCHEDULE_FOR_INSERT : PersistMode::WITHOUT_PERSISTING
348+
)
349+
->create([$inverseField => $object]);
345350

346-
$inverseObjects = unproxy($inverseObjects);
351+
$inverseObjects = unproxy($inverseObjects, withAutoRefresh: false);
347352

348353
// if the collection is indexed by a field, index the array
349354
if ($inverseRelationshipMetadata->collectionIndexedBy) {
@@ -379,9 +384,7 @@ protected function normalizeObject(object $object): object
379384
return $object;
380385
}
381386

382-
if ($object instanceof Proxy) {
383-
$object = $object->_real(withAutoRefresh: false);
384-
}
387+
$object = unproxy($object, withAutoRefresh: false);
385388

386389
$persistenceManager = $configuration->persistence();
387390
if (!$persistenceManager->hasPersistenceFor($object)) {

‎src/Persistence/ProxyGenerator.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,18 @@ public static function wrapFactory(PersistentProxyObjectFactory $factory, callab
6666
*
6767
* @return T
6868
*/
69-
public static function unwrap(mixed $what): mixed
69+
public static function unwrap(mixed $what, bool $withAutoRefresh = true): mixed
7070
{
7171
if (\is_array($what)) {
72-
return \array_map(self::unwrap(...), $what); // @phpstan-ignore return.type
72+
return \array_map(static fn(mixed $w) => self::unwrap($w, $withAutoRefresh), $what); // @phpstan-ignore return.type
7373
}
7474

7575
if (\is_string($what) && \is_a($what, Proxy::class, true)) {
7676
return \get_parent_class($what) ?: throw new \LogicException('Could not unwrap proxy.'); // @phpstan-ignore return.type
7777
}
7878

7979
if ($what instanceof Proxy) {
80-
return $what->_real(); // @phpstan-ignore return.type
80+
return $what->_real($withAutoRefresh); // @phpstan-ignore return.type
8181
}
8282

8383
return $what;

‎src/Persistence/functions.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,9 @@ function proxy(object $object): object
107107
*
108108
* @return T
109109
*/
110-
function unproxy(mixed $what): mixed
110+
function unproxy(mixed $what, bool $withAutoRefresh = true): mixed
111111
{
112-
return ProxyGenerator::unwrap($what);
112+
return ProxyGenerator::unwrap($what, $withAutoRefresh);
113113
}
114114

115115
/**

‎tests/Integration/ORM/EntityRelationship/EntityFactoryRelationshipTestCase.php

+31
Original file line numberDiff line numberDiff line change
@@ -323,11 +323,21 @@ public function disabling_persistence_cascades_to_children(): void
323323
foreach ($contact->getTags() as $tag) {
324324
$this->assertNull($tag->id);
325325
}
326+
}
326327

328+
/** @test */
329+
#[Test]
330+
#[DataProvider('provideCascadeRelationshipsCombinations')]
331+
#[UsingRelationships(Contact::class, ['category'])]
332+
public function disabling_persistence_cascades_to_children_one_to_many(): void
333+
{
327334
$category = static::categoryFactory()->withoutPersisting()->create([
328335
'contacts' => static::contactFactory()->many(3),
329336
]);
330337

338+
// ensure nothing was persisted in Doctrine by flushing
339+
self::getContainer()->get(EntityManagerInterface::class)->flush(); // @phpstan-ignore method.notFound
340+
331341
static::contactFactory()::assert()->empty();
332342
static::categoryFactory()::assert()->empty();
333343

@@ -339,6 +349,27 @@ public function disabling_persistence_cascades_to_children(): void
339349
}
340350
}
341351

352+
/** @test */
353+
#[Test]
354+
#[DataProvider('provideCascadeRelationshipsCombinations')]
355+
#[UsingRelationships(Contact::class, ['address'])]
356+
public function disabling_persistence_cascades_to_children_inversed_one_to_one(): void
357+
{
358+
$address = static::addressFactory()->withoutPersisting()->create([
359+
'contact' => static::contactFactory(),
360+
]);
361+
362+
// ensure nothing was persisted in Doctrine by flushing
363+
self::getContainer()->get(EntityManagerInterface::class)->flush(); // @phpstan-ignore method.notFound
364+
365+
static::contactFactory()::assert()->empty();
366+
static::addressFactory()::assert()->empty();
367+
368+
$this->assertNull($address->id);
369+
$this->assertInstanceOf(Contact::class, $address->getContact());
370+
$this->assertNull($address->getContact()->id);
371+
}
372+
342373
/** @test */
343374
#[Test]
344375
#[DataProvider('provideCascadeRelationshipsCombinations')]

‎tests/Unit/Persistence/PersistentObjectFactoryTest.php

+27
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use PHPUnit\Framework\Attributes\Test;
1515
use PHPUnit\Framework\TestCase;
16+
use Zenstruck\Foundry\FactoryCollection;
1617
use Zenstruck\Foundry\Test\Factories;
1718
use Zenstruck\Foundry\Tests\Fixture\Entity\GenericEntity;
1819
use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory;
@@ -62,4 +63,30 @@ public function random_or_create(): void
6263

6364
$this->assertSame('foo', $entity->getProp1());
6465
}
66+
67+
/**
68+
* @test
69+
* @dataProvider factoryCollectionDataProvider
70+
* @param FactoryCollection<GenericEntity, GenericEntityFactory> $collection
71+
*/
72+
public function can_use_factory_collection_methods_in_data_providers(FactoryCollection $collection): void // @phpstan-ignore generics.notSubtype
73+
{
74+
self::assertEquals(
75+
[
76+
new GenericEntity('foo'),
77+
],
78+
$collection->create(),
79+
);
80+
}
81+
82+
public static function factoryCollectionDataProvider(): iterable
83+
{
84+
yield [
85+
GenericEntityFactory::new()->sequence([
86+
[
87+
'prop1' => 'foo',
88+
],
89+
]),
90+
];
91+
}
6592
}

0 commit comments

Comments
 (0)