Skip to content

Commit

Permalink
Merge pull request #37 from spiral-packages/hotifx/heap
Browse files Browse the repository at this point in the history
Update ORM Heap Cleaning Default Behavior
  • Loading branch information
butschster authored Jan 19, 2024
2 parents 557fb50 + cad6c57 commit 361c64f
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 38 deletions.
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

0 comments on commit 361c64f

Please sign in to comment.