feat(infra): add Brand Eloquent models, migrations, casts, adapters and seeders
This commit is contained in:
parent
a42a79861d
commit
20ec692160
22 changed files with 1095 additions and 2 deletions
28
database/factories/ArcadiaBrandFactory.php
Normal file
28
database/factories/ArcadiaBrandFactory.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Infra\Database\Models\ArcadiaBrand;
|
||||
|
||||
/**
|
||||
* @extends Factory<ArcadiaBrand>
|
||||
*/
|
||||
final class ArcadiaBrandFactory extends Factory
|
||||
{
|
||||
protected $model = ArcadiaBrand::class;
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[\Override]
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'code' => fake()->unique()->numberBetween(1, 999),
|
||||
'libelle' => fake()->company(),
|
||||
];
|
||||
}
|
||||
}
|
||||
36
database/factories/BrandFactory.php
Normal file
36
database/factories/BrandFactory.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Domain\Services\BrandCodeGenerator;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Infra\Database\Models\ArcadiaBrand;
|
||||
use Infra\Database\Models\Brand;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
/**
|
||||
* @extends Factory<Brand>
|
||||
*/
|
||||
final class BrandFactory extends Factory
|
||||
{
|
||||
protected $model = Brand::class;
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[\Override]
|
||||
public function definition(): array
|
||||
{
|
||||
$generator = new BrandCodeGenerator();
|
||||
$code = $generator->generate(new BrandName(fake()->company()));
|
||||
|
||||
return [
|
||||
'id' => Uuid::uuid7()->toString(),
|
||||
'arcadia_id' => ArcadiaBrand::factory(),
|
||||
'uwil_public_id' => $code->value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('brands', function (Blueprint $table) {
|
||||
$table->uuid('id')->primary();
|
||||
|
||||
$table->string('uwil_public_id')->unique()
|
||||
->comment('A unique public id that is generated spefically for the UWIL eco system');
|
||||
$table->bigInteger('arcadia_id')->unique()
|
||||
->comment('An unique external autoincrement id for the brand, it may be exposed but should not be used');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('brands');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
$schema = Schema::connection(Config::string('database.arcadia_meta_model'));
|
||||
|
||||
$schema->dropIfExists('arcadia_brands');
|
||||
|
||||
$schema->create('arcadia_brands', function (Blueprint $table) {
|
||||
$table->integer('code')->unsigned()->primary();
|
||||
$table->string('libelle');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
$schema = Schema::connection(Config::string('database.arcadia_meta_model'));
|
||||
|
||||
$schema->dropIfExists('arcadia_brands');
|
||||
}
|
||||
};
|
||||
44
database/seeders/BrandSeeder.php
Normal file
44
database/seeders/BrandSeeder.php
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Infra\Database\Models\ArcadiaBrand;
|
||||
use Infra\Database\Models\Brand;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
final class BrandSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Seed the application's database.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
/** @var list<array{code: int, libelle: string, uwil_public_id: string}> $brands */
|
||||
$brands = [
|
||||
['code' => 1, 'libelle' => 'Le Gaulois', 'uwil_public_id' => 'LGL01'],
|
||||
['code' => 2, 'libelle' => 'Maître CoQ', 'uwil_public_id' => 'MCQ02'],
|
||||
['code' => 3, 'libelle' => 'Loué', 'uwil_public_id' => 'LOU03'],
|
||||
['code' => 4, 'libelle' => 'Marie', 'uwil_public_id' => 'MAR04'],
|
||||
['code' => 5, 'libelle' => "Tradition d'Asie", 'uwil_public_id' => 'TDA05'],
|
||||
['code' => 6, 'libelle' => 'Réghalal', 'uwil_public_id' => 'REG06'],
|
||||
];
|
||||
|
||||
foreach ($brands as $brandData) {
|
||||
ArcadiaBrand::query()->updateOrCreate(
|
||||
['code' => $brandData['code']],
|
||||
['libelle' => $brandData['libelle']],
|
||||
);
|
||||
|
||||
Brand::query()->firstOrCreate(
|
||||
['arcadia_id' => $brandData['code']],
|
||||
[
|
||||
'id' => Uuid::uuid7()->toString(),
|
||||
'uwil_public_id' => $brandData['uwil_public_id'],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ final class DatabaseSeeder extends Seeder
|
|||
EmployeeSeeder::class,
|
||||
IncoSheetSeeder::class,
|
||||
ApiClientSeeder::class,
|
||||
BrandSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Infra\Adapters\Directories;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaBrand;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Domain\Ports\Directories\ArcadiaBrandDirectory;
|
||||
use Infra\Database\Models\ArcadiaBrand as ArcadiaBrandModel;
|
||||
|
||||
final class EloquentArcadiaBrandDirectory implements ArcadiaBrandDirectory
|
||||
{
|
||||
#[\Override]
|
||||
public function findAll(): array
|
||||
{
|
||||
return array_values(
|
||||
ArcadiaBrandModel::query()
|
||||
->get()
|
||||
->map(fn (ArcadiaBrandModel $model): ArcadiaBrand => $this->toDomain($model))
|
||||
->all()
|
||||
);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function find(ArcadiaId $id): ?ArcadiaBrand
|
||||
{
|
||||
$model = ArcadiaBrandModel::query()->find($id->value);
|
||||
|
||||
if ($model === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->toDomain($model);
|
||||
}
|
||||
|
||||
private function toDomain(ArcadiaBrandModel $model): ArcadiaBrand
|
||||
{
|
||||
return new ArcadiaBrand(
|
||||
id: new ArcadiaId((int) $model->code),
|
||||
name: new BrandName((string) $model->libelle),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Infra\Adapters\Directories;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaBrand;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Ports\Directories\ArcadiaBrandDirectory;
|
||||
|
||||
final class InMemoryArcadiaBrandDirectory implements ArcadiaBrandDirectory
|
||||
{
|
||||
/** @var array<int, ArcadiaBrand> */
|
||||
private array $brands = [];
|
||||
|
||||
public function add(ArcadiaBrand ...$brands): self
|
||||
{
|
||||
foreach ($brands as $brand) {
|
||||
$this->brands[$brand->id->value] = $brand;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
$this->brands = [];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function findAll(): array
|
||||
{
|
||||
return array_values($this->brands);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function find(ArcadiaId $id): ?ArcadiaBrand
|
||||
{
|
||||
return $this->brands[$id->value] ?? null;
|
||||
}
|
||||
}
|
||||
110
src/Infra/Adapters/Repositories/EloquentBrandRepository.php
Normal file
110
src/Infra/Adapters/Repositories/EloquentBrandRepository.php
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Infra\Adapters\Repositories;
|
||||
|
||||
use Domain\Exceptions\BrandNotFoundException;
|
||||
use Domain\Models\Entities\Brand;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Brand\BrandId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Domain\Ports\Repositories\BrandRepository;
|
||||
use Infra\Database\Models\Brand as BrandModel;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
final class EloquentBrandRepository implements BrandRepository
|
||||
{
|
||||
#[\Override]
|
||||
public function find(BrandId $id): ?Brand
|
||||
{
|
||||
$model = BrandModel::query()
|
||||
->with('arcadiaBrand')
|
||||
->find($id->value);
|
||||
|
||||
if ($model === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->toDomain($model);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function findByCode(BrandCode $code): ?Brand
|
||||
{
|
||||
$model = BrandModel::query()
|
||||
->with('arcadiaBrand')
|
||||
->where('uwil_public_id', $code->value)
|
||||
->first();
|
||||
|
||||
if ($model === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->toDomain($model);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function findByArcadiaId(ArcadiaId $arcadiaId): ?Brand
|
||||
{
|
||||
$model = BrandModel::query()
|
||||
->with('arcadiaBrand')
|
||||
->where('arcadia_id', $arcadiaId->value)
|
||||
->first();
|
||||
|
||||
if ($model === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->toDomain($model);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function findAll(): array
|
||||
{
|
||||
return array_values(
|
||||
BrandModel::query()
|
||||
->with('arcadiaBrand')
|
||||
->get()
|
||||
->map(fn (BrandModel $model): Brand => $this->toDomain($model))
|
||||
->all()
|
||||
);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function persist(Brand $entity): void
|
||||
{
|
||||
BrandModel::query()->updateOrCreate(
|
||||
['id' => $entity->id()->value],
|
||||
[
|
||||
'arcadia_id' => $entity->arcadiaId,
|
||||
'uwil_public_id' => $entity->code,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function delete(Brand $entity): void
|
||||
{
|
||||
$deleted = BrandModel::query()
|
||||
->where('id', $entity->id()->value)
|
||||
->delete();
|
||||
|
||||
if ($deleted === 0) {
|
||||
throw BrandNotFoundException::forBrand($entity->id());
|
||||
}
|
||||
}
|
||||
|
||||
private function toDomain(BrandModel $model): Brand
|
||||
{
|
||||
$name = $model->arcadiaBrand !== null ? $model->arcadiaBrand->libelle : '';
|
||||
|
||||
return Brand::hydrate(
|
||||
id: new BrandId(Uuid::fromString($model->id)),
|
||||
name: new BrandName($name),
|
||||
code: $model->uwil_public_id,
|
||||
arcadiaId: $model->arcadia_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
84
src/Infra/Adapters/Repositories/InMemoryBrandRepository.php
Normal file
84
src/Infra/Adapters/Repositories/InMemoryBrandRepository.php
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Infra\Adapters\Repositories;
|
||||
|
||||
use Domain\Exceptions\BrandNotFoundException;
|
||||
use Domain\Models\Entities\Brand;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Brand\BrandId;
|
||||
use Domain\Ports\Repositories\BrandRepository;
|
||||
|
||||
final class InMemoryBrandRepository implements BrandRepository
|
||||
{
|
||||
/** @var array<string, Brand> */
|
||||
private array $brands = [];
|
||||
|
||||
public function add(Brand ...$brands): self
|
||||
{
|
||||
foreach ($brands as $brand) {
|
||||
$this->brands[$brand->id()->value] = $brand;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
$this->brands = [];
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function find(BrandId $id): ?Brand
|
||||
{
|
||||
return $this->brands[$id->value] ?? null;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function findByCode(BrandCode $code): ?Brand
|
||||
{
|
||||
foreach ($this->brands as $brand) {
|
||||
if ($brand->code->value === $code->value) {
|
||||
return $brand;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function findByArcadiaId(ArcadiaId $arcadiaId): ?Brand
|
||||
{
|
||||
foreach ($this->brands as $brand) {
|
||||
if ($brand->arcadiaId->value === $arcadiaId->value) {
|
||||
return $brand;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function findAll(): array
|
||||
{
|
||||
return array_values($this->brands);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function persist(Brand $entity): void
|
||||
{
|
||||
$this->brands[$entity->id()->value] = $entity;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function delete(Brand $entity): void
|
||||
{
|
||||
if (! isset($this->brands[$entity->id()->value])) {
|
||||
throw BrandNotFoundException::forBrand($entity->id());
|
||||
}
|
||||
|
||||
unset($this->brands[$entity->id()->value]);
|
||||
}
|
||||
}
|
||||
47
src/Infra/Casts/ArcadiaIdCast.php
Normal file
47
src/Infra/Casts/ArcadiaIdCast.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Infra\Casts;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/** @implements CastsAttributes<ArcadiaId, int|string|ArcadiaId> */
|
||||
final class ArcadiaIdCast implements CastsAttributes
|
||||
{
|
||||
public function get(Model $model, string $key, mixed $value, array $attributes): ArcadiaId
|
||||
{
|
||||
if ($value === null) {
|
||||
throw new \InvalidArgumentException('ArcadiaId value cannot be null');
|
||||
}
|
||||
|
||||
if (! is_int($value) && ! (is_string($value) && ctype_digit($value))) {
|
||||
throw new \InvalidArgumentException('Value must be an integer or numeric string');
|
||||
}
|
||||
|
||||
return new ArcadiaId((int) $value);
|
||||
}
|
||||
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): int
|
||||
{
|
||||
if ($value === null) {
|
||||
throw new \InvalidArgumentException('ArcadiaId value cannot be null');
|
||||
}
|
||||
|
||||
if ($value instanceof ArcadiaId) {
|
||||
return $value->value;
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
return (new ArcadiaId($value))->value;
|
||||
}
|
||||
|
||||
if (is_string($value) && ctype_digit($value)) {
|
||||
return (new ArcadiaId((int) $value))->value;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Value must be an integer, numeric string, or instance of ArcadiaId');
|
||||
}
|
||||
}
|
||||
43
src/Infra/Casts/BrandCodeCast.php
Normal file
43
src/Infra/Casts/BrandCodeCast.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Infra\Casts;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/** @implements CastsAttributes<BrandCode, string|BrandCode> */
|
||||
final class BrandCodeCast implements CastsAttributes
|
||||
{
|
||||
public function get(Model $model, string $key, mixed $value, array $attributes): BrandCode
|
||||
{
|
||||
if ($value === null) {
|
||||
throw new \InvalidArgumentException('BrandCode value cannot be null');
|
||||
}
|
||||
|
||||
if (! is_string($value)) {
|
||||
throw new \InvalidArgumentException('Value must be a string');
|
||||
}
|
||||
|
||||
return new BrandCode($value);
|
||||
}
|
||||
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): string
|
||||
{
|
||||
if ($value === null) {
|
||||
throw new \InvalidArgumentException('BrandCode value cannot be null');
|
||||
}
|
||||
|
||||
if ($value instanceof BrandCode) {
|
||||
return $value->value;
|
||||
}
|
||||
|
||||
if (is_string($value)) {
|
||||
return (new BrandCode($value))->value;
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Value must be a string or instance of BrandCode');
|
||||
}
|
||||
}
|
||||
61
src/Infra/Database/Models/ArcadiaBrand.php
Normal file
61
src/Infra/Database/Models/ArcadiaBrand.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Infra\Database\Models;
|
||||
|
||||
use Database\Factories\ArcadiaBrandFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
|
||||
/**
|
||||
* @property int $code
|
||||
* @property string $libelle
|
||||
*
|
||||
* @method static \Database\Factories\ArcadiaBrandFactory factory($count = null, $state = [])
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
final class ArcadiaBrand extends Model
|
||||
{
|
||||
/** @use HasFactory<ArcadiaBrandFactory> */
|
||||
use HasFactory;
|
||||
|
||||
protected static function newFactory(): ArcadiaBrandFactory
|
||||
{
|
||||
return ArcadiaBrandFactory::new();
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function getConnectionName(): string
|
||||
{
|
||||
return Config::string('database.arcadia_meta_model');
|
||||
}
|
||||
|
||||
protected $table = 'arcadia_brands';
|
||||
|
||||
protected $primaryKey = 'code';
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
/** @var string */
|
||||
protected $keyType = 'int';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = [
|
||||
'code',
|
||||
'libelle',
|
||||
];
|
||||
|
||||
/** @return array<string, string> */
|
||||
#[\Override]
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'code' => 'integer',
|
||||
];
|
||||
}
|
||||
}
|
||||
76
src/Infra/Database/Models/Brand.php
Normal file
76
src/Infra/Database/Models/Brand.php
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Infra\Database\Models;
|
||||
|
||||
use Database\Factories\BrandFactory;
|
||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Infra\Casts\ArcadiaIdCast;
|
||||
use Infra\Casts\BrandCodeCast;
|
||||
use Infra\Database\Relations\ArcadiaBrandBelongsTo;
|
||||
|
||||
/**
|
||||
* @phpcs:disable Generic.Files.LineLength.TooLong
|
||||
*
|
||||
* @property string $id
|
||||
* @property \Domain\Models\ValueObjects\Brand\BrandCode $uwil_public_id A unique public id that is generated spefically for the UWIL eco system
|
||||
* @property \Domain\Models\ValueObjects\Brand\ArcadiaId $arcadia_id An unique external autoincrement id for the brand, it may be exposed but should not be used
|
||||
* @property-read \Infra\Database\Models\ArcadiaBrand|null $arcadiaBrand
|
||||
*
|
||||
* @method static \Database\Factories\BrandFactory factory($count = null, $state = [])
|
||||
*
|
||||
* @mixin \Eloquent
|
||||
*/
|
||||
final class Brand extends Model
|
||||
{
|
||||
/** @use HasFactory<BrandFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use HasUuids;
|
||||
|
||||
protected static function newFactory(): BrandFactory
|
||||
{
|
||||
return BrandFactory::new();
|
||||
}
|
||||
|
||||
protected $table = 'brands';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
/** @var list<string> */
|
||||
protected $fillable = [
|
||||
'id',
|
||||
'arcadia_id',
|
||||
'uwil_public_id',
|
||||
];
|
||||
|
||||
/** @return array<string, string> */
|
||||
#[\Override]
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'arcadia_id' => ArcadiaIdCast::class,
|
||||
'uwil_public_id' => BrandCodeCast::class,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArcadiaBrandBelongsTo
|
||||
*/
|
||||
public function arcadiaBrand(): BelongsTo
|
||||
{
|
||||
$instance = $this->newRelatedInstance(ArcadiaBrand::class);
|
||||
|
||||
return new ArcadiaBrandBelongsTo(
|
||||
$instance->newQuery(),
|
||||
$this,
|
||||
'arcadia_id',
|
||||
'code',
|
||||
'arcadiaBrand'
|
||||
);
|
||||
}
|
||||
}
|
||||
27
src/Infra/Database/Relations/ArcadiaBrandBelongsTo.php
Normal file
27
src/Infra/Database/Relations/ArcadiaBrandBelongsTo.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Infra\Database\Relations;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @extends BelongsTo<\Infra\Database\Models\ArcadiaBrand, \Infra\Database\Models\Brand>
|
||||
*/
|
||||
final class ArcadiaBrandBelongsTo extends BelongsTo
|
||||
{
|
||||
#[\Override]
|
||||
protected function getForeignKeyFrom(Model $model): mixed
|
||||
{
|
||||
$foreignKey = parent::getForeignKeyFrom($model);
|
||||
|
||||
if ($foreignKey instanceof ArcadiaId) {
|
||||
return $foreignKey->value;
|
||||
}
|
||||
|
||||
return $foreignKey;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,16 +4,20 @@ declare(strict_types=1);
|
|||
|
||||
namespace Infra\Providers;
|
||||
|
||||
use Domain\Ports\Directories\ArcadiaBrandDirectory;
|
||||
use Domain\Ports\Directories\EmployeeDirectory;
|
||||
use Domain\Ports\Directories\IncoCompositionDirectory;
|
||||
use Domain\Ports\Directories\IncoSheetDirectory;
|
||||
use Domain\Ports\Directories\IncoShortSheetDirectory;
|
||||
use Domain\Ports\Repositories\BrandRepository;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Infra\Adapters\Directories\EloquentArcadiaBrandDirectory;
|
||||
use Infra\Adapters\Directories\EloquentEmployeeDirectory;
|
||||
use Infra\Adapters\Directories\EloquentIncoCompositionDirectory;
|
||||
use Infra\Adapters\Directories\EloquentIncoSheetDirectory;
|
||||
use Infra\Adapters\Directories\EloquentIncoShortSheetDirectory;
|
||||
use Infra\Adapters\Events\LaravelDomainEventDispatcher;
|
||||
use Infra\Adapters\Repositories\EloquentBrandRepository;
|
||||
use Infra\Usecase\LaravelExecutor;
|
||||
use Shared\DDD\Event\DomainEventDispatcher;
|
||||
use Shared\DDD\Usecase\Builder\UsecaseExecutor;
|
||||
|
|
@ -27,6 +31,8 @@ class DomainServiceProvider extends ServiceProvider
|
|||
$this->app->bind(IncoSheetDirectory::class, EloquentIncoSheetDirectory::class);
|
||||
$this->app->bind(IncoShortSheetDirectory::class, EloquentIncoShortSheetDirectory::class);
|
||||
$this->app->bind(IncoCompositionDirectory::class, EloquentIncoCompositionDirectory::class);
|
||||
$this->app->bind(ArcadiaBrandDirectory::class, EloquentArcadiaBrandDirectory::class);
|
||||
$this->app->bind(BrandRepository::class, EloquentBrandRepository::class);
|
||||
$this->app->bind(DomainEventDispatcher::class, LaravelDomainEventDispatcher::class);
|
||||
$this->app->singleton(UsecaseExecutor::class, LaravelExecutor::class);
|
||||
}
|
||||
|
|
|
|||
35
tests/Integration/Database/Seeders/BrandSeederTest.php
Normal file
35
tests/Integration/Database/Seeders/BrandSeederTest.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Integration\Database\Seeders;
|
||||
|
||||
use Database\Seeders\BrandSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Infra\Database\Models\ArcadiaBrand;
|
||||
use Infra\Database\Models\Brand;
|
||||
use Tests\TestCase;
|
||||
|
||||
covers(Brand::class, ArcadiaBrand::class);
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
describe('BrandSeeder Integration Test', function (): void {
|
||||
it('seeds default brand and arcadia brand records into the database', function (): void {
|
||||
// GIVEN
|
||||
$seeder = new BrandSeeder();
|
||||
|
||||
// WHEN
|
||||
$seeder->run();
|
||||
|
||||
// THEN
|
||||
expect(ArcadiaBrand::count())->toBe(6)
|
||||
->and(Brand::count())->toBe(6);
|
||||
|
||||
$brand = Brand::query()->where('uwil_public_id', 'LGL01')->first();
|
||||
expect($brand)->not->toBeNull()
|
||||
->and($brand?->arcadia_id->value)->toBe(1)
|
||||
->and($brand?->arcadiaBrand)->not->toBeNull()
|
||||
->and($brand?->arcadiaBrand?->libelle)->toBe('Le Gaulois');
|
||||
});
|
||||
});
|
||||
|
|
@ -6,11 +6,13 @@ namespace Tests\Integration\Database\Seeders;
|
|||
|
||||
use Database\Seeders\DatabaseSeeder;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Infra\Database\Models\ArcadiaBrand;
|
||||
use Infra\Database\Models\Brand;
|
||||
use Infra\Database\Models\Employee;
|
||||
use Infra\Database\Models\IncoSheet;
|
||||
use Tests\TestCase;
|
||||
|
||||
covers(Employee::class, IncoSheet::class);
|
||||
covers(Employee::class, IncoSheet::class, Brand::class, ArcadiaBrand::class);
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
|
|
@ -24,6 +26,8 @@ describe('DatabaseSeeder Integration Test', function (): void {
|
|||
|
||||
// THEN
|
||||
expect(Employee::count())->toBeGreaterThan(0)
|
||||
->and(IncoSheet::count())->toBeGreaterThan(0);
|
||||
->and(IncoSheet::count())->toBeGreaterThan(0)
|
||||
->and(Brand::count())->toBeGreaterThan(0)
|
||||
->and(ArcadiaBrand::count())->toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Infra\Adapters\Directories;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaBrand;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Infra\Adapters\Directories\InMemoryArcadiaBrandDirectory;
|
||||
|
||||
covers(InMemoryArcadiaBrandDirectory::class);
|
||||
|
||||
describe('InMemoryArcadiaBrandDirectory Unit Tests', function (): void {
|
||||
it('finds an arcadia brand by id or returns null if not found', function (): void {
|
||||
// GIVEN
|
||||
$directory = new InMemoryArcadiaBrandDirectory();
|
||||
$brand = new ArcadiaBrand(
|
||||
id: new ArcadiaId(1),
|
||||
name: new BrandName('Le Gaulois'),
|
||||
);
|
||||
$directory->add($brand);
|
||||
|
||||
// WHEN
|
||||
$found = $directory->find(new ArcadiaId(1));
|
||||
$missing = $directory->find(new ArcadiaId(999));
|
||||
|
||||
// THEN
|
||||
expect($found)->toBe($brand)
|
||||
->and($missing)->toBeNull();
|
||||
});
|
||||
|
||||
it('returns all arcadia brands and clears them', function (): void {
|
||||
// GIVEN
|
||||
$directory = new InMemoryArcadiaBrandDirectory();
|
||||
$b1 = new ArcadiaBrand(new ArcadiaId(1), new BrandName('Le Gaulois'));
|
||||
$b2 = new ArcadiaBrand(new ArcadiaId(2), new BrandName('Maître CoQ'));
|
||||
$directory->add($b1, $b2);
|
||||
|
||||
// WHEN
|
||||
$all = $directory->findAll();
|
||||
|
||||
// THEN
|
||||
expect($all)->toHaveCount(2);
|
||||
|
||||
// WHEN
|
||||
$directory->clear();
|
||||
|
||||
// THEN
|
||||
expect($directory->findAll())->toBeEmpty();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Infra\Adapters\Repositories;
|
||||
|
||||
use Domain\Exceptions\BrandNotFoundException;
|
||||
use Domain\Models\Entities\Brand;
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Domain\Models\ValueObjects\Brand\BrandId;
|
||||
use Domain\Models\ValueObjects\Brand\BrandName;
|
||||
use Infra\Adapters\Repositories\InMemoryBrandRepository;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
|
||||
covers(InMemoryBrandRepository::class);
|
||||
|
||||
describe('InMemoryBrandRepository Unit Tests', function (): void {
|
||||
function createBrand(string $code, string $name, int $arcadiaId): Brand
|
||||
{
|
||||
return Brand::hydrate(
|
||||
id: new BrandId(Uuid::uuid7()),
|
||||
name: new BrandName($name),
|
||||
code: new BrandCode($code),
|
||||
arcadiaId: new ArcadiaId($arcadiaId),
|
||||
);
|
||||
}
|
||||
|
||||
it('finds a brand by id and by code', function (): void {
|
||||
// GIVEN
|
||||
$repository = new InMemoryBrandRepository();
|
||||
$brand = createBrand('LGL01', 'Le Gaulois', 1);
|
||||
$repository->add($brand);
|
||||
|
||||
// WHEN
|
||||
$foundById = $repository->find($brand->id());
|
||||
$foundByCode = $repository->findByCode(new BrandCode('LGL01'));
|
||||
$missingById = $repository->find(new BrandId(Uuid::uuid7()));
|
||||
$missingByCode = $repository->findByCode(new BrandCode('MCQ98'));
|
||||
|
||||
// THEN
|
||||
expect($foundById)->toBe($brand)
|
||||
->and($foundByCode)->toBe($brand)
|
||||
->and($missingById)->toBeNull()
|
||||
->and($missingByCode)->toBeNull();
|
||||
});
|
||||
|
||||
it('persists and deletes a brand', function (): void {
|
||||
// GIVEN
|
||||
$repository = new InMemoryBrandRepository();
|
||||
$brand = createBrand('MCQ02', 'Maître CoQ', 2);
|
||||
|
||||
// WHEN
|
||||
$repository->persist($brand);
|
||||
|
||||
// THEN
|
||||
expect($repository->find($brand->id()))->toBe($brand);
|
||||
|
||||
// WHEN
|
||||
$repository->delete($brand);
|
||||
|
||||
// THEN
|
||||
expect($repository->find($brand->id()))->toBeNull();
|
||||
});
|
||||
|
||||
it('throws BrandNotFoundException when deleting non-existent brand', function (): void {
|
||||
// GIVEN
|
||||
$repository = new InMemoryBrandRepository();
|
||||
$brand = createBrand('MCQ02', 'Maître CoQ', 2);
|
||||
|
||||
// WHEN & THEN
|
||||
expect(fn () => $repository->delete($brand))
|
||||
->toThrow(BrandNotFoundException::class);
|
||||
});
|
||||
|
||||
it('clears all brands', function (): void {
|
||||
// GIVEN
|
||||
$repository = new InMemoryBrandRepository();
|
||||
$brand = createBrand('MCQ02', 'Maître CoQ', 2);
|
||||
$repository->add($brand);
|
||||
|
||||
// WHEN
|
||||
$repository->clear();
|
||||
|
||||
// THEN
|
||||
expect($repository->find($brand->id()))->toBeNull();
|
||||
});
|
||||
});
|
||||
108
tests/Unit/Infra/Casts/ArcadiaIdCastTest.php
Normal file
108
tests/Unit/Infra/Casts/ArcadiaIdCastTest.php
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Infra\Casts;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\ArcadiaId;
|
||||
use Infra\Casts\ArcadiaIdCast;
|
||||
use Infra\Database\Models\Brand;
|
||||
|
||||
covers(ArcadiaIdCast::class);
|
||||
|
||||
describe('ArcadiaIdCast Unit Tests', function (): void {
|
||||
$cast = new ArcadiaIdCast();
|
||||
$model = new Brand();
|
||||
|
||||
it('transforms integer from database to ArcadiaId instance on get', function () use ($cast, $model): void {
|
||||
// GIVEN
|
||||
$dbValue = 123;
|
||||
|
||||
// WHEN
|
||||
$result = $cast->get($model, 'arcadia_id', $dbValue, []);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBeInstanceOf(ArcadiaId::class)
|
||||
->and($result->value)->toBe(123);
|
||||
});
|
||||
|
||||
it('transforms numeric string from database to ArcadiaId instance on get', function () use ($cast, $model): void {
|
||||
// GIVEN
|
||||
$dbValue = '456';
|
||||
|
||||
// WHEN
|
||||
$result = $cast->get($model, 'arcadia_id', $dbValue, []);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBeInstanceOf(ArcadiaId::class)
|
||||
->and($result->value)->toBe(456);
|
||||
});
|
||||
|
||||
it('throws InvalidArgumentException on get when value is null', function () use ($cast, $model): void {
|
||||
// GIVEN & WHEN
|
||||
expect(fn () => $cast->get($model, 'arcadia_id', null, []))
|
||||
|
||||
// THEN
|
||||
->toThrow(\InvalidArgumentException::class, 'ArcadiaId value cannot be null');
|
||||
});
|
||||
|
||||
it('throws InvalidArgumentException on get when value is invalid type', function () use ($cast, $model): void {
|
||||
// GIVEN & WHEN
|
||||
expect(fn () => $cast->get($model, 'arcadia_id', 'abc', []))
|
||||
|
||||
// THEN
|
||||
->toThrow(\InvalidArgumentException::class, 'Value must be an integer or numeric string');
|
||||
});
|
||||
|
||||
it('transforms ArcadiaId instance to integer on set', function () use ($cast, $model): void {
|
||||
// GIVEN
|
||||
$arcadiaId = new ArcadiaId(789);
|
||||
|
||||
// WHEN
|
||||
$result = $cast->set($model, 'arcadia_id', $arcadiaId, []);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBe(789);
|
||||
});
|
||||
|
||||
it('transforms raw integer to integer on set', function () use ($cast, $model): void {
|
||||
// GIVEN
|
||||
$value = 42;
|
||||
|
||||
// WHEN
|
||||
$result = $cast->set($model, 'arcadia_id', $value, []);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBe(42);
|
||||
});
|
||||
|
||||
it('transforms numeric string to integer on set', function () use ($cast, $model): void {
|
||||
// GIVEN
|
||||
$value = '99';
|
||||
|
||||
// WHEN
|
||||
$result = $cast->set($model, 'arcadia_id', $value, []);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBe(99);
|
||||
});
|
||||
|
||||
it('throws InvalidArgumentException on set when value is null', function () use ($cast, $model): void {
|
||||
// GIVEN & WHEN
|
||||
expect(fn () => $cast->set($model, 'arcadia_id', null, []))
|
||||
|
||||
// THEN
|
||||
->toThrow(\InvalidArgumentException::class, 'ArcadiaId value cannot be null');
|
||||
});
|
||||
|
||||
it('throws InvalidArgumentException on set when value is invalid type', function () use ($cast, $model): void {
|
||||
// GIVEN
|
||||
$invalid = ['array-not-supported'];
|
||||
|
||||
// WHEN
|
||||
expect(fn () => $cast->set($model, 'arcadia_id', $invalid, []))
|
||||
|
||||
// THEN
|
||||
->toThrow(\InvalidArgumentException::class, 'Value must be an integer, numeric string, or instance of ArcadiaId');
|
||||
});
|
||||
});
|
||||
88
tests/Unit/Infra/Casts/BrandCodeCastTest.php
Normal file
88
tests/Unit/Infra/Casts/BrandCodeCastTest.php
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Infra\Casts;
|
||||
|
||||
use Domain\Models\ValueObjects\Brand\BrandCode;
|
||||
use Infra\Casts\BrandCodeCast;
|
||||
use Infra\Database\Models\Brand;
|
||||
|
||||
covers(BrandCodeCast::class);
|
||||
|
||||
describe('BrandCodeCast Unit Tests', function (): void {
|
||||
$cast = new BrandCodeCast();
|
||||
$model = new Brand();
|
||||
|
||||
it('transforms string from database to BrandCode instance on get', function () use ($cast, $model): void {
|
||||
// GIVEN
|
||||
$dbValue = 'LGL89';
|
||||
|
||||
// WHEN
|
||||
$result = $cast->get($model, 'uwil_public_id', $dbValue, []);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBeInstanceOf(BrandCode::class)
|
||||
->and($result->value)->toBe('LGL89');
|
||||
});
|
||||
|
||||
it('throws InvalidArgumentException on get when value is null', function () use ($cast, $model): void {
|
||||
// GIVEN & WHEN
|
||||
expect(fn () => $cast->get($model, 'uwil_public_id', null, []))
|
||||
|
||||
// THEN
|
||||
->toThrow(\InvalidArgumentException::class, 'BrandCode value cannot be null');
|
||||
});
|
||||
|
||||
it('throws InvalidArgumentException on get when value is not a string', function () use ($cast, $model): void {
|
||||
// GIVEN
|
||||
/** @var mixed $invalid */
|
||||
$invalid = 12345;
|
||||
|
||||
// WHEN
|
||||
expect(fn () => $cast->get($model, 'uwil_public_id', $invalid, []))
|
||||
|
||||
// THEN
|
||||
->toThrow(\InvalidArgumentException::class, 'Value must be a string');
|
||||
});
|
||||
|
||||
it('transforms BrandCode instance to string on set', function () use ($cast, $model): void {
|
||||
// GIVEN
|
||||
$brandCode = new BrandCode('ABC12');
|
||||
|
||||
// WHEN
|
||||
$result = $cast->set($model, 'uwil_public_id', $brandCode, []);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBe('ABC12');
|
||||
});
|
||||
|
||||
it('transforms raw string to validated string on set', function () use ($cast, $model): void {
|
||||
// GIVEN
|
||||
$value = 'XYZ98';
|
||||
|
||||
// WHEN
|
||||
$result = $cast->set($model, 'uwil_public_id', $value, []);
|
||||
|
||||
// THEN
|
||||
expect($result)->toBe('XYZ98');
|
||||
});
|
||||
|
||||
it('throws InvalidArgumentException on set when value is null', function () use ($cast, $model): void {
|
||||
// GIVEN & WHEN
|
||||
expect(fn () => $cast->set($model, 'uwil_public_id', null, []))
|
||||
// THEN
|
||||
->toThrow(\InvalidArgumentException::class, 'BrandCode value cannot be null');
|
||||
});
|
||||
|
||||
it('throws InvalidArgumentException on set when value is not a string or BrandCode', function () use ($cast, $model): void {
|
||||
// GIVEN
|
||||
$invalid = 12345;
|
||||
|
||||
// WHEN
|
||||
expect(fn () => $cast->set($model, 'uwil_public_id', $invalid, []))
|
||||
|
||||
// THEN
|
||||
->toThrow(\InvalidArgumentException::class, 'Value must be a string or instance of BrandCode');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue