Skip to content
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

Update ORM Heap Cleaning Default Behavior #37

Merged
merged 2 commits into from
Jan 19, 2024
Merged
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
8 changes: 4 additions & 4 deletions src/Database/Cleaner.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
class Cleaner
{
public function __construct(
protected readonly DatabaseProviderInterface $provider
protected readonly DatabaseProviderInterface $provider,
) {
}

Expand All @@ -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);

Expand Down Expand Up @@ -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();
}
Expand All @@ -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();
}
Expand Down
175 changes: 141 additions & 34 deletions src/Factory/AbstractFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,27 +21,39 @@

/**
* @template TEntity of object
*
* @implements FactoryInterface<TEntity>
*
* @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
{
/** @internal */
private Factory $entityFactory;
/** @psalm-var positive-int */
private int $amount = 1;
/** @var array<Closure|callable> */
/** @var TCallback[] */
private array $afterCreate = [];
/** @var array<Closure|callable> */
/** @var TCallback[] */
private array $afterMake = [];
protected Generator $faker;
/** @var array<Closure> */
/** @var TState[] */
private array $states = [];
/** @var array<Closure> */
/** @var TEntityState[] */
private array $entityStates = [];

protected Generator $faker;

public static bool $cleanHeap = false;

/**
* @param TDefinition $replaces
*/
private function __construct(
private readonly array $replaces = [],
) {
Expand Down Expand Up @@ -74,56 +87,142 @@ public function times(int $amount): self
return $this;
}

/**
* Define a state for the entity using a closure with the given definition.
*
* Example:
* <code>
* $factory->state(fn(\Faker\Generator $faker, array $definition) => [
* 'admin' => $faker->boolean(),
* ])->times(10)->create();
* </code>
*
* Example usage in factory:
* <code>
* public function admin(): self
* {
* return $this->state(fn(\Faker\Generator $faker, array $definition) => [
* 'admin' => true,
* ]);
* }
* </code>
*
* @param TState $state
*
*/
public function state(Closure $state): self
{
$this->states[] = $state;

return $this;
}

/**
* Define a state for the entity using a closure with the given entity.
*
* Example:
* <code>
* $factory->entityState(static function(User $user) {
* return $user->markAsDeleted();
* })->times(10)->create();
* </code>
*
* Example usage in factory:
* <code>
* public function withBirthday(\DateTimeImmutable $date): self
* {
* return $this->entityState(static function (User $user) use ($date) {
* $user->birthday = $date;
* return $user;
* });
* }
* </code>
*
* @param TEntityState $state
*/
public function entityState(Closure $state): self
{
$this->entityStates[] = $state;

return $this;
}

/**
* Define a callback to run after creating a model.
*
* Example:
*
* <code>
* $factory->afterCreate(static function(User $user): void {
* $user->markAsDeleted();
* })->create();
* </code>
*
* @param TCallback $afterCreate
*/
public function afterCreate(callable $afterCreate): self
{
$this->afterCreate[] = $afterCreate;

return $this;
}

/**
* Define a callback to run after making a model.
*
* Example:
*
* <code>
* $factory->afterMake(static function(User $user): void {
* $user->verify();
* })->create();
* </code>
*
* @param TCallback $afterMake
*/
public function afterMake(callable $afterMake): self
{
$this->afterMake[] = $afterMake;

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]);

Expand All @@ -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);
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions src/Factory/FactoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

/**
* @template TEntity of object
*
* @psalm-type TDefinition = array<string, mixed>
*/
interface FactoryInterface
{
Expand All @@ -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;

Expand Down Expand Up @@ -53,6 +62,7 @@ public function make(): array;

/**
* Make an entity without persisting it to the database.
*
* @return TEntity
*/
public function makeOne(): object;
Expand Down
Loading