|
| 1 | +<?php |
| 2 | + |
| 3 | +namespace Zenstruck\Foundry\Persistence; |
| 4 | + |
| 5 | +/** |
| 6 | + * If a persistent object has been created in a data provider, we need to initialize the proxy object, |
| 7 | + * which will trigger the object to be persisted. |
| 8 | + * |
| 9 | + * Otherwise, such test would not pass: |
| 10 | + * ```php |
| 11 | + * #[DataProvider('provide')] |
| 12 | + * public function testSomething(MyEntity $entity): void |
| 13 | + * { |
| 14 | + * MyEntityFactory::assert()->count(1); |
| 15 | + * } |
| 16 | + * |
| 17 | + * public static function provide(): iterable |
| 18 | + * { |
| 19 | + * yield [MyEntityFactory::createOne()]; |
| 20 | + * } |
| 21 | + * ``` |
| 22 | + * |
| 23 | + * Sadly, this cannot be done directly a subscriber, since PHPUnit does not give access to the actual tests instances. |
| 24 | + * |
| 25 | + * This class is highly hacky! |
| 26 | + * We collect all the "datasets" and we trigger the persistence for each one before the test is executed. |
| 27 | + * This means that de data providers are called twice. |
| 28 | + * To prevent the persisted object from being different from the one returned by the data provider, we use a "buffer" so |
| 29 | + * that we can return the same object for each data provider call. |
| 30 | + * |
| 31 | + * @internal |
| 32 | + */ |
| 33 | +final class PersistentObjectFromDataProviderRegistry |
| 34 | +{ |
| 35 | + private static ?self $instance = null; |
| 36 | + |
| 37 | + /** @var array<string, array<array-key, mixed>> */ |
| 38 | + private array $datasets = []; |
| 39 | + |
| 40 | + /** @var list<object> */ |
| 41 | + private array $objectsBuffer = []; |
| 42 | + |
| 43 | + private bool $shouldReturnExistingObject = false; |
| 44 | + |
| 45 | + public static function instance(): self |
| 46 | + { |
| 47 | + return self::$instance ?? self::$instance = new self(); |
| 48 | + } |
| 49 | + |
| 50 | + /** |
| 51 | + * @param callable():iterable<array-key, mixed> $dataProviderResult |
| 52 | + */ |
| 53 | + public function addDataset(string $className, string $methodName, callable $dataProviderResult): void |
| 54 | + { |
| 55 | + $this->shouldReturnExistingObject = false; |
| 56 | + |
| 57 | + $dataProviderResult = $dataProviderResult(); |
| 58 | + |
| 59 | + if (!is_array($dataProviderResult)) { |
| 60 | + $dataProviderResult = iterator_to_array($dataProviderResult); |
| 61 | + } |
| 62 | + |
| 63 | + $testCaseContext = $this->testCaseContext($className, $methodName); |
| 64 | + $this->datasets[$testCaseContext] = $dataProviderResult; |
| 65 | + |
| 66 | + $this->shouldReturnExistingObject = true; |
| 67 | + } |
| 68 | + |
| 69 | + /** |
| 70 | + * @template T of object |
| 71 | + * |
| 72 | + * @param PersistentObjectFactory<T> $factory |
| 73 | + * |
| 74 | + * @return ($factory is PersistentProxyObjectFactory<T> ? T&Proxy<T> : T) |
| 75 | + */ |
| 76 | + public function deferObjectCreation(PersistentObjectFactory $factory): object |
| 77 | + { |
| 78 | + if (!$this->shouldReturnExistingObject) { |
| 79 | + return $this->objectsBuffer[] = ProxyGenerator::wrapFactory($factory); |
| 80 | + } |
| 81 | + |
| 82 | + return array_shift($this->objectsBuffer); // @phpstan-ignore return.type |
| 83 | + } |
| 84 | + |
| 85 | + public function triggerPersistenceForDataset(string $className, string $methodName, int|string $dataSetName): void |
| 86 | + { |
| 87 | + $testCaseContext = $this->testCaseContext($className, $methodName); |
| 88 | + |
| 89 | + if (!isset($this->datasets[$testCaseContext][$dataSetName])) { |
| 90 | + throw new \LogicException("No data found for test case context \"{$testCaseContext}\" with dataset name \"{$dataSetName}\"."); |
| 91 | + } |
| 92 | + |
| 93 | + initialize_proxy_object($this->datasets[$testCaseContext][$dataSetName]); |
| 94 | + |
| 95 | + unset($this->datasets[$testCaseContext][$dataSetName]); |
| 96 | + } |
| 97 | + |
| 98 | + private function testCaseContext(string $className, string $methodName): string |
| 99 | + { |
| 100 | + return "{$className}::{$methodName}"; |
| 101 | + } |
| 102 | +} |
0 commit comments