refactor(domain): make read-only entity constructors private and introduce hydrate static methods

This commit is contained in:
Jérémy Munsch 2026-08-20 15:05:33 +02:00
parent 66fc714ca0
commit 6be00bb21e
Signed by: forge-joniko
GPG key ID: D7EFE2F5CB6CC0E4
23 changed files with 165 additions and 31 deletions

View file

@ -13,7 +13,7 @@ use Shared\DDD\Model\Entity;
/** @implements Entity<ArcadiaEmployeeId> */
final readonly class Employee implements Entity
{
public function __construct(
private function __construct(
public ArcadiaEmployeeId $publicEmployeeNumber,
public string $lastName,
public string $firstName,
@ -33,4 +33,32 @@ final readonly class Employee implements Entity
{
return $this->publicEmployeeNumber;
}
public static function hydrate(
ArcadiaEmployeeId $publicEmployeeNumber,
string $lastName,
string $firstName,
\DateTimeInterface $startDate,
?\DateTimeInterface $endDate,
?\DateTimeInterface $plannedEndDate,
EstablishmentCode $establishment,
SiteId $site,
ServiceId $service,
\DateTimeInterface $createdAt,
?\DateTimeInterface $updatedAt,
): self {
return new self(
publicEmployeeNumber: $publicEmployeeNumber,
lastName: $lastName,
firstName: $firstName,
startDate: $startDate,
endDate: $endDate,
plannedEndDate: $plannedEndDate,
establishment: $establishment,
site: $site,
service: $service,
createdAt: $createdAt,
updatedAt: $updatedAt,
);
}
}

View file

@ -25,7 +25,7 @@ use Shared\DDD\Model\Entity;
/** @implements Entity<IncoCompositionId> */
final readonly class IncoComposition implements Entity
{
public function __construct(
private function __construct(
public IncoCompositionId $id,
public IncoId $idInco,
public Identification $identification,
@ -50,4 +50,42 @@ final readonly class IncoComposition implements Entity
{
return $this->id;
}
public static function hydrate(
IncoCompositionId $id,
IncoId $idInco,
Identification $identification,
Energy $energy,
ReferenceMacronutrients $referenceMacronutrients,
PortionMacronutrients $portionMacronutrients,
FreshProduceAndNuts $freshProduceAndNuts,
ReferenceIntakes $referenceIntakes,
NutriScore $nutriScore,
NutriScorePoints $nutriScorePoints,
Minerals $minerals,
Vitamins $vitamins,
CarbohydratesDetail $carbohydratesDetail,
FattyAcids $fattyAcids,
AminoAcids $aminoAcids,
SpecialCompounds $specialCompounds,
): self {
return new self(
id: $id,
idInco: $idInco,
identification: $identification,
energy: $energy,
referenceMacronutrients: $referenceMacronutrients,
portionMacronutrients: $portionMacronutrients,
freshProduceAndNuts: $freshProduceAndNuts,
referenceIntakes: $referenceIntakes,
nutriScore: $nutriScore,
nutriScorePoints: $nutriScorePoints,
minerals: $minerals,
vitamins: $vitamins,
carbohydratesDetail: $carbohydratesDetail,
fattyAcids: $fattyAcids,
aminoAcids: $aminoAcids,
specialCompounds: $specialCompounds,
);
}
}

View file

@ -22,7 +22,7 @@ final readonly class IncoSheet implements Entity
* @param array<\Domain\Models\ValueObjects\Inco\Allergen> $allergens
* @param array<\Domain\Models\ValueObjects\Inco\Additive> $additives
*/
public function __construct(
private function __construct(
public IncoId $id,
public string $name,
public string $label,
@ -52,4 +52,56 @@ final readonly class IncoSheet implements Entity
{
return $this->id;
}
/**
* @param array<\Domain\Models\ValueObjects\Inco\Allergen> $allergens
* @param array<\Domain\Models\ValueObjects\Inco\Additive> $additives
*/
public static function hydrate(
IncoId $id,
string $name,
string $label,
string $description,
Allegations $allegations,
string $sellArguments,
string $preparationAdvices,
?string $tips,
?string $sellFamilly,
Ingredients $ingredients,
?PortionAmount $portions,
array $allergens,
Brand $brand,
bool $isSeasonal,
?IncoWeight $weight,
?Nutriscore $nutriscore,
?StorageCondition $storageCondition,
array $additives,
?IncoCompositionId $compo,
?\DateTimeInterface $createdAt,
?\DateTimeInterface $updatedAt,
): self {
return new self(
id: $id,
name: $name,
label: $label,
description: $description,
allegations: $allegations,
sellArguments: $sellArguments,
preparationAdvices: $preparationAdvices,
tips: $tips,
sellFamilly: $sellFamilly,
ingredients: $ingredients,
portions: $portions,
allergens: $allergens,
brand: $brand,
isSeasonal: $isSeasonal,
weight: $weight,
nutriscore: $nutriscore,
storageCondition: $storageCondition,
additives: $additives,
compo: $compo,
createdAt: $createdAt,
updatedAt: $updatedAt,
);
}
}

View file

@ -10,7 +10,7 @@ use Shared\DDD\Model\Entity;
/** @implements Entity<IncoId> */
final readonly class IncoShortSheet implements Entity
{
public function __construct(
private function __construct(
public IncoId $id,
public string $name,
public string $label,
@ -24,4 +24,20 @@ final readonly class IncoShortSheet implements Entity
{
return $this->id;
}
public static function hydrate(
IncoId $id,
string $name,
string $label,
?\DateTimeInterface $createdAt,
?\DateTimeInterface $updatedAt,
): self {
return new self(
id: $id,
name: $name,
label: $label,
createdAt: $createdAt,
updatedAt: $updatedAt,
);
}
}

View file

@ -93,7 +93,7 @@ final class EloquentEmployeeDirectory implements EmployeeDirectory
throw new \UnexpectedValueException('Employee created_at cannot be null.');
}
return new Employee(
return Employee::hydrate(
publicEmployeeNumber: new ArcadiaEmployeeId((string) $employee->public_employee_number),
lastName: (string) $employee->last_name,
firstName: (string) $employee->first_name,

View file

@ -60,7 +60,7 @@ final class EloquentIncoCompositionDirectory implements IncoCompositionDirectory
private function toDomain(IncoCompoModel $model): IncoComposition
{
return new IncoComposition(
return IncoComposition::hydrate(
id: new IncoCompositionId((string) $model->id),
idInco: new IncoId((string) $model->id_inco),
identification: $this->identificationFrom($model),

View file

@ -128,7 +128,7 @@ final class EloquentIncoSheetDirectory implements IncoSheetDirectory
$compo = $model->compo_id !== null ? new IncoCompositionId((string) $model->compo_id) : null;
return new IncoSheet(
return IncoSheet::hydrate(
id: new IncoId((string) $model->id),
name: (string) $model->name,
label: (string) $model->label,

View file

@ -121,7 +121,7 @@ final class EloquentIncoShortSheetDirectory implements IncoShortSheetDirectory
$createdAt = is_string($rawCreatedAt) ? DatesHelper::parseDateTime($rawCreatedAt) : null;
$updatedAt = is_string($rawUpdatedAt) ? DatesHelper::parseDateTime($rawUpdatedAt) : null;
return new IncoShortSheet(
return IncoShortSheet::hydrate(
id: new IncoId($id),
name: $name,
label: $label,

View file

@ -4,6 +4,6 @@ declare(strict_types=1);
namespace Shared\DDD\Exception;
class NotFoundException extends \RuntimeException
class NotFoundException extends \DomainException
{
}

View file

@ -35,7 +35,7 @@ describe('FindAllEmployees Usecase Unit Tests', function (): void {
it('executes successfully and returns paginated output DTO', function () use ($directory): void {
// GIVEN
$employeeId = new ArcadiaEmployeeId('01155902');
$employee = new Employee(
$employee = Employee::hydrate(
publicEmployeeNumber: $employeeId,
lastName: 'DOE',
firstName: 'John',

View file

@ -31,7 +31,7 @@ describe('FindEmployee Usecase Unit Tests', function (): void {
it('executes successfully and returns Output when employee exists', function () use ($directory): void {
// GIVEN
$employeeId = new ArcadiaEmployeeId('01155902');
$employee = new Employee(
$employee = Employee::hydrate(
publicEmployeeNumber: $employeeId,
lastName: 'DOE',
firstName: 'John',

View file

@ -72,7 +72,7 @@ describe('FindIncoComposition Usecase Unit Tests', function (): void {
$amino = new AminoAcids('0', '0', '0');
$special = new SpecialCompounds('0', '0', '0', '0');
return new IncoComposition(
return IncoComposition::hydrate(
id: $compoId,
idInco: $incoId,
identification: $identification,

View file

@ -42,7 +42,7 @@ describe('FindAllIncoSheets DTOs Instantiation & Logic', function (): void {
it('creates PaginatedIncoSheet and PaginatedShortIncoSheet from domain entity and instantiates Output DTO', function (): void {
// GIVEN
$incoId = new IncoId('12345');
$incoSheet = new IncoSheet(
$incoSheet = IncoSheet::hydrate(
id: $incoId,
name: 'Poulet Rôti 1kg',
label: 'Poulet Rôti Classe A',
@ -66,7 +66,7 @@ describe('FindAllIncoSheets DTOs Instantiation & Logic', function (): void {
updatedAt: null,
);
$shortSheet = new IncoShortSheet(
$shortSheet = IncoShortSheet::hydrate(
id: $incoId,
name: 'Poulet Rôti 1kg',
label: 'Poulet Rôti Classe A',

View file

@ -40,7 +40,7 @@ describe('FindAllIncoSheets Usecase Unit Tests', function (): void {
it('executes successfully and returns paginated output DTO', function () use ($directory, $shortDirectory): void {
// GIVEN
$incoId = new IncoId('12345');
$incoSheet = new IncoSheet(
$incoSheet = IncoSheet::hydrate(
id: $incoId,
name: 'Poulet Rôti 1kg',
label: 'Poulet Rôti Classe A',

View file

@ -31,7 +31,7 @@ describe('FindIncoSheet Usecase Unit Tests', function (): void {
it('executes successfully and returns Output when inco sheet exists', function () use ($directory): void {
// GIVEN
$incoId = new IncoId('12345');
$incoSheet = new IncoSheet(
$incoSheet = IncoSheet::hydrate(
id: $incoId,
name: 'Poulet Rôti 1kg',
label: 'Poulet Rôti Classe A',

View file

@ -39,7 +39,7 @@ describe('SearchIncoSheets Usecase Unit Tests', function (): void {
it('searches full inco sheets matching query and brands', function () use ($directory, $shortDirectory): void {
// GIVEN
$incoSheet = new IncoSheet(
$incoSheet = IncoSheet::hydrate(
id: new IncoId('12345'),
name: 'Poulet Rôti 1kg',
label: 'Poulet Rôti Classe A',
@ -83,7 +83,7 @@ describe('SearchIncoSheets Usecase Unit Tests', function (): void {
it('searches short inco sheets when short option is true', function () use ($directory, $shortDirectory): void {
// GIVEN
$shortSheet = new IncoShortSheet(
$shortSheet = IncoShortSheet::hydrate(
id: new IncoId('12345'),
name: 'Poulet Rôti 1kg',
label: 'Poulet Rôti Classe A',

View file

@ -19,7 +19,7 @@ describe('Employee Entity', function (): void {
$now = new \DateTimeImmutable();
// WHEN
$employee = new Employee(
$employee = Employee::hydrate(
publicEmployeeNumber: $id,
lastName: 'Dupont',
firstName: 'Jean',

View file

@ -201,7 +201,7 @@ describe('IncoComposition Domain Entity Unit Tests', function (): void {
$specialCompounds = new SpecialCompounds('0', '0', '0', '0');
// WHEN
$compo = new IncoComposition(
$compo = IncoComposition::hydrate(
id: $id,
idInco: $idInco,
identification: $identification,

View file

@ -22,7 +22,7 @@ describe('IncoSheet Domain Entity Unit Tests', function (): void {
$brand = new Brand('M01', 'Le Gaulois');
// WHEN
$sheet = new IncoSheet(
$sheet = IncoSheet::hydrate(
id: $id,
name: 'Poulet Rôti 1kg',
label: 'Poulet Rôti Classe A',

View file

@ -22,7 +22,7 @@ describe('InMemoryEmployeeDirectory Unit Tests', function (): void {
// GIVEN
$directory = new InMemoryEmployeeDirectory();
$employeeId = new ArcadiaEmployeeId('01155902');
$employee = new Employee(
$employee = Employee::hydrate(
publicEmployeeNumber: $employeeId,
lastName: 'DOE',
firstName: 'John',
@ -51,7 +51,7 @@ describe('InMemoryEmployeeDirectory Unit Tests', function (): void {
// GIVEN
$directory = new InMemoryEmployeeDirectory();
$emp1 = new Employee(
$emp1 = Employee::hydrate(
publicEmployeeNumber: new ArcadiaEmployeeId('00000001'),
lastName: 'ALICE',
firstName: 'A',
@ -65,7 +65,7 @@ describe('InMemoryEmployeeDirectory Unit Tests', function (): void {
updatedAt: null,
);
$emp2 = new Employee(
$emp2 = Employee::hydrate(
publicEmployeeNumber: new ArcadiaEmployeeId('00000002'),
lastName: 'BOB',
firstName: 'B',
@ -98,7 +98,7 @@ describe('InMemoryEmployeeDirectory Unit Tests', function (): void {
// GIVEN
$directory = new InMemoryEmployeeDirectory();
$emp1 = new Employee(
$emp1 = Employee::hydrate(
publicEmployeeNumber: new ArcadiaEmployeeId('00000001'),
lastName: 'ALICE',
firstName: 'A',
@ -112,7 +112,7 @@ describe('InMemoryEmployeeDirectory Unit Tests', function (): void {
updatedAt: null,
);
$emp2 = new Employee(
$emp2 = Employee::hydrate(
publicEmployeeNumber: new ArcadiaEmployeeId('00000002'),
lastName: 'BOB',
firstName: 'B',
@ -126,7 +126,7 @@ describe('InMemoryEmployeeDirectory Unit Tests', function (): void {
updatedAt: new \DateTimeImmutable('2024-02-01'),
);
$emp3 = new Employee(
$emp3 = Employee::hydrate(
publicEmployeeNumber: new ArcadiaEmployeeId('00000003'),
lastName: 'CHARLIE',
firstName: 'C',
@ -160,7 +160,7 @@ describe('InMemoryEmployeeDirectory Unit Tests', function (): void {
// GIVEN
$directory = new InMemoryEmployeeDirectory();
$employeeId = new ArcadiaEmployeeId('01155902');
$employee = new Employee(
$employee = Employee::hydrate(
publicEmployeeNumber: $employeeId,
lastName: 'DOE',
firstName: 'John',

View file

@ -53,7 +53,7 @@ describe('InMemoryIncoCompositionDirectory Unit Tests', function (): void {
$amino = new AminoAcids('0', '0', '0');
$special = new SpecialCompounds('0', '0', '0', '0');
return new IncoComposition(
return IncoComposition::hydrate(
id: $compoId,
idInco: $incoId,
identification: $identification,

View file

@ -21,7 +21,7 @@ covers(InMemoryIncoSheetDirectory::class);
describe('InMemoryIncoSheetDirectory Unit Tests', function (): void {
function createSheet(string $idStr, string $brandCode, string $brandLabel): IncoSheet
{
return new IncoSheet(
return IncoSheet::hydrate(
id: new IncoId($idStr),
name: 'Sheet ' . $idStr,
label: 'Label ' . $idStr,

View file

@ -18,7 +18,7 @@ covers(InMemoryIncoShortSheetDirectory::class);
describe('InMemoryIncoShortSheetDirectory Unit Tests', function (): void {
function createShortSheet(string $idStr): IncoShortSheet
{
return new IncoShortSheet(
return IncoShortSheet::hydrate(
id: new IncoId($idStr),
name: 'Sheet ' . $idStr,
label: 'Label ' . $idStr,