Skip to content

feat: use PHP 8.4 native proxy system for create() calls in data providers #910

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: 2.6.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1163,7 +1163,7 @@ once. To do this, wrap the operations in a ``flush_after()`` callback:
TagFactory::createMany(200); // instantiated/persisted but not flushed
}); // single flush

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

::

Expand Down
4 changes: 4 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ parameters:
- identifier: missingType.callable
path: tests/Fixture/Maker/expected/

# we're currently running static analysis with PHP 8.3
- message: '#Call to an undefined method ReflectionClass\<(.*)\>::isUninitializedLazyObject\(\).#'
- message: '#Call to an undefined method ReflectionClass\<(.*)\>::initializeLazyObject\(\).#'

excludePaths:
- tests/Fixture/Maker/expected/can_create_factory_with_auto_activated_not_persisted_option.php
- tests/Fixture/Maker/expected/can_create_factory_interactively.php
Expand Down
8 changes: 6 additions & 2 deletions src/Persistence/PersistentObjectFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ final public static function truncate(): void
*/
public function create(callable|array $attributes = []): object
{
$configuration = Configuration::instance();

if ($configuration->inADataProvider() && \PHP_VERSION_ID >= 80400 && !$this instanceof PersistentProxyObjectFactory) {
return ProxyGenerator::wrapFactoryNativeProxy($this, $attributes);
}

$object = parent::create($attributes);

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

$configuration = Configuration::instance();

if ($configuration->flushOnce && !$this->isRootFactory) {
return $object;
}
Expand Down
23 changes: 23 additions & 0 deletions src/Persistence/ProxyGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,25 @@ public static function wrapFactory(PersistentProxyObjectFactory $factory, callab
return self::generateClassFor($factory)::createLazyProxy(static fn() => unproxy($factory->create($attributes))); // @phpstan-ignore-line
}

/**
* @template T of object
*
* @param PersistentObjectFactory<T> $factory
* @phpstan-param Attributes $attributes
*
* @return T
*/
public static function wrapFactoryNativeProxy(PersistentObjectFactory $factory, callable|array $attributes): object
{
if (\PHP_VERSION_ID < 80400) {
throw new \LogicException('Native proxy generation requires PHP 8.4 or higher.');
}

$reflector = new \ReflectionClass($factory::class());

return $reflector->newLazyProxy(static fn() => $factory->create($attributes)); // @phpstan-ignore-line
}

/**
* @template T
*
Expand All @@ -80,6 +99,10 @@ public static function unwrap(mixed $what, bool $withAutoRefresh = true): mixed
return $what->_real($withAutoRefresh); // @phpstan-ignore return.type
}

if (\PHP_VERSION_ID >= 80400 && is_object($what) && ($reflector = new \ReflectionClass($what))->isUninitializedLazyObject($what)) {
return $reflector->initializeLazyObject($what);
}

return $what;
}

Expand Down
6 changes: 6 additions & 0 deletions src/Persistence/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@ function enable_persisting(): void
*/
function initialize_proxy_object(mixed $what): void
{
if (\PHP_VERSION_ID >= 80400 && is_object($what) && ($reflector = new \ReflectionClass($what))->isUninitializedLazyObject($what)) {
$reflector->initializeLazyObject($what);

return;
}

match (true) {
$what instanceof Proxy => $what->_initializeLazyObject(),
\is_array($what) => \array_map(initialize_proxy_object(...), $what),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace Zenstruck\Foundry\Tests\Integration\DataProvider;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\RequiresPhp;
use PHPUnit\Framework\Attributes\RequiresPhpunit;
use PHPUnit\Framework\Attributes\RequiresPhpunitExtension;
use PHPUnit\Framework\Attributes\Test;
Expand All @@ -28,6 +29,7 @@
* @requires PHPUnit >=11.4
*/
#[RequiresPhpunit('>=11.4')]
#[RequiresPhp('<8.4')]
#[RequiresPhpunitExtension(FoundryExtension::class)]
final class DataProviderWithNonProxyFactoryInKernelTestCaseTest extends KernelTestCase
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

/*
* This file is part of the zenstruck/foundry package.
*
* (c) Kevin Bond <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Zenstruck\Foundry\Tests\Integration\DataProvider;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
use PHPUnit\Framework\Attributes\RequiresPhp;
use PHPUnit\Framework\Attributes\RequiresPhpunit;
use PHPUnit\Framework\Attributes\RequiresPhpunitExtension;
use PHPUnit\Framework\Attributes\Test;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
use Zenstruck\Foundry\Persistence\Proxy;
use Zenstruck\Foundry\PHPUnit\FoundryExtension;
use Zenstruck\Foundry\Test\Factories;
use Zenstruck\Foundry\Test\ResetDatabase;
use Zenstruck\Foundry\Tests\Fixture\Factories\Entity\GenericEntityFactory;
use Zenstruck\Foundry\Tests\Fixture\Model\GenericModel;

use function Zenstruck\Foundry\Persistence\unproxy;

/**
* @author Nicolas PHILIPPE <[email protected]>
* @requires PHPUnit >=11.4
*/
#[RequiresPhpunit('>=11.4')]
#[RequiresPhp('>=8.4')]
#[RequiresPhpunitExtension(FoundryExtension::class)]
final class DataProviderWithPersistentFactoryAndPHP84InKernelTest extends KernelTestCase
{
use Factories;
use ResetDatabase;

#[Test]
#[DataProvider('createOneObjectInDataProvider')]
public function assert_it_can_create_one_object_in_data_provider(?GenericModel $providedData): void
{
GenericEntityFactory::assert()->count(1);

self::assertNotNull($providedData);
self::assertFalse((new \ReflectionClass($providedData))->isUninitializedLazyObject($providedData));
self::assertSame('value set in data provider', $providedData->getProp1());
}

public static function createOneObjectInDataProvider(): iterable
{
yield 'createOne()' => [
GenericEntityFactory::createOne(['prop1' => 'value set in data provider']),
];

yield 'create()' => [
GenericEntityFactory::new()->create(['prop1' => 'value set in data provider']),
];
}

#[Test]
#[DataProvider('createMultipleObjectsInDataProvider')]
public function assert_it_can_create_multiple_objects_in_data_provider(?array $providedData): void
{
self::assertIsArray($providedData);
GenericEntityFactory::assert()->count(2);

foreach ($providedData as $providedDatum) {
self::assertFalse((new \ReflectionClass($providedDatum))->isUninitializedLazyObject($providedDatum));
}

self::assertSame('prop 1', $providedData[0]->getProp1());
self::assertSame('prop 2', $providedData[1]->getProp1());
}

public static function createMultipleObjectsInDataProvider(): iterable
{
yield 'createSequence()' => [
GenericEntityFactory::createSequence([
['prop1' => 'prop 1'],
['prop1' => 'prop 2'],
]),
];

yield 'FactoryCollection::create()' => [
GenericEntityFactory::new()->sequence([
['prop1' => 'prop 1'],
['prop1' => 'prop 2'],
])->create(),
];
}
}
Loading