Skip to content

Commit 57b3f02

Browse files
committed
fix: review
1 parent 76df287 commit 57b3f02

25 files changed

+533
-154
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ jobs:
329329
- name: Setup PHP
330330
uses: shivammathur/setup-php@v2
331331
with:
332-
php-version: 8.4
332+
php-version: 8.3
333333
coverage: none
334334

335335
- name: Install dependencies

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@
3737
"doctrine/orm": "^2.16|^3.0",
3838
"doctrine/persistence": "^2.0|^3.0|^4.0",
3939
"phpunit/phpunit": "^9.5.0 || ^10.0 || ^11.0 || ^12.0",
40-
"symfony/browser-kit": "^7.2",
40+
"symfony/browser-kit": "^6.4|^7.0",
4141
"symfony/console": "^6.4|^7.0",
4242
"symfony/dotenv": "^6.4|^7.0",
4343
"symfony/framework-bundle": "^6.4|^7.0",
4444
"symfony/maker-bundle": "^1.55",
4545
"symfony/phpunit-bridge": "^6.4|^7.0",
46-
"symfony/routing": "^7.2",
46+
"symfony/routing": "^6.4|^7.0",
4747
"symfony/runtime": "^6.4|^7.0",
4848
"symfony/translation-contracts": "^3.4",
4949
"symfony/uid": "^6.4|^7.0",

config/persistence.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
44

5+
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
56
use Symfony\Component\HttpKernel\Event\TerminateEvent;
7+
use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent;
68
use Zenstruck\Foundry\Persistence\PersistenceManager;
7-
use Zenstruck\Foundry\Persistence\Proxy\KernelTerminateListener;
9+
use Zenstruck\Foundry\Persistence\Proxy\ObjectsTracker;
810
use Zenstruck\Foundry\Persistence\ResetDatabase\ResetDatabaseManager;
911

1012
return static function (ContainerConfigurator $container): void {
@@ -22,8 +24,11 @@
2224
;
2325

2426
if (PHP_VERSION_ID >= 80400) {
25-
$container->services()->set('.foundry.proxy.kernel_terminate_listener', KernelTerminateListener::class)
26-
->tag('kernel.event_listener', ['event' => TerminateEvent::class, 'method' => '__invoke'])
27+
$container->services()->set('.foundry.persistence.objects_tracker', ObjectsTracker::class)
28+
->tag('kernel.reset', ['method' => 'refresh'])
29+
->tag('kernel.event_listener', ['event' => TerminateEvent::class, 'method' => 'refresh'])
30+
->tag('kernel.event_listener', ['event' => ConsoleTerminateEvent::class, 'method' => 'refresh'])
31+
->tag('kernel.event_listener', ['event' => WorkerMessageHandledEvent::class, 'method' => 'refresh']) // @phpstan-ignore class.notFound
2732
;
2833
}
2934
};

config/services.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
param('zenstruck_foundry.persistence.flush_once'),
3636
'%env(default:zenstruck_foundry.faker.seed:int:FOUNDRY_FAKER_SEED)%',
3737
service('.zenstruck_foundry.in_memory.repository_registry'),
38+
service('.foundry.persistence.objects_tracker')->nullOnInvalid(),
3839
])
3940
->public()
4041
;

phpstan.neon

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ parameters:
6969
- identifier: missingType.callable
7070
path: tests/Fixture/Maker/expected/
7171

72+
# we're currently running static analysis with PHP 8.3
73+
- message: '#Call to an undefined method ReflectionClass\<(.*)\>::isUninitializedLazyObject\(\).#'
74+
- message: '#Call to an undefined method ReflectionClass\<(.*)\>::resetAsLazyProxy\(\).#'
75+
- message: '#Call to an undefined method ReflectionClass\<(.*)\>::initializeLazyObject\(\).#'
76+
7277
excludePaths:
7378
- tests/Fixture/Maker/expected/can_create_factory_with_auto_activated_not_persisted_option.php
7479
- tests/Fixture/Maker/expected/can_create_factory_interactively.php

phpunit

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ set -o errexit
44
set -o nounset
55

66
check_phpunit_version() {
7-
INSTALLED_PHPUNIT_VERSION=$(composer info phpunit/phpunit | grep versions | cut -c 14-)
7+
COMPOSER_BIN=$(which composer)
8+
INSTALLED_PHPUNIT_VERSION=$(php -d xdebug.mode=off ${COMPOSER_BIN} info phpunit/phpunit | grep versions | cut -c 14-)
89

910
REQUIRED_PHPUNIT_VERSION="${1?}"
1011

phpunit-deprecation-baseline.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<file path="vendor/symfony/deprecation-contracts/function.php">
44
<line number="25" hash="c6af5d66288d0667e424978000f29571e4954b81">
55
<issue><![CDATA[Since symfony/framework-bundle 6.4: Not setting the "framework.php_errors.log" config option is deprecated. It will default to "true" in 7.0.]]></issue>
6+
<issue><![CDATA[Since zenstruck/foundry 2.6: Proxy usage is deprecated in PHP 8.4. Use directly PersistentObjectFactory, Foundry now leverages the native PHP lazy system to auto-refresh objects.]]></issue>
67
</line>
78
</file>
89
</files>

src/Configuration.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Zenstruck\Foundry\InMemory\CannotEnableInMemory;
2020
use Zenstruck\Foundry\InMemory\InMemoryRepositoryRegistry;
2121
use Zenstruck\Foundry\Persistence\PersistenceManager;
22+
use Zenstruck\Foundry\Persistence\Proxy\ObjectsTracker;
2223

2324
/**
2425
* @author Kevin Bond <[email protected]>
@@ -60,6 +61,7 @@ public function __construct(
6061
public readonly bool $flushOnce = false,
6162
?int $forcedFakerSeed = null,
6263
public readonly ?InMemoryRepositoryRegistry $inMemoryRepositoryRegistry = null,
64+
public readonly ?ObjectsTracker $objectsTracker = null,
6365
) {
6466
$this->faker->seed(self::fakerSeed($forcedFakerSeed));
6567

src/Persistence/PersistenceManager.php

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
use Zenstruck\Foundry\Persistence\Relationship\RelationshipMetadata;
2323
use Zenstruck\Foundry\Persistence\ResetDatabase\ResetDatabaseManager;
2424

25+
use function Zenstruck\Foundry\set;
26+
2527
/**
2628
* @author Kevin Bond <[email protected]>
2729
*
@@ -147,7 +149,7 @@ public function flush(ObjectManager $om): void
147149
*
148150
* @return T
149151
*/
150-
public function refresh(object &$object, bool $force = false): object
152+
public function refresh(object &$object, bool $force = false, bool $allowRefreshDeletedObject = false): object
151153
{
152154
if (!$this->flush && !$force) {
153155
return $object;
@@ -157,6 +159,11 @@ public function refresh(object &$object, bool $force = false): object
157159
return $object->_refresh();
158160
}
159161

162+
if (\PHP_VERSION_ID >= 80400 && ($reflector = new \ReflectionClass($object))->isUninitializedLazyObject($object)) {
163+
/** @var T $object */
164+
$object = $reflector->initializeLazyObject($object);
165+
}
166+
160167
$strategy = $this->strategyFor($object::class);
161168

162169
if ($strategy->hasChanges($object)) {
@@ -165,26 +172,45 @@ public function refresh(object &$object, bool $force = false): object
165172

166173
$om = $strategy->objectManagerFor($object::class);
167174

168-
if ($strategy->contains($object)) {
169-
try {
170-
$om->refresh($object);
171-
} catch (\LogicException|\Error) {
172-
// prevent entities/documents with readonly properties to create an error
173-
// LogicException is for ORM / Error is for ODM
174-
// @see https://github.com/doctrine/orm/issues/9505
175-
}
176-
177-
return $object;
178-
}
179-
180175
if ($strategy->isEmbeddable($object)) {
181176
return $object;
182177
}
183178

184-
$id = $om->getClassMetadata($object::class)->getIdentifierValues($object);
179+
if (!$strategy->contains($object)) {
180+
$objectFromDb = null;
181+
182+
$id = $om->getClassMetadata($object::class)->getIdentifierValues($object);
183+
184+
if ($id) {
185+
// "merge" object if it is not managed
186+
$objectFromDb = $om->find($object::class, $id);
187+
}
185188

186-
if (!$id || !($object = $om->find($object::class, $id))) { // @phpstan-ignore parameterByRef.type
187-
throw RefreshObjectFailed::objectNoLongExists();
189+
if ($objectFromDb) {
190+
$object = $objectFromDb;
191+
} else {
192+
if ($allowRefreshDeletedObject) {
193+
$identifierFields = $om->getClassMetadata($object::class)->getIdentifierFieldNames();
194+
foreach ($identifierFields as $field) {
195+
try {
196+
set($object, $field, null);
197+
} catch (\Throwable) {
198+
}
199+
}
200+
201+
return $object;
202+
}
203+
204+
throw RefreshObjectFailed::objectNoLongExists();
205+
}
206+
}
207+
208+
try {
209+
$om->refresh($object);
210+
} catch (\LogicException|\Error) {
211+
// prevent entities/documents with readonly properties to create an error
212+
// LogicException is for ORM / Error is for ODM
213+
// @see https://github.com/doctrine/orm/issues/9505
188214
}
189215

190216
return $object;

src/Persistence/PersistentObjectFactory.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,11 @@
2323
use Zenstruck\Foundry\ObjectFactory;
2424
use Zenstruck\Foundry\Persistence\Exception\NotEnoughObjects;
2525
use Zenstruck\Foundry\Persistence\Exception\RefreshObjectFailed;
26+
use Zenstruck\Foundry\Persistence\Proxy\ObjectsTracker;
2627
use Zenstruck\Foundry\Persistence\Relationship\ManyToOneRelationship;
2728
use Zenstruck\Foundry\Persistence\Relationship\OneToManyRelationship;
2829
use Zenstruck\Foundry\Persistence\Relationship\OneToOneRelationship;
2930

30-
use Zenstruck\Foundry\Persistence\Proxy\CreatedObjectsTracker;
31-
3231
use function Zenstruck\Foundry\force;
3332
use function Zenstruck\Foundry\get;
3433
use function Zenstruck\Foundry\set;
@@ -456,14 +455,14 @@ final protected function initializeInternal(): static
456455
return parent::initializeInternal()
457456
->afterInstantiate(
458457
static function(object $object, array $parameters, PersistentObjectFactory $factoryUsed): void {
459-
if (PHP_VERSION_ID >= 80400) {
460-
CreatedObjectsTracker::add($object);
461-
}
462-
463458
if (!$factoryUsed->isPersisting()) {
464459
return;
465460
}
466461

462+
if (\PHP_VERSION_ID >= 80400 && !$factoryUsed instanceof PersistentProxyObjectFactory) {
463+
ObjectsTracker::add($object);
464+
}
465+
467466
$afterPersistCallbacks = [];
468467

469468
foreach ($factoryUsed->afterPersist as $afterPersist) {

src/Persistence/PersistentProxyObjectFactory.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@
2222
*/
2323
abstract class PersistentProxyObjectFactory extends PersistentObjectFactory
2424
{
25+
public function __construct()
26+
{
27+
parent::__construct();
28+
29+
if (\PHP_VERSION_ID >= 80400) {
30+
trigger_deprecation(
31+
'zenstruck/foundry',
32+
'2.6',
33+
'Proxy usage is deprecated in PHP 8.4. Use directly PersistentObjectFactory, Foundry now leverages the native PHP lazy system to auto-refresh objects.',
34+
);
35+
}
36+
}
37+
2538
/**
2639
* @return class-string<T>
2740
*/

src/Persistence/Proxy/CreatedObjectsTracker.php

Lines changed: 0 additions & 51 deletions
This file was deleted.

src/Persistence/Proxy/KernelTerminateListener.php

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the zenstruck/foundry package.
5+
*
6+
* (c) Kevin Bond <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Zenstruck\Foundry\Persistence\Proxy;
13+
14+
use Zenstruck\Foundry\Configuration;
15+
16+
/**
17+
* @internal
18+
*/
19+
final class ObjectsTracker
20+
{
21+
/**
22+
* This buffer of objects needs to be static to be kept between two kernel.reset events.
23+
*
24+
* @var list<\WeakReference<object>>
25+
*/
26+
private static $buffer = [];
27+
28+
public function refresh(): void
29+
{
30+
self::proxifyObjects();
31+
}
32+
33+
public static function add(object $object): void
34+
{
35+
self::$buffer[] = \WeakReference::create($object);
36+
}
37+
38+
public function reset(): void
39+
{
40+
self::$buffer = [];
41+
}
42+
43+
public static function countObjects(): int
44+
{
45+
return \count(
46+
\array_filter(self::$buffer, static fn(\WeakReference $weakRef) => null !== $weakRef->get())
47+
);
48+
}
49+
50+
private static function proxifyObjects(): void
51+
{
52+
self::$buffer = \array_values(
53+
\array_map(
54+
static function(\WeakReference $weakRef) {
55+
$object = $weakRef->get() ?? throw new \LogicException('Object cannot be null.');
56+
57+
$reflector = new \ReflectionClass($object);
58+
59+
if ($reflector->isUninitializedLazyObject($object)) {
60+
return \WeakReference::create($object);
61+
}
62+
63+
$clone = clone $object;
64+
$reflector->resetAsLazyProxy($object, function() use ($clone, $reflector) {
65+
Configuration::instance()->persistence()->refresh($clone, allowRefreshDeletedObject: true);
66+
67+
return $reflector->initializeLazyObject($clone);
68+
});
69+
70+
return \WeakReference::create($object);
71+
},
72+
73+
// remove all empty references
74+
\array_filter(self::$buffer, static fn(\WeakReference $weakRef) => null !== $weakRef->get()),
75+
)
76+
);
77+
}
78+
}

0 commit comments

Comments
 (0)