Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ddd Foundation Laravel Package

alexandrebulete/ddd-foundation

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps to First Use

  1. Install the Package

    composer require alexandrebulete/ddd-foundation
    

    Add to composer.json under require-dev if only for testing:

    "alexandrebulete/ddd-foundation": "^1.1"
    
  2. Define a Value Object Create a domain-specific value object (e.g., EmailVO) in app/Domain/ValueObject/EmailVO.php:

    use AlexandreBulete\DddFoundation\Domain\ValueObject\StringVO;
    
    final class EmailVO extends StringVO
    {
        public static function fromString(string $value): self
        {
            return parent::fromString($value);
        }
    }
    
  3. Use in a Model Integrate into a Laravel model (e.g., User.php):

    use App\Domain\ValueObject\EmailVO;
    
    class User
    {
        private EmailVO $email;
    
        public function __construct(EmailVO $email)
        {
            $this->email = $email;
        }
    
        public function getEmail(): EmailVO
        {
            return $this->email;
        }
    }
    
  4. Create a Command/Handler Define a simple command and handler in app/Application/Command/CreateUserCommand.php and app/Application/Handler/CreateUserHandler.php:

    // Command
    use AlexandreBulete\DddFoundation\Application\Command\CommandInterface;
    
    readonly class CreateUserCommand implements CommandInterface
    {
        public function __construct(
            public string $email,
            public string $name,
        ) {}
    }
    
    // Handler
    use AlexandreBulete\DddFoundation\Application\Command\AsCommandHandler;
    
    #[AsCommandHandler]
    readonly class CreateUserHandler
    {
        public function __invoke(CreateUserCommand $command): User
        {
            return new User(EmailVO::fromString($command->email));
        }
    }
    
  5. Register the Command Bus Bind the command bus in a service provider (e.g., AppServiceProvider.php):

    use AlexandreBulete\DddFoundation\Application\Command\CommandBusInterface;
    use AlexandreBulete\DddFoundation\Infrastructure\CommandBus;
    
    public function register(): void
    {
        $this->app->singleton(CommandBusInterface::class, fn () => new CommandBus());
    }
    
  6. Dispatch a Command Use the command bus in a controller or service:

    use App\Application\Command\CreateUserCommand;
    use Illuminate\Support\Facades\App;
    
    $commandBus = App::make(CommandBusInterface::class);
    $user = $commandBus->dispatch(new CreateUserCommand('test@example.com', 'John Doe'));
    

First Use Case: Domain Validation

Replace Laravel’s form validation with domain-level validation:

use AlexandreBulete\DddFoundation\Domain\ValueObject\EmailVO;

final class EmailVO extends StringVO
{
    public static function fromString(string $value): self
    {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException('Invalid email format.');
        }
        return parent::fromString($value);
    }
}

Now validation is enforced at the domain layer, not just the application layer.


Implementation Patterns

Core Workflows

1. Domain Layer Patterns

  • Value Objects as Immutables Use StringVO, IdentifierVO, or custom VOs to enforce immutability and validation:

    final class PriceVO
    {
        private function __construct(private float $amount) {}
    
        public static function fromFloat(float $amount): self
        {
            if ($amount < 0) {
                throw new \InvalidArgumentException('Price cannot be negative.');
            }
            return new self($amount);
        }
    
        public function getAmount(): float { return $this->amount; }
    }
    
  • Entities with Identity Use IdentifierVO for entity IDs to decouple from database concerns:

    use AlexandreBulete\DddFoundation\Domain\ValueObject\IdentifierVO;
    
    final class User
    {
        private function __construct(
            private IdentifierVO $id,
            private EmailVO $email,
        ) {}
    
        public static function create(EmailVO $email): self
        {
            return new self(IdentifierVO::generate(), $email);
        }
    
        public function getId(): IdentifierVO { return $this->id; }
    }
    
  • Domain Events Emit events from entities (e.g., UserCreated, OrderShipped) and handle them via Laravel’s event system:

    use Illuminate\Support\Facades\Event;
    
    class User
    {
        public function __construct(EmailVO $email)
        {
            Event::dispatch(new UserCreated($this));
        }
    }
    

2. Application Layer Patterns

  • CQRS with Command/Query Handlers Separate read (queries) and write (commands) operations:

    // Query
    use AlexandreBulete\DddFoundation\Application\Query\QueryInterface;
    
    readonly class GetUserQuery implements QueryInterface
    {
        public function __construct(public IdentifierVO $userId) {}
    }
    
    // Handler
    use AlexandreBulete\DddFoundation\Application\Query\AsQueryHandler;
    
    #[AsQueryHandler]
    readonly class GetUserHandler
    {
        public function __invoke(GetUserQuery $query): User
        {
            return $this->userRepository->find($query->userId);
        }
    }
    
  • Criteria for Filtering Use CriteriaBuilder to construct complex filters:

    $criteria = (new CriteriaBuilder())
        ->eq('status', 'published')
        ->gte('createdAt', now()->subDays(7))
        ->in('category', ['news', 'tech']);
    
    $users = $this->userRepository->findAll($criteria);
    
  • Normalizing Criteria Extend CriteriaNormalizer to add domain logic:

    final class UserCriteriaNormalizer extends CriteriaNormalizer
    {
        public function normalize(array $criteria): array
        {
            $criteria = parent::normalize($criteria);
            return $this->normalizeSearchCriteria($criteria);
        }
    
        private function normalizeSearchCriteria(array $criteria): array
        {
            if (isset($criteria['search'])) {
                return $this->mergeCriteria($criteria, [
                    'name' => ['type' => 'like', 'value' => '%' . $criteria['search'] . '%'],
                    'email' => ['type' => 'like', 'value' => '%' . $criteria['search'] . '%'],
                ]);
            }
            return $criteria;
        }
    }
    

3. Infrastructure Layer Patterns

  • In-Memory Repository for Testing Use InMemoryRepository in PHPUnit tests:

    use AlexandreBulete\DddFoundation\Infrastructure\InMemory\InMemoryRepository;
    
    $repository = new InMemoryRepository();
    $repository->save(new User(EmailVO::fromString('test@example.com')));
    $user = $repository->find(IdentifierVO::fromString('user-id'));
    
  • Repository Interface Implementation Implement RepositoryInterface for Eloquent or other ORMs:

    use AlexandreBulete\DddFoundation\Domain\Repository\RepositoryInterface;
    use App\Models\User as EloquentUser;
    
    class UserRepository implements RepositoryInterface
    {
        public function find(IdentifierVO $id): ?User
        {
            $eloquentUser = EloquentUser::find($id->toString());
            return $eloquentUser ? new User($eloquentUser->id, EmailVO::fromString($eloquentUser->email)) : null;
        }
    
        public function save(User $user): void
        {
            EloquentUser::updateOrCreate(
                ['id' => $user->getId()->toString()],
                [
                    'email' => $user->getEmail()->toString(),
                ]
            );
        }
    }
    

4. Integration with Laravel

  • Service Provider Setup Register the package in AppServiceProvider:

    public function register(): void
    {
        $this->app->singleton(CommandBusInterface::class, fn () => new CommandBus());
        $this->app->singleton(QueryBusInterface::class, fn () => new QueryBus());
    }
    
  • Command/Query Dispatching Use dependency injection to dispatch commands/queries:

    use App\Application\Command\CreateUserCommand;
    use App\Application\Query\GetUserQuery;
    
    class UserController extends Controller
    {
        public function __construct(
            private CommandBusInterface $commandBus,
            private QueryBusInterface $queryBus,
        ) {}
    
        public function store(CreateUserRequest $request)
        {
            $command = new CreateUserCommand(
                $request->email,
                $request->name,
            );
            $this->commandBus->dispatch($command);
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
terminal42/code-quality-tools
codifyo/ts-generator-bundle
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky