定制 friends-of-ddd/event-driven 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

friends-of-ddd/event-driven

最新稳定版本:0.3.0

Composer 安装命令:

composer require friends-of-ddd/event-driven

包简介

Interfaces for event-driven architecture

README 文档

README

An abstraction for event bus and command bus patterns following Domain-Driven Design principles.

This library provides a set of interfaces and utilities to implement event-driven architecture in PHP applications, supporting both Command Bus and Event Bus patterns with type-safe message handling.

Requirements

  • PHP: >= 8.2
  • Dependencies:
    • psr/container: ^2.0

Installation

Install the package via Composer:

composer require friends-of-ddd/event-driven

Features

  • 🎯 Command Bus Pattern: Type-safe command dispatching with generic support
  • 📢 Event Bus Pattern: Event-driven architecture support
  • 🧩 Event-Aware Entities: Built-in trait for domain entities to record and dispatch events
  • 📦 Message Collections: Type-safe collections for managing messages with filtering capabilities
  • 🔒 Type Safety: Full PHPStan level 8 compatibility with generics support
  • 🎨 PSR Compliant: Follows PSR standards and best practices

Core Concepts

Messages

All messages (commands and events) implement the base MessageInterface:

<?php

use FriendsOfDdd\EventDriven\Domain\MessageInterface;

interface MessageInterface
{
}

Commands

Commands represent intentions to perform an action in your application:

<?php

use FriendsOfDdd\EventDriven\Application\CommandInterface;

final readonly class CreateTicketCommand implements CommandInterface
{
    public function __construct(
        public string $title,
        public int $clientId,
        public ?int $topicId = null,
    ) {
    }
}

Events

Events represent something that has happened in your domain:

<?php

use FriendsOfDdd\EventDriven\Domain\EventInterface;

final readonly class TicketCreatedEvent implements EventInterface
{
    public function __construct(
        public int $ticketId,
        public string $title,
    ) {
    }
}

Usage Examples

1. Command Bus

Implement the CommandBusInterface to create a command bus. For example, wrap a symfony messenger command bus:

<?php

use FriendsOfDdd\EventDriven\Application\CommandInterface;
use FriendsOfDdd\EventDriven\Application\CommandBusInterface;
use Symfony\Component\Messenger\MessageBusInterface;

final readonly class CommandBus implements CommandBusInterface
{
    public function __construct(
        private MessageBusInterface $commandBus,
    ) {
    }

    public function dispatch(CommandInterface ...$commands): void
    {
        foreach ($commands as $command) {
            $this->commandBus->dispatch($command);
        }
    }
}

Using the Command Bus:

<?php

// Create a command
$command = new CreateTicketCommand(
    title: 'Fix login issue',
    clientId: 123,
    topicId: 5,
);

// Dispatch the command
$commandBus->dispatch($command);

// You can also dispatch multiple commands at once
$commandBus->dispatch(
    new CreateTicketCommand('First ticket', 123, 5),
    new CreateTicketCommand('Second ticket', 124, 6),
);

2. Event Bus

Implement the EventBusInterface to create an event bus. For example, wrap a symfony messenger command bus:

<?php

use FriendsOfDdd\EventDriven\Application\EventBusInterface;
use FriendsOfDdd\EventDriven\Domain\EventInterface;
use Psr\Container\ContainerInterface;
use Symfony\Component\Messenger\MessageBusInterface;

final class EventBus implements EventBusInterface
{
    public function __construct(
        private MessageBusInterface $eventBus,
    ) {
    }

    public function dispatch(EventInterface ...$events): void
    {
        foreach ($events as $event) {
            $this->eventBus->dispatch($event);
        }
    }
}

Using the Event Bus:

<?php

// Create an event
$event = new TicketCreatedEvent(1, 'Ticket title');

// Dispatch the event
$eventBus->dispatch($event);

// You can also dispatch multiple events at once
$eventBus->dispatch(
    new TicketCreatedEvent(2, 'Another ticket'),
    new TicketCreatedEvent(3, 'Third ticket'),
);

3. Event-Aware Entities

Use the EventAwareTrait to enable domain entities to record events:

<?php

use FriendsOfDdd\EventDriven\Domain\EventAwareTrait;

class TicketEntity
{
    use EventAwareTrait;

    private function __construct(
        public readonly int $id,
        private string $title,
        public readonly int $clientId,
        private ?int $topicId = null,
    ) {
    }

    public static function createNew(
        int $id,
        string $title,
        int $clientId,
        ?int $topicId = null,
    ): self {
        $instance = new self($id, $title, $clientId, $topicId);
        
        // Record domain event
        $instance->recordEvents(
            new TicketCreatedEvent($id, $title)
        );

        return $instance;
    }

    public function updateTitle(string $newTitle): void
    {
        $this->title = $newTitle;
        
        // Record multiple events
        $this->recordEvents(
            new TicketUpdatedEvent($this->id),
            new TitleChangedEvent($this->id, $newTitle),
        );
    }

    public function getTitle(): string
    {
        return $this->title;
    }
}

Retrieving and Dispatching Recorded Events:

<?php

// Create a new ticket
$ticket = TicketEntity::createNew(
    id: 1,
    title: 'Login issue',
    clientId: 123,
);

// Pop recorded events (this clears the internal event collection)
$events = $ticket->popRecordedEvents();

// Dispatch events to an event bus
foreach ($events as $event) {
    $eventBus->dispatch($event);
}

// Or dispatch all at once if your bus supports it
$eventBus->dispatch(...$events->toArray());

4. Complete Example: Ticket System

Here's a complete example showing all components working together:

<?php

namespace App\Ticketing;

use FriendsOfDdd\EventDriven\Application\CommandInterface;
use FriendsOfDdd\EventDriven\Application\CommandBusInterface;
use FriendsOfDdd\EventDriven\Application\EventBusInterface;
use FriendsOfDdd\EventDriven\Domain\EventInterface;
use FriendsOfDdd\EventDriven\Domain\EventAwareTrait;

// 1. Define Commands
final readonly class CreateTicketCommand implements CommandInterface
{
    public function __construct(
        public string $title,
        public int $clientId,
    ) {
    }
}

// 2. Define Events
final readonly class TicketCreatedEvent implements EventInterface
{
    public function __construct(
        public int $ticketId,
        public string $title,
    ) {
    }
}

// 3. Create Entity
class Ticket
{
    use EventAwareTrait;

    private function __construct(
        public readonly int $id,
        public readonly string $title,
        public readonly int $clientId,
    ) {
    }

    public static function create(int $id, string $title, int $clientId): self
    {
        $ticket = new self($id, $title, $clientId);
        $ticket->recordEvents(new TicketCreatedEvent($id, $title));
        return $ticket;
    }
}

// 4. Create Command Handler
final class CreateTicketCommandHandler
{
    public function __construct(
        private readonly TicketRepository $repository,
        private readonly CommandBusInterface $commandBus,
        private readonly EventBusInterface $eventBus,
    ) {
    }

    public function handle(CreateTicketCommand $command): void
    {
        $ticket = Ticket::create(
            id: $this->repository->nextId(),
            title: $command->title,
            clientId: $command->clientId,
        );

        $this->repository->save($ticket);

        // Dispatch domain events
        $this->eventBus->dispatch(...$ticket->popRecordedEvents()->toArray());
    }
}

// 5. Create Event Handler
final class NotifyClientWhenTicketCreated
{
    public function __construct(
        private readonly NotificationService $notificationService,
    ) {
    }

    public function handle(TicketCreatedEvent $event): void
    {
        $this->notificationService->notify(
            "Ticket #{$event->ticketId} has been created"
        );
    }
}

// 6. Usage
$command = new CreateTicketCommand('Fix login bug', 123);
$commandBus->dispatch($command);

Testing

The library includes a test implementation of a command bus that can be useful for testing:

<?php

use FriendsOfDdd\EventDriven\Tests\Kit\Messaging\TestCommandBus;

$commandBus = new TestCommandBus();

// Register handlers
$commandBus->addCommandHandler(
    CreateTicketCommand::class,
    function (CreateTicketCommand $command) {
        // Handle command in test
        $this->assertSame('Expected title', $command->title);
    }
);

// Dispatch command
$commandBus->dispatch(new CreateTicketCommand('Expected title', 123));

Best Practices

  1. Keep Commands and Events Immutable: Use readonly classes to ensure messages cannot be modified after creation.

  2. Record Events in Entities: Use the EventAwareTrait to record domain events within your entities, not in application services.

  3. One Handler Per Command: Each command should have exactly one handler.

  4. Multiple Handlers Per Event: Events can have zero or more handlers (subscribers).

  5. Type Safety: Leverage PHPStan generics to ensure type safety in your message buses.

  6. Command Naming: Commands should be named in imperative form (e.g., CreateTicket, UpdateUser).

  7. Event Naming: Events should be named in past tense (e.g., TicketCreated, UserUpdated).

License

This library is licensed under the MIT License. See the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Author

统计信息

  • 总下载量: 27
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 4
  • 依赖项目数: 1
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-11-03

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固