Skip to content
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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,20 @@ Focused package documentation lives next to the relevant source folders:
- [Logging](./src/Logging/README.md)
- [Mongo](./src/Mongo/README.md)
- [Middleware](./src/Middleware/README.md)
- [Queue](./src/Queue/README.md)
- [Rate Limiting](./src/RateLimit/README.md)
- [Redis](./src/Redis/README.md)
- [Routing](./src/Routing/README.md)
- [Storage](./src/Storage/README.md)
- [Support and Facades](./src/Support/README.md)
- [Validation](./src/Validation/README.md)

## Install via Composer

```bash
composer require 200mph/myxa-framework
```

## Docker Setup

The repository includes a PHP 8.4 CLI container and a MySQL container.
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "200mph/myxa-framework",
"description": "Myxa core framework package",
"description": "The core framework package for Myxa, a lightweight and flexible AI-powered PHP framework.",
"type": "library",
"license": "MIT",
"authors": [
Expand Down
221 changes: 221 additions & 0 deletions src/Database/Factory/Factory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
<?php

declare(strict_types=1);

namespace Myxa\Database\Factory;

use InvalidArgumentException;
use Myxa\Database\DatabaseManager;
use Myxa\Database\Model\Model;

/**
* Small base class for model factories backed by FakeData.
*
* @phpstan-consistent-constructor
*/
abstract class Factory
{
/** @var list<array<string, mixed>|callable(array<string, mixed>, FakeData): array<string, mixed>>> */
private array $states = [];

private int $count = 1;

public function __construct(
protected FakeData $faker = new FakeData(),
protected ?DatabaseManager $manager = null,
) {
}

/**
* Create a new factory instance.
*/
public static function new(?FakeData $faker = null, ?DatabaseManager $manager = null): static
{
return new static($faker ?? new FakeData(), $manager);
}

/**
* Return the target model class for this factory.
*
* @return class-string<Model>
*/
abstract protected function model(): string;

/**
* Return the default attributes for a model instance.
*
* @return array<string, mixed>
*/
abstract protected function definition(): array;

/**
* Return the fake data generator used by the factory.
*/
public function faker(): FakeData
{
return $this->faker;
}

/**
* Return a clone that uses the given database manager.
*/
public function withManager(?DatabaseManager $manager): static
{
$factory = clone $this;
$factory->manager = $manager;

return $factory;
}

/**
* Return a clone that will build the given number of models.
*/
public function count(int $count): static
{
if ($count < 1) {
throw new InvalidArgumentException('Factory count must be at least 1.');
}

$factory = clone $this;
$factory->count = $count;

return $factory;
}

/**
* Return a clone with an extra state transformation.
*
* @param array<string, mixed>|callable(array<string, mixed>, FakeData): array<string, mixed> $state
*/
public function state(array|callable $state): static
{
$factory = clone $this;
$factory->states[] = $state;

return $factory;
}

/**
* Return the raw attribute payload.
*
* @param array<string, mixed> $attributes
* @return array<string, mixed>|list<array<string, mixed>>
*/
public function raw(array $attributes = []): array
{
if ($this->count === 1) {
return $this->buildAttributes($attributes);
}

$rows = [];

for ($index = 0; $index < $this->count; $index++) {
$rows[] = $this->buildAttributes($attributes);
}

return $rows;
}

/**
* Build unsaved model instances.
*
* @param array<string, mixed> $attributes
* @return Model|list<Model>
*/
public function make(array $attributes = []): Model|array
{
if ($this->count === 1) {
return $this->makeOne($attributes);
}

$models = [];

for ($index = 0; $index < $this->count; $index++) {
$models[] = $this->makeOne($attributes);
}

return $models;
}

/**
* Build and persist model instances.
*
* @param array<string, mixed> $attributes
* @return Model|list<Model>
*/
public function create(array $attributes = []): Model|array
{
if ($this->count === 1) {
return $this->createOne($attributes);
}

$models = [];

for ($index = 0; $index < $this->count; $index++) {
$models[] = $this->createOne($attributes);
}

return $models;
}

/**
* Hook for adjusting a model after make().
*/
protected function afterMaking(Model $model): void
{
}

/**
* Hook for adjusting a model after create().
*/
protected function afterCreating(Model $model): void
{
}

/**
* @param array<string, mixed> $attributes
* @return array<string, mixed>
*/
private function buildAttributes(array $attributes): array
{
$payload = $this->definition();

foreach ($this->states as $state) {
$resolved = is_array($state)
? $state
: $state($payload, $this->faker);

if (!is_array($resolved)) {
throw new InvalidArgumentException('Factory state callbacks must return an attribute array.');
}

$payload = array_replace($payload, $resolved);
}

return array_replace($payload, $attributes);
}

/**
* @param array<string, mixed> $attributes
*/
private function makeOne(array $attributes): Model
{
$modelClass = $this->model();
$model = new $modelClass($this->buildAttributes($attributes), $this->manager);
$this->afterMaking($model);

return $model;
}

/**
* @param array<string, mixed> $attributes
*/
private function createOne(array $attributes): Model
{
$model = $this->makeOne($attributes);
$model->save();
$this->afterCreating($model);

return $model;
}
}
Loading
Loading