diff --git a/src/Database/Cleaner.php b/src/Database/Cleaner.php index fb37a06..2a03fe6 100644 --- a/src/Database/Cleaner.php +++ b/src/Database/Cleaner.php @@ -9,7 +9,7 @@ class Cleaner { public function __construct( - protected readonly DatabaseProviderInterface $provider + protected readonly DatabaseProviderInterface $provider, ) { } @@ -20,7 +20,7 @@ public function __construct( public function truncateTable( string $table, ?string $database = null, - bool $disableForeignKeyConstraints = true + bool $disableForeignKeyConstraints = true, ): void { $db = $this->provider->database($database); @@ -119,7 +119,7 @@ public function disableForeignKeyConstraints(?string $database = null): void $db = $this->provider->database($database); /** - *@psalm-suppress UndefinedInterfaceMethod + * @psalm-suppress UndefinedInterfaceMethod */ $db->getDriver()->getSchemaHandler()->disableForeignKeyConstraints(); } @@ -129,7 +129,7 @@ public function enableForeignKeyConstraints(?string $database = null): void $db = $this->provider->database($database); /** - *@psalm-suppress UndefinedInterfaceMethod + * @psalm-suppress UndefinedInterfaceMethod */ $db->getDriver()->getSchemaHandler()->enableForeignKeyConstraints(); } diff --git a/src/Factory/AbstractFactory.php b/src/Factory/AbstractFactory.php index 99e31f8..07eba1f 100644 --- a/src/Factory/AbstractFactory.php +++ b/src/Factory/AbstractFactory.php @@ -11,6 +11,7 @@ use Cycle\ORM\ORMInterface; use Faker\Factory as FakerFactory; use Faker\Generator; +use Faker\Generator as Faker; use Laminas\Hydrator\ReflectionHydrator; use Butschster\EntityFaker\Factory; use Butschster\EntityFaker\LaminasEntityFactory; @@ -20,10 +21,16 @@ /** * @template TEntity of object - * * @implements FactoryInterface * - * @property-read $data + * @psalm-import-type TDefinition from FactoryInterface + * @psalm-type TState = Closure(Faker, TDefinition):TDefinition + * @psalm-type TEntityState = Closure(TEntity):TEntity + * @psalm-type TCallback = Closure(TEntity):void + * + * @property-read $data TDefinition|TDefinition[] Will return an array if {@see static::$amount} is greater than 1 otherwise will return a single entity. + * @property-read $entity TEntity + * @property-read $entities TEntity[] */ abstract class AbstractFactory implements FactoryInterface { @@ -31,16 +38,22 @@ abstract class AbstractFactory implements FactoryInterface private Factory $entityFactory; /** @psalm-var positive-int */ private int $amount = 1; - /** @var array */ + /** @var TCallback[] */ private array $afterCreate = []; - /** @var array */ + /** @var TCallback[] */ private array $afterMake = []; - protected Generator $faker; - /** @var array */ + /** @var TState[] */ private array $states = []; - /** @var array */ + /** @var TEntityState[] */ private array $entityStates = []; + protected Generator $faker; + + public static bool $cleanHeap = false; + + /** + * @param TDefinition $replaces + */ private function __construct( private readonly array $replaces = [], ) { @@ -74,6 +87,29 @@ public function times(int $amount): self return $this; } + /** + * Define a state for the entity using a closure with the given definition. + * + * Example: + * + * $factory->state(fn(\Faker\Generator $faker, array $definition) => [ + * 'admin' => $faker->boolean(), + * ])->times(10)->create(); + * + * + * Example usage in factory: + * + * public function admin(): self + * { + * return $this->state(fn(\Faker\Generator $faker, array $definition) => [ + * 'admin' => true, + * ]); + * } + * + * + * @param TState $state + * + */ public function state(Closure $state): self { $this->states[] = $state; @@ -81,6 +117,29 @@ public function state(Closure $state): self return $this; } + /** + * Define a state for the entity using a closure with the given entity. + * + * Example: + * + * $factory->entityState(static function(User $user) { + * return $user->markAsDeleted(); + * })->times(10)->create(); + * + * + * Example usage in factory: + * + * public function withBirthday(\DateTimeImmutable $date): self + * { + * return $this->entityState(static function (User $user) use ($date) { + * $user->birthday = $date; + * return $user; + * }); + * } + * + * + * @param TEntityState $state + */ public function entityState(Closure $state): self { $this->entityStates[] = $state; @@ -88,6 +147,19 @@ public function entityState(Closure $state): self return $this; } + /** + * Define a callback to run after creating a model. + * + * Example: + * + * + * $factory->afterCreate(static function(User $user): void { + * $user->markAsDeleted(); + * })->create(); + * + * + * @param TCallback $afterCreate + */ public function afterCreate(callable $afterCreate): self { $this->afterCreate[] = $afterCreate; @@ -95,6 +167,19 @@ public function afterCreate(callable $afterCreate): self return $this; } + /** + * Define a callback to run after making a model. + * + * Example: + * + * + * $factory->afterMake(static function(User $user): void { + * $user->verify(); + * })->create(); + * + * + * @param TCallback $afterMake + */ public function afterMake(callable $afterMake): self { $this->afterMake[] = $afterMake; @@ -102,28 +187,42 @@ public function afterMake(callable $afterMake): self return $this; } - public function create(): array + /** + * Create many entities with persisting them to the database. + * + * @param bool|null $cleanHeap Clean the heap after creating entities. + * + * @note To change the default value use {@see static::$cleanHeap} property. + */ + public function create(?bool $cleanHeap = null): array { $entities = $this->object(fn() => $this->definition()); if (!\is_array($entities)) { $entities = [$entities]; } - $this->storeEntities($entities); + $this->storeEntities($entities, $cleanHeap); $this->callAfterCreating($entities); return $entities; } - public function createOne(): object + /** + * Create an entity with persisting it to the database. + * + * @param bool|null $cleanHeap Clean the heap after creating entity. Default value is false. + * + * @note To change the default value use {@see static::$cleanHeap} property. + */ + public function createOne(?bool $cleanHeap = null): object { $entity = $this->object(fn() => $this->definition()); if (\is_array($entity)) { $entity = \array_shift($entity); } - $this->storeEntities([$entity]); + $this->storeEntities([$entity], $cleanHeap); $this->callAfterCreating([$entity]); @@ -150,6 +249,12 @@ public function makeOne(): object return $entity; } + /** + * Get the raw array data. This data will be used to make an entity or entities. + * + * @param TState $definition + * @return TDefinition|TDefinition[] Will return an array if {@see static::$amount} is greater than 1 otherwise will return a single entity. + */ public function raw(Closure $definition): array { $this->entityFactory->define($this->entity(), $definition); @@ -159,15 +264,17 @@ public function raw(Closure $definition): array return \array_is_list($data) ? $data[0] : $data; } - public function __get(string $name): array + public function __get(string $name): mixed { return match ($name) { 'data' => $this->raw(fn() => $this->definition()), + 'entity' => $this->createOne(), + 'entities' => $this->create(), default => throw new FactoryException('Undefined magic property.') }; } - private function storeEntities(array $entities): void + private function storeEntities(array $entities, ?bool $cleanHeap = null): void { $container = ContainerScope::getContainer(); if ($container === null) { @@ -187,44 +294,44 @@ private function storeEntities(array $entities): void } $em->run(); - $container->get(ORMInterface::class)->getHeap()->clean(); + if ($cleanHeap ?? self::$cleanHeap) { + $container->get(ORMInterface::class)->getHeap()->clean(); + } } - /** - * @internal - * - * @return TEntity|array - */ + /** @internal */ private function object(Closure $definition): object|array { + $entityClass = $this->entity(); + $this->entityFactory ->creationStrategy( - $this->entity(), - new ClosureStrategy(fn(string $class, array $data) => $this->makeEntity($data)), + $entityClass, + new ClosureStrategy( + fn(string $class, array $data): object => $this->makeEntity($data), + ), ) - ->define($this->entity(), $definition) - ->states($this->entity(), $this->states); + ->define($entityClass, $definition) + ->states($entityClass, $this->states); foreach ($this->afterMake as $afterMakeCallable) { - $this->entityFactory->afterMaking($this->entity(), $afterMakeCallable); + $this->entityFactory->afterMaking($entityClass, $afterMakeCallable); } - $result = $this->entityFactory->of($this->entity())->times($this->amount)->make($this->replaces); + /** @var TEntity|TEntity[] $result */ + $result = $this->entityFactory->of($entityClass)->times($this->amount)->make($this->replaces); if (\is_array($result)) { - return \array_map(function (object $entity) { - return $this->applyEntityState($entity); - }, $result); + return \array_map( + fn(object $entity): object => $this->applyEntityState($entity), + $result, + ); } return $this->applyEntityState($result); } - /** - * @internal - * - * @return TEntity - */ + /** @internal */ private function applyEntityState(object $entity): object { foreach ($this->entityStates as $state) { @@ -235,7 +342,7 @@ private function applyEntityState(object $entity): object } /** @internal */ - private function callAfterCreating(array $entities) + private function callAfterCreating(array $entities): void { foreach ($entities as $entity) { \array_map(static fn(callable $callable) => $callable($entity), $this->afterCreate); diff --git a/src/Factory/FactoryInterface.php b/src/Factory/FactoryInterface.php index 2ab3782..db859c1 100644 --- a/src/Factory/FactoryInterface.php +++ b/src/Factory/FactoryInterface.php @@ -6,6 +6,8 @@ /** * @template TEntity of object + * + * @psalm-type TDefinition = array */ interface FactoryInterface { @@ -16,10 +18,17 @@ interface FactoryInterface */ public function entity(): string; + /** + * Create a new factory instance for the given entity. + * + * @param TDefinition $replace The attributes to replace. + */ public static function new(): static; /** * Entity data definition. This data will be used to create an entity. + * + * @return TDefinition */ public function definition(): array; @@ -53,6 +62,7 @@ public function make(): array; /** * Make an entity without persisting it to the database. + * * @return TEntity */ public function makeOne(): object;