Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 47b0d79

Browse files
committed
feat: use ghost objects for auto refresh mechanism (#967)
* tests: improve test for auto refresh with lazy proxy * refactor: use lazy ghost instead of lazy proxy
1 parent b38aff5 commit 47b0d79

23 files changed

Lines changed: 418 additions & 179 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88

99
jobs:
1010
tests:
11-
name: P:${{ matrix.php }}, S:${{ matrix.symfony }}, D:${{ matrix.database }}, PU:${{ matrix.phpunit }}${{ matrix.deps == 'lowest' && ' (lowest)' || '' }}${{ matrix.use-phpunit-extension == 1 && ' (phpunit extension)' || '' }}
11+
name: P:${{ matrix.php }}, S:${{ matrix.symfony }}, D:${{ matrix.database }}, PU:${{ matrix.phpunit }}${{ matrix.deps == 'lowest' && ' (lowest)' || '' }}${{ matrix.use-phpunit-extension == 1 && ' (phpunit extension)' || '' }}${{ (matrix.use-php-84-lazy-objects != 1 || matrix.php != '8.4') && ' (legacy proxy)' || '' }}
1212
runs-on: ubuntu-latest
1313
strategy:
1414
fail-fast: false

UPGRADE-2.7.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,6 @@ You'll also need to update the PHPDoc `@extends` annotation to use the new class
9595
- `_assertPersisted()` => `\Zenstruck\Foundry\Persistence\assert_persisted()`
9696
- `_assertNotPersisted()` => `\Zenstruck\Foundry\Persistence\assert_not_persisted()`
9797

98-
> [!NOTE]
99-
> Calls to `Proxy::refresh()` also need to be replaced, but they are not covered by a Rector rule.
100-
10198
- Remove `Proxy` type for parameters in the prototype in methods and functions (covered by `ChangeProxyParamTypesRector`).
10299
- Remove `Proxy` return type in methods and functions (covered by `ChangeProxyReturnTypesRector`).
103100
- Remove all `Proxy` type hints in PHPDoc (covered by `RemovePhpDocProxyTypeHintRector`).

phpstan.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ parameters:
7272
# we're currently running static analysis with PHP 8.3
7373
- message: '#Call to an undefined method ReflectionClass\<(.*)\>::isUninitializedLazyObject\(\).#'
7474
- message: '#Call to an undefined method ReflectionClass\<(.*)\>::initializeLazyObject\(\).#'
75-
- message: '#Call to an undefined method ReflectionClass\<(.*)\>::resetAsLazyProxy\(\).#'
75+
- message: '#Call to an undefined method ReflectionClass\<(.*)\>::resetAsLazyGhost\(\).#'
7676

7777
excludePaths:
7878
- tests/Fixture/Maker/expected/can_create_factory_with_auto_activated_not_persisted_option.php

phpunit-deprecation-baseline.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
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>
66
<issue><![CDATA[Since symfony/var-exporter 7.3: Generating lazy proxy for class "Zenstruck\Foundry\Tests\Integration\ForceFactoriesTraitUsage\SomeObject" is deprecated; leverage native lazy objects instead.]]></issue>
7+
<issue><![CDATA[Since symfony/var-exporter 7.3: Using ProxyHelper::generateLazyGhost() is deprecated, use native lazy objects instead.]]></issue>
8+
<issue><![CDATA[Since symfony/var-exporter 7.3: The "Symfony\Component\VarExporter\LazyGhostTrait" trait is deprecated, use native lazy objects instead.]]></issue>
79
<issue><![CDATA[Since zenstruck/foundry 2.7: Proxy usage is deprecated in PHP 8.4. Use directly PersistentObjectFactory, Foundry now leverages the native PHP lazy system to auto-refresh objects.]]></issue>
810
<issue><![CDATA[Since zenstruck/foundry 2.7: Proxy usage is deprecated in PHP 8.4. You should extend directly PersistentObjectFactory in your factories.
911
Foundry now leverages the native PHP lazy system to auto-refresh objects (it can be enabled with "zenstruck_foundry.enable_auto_refresh_with_lazy_objects" configuration).

src/Configuration.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ public static function isBooted(): bool
130130
/** @param \Closure():self|self $configuration */
131131
public static function boot(\Closure|self $configuration): void
132132
{
133+
PersistedObjectsTracker::reset();
133134
self::$instance = $configuration;
134135
}
135136

@@ -142,6 +143,7 @@ public static function bootForDataProvider(\Closure|self $configuration): void
142143

143144
public static function shutdown(): void
144145
{
146+
PersistedObjectsTracker::reset();
145147
StoryRegistry::reset();
146148
self::$instance = null;
147149
}

src/Object/Hydrator.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,29 @@ public static function get(object $object, string $property): mixed
141141
return self::accessibleProperty($object, $property)->getValue($object);
142142
}
143143

144+
/**
145+
* @template T of object
146+
*
147+
* @param T $object
148+
* @param T $other
149+
*/
150+
public static function hydrateFromOtherObject(object $object, object $other): void
151+
{
152+
$classes = [$object::class, ...\array_values(\class_parents($object))];
153+
154+
$properties = [];
155+
foreach ($classes as $class) {
156+
$reflector = new \ReflectionClass($class);
157+
foreach ($reflector->getProperties() as $property) {
158+
$properties[$property->getName()] = $property->getName();
159+
}
160+
}
161+
162+
foreach ($properties as $property) {
163+
self::set($object, $property, self::get($other, $property), catchErrors: true);
164+
}
165+
}
166+
144167
private static function accessibleProperty(object $object, string $name): \ReflectionProperty
145168
{
146169
$class = new \ReflectionClass($object);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the zenstruck/foundry package.
7+
*
8+
* (c) Kevin Bond <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Zenstruck\Foundry\Persistence\Exception;
15+
16+
final class ObjectHasUnsavedChanges extends RefreshObjectFailed
17+
{
18+
public function __construct(string $objectClass)
19+
{
20+
parent::__construct(
21+
"Cannot auto refresh \"{$objectClass}\" as there are unsaved changes. Be sure to call ->_save() or disable auto refreshing (see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#auto-refresh for details)."
22+
);
23+
}
24+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the zenstruck/foundry package.
7+
*
8+
* (c) Kevin Bond <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Zenstruck\Foundry\Persistence\Exception;
15+
16+
final class ObjectNoLongerExist extends RefreshObjectFailed
17+
{
18+
public function __construct(public readonly object $originalObject)
19+
{
20+
parent::__construct('object no longer exists...');
21+
}
22+
}

src/Persistence/Exception/RefreshObjectFailed.php

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,6 @@
1313

1414
namespace Zenstruck\Foundry\Persistence\Exception;
1515

16-
final class RefreshObjectFailed extends \RuntimeException
16+
abstract class RefreshObjectFailed extends \RuntimeException
1717
{
18-
private function __construct(string $message, private bool $objectWasDeleted = false)
19-
{
20-
parent::__construct($message);
21-
}
22-
23-
public static function objectNoLongExists(): static
24-
{
25-
return new self('object no longer exists...', objectWasDeleted: true);
26-
}
27-
28-
/**
29-
* @param class-string $objectClass
30-
*/
31-
public static function objectHasUnsavedChanges(string $objectClass): static
32-
{
33-
return new self(
34-
"Cannot auto refresh \"{$objectClass}\" as there are unsaved changes. Be sure to call ->_save() or disable auto refreshing (see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#auto-refresh for details)."
35-
);
36-
}
37-
38-
public function objectWasDeleted(): bool
39-
{
40-
return $this->objectWasDeleted;
41-
}
4218
}

src/Persistence/IsProxy.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
use Zenstruck\Foundry\Configuration;
1717
use Zenstruck\Foundry\Exception\PersistenceNotAvailable;
1818
use Zenstruck\Foundry\Object\Hydrator;
19-
use Zenstruck\Foundry\Persistence\Exception\RefreshObjectFailed;
19+
use Zenstruck\Foundry\Persistence\Exception\ObjectNoLongerExist;
2020

2121
/**
2222
* @author Kevin Bond <[email protected]>
@@ -152,10 +152,7 @@ private function _autoRefresh(): void
152152
// we don't want that "transparent" calls to _refresh() to trigger a PersistenceNotAvailable exception
153153
// or a RefreshObjectFailed exception when the object was deleted
154154
$this->_refresh();
155-
} catch (PersistenceNotAvailable|RefreshObjectFailed $e) {
156-
if ($e instanceof RefreshObjectFailed && false === $e->objectWasDeleted()) {
157-
throw $e;
158-
}
155+
} catch (PersistenceNotAvailable|ObjectNoLongerExist) {
159156
}
160157
}
161158

0 commit comments

Comments
 (0)