smgladkovskiy/querier

Laravel中命令查询分离原则的查询

1.1.0 2014-10-22 12:36 UTC

This package is not auto-updated.

Last update: 2024-09-24 02:52:38 UTC


README

此包为您在Laravel项目中利用命令查询分离原则的查询提供了一种简单的方法。

安装

通常情况下,您可以通过Composer安装Querier。

"require": {
    "smgladkovskiy/querier": "~1.0"
}

接下来,更新app/config/app.php以将此包的服务提供者包含在提供者数组中。

'providers' => [
    'SMGladkovskiy\Querier\QuerierServiceProvider'
]

用法

最重要的建议是,请记住这种方法并不适合所有情况。如果您正在构建一个简单的CRUD应用程序,并且没有太多业务逻辑,那么您可能不需要这个。仍然想继续吗?好吧 - 让我们继续!

目标

想象您正在构建一个用于分析职位广告的应用程序。当您制作包含大量报告的页面时,许多工作都是为了准备要显示的数据,对吗?嗯,不要把所有这些内容都放在您的控制器中!相反,让我们利用查询和处理器来清理我们的代码。

控制器

首先,我们可以将此包的QuerierTrait注入到您的控制器中(或者如果您愿意,是一个BaseController)。这将为您提供一些辅助方法来管理将查询传递到查询总线的过程。

<?php

use SMGladkovskiy\Querier\QuerierTrait;

class AdvertisementsController extends \BaseController {

	use QuerierTrait;

	/**
	 * Build a report of job advertisements.
	 *
	 * @return Response
	 */
	public function getAdvertisementsList()
	{

	}

}

很好?接下来,我们将这个“指令”(获取报告数据)表示为一个查询。这将只是一个简单的DTO。

<?php

use SMGladkovskiy\Querier\QuerierTrait;
use Acme\Queries\AdvertisementsListQuery;

class AdvertisementsController extends \BaseController {

	use QuerierTrait;

	/**
	 * Build a report of job advertisements.
	 *
	 * @return Response
	 */
	public function getAdvertisementsList()
	{
		$advertisements = $this->executeQuery(AdvertisementsListQuery::class);

		return View::make('reports.advertisements', compact('advertisements'));
	}

请注意,我们是如何将用户的指令(或查询)表示为一个可读的类的:AdvertisementsListQueryexecuteQuery方法将期望查询的类路径,作为一个字符串。上面,我们使用了有用的AdvertisementsListQuery::class来获取这个。或者,您也可以手动将路径作为字符串写出来。

查询DTO

很简单,对吧?我们创建一个查询来表示指令,然后将这个查询扔到查询总线中。下面是这个查询可能的样子

<?php namespace Acme\Queries;

class AdvertisementsListQuery {

    public $title;

    public $position_id;

    public function __construct($title = null, $position = null)
    {
        $this->title = $title;
        $this->position_id = $position;
    }

}

当您在QuerierTrait上调用executeQuery方法时,它将自动将来自Input::all()的数据映射到您的查询。您不需要担心手动做这件事。

那么查询总线到底做什么呢?把它想象成一个简单的实用工具,它将这个查询转换成相关的处理器类,这将处理查询!在这种情况下,这意味着根据需要委派以获取职位广告列表。

默认情况下,查询总线将对查询类的名称进行快速搜索和替换,以确定从IoC容器中解析哪个处理器类。因此

  • AdvertisementsListQuery > AdvertisementsListQueryHandler
  • AdvertisementsInArchiveQuery > AdvertisementsInArchiveQueryHandler

明白了吗?好的。不过,请记住,如果您更喜欢不同的命名约定,可以覆盖默认值。请参阅以下内容。

装饰查询总线

有时您可能想装饰查询总线,首先执行某种操作……也许您需要首先清理一些数据。这很容易。首先,创建一个实现SMGladkovskiy\Querier\QueryBus合约的类...

<?php namespace Acme\Queries;

use SMGladkovskiy\Querier\QueryBus;

class AdvertisementSanitizer implements QueryBus {

    public function executeQuery($query)
    {
       // sanitize the job data
    }

}

……然后,在您控制器中执行查询时引用此类。

$this->executeQuery(AdvertisementsListQuery::class, null, [
    'AdvertisementSanitizer'
]);

就这样!现在,您有一个钩子可以在将查询/数据传递给处理器类之前对其进行清理。关于这一点……

处理器类

现在让我们创建我们的第一个处理器类

<?php namespace Acme\Queries;

use SMGladkovskiy\Querier\QueryHandler;

class AdvertisementsListQueryHandler implements QueryHandler {

    public function handle($query)
    {
        $advertisements = Advertisement::where(function($query) use ($query as $filterData)
        {
            $query->where('title', $filterData->title);
            $query->where('position_id', $filterData->position_id);
        })->paginate(15);
        
        return $advertisements;
    }

}

对于这个演示,我们的处理器相当简单。在现实生活中,这里会有更多的事情发生。

验证

此包还包括一个自动的验证触发器。例如,当你将查询投放到查询总线时,它还会确定是否存在关联的验证器对象。如果存在,它将在这个类上调用一个validate方法。如果不存在,它将简单地继续。因此,这为您提供了一个很好的钩子,可以在执行查询和触发领域事件之前进行验证。惯例是

  • AdvertisementsListQuery => AdvertisementsListValidator

因此,只需创建该类,并包含一个validate方法,我们将接收AdvertisementsListQuery对象。然后,按照通常的方式执行验证。我建议,对于失败的验证,您抛出一个异常——可能是ValidationFailedException。这样,您就可以在控制器内部——甚至global.php中——适当地处理失败的验证(可能通过链接回表单并通知用户)。

覆盖路径

默认情况下,此包对您的文件结构有一些假设。如上所示

  • Path/To/AdvertisementsListQuery => Path/To/AdvertisementsListQueryHandler
  • Path/To/AdvertisementsListQuery => Path/To/AdvertisementsListValidator

也许您有其他的想法。没问题!只需创建自己的查询转换器类,实现SMGladkovskiy\Querier\QueryTranslator接口。此接口包括两个方法

  • toQueryHandler
  • toValidator

也许您想将验证器放在一个Validators/目录中。好的

<?php namespace Acme\Core;

use SMGladkovskiy\Querier\QueryTranslator;

class MyQueryTranslator implements QueryTranslator {

    /**
     * Translate a query to its handler counterpart
     *
     * @param $query
     * @return mixed
     * @throws HandlerNotRegisteredException
     */
    public function toQueryHandler($query)
    {
        $handler = str_replace('Query', 'Handler', get_class($query));

        if ( ! class_exists($handler))
        {
            $message = "Query handler [$handler] does not exist.";

            throw new HandlerNotRegisteredException($message);
        }

        return $handler;
    }

    /**
     * Translate a query to its validator counterpart
     *
     * @param $query
     * @return mixed
     */
    public function toValidator($query)
    {
        $segments = explode('\\', get_class($query));

        array_splice($segments, -1, false, 'Validators');

        return str_replace('Query', 'Validator', implode('\\', $segments));
    }

}

现在,一个Path/To/MyGreatQuery将寻找一个Path/To/Validators/MyGreatValidator类。

可能需要复制和粘贴SMGladkovskiy\Querier\BasicQueryTranslator类,并根据需要进行修改。

最后一步是更新IoC容器中的绑定。

// We want to use our own custom translator class
App::bind(
    'SMGladkovskiy\Querier\QueryTranslator',
    'Acme\Core\MyQueryTranslator'
);

完成了!

文件生成

您可能会发现自己手动创建大量查询和处理类。相反,使用此包中包含的Artisan命令!只需运行

php artisan querier:generate Acme/Bar/UsersQuery

这将生成UsersQueryUsersQueryHandler类。默认情况下,它将在“app/”中的“Acme”目录中查找。如果您的基领域目录在其他地方,请传递--base="src"

查询

<?php namespace Acme\Bar;

class UsersQuery {

    /**
     * Constructor
     */
    public function __construct()
    {
    }

}

处理程序

<?php namespace Acme\Bar;

use SMGladkovskiy\Querier\QueryHandler;

class UsersQueryHandler implements QueryHandler {

    /**
     * Handle the command.
     *
     * @param object $command
     * @return void
     */
    public function handle($command)
    {

    }

}

或者,如果您还想要属性的样板代码,您也可以这样做。

php artisan querier:generate Acme/Bar/UsersQuery --properties="first, last"

当您添加--properties标志时,处理类将保持不变,但命令本身将被搭建,如下所示

<?php namespace Acme\Bar;

class UsersQuery {

    /**
     * @var string
     */
    public $first;

    /**
     * @var string
     */
    public $last;

    /**
     * Constructor
     *
     * @param string first
     * @param string last
     */
    public function __construct($first, $last)
    {
        $this->first = $first;
        $this->last = $last;
    }

}

很酷,对吧?这将为您节省大量时间,所以请记住使用它。

调用此命令时,请使用正斜杠作为类路径:Acme/Bar/MyQuery。如果您愿意使用反斜杠,则需要将其用引号括起来。

这就完成了!