此包已被废弃,不再维护。未建议替代包。

在PHP中应用Command Query Responsibility Segregation模式。

1.0.0 2017-01-07 13:15 UTC

This package is not auto-updated.

Last update: 2022-02-01 13:04:23 UTC


README

PHP中的命令查询责任分离

目录

前言

如果你不知道命令查询责任分离是什么,你应该阅读由Martin Fowler撰写的一篇非常好的文章。要阅读文章,请点击这里

此库是该模式在PHP中的实现。

以下描述了库的主要组件。

组件

命令

命令通常表示一些领域逻辑。它可以包含数据验证、数据处理等。命令的结果通常保存在数据库中。例如,RegisterUser、SendEmail等。你应该将命令视为“数据持有者”。在构造函数参数中,你应该传递处理命令所需的所有数据。

查询

查询是一种特殊的命令。它在存储库中查找数据,并将结果作为返回。例如,FindUser、FindProduct等。

命令处理器

它处理命令。在这个对象中,你应该使用在命令构造函数中传递的参数。在这个对象中,你可以验证、更改和处理数据。命令处理器可以在命令总线上注册。参见章节[#how-to-register-commandhandler]。

示例
<?php

use BartoszBartniczak\CQRS\Command\Command;
use BartoszBartniczak\CQRS\Command\Handler\CommandHandler;

interface EmailSenderService
{

    public function sendEmail(string $receiver, string $subject, string $body);

}

class FakeEmailSenderService implements EmailSenderService
{
    public function sendEmail(string $receiver, string $subject, string $body)
    {
        // TODO: Here you should send email!
    }

}

class SendEmailCommand implements Command
{

    /**
     * @var string
     */
    private $receiver;

    /**
     * @var string
     */
    private $subject;

    /**
     * @var string
     */
    private $body;

    /**
     * @var EmailSenderService
     */
    private $emailSenderService;

    /**
     * SendEmail constructor.
     * @param string $receiver
     * @param string $subject
     * @param string $body
     * @param EmailSenderService $emailSenderService
     */
    public function __construct(string $receiver, string $subject, string $body, EmailSenderService $emailSenderService)
    {
        $this->receiver = $receiver;
        $this->subject = $subject;
        $this->body = $body;
        $this->emailSenderService = $emailSenderService;
    }

    /**
     * @return string
     */
    public function getReceiver(): string
    {
        return $this->receiver;
    }

    /**
     * @return string
     */
    public function getSubject(): string
    {
        return $this->subject;
    }

    /**
     * @return string
     */
    public function getBody(): string
    {
        return $this->body;
    }

    /**
     * @return EmailSenderService
     */
    public function getEmailSenderService(): EmailSenderService
    {
        return $this->emailSenderService;
    }

}

class SendEmailHandler extends CommandHandler
{
    public function handle(Command $command)
    {
        //TODO: Add some validation here

        /* @var $command SendEmailCommand */
        $command->getEmailSenderService()->sendEmail(
            $command->getReceiver(),
            $command->getSubject(),
            $command->getBody()
        );
    }
}

$fakeEmailSenderService = new FakeEmailSenderService();
$sendEmailCommand = new SendEmailCommand('client@emial.com', 'Very important message!', 'Here is the body of the message.', $fakeEmailSenderService);

$sendEmailHandler = new SendEmailHandler();
$sendEmailHandler->handle($sendEmailCommand);

命令总线

命令总线可以接收命令并使用命令处理器执行它们。为此,您需要注册命令处理器。

如何注册命令处理器?
use BartoszBartniczak\CQRS\Command\Bus\CommandBus;

class SimpleCommandBus extends CommandBus{

    protected function handleHandlerException(CommandHandler $handler)
    {
        // TODO: Here you can react on HandlerException and then you should throw the CannotExecuteTheCommandException
    }

    protected function saveDataInRepository($data)
    {
        // TODO: Here you shoud persist the data
    }

}

$simpleCommandBus = new SimpleCommandBus();
$simpleCommandBus->registerHandler(SendEmailCommand::class, $sendEmailHandler);

现在您可以使用命令总线执行命令

try{
    $simpleCommandBus->execute($sendEmailCommand);
}catch(CannotExecuteTheCommandException $cannotExecuteTheCommandException){
// TODO: //Do some buisiness logic in here.
}
命令是如何执行的?

在您传递命令以执行后,命令总线正在寻找适当的命令处理器来处理命令。如果命令处理器返回数据,它可能被保存在存储库中。命令处理器可以将其他命令传递给命令总线以进一步执行。

CommandExecution.svg

查询是如何执行的?

您可以将查询传递给命令总线进行执行。命令总线会寻找命令处理器。在handle()方法中,您可以在仓库中查找数据,然后作为结果返回。命令总线知道,在执行查询的结果中,您返回了一些结果,因此它将其保存到输出中。输出是execute()方法返回的结果。

QueryExecution.svg

示例
use BartoszBartniczak\CQRS\Command\Handler\CannotHandleTheCommandException;
use BartoszBartniczak\CQRS\Command\Query;

interface ProductRepository
{

    /**
     * @param ProductId $productId
     * @return Product
     * @throws CannotFindProductException
     */
    public function findProductById(ProductId $productId);

}

class FindProductInRepositoryCommand implements Query
{

    /**
     * @var ProductRepository
     */
    private $productRepository;

    /**
     * @var ProductId
     */
    private $productId;

    /**
     * FindProductInRepositoryCommand constructor.
     * @param ProductId $productId
     * @param ProductRepository $productRepository
     */
    public function __construct(ProductId $productId, ProductRepository $productRepository)
    {
        $this->productRepository = $productRepository;
        $this->productId = $productId;
    }


    /**
     * @return ProductRepository
     */
    public function getProductRepository(): ProductRepository
    {
        return $this->productRepository;
    }

    /**
     * @return ProductId
     */
    public function getProductId(): ProductId
    {
        return $this->productId;
    }

}

class FindProductInRepositoryHandler extends CommandHandler
{
    public function handle(Command $command): Product
    {
        /* @var $command FindProductInRepositoryCommand */
        try {
            $product = $command->getProductRepository()->findProductById(
                $command->getProductId()
            );
            return $product;
        } catch (CannotFindProductException $cannotFindProductException) {
            //TODO: Do some buisiness logic in here. E.g. Save the wrong phrase/id for further computing.
            throw new CannotHandleTheCommandException("Product cannot be found.", null, $cannotFindProductException);
        }
    }
}

测试

单元测试

要运行单元测试,请执行以下命令:

php vendor/phpunit/phpunit/phpunit --configuration tests/unit-tests/configuration.xml