Skip to content

Commit be382e1

Browse files
committed
feat: use native proxies for object creation in data providers
1 parent cdbacdd commit be382e1

7 files changed

+140
-3
lines changed

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1163,7 +1163,7 @@ once. To do this, wrap the operations in a ``flush_after()`` callback:
11631163
TagFactory::createMany(200); // instantiated/persisted but not flushed
11641164
}); // single flush
11651165

1166-
The ``flush_after()`` function forwards the callbacks return, in case you need to use the objects in your tests:
1166+
The ``flush_after()`` function forwards the callback's return, in case you need to use the objects in your tests:
11671167

11681168
::
11691169

phpstan.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ 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\<(.*)\>::initializeLazyObject\(\).#'
75+
7276
excludePaths:
7377
- tests/Fixture/Maker/expected/can_create_factory_with_auto_activated_not_persisted_option.php
7478
- tests/Fixture/Maker/expected/can_create_factory_interactively.php

src/Persistence/PersistentObjectFactory.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,12 @@ final public static function truncate(): void
203203
*/
204204
public function create(callable|array $attributes = []): object
205205
{
206+
$configuration = Configuration::instance();
207+
208+
if ($configuration->inADataProvider() && \PHP_VERSION_ID >= 80400 && !$this instanceof PersistentProxyObjectFactory) {
209+
return ProxyGenerator::wrapFactoryNativeProxy($this, $attributes);
210+
}
211+
206212
$object = parent::create($attributes);
207213

208214
foreach ($this->tempAfterInstantiate as $callback) {
@@ -217,8 +223,6 @@ public function create(callable|array $attributes = []): object
217223
return $object;
218224
}
219225

220-
$configuration = Configuration::instance();
221-
222226
if ($configuration->flushOnce && !$this->isRootFactory) {
223227
return $object;
224228
}

src/Persistence/ProxyGenerator.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,25 @@ public static function wrapFactory(PersistentProxyObjectFactory $factory, callab
5959
return self::generateClassFor($factory)::createLazyProxy(static fn() => unproxy($factory->create($attributes))); // @phpstan-ignore-line
6060
}
6161

62+
/**
63+
* @template T of object
64+
*
65+
* @param PersistentObjectFactory<T> $factory
66+
* @phpstan-param Attributes $attributes
67+
*
68+
* @return T
69+
*/
70+
public static function wrapFactoryNativeProxy(PersistentObjectFactory $factory, callable|array $attributes): object
71+
{
72+
if (\PHP_VERSION_ID < 80400) {
73+
throw new \LogicException('Native proxy generation requires PHP 8.4 or higher.');
74+
}
75+
76+
$reflector = new \ReflectionClass($factory::class());
77+
78+
return $reflector->newLazyProxy(static fn() => $factory->create($attributes)); // @phpstan-ignore-line
79+
}
80+
6281
/**
6382
* @template T
6483
*
@@ -80,6 +99,10 @@ public static function unwrap(mixed $what, bool $withAutoRefresh = true): mixed
8099
return $what->_real($withAutoRefresh); // @phpstan-ignore return.type
81100
}
82101

102+
if (\PHP_VERSION_ID >= 80400 && is_object($what) && ($reflector = new \ReflectionClass($what))->isUninitializedLazyObject($what)) {
103+
return $reflector->initializeLazyObject($what);
104+
}
105+
83106
return $what;
84107
}
85108

src/Persistence/functions.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,12 @@ function enable_persisting(): void
181181
*/
182182
function initialize_proxy_object(mixed $what): void
183183
{
184+
if (\PHP_VERSION_ID >= 80400 && is_object($what) && ($reflector = new \ReflectionClass($what))->isUninitializedLazyObject($what)) {
185+
$reflector->initializeLazyObject($what);
186+
187+
return;
188+
}
189+
184190
match (true) {
185191
$what instanceof Proxy => $what->_initializeLazyObject(),
186192
\is_array($what) => \array_map(initialize_proxy_object(...), $what),

tests/Integration/DataProvider/DataProviderWithNonProxyFactoryInKernelTestCaseTest.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace Zenstruck\Foundry\Tests\Integration\DataProvider;
1515

1616
use PHPUnit\Framework\Attributes\DataProvider;
17+
use PHPUnit\Framework\Attributes\RequiresPhp;
1718
use PHPUnit\Framework\Attributes\RequiresPhpunit;
1819
use PHPUnit\Framework\Attributes\RequiresPhpunitExtension;
1920
use PHPUnit\Framework\Attributes\Test;
@@ -28,6 +29,7 @@
2829
* @requires PHPUnit >=11.4
2930
*/
3031
#[RequiresPhpunit('>=11.4')]
32+
#[RequiresPhp('<8.4')]
3133
#[RequiresPhpunitExtension(FoundryExtension::class)]
3234
final class DataProviderWithNonProxyFactoryInKernelTestCaseTest extends KernelTestCase
3335
{
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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\Tests\Integration\DataProvider;
15+
16+
use PHPUnit\Framework\Attributes\DataProvider;
17+
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
18+
use PHPUnit\Framework\Attributes\RequiresPhp;
19+
use PHPUnit\Framework\Attributes\RequiresPhpunit;
20+
use PHPUnit\Framework\Attributes\RequiresPhpunitExtension;
21+
use PHPUnit\Framework\Attributes\Test;
22+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
23+
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
24+
use Zenstruck\Foundry\Persistence\Proxy;
25+
use Zenstruck\Foundry\PHPUnit\FoundryExtension;
26+
use Zenstruck\Foundry\Test\Factories;
27+
use Zenstruck\Foundry\Test\ResetDatabase;
28+
use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory;
29+
use Zenstruck\Foundry\Tests\Fixture\Model\GenericModel;
30+
31+
use function Zenstruck\Foundry\Persistence\unproxy;
32+
33+
/**
34+
* @author Nicolas PHILIPPE <[email protected]>
35+
* @requires PHPUnit >=11.4
36+
*/
37+
#[RequiresPhpunit('>=11.4')]
38+
#[RequiresPhp('>=8.4')]
39+
#[RequiresPhpunitExtension(FoundryExtension::class)]
40+
final class DataProviderWithPersistentFactoryAndPHP84InKernelTest extends KernelTestCase
41+
{
42+
use Factories;
43+
use ResetDatabase;
44+
45+
#[Test]
46+
#[DataProvider('createOneObjectInDataProvider')]
47+
public function assert_it_can_create_one_object_in_data_provider(?GenericModel $providedData): void
48+
{
49+
GenericEntityFactory::assert()->count(1);
50+
51+
self::assertNotNull($providedData);
52+
self::assertFalse((new \ReflectionClass($providedData))->isUninitializedLazyObject($providedData));
53+
self::assertSame('value set in data provider', $providedData->getProp1());
54+
}
55+
56+
public static function createOneObjectInDataProvider(): iterable
57+
{
58+
yield 'createOne()' => [
59+
GenericEntityFactory::createOne(['prop1' => 'value set in data provider']),
60+
];
61+
62+
yield 'create()' => [
63+
GenericEntityFactory::new()->create(['prop1' => 'value set in data provider']),
64+
];
65+
}
66+
67+
#[Test]
68+
#[DataProvider('createMultipleObjectsInDataProvider')]
69+
public function assert_it_can_create_multiple_objects_in_data_provider(?array $providedData): void
70+
{
71+
self::assertIsArray($providedData);
72+
GenericEntityFactory::assert()->count(2);
73+
74+
foreach ($providedData as $providedDatum) {
75+
self::assertFalse((new \ReflectionClass($providedDatum))->isUninitializedLazyObject($providedDatum));
76+
}
77+
78+
self::assertSame('prop 1', $providedData[0]->getProp1());
79+
self::assertSame('prop 2', $providedData[1]->getProp1());
80+
}
81+
82+
public static function createMultipleObjectsInDataProvider(): iterable
83+
{
84+
yield 'createSequence()' => [
85+
GenericEntityFactory::createSequence([
86+
['prop1' => 'prop 1'],
87+
['prop1' => 'prop 2'],
88+
]),
89+
];
90+
91+
yield 'FactoryCollection::create()' => [
92+
GenericEntityFactory::new()->sequence([
93+
['prop1' => 'prop 1'],
94+
['prop1' => 'prop 2'],
95+
])->create(),
96+
];
97+
}
98+
}

0 commit comments

Comments
 (0)