PHP code example of andydefer / domain-structures

1. Go to this page and download the library: Download andydefer/domain-structures library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

andydefer / domain-structures example snippets


use AndyDefer\DomainStructures\Abstracts\AbstractValueObject;

final class EmailAddress extends AbstractValueObject
{
    public function __construct(
        private readonly string $value
    ) {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException("Invalid email address");
        }
    }
    
    public function getValue(): string
    {
        return $this->value;
    }
    
    public function getDomain(): string
    {
        return substr(strrchr($this->value, "@"), 1);
    }
    
    public function isGmail(): bool
    {
        return $this->getDomain() === 'gmail.com';
    }
}

// Utilisation
$email = new EmailAddress('[email protected]');
echo $email->getDomain(); // 'example.com'

use AndyDefer\DomainStructures\Abstracts\AbstractRecord;

final class UserRecord extends AbstractRecord
{
    public function __construct(
        public readonly ?int $id,
        public readonly string $name,
        public readonly EmailAddress $email,
        public readonly UserRole $role,
        public readonly Iso8601DateTime $createdAt,
    ) {}
}

use AndyDefer\DomainStructures\Abstracts\AbstractData;

final class UserData extends AbstractData
{
    public function __construct(
        public readonly UserId $id,
        public readonly PersonName $name,
        public readonly EmailAddress $email,
        public readonly Iso8601DateTime $createdAt,
        public readonly UserRole $role,
    ) {}
}

use AndyDefer\DomainStructures\Collections\Utility\StringTypedCollection;

// Création et ajout
$strings = new StringTypedCollection();
$strings->add('hello', 'world', 'foo');

// Accès
foreach ($strings as $string) {
    echo $string;
}

// Création de collection spécialisée
final class UserRecordCollection extends TypedCollection
{
    public function __construct()
    {
        parent::__construct(UserRecord::class);
    }
    
    public function getAdmins(): self
    {
        return $this->filter(fn(UserRecord $user) => $user->role === UserRole::ADMIN);
    }
}

use AndyDefer\DomainStructures\Utils\DataObject;

// Source externe (snake_case)
$apiData = [
    'user_id' => 123,
    'first_name' => 'John',
    'last_name' => 'Doe'
];

// Création via le constructeur
$normalized = new DataObject($apiData);

// Accès indifférent camelCase/snake_case
echo $normalized->userId;      // 123
echo $normalized->first_name;  // "John"
echo $normalized->lastName;    // "Doe"

// Transformation immuable
$updated = $normalized->with('email', '[email protected]');
$merged = $updated->merge(['role' => 'admin']);
$without = $merged->without('temp_field');

use AndyDefer\DomainStructures\Services\HydrationService;

$hydration = new HydrationService();

// Hydratation d'un seul item
$user = $hydration->hydrate(UserRecord::class, [
    'id' => 123,
    'name' => 'John Doe',
    'email' => '[email protected]',
    'role' => 'admin',
    'created_at' => '2024-01-01T12:00:00+00:00'
]);

// Hydratation depuis JSON
$user = $hydration->hydrateFromJson(UserRecord::class, $jsonString);

// Hydratation d'une collection
$users = $hydration->collect($rows, UserRecordCollection::class);

// Hydratation d'une collection depuis JSON
$users = $hydration->collectFromJson($jsonArray, UserRecordCollection::class);

use AndyDefer\DomainStructures\Normalizers\NormalizerChain;

$user = $hydration->hydrate(UserRecord::class, $row);

// Normalisation automatique
$normalized = NormalizerChain::get()->normalize($user);
// Résultat : ['id' => 123, 'name' => 'John Doe', 'email' => '[email protected]', 'role' => 'admin', 'created_at' => '2024-01-01T12:00:00+00:00']

// JSON direct
$json = json_encode($normalized);

use AndyDefer\DomainStructures\Services\HydrationService;
use AndyDefer\DomainStructures\Normalizers\NormalizerChain;
use AndyDefer\DomainStructures\Utils\DataObject;

// 1. Définir les Value Objects
final class EmailAddress extends AbstractValueObject
{
    public function __construct(private readonly string $value)
    {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException("Invalid email");
        }
    }
    
    public function getValue(): string { return $this->value; }
}

final class Iso8601DateTime extends AbstractValueObject
{
    public function __construct(private readonly string $value)
    {
        if (!strtotime($value)) {
            throw new \InvalidArgumentException("Invalid date");
        }
    }
    
    public function getValue(): string { return $this->value; }
}

// 2. Définir l'Enum
enum UserRole: string 
{ 
    case ADMIN = 'admin'; 
    case USER = 'user'; 
}

// 3. Définir le Record
final class UserRecord extends AbstractRecord
{
    public function __construct(
        public readonly ?int $id,
        public readonly string $name,
        public readonly EmailAddress $email,
        public readonly UserRole $role,
        public readonly Iso8601DateTime $createdAt,
    ) {}
}

// 4. Définir la collection spécialisée
final class UserRecordCollection extends TypedCollection
{
    public function __construct()
    {
        parent::__construct(UserRecord::class);
    }
    
    public function getAdmins(): self
    {
        return $this->filter(fn(UserRecord $user) => $user->role === UserRole::ADMIN);
    }
}

// 5. Définir le Data DTO pour l'API
final class UserData extends AbstractData
{
    public function __construct(
        public readonly int $id,
        public readonly string $name,
        public readonly string $email,
        public readonly string $role,
        public readonly string $createdAt,
    ) {}
}

// 6. Utilisation dans un Repository
class UserRepository
{
    private HydrationService $hydration;

    public function __construct(HydrationService $hydration)
    {
        $this->hydration = $hydration;
    }

    public function find(int $id): ?UserRecord
    {
        $row = $this->db->fetchAssoc('SELECT * FROM users WHERE id = ?', [$id]);
        if (!$row) return null;
        
        return $this->hydration->hydrate(UserRecord::class, $row);
    }
    
    public function findAll(): UserRecordCollection
    {
        $rows = $this->db->fetchAllAssoc('SELECT * FROM users');
        return $this->hydration->collect($rows, UserRecordCollection::class);
    }
}

// 7. Utilisation dans un Controller
class UserController
{
    public function show(int $id): JsonResponse
    {
        $user = $this->userRepository->find($id);
        
        // Convertir Record en Data DTO pour l'API
        $userData = new UserData(
            id: $user->id,
            name: $user->name,
            email: $user->email->getValue(),
            role: $user->role->value,
            createdAt: $user->createdAt->getValue()
        );
        
        // Normalisation automatique (camelCase pour le client)
        return response()->json(NormalizerChain::get()->normalize($userData));
    }
}

// 8. Utilisation de DataObject pour les sources externes
$apiResponse = new DataObject([
    'user_id' => 123,
    'first_name' => 'John',
    'last_name' => 'Doe'
]);

echo $apiResponse->user_id;      // 123
echo $apiResponse->firstName;    // 'John'
echo $apiResponse->last_name;    // 'Doe'

// ✅ BON - Validation centralisée dans le constructeur
final class EmailAddress extends AbstractValueObject
{
    public function __construct(private readonly string $value)
    {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException("Invalid email");
        }
    }
}

// ❌ MAUVAIS - Validation dispersée
$email = $_POST['email'];
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { /* ... */ }

// ✅ BON - Uniquement pour la communication interne
public function find(int $id): UserRecord

// ❌ MAUVAIS - Dans une réponse API (utilisez Data DTO)
return response()->json($userRecord);

// ✅ BON - Pour les réponses API
return response()->json($userData);

// ❌ MAUVAIS - Avec des types primitifs
public readonly int $id; // Interdit ! Utilisez UserId $id

// ✅ BON - Collection spécialisée avec comportement métier
final class ProductRecordCollection extends TypedCollection
{
    public function getActive(): self { /* ... */ }
}

// ❌ MAUVAIS - TypedCollection générique sans comportement
public readonly TypedCollection $products;

// ✅ BON - Utilisation du service d'hydratation
$hydration = new HydrationService();
$user = $hydration->hydrate(UserRecord::class, $data);

// ❌ MAUVAIS - Hydratation manuelle
$user = new UserRecord(
    id: $data['id'],
    name: $data['name'],
    // ... des dizaines de champs à mapper manuellement
);

// ✅ BON - Utilisation du constructeur
$data = new DataObject($source);

// ❌ MAUVAIS - Utilisation de ::from() (déprécié)
$data = DataObject::from($source);