Searcher是一个与框架无关的搜索查询构建器。搜索查询使用条件编写,可以针对MySQL、MongoDB、ElasticSearch或文件运行。

5.0.0 2016-12-02 09:42 UTC

README

Searcher Build Status Scrutinizer Code Quality Code Coverage Packagist SensioLabsInsight

这是什么?

Searcher是一个与框架无关的搜索查询构建器。搜索查询使用条件编写,可以针对MySQL、MongoDB、ElasticSearch、文件或其他任何你喜欢的东西运行。最新版本仅支持PHP 7现在也测试了Humbug

查看这个演示以更好地理解

为什么?

你是否曾见过基于许多不同条件的代码负责搜索某个东西?这可能会变得相当混乱!想象一下,你有一个包含20个字段的形式,所有这些字段都对搜索条件有影响。将整个表单传递给某个服务并在一个地方解析所有内容并不是一个好主意。感谢这个库,你可以将构建查询条件的责任分割到几个更小的类中。一个类对应一个过滤器。一个CriteriaBuilder对应一个Criteria。这样,在CriteriaBuilder中,你只关心一个Criteria,这使得它更加易于阅读和维护。你可以稍后使用完全相同的Criteria进行不同的搜索,使用不同的CriteriaBuilder和甚至不同的SearchingContext,这些上下文可以使用不同的数据库。你甚至可以使用searcher通过FinderSearchingContext在系统上查找文件

完整文档

完整文档可在http://searcher.rtfd.io/找到

安装

您可以通过在终端中输入以下命令通过composer安装库

$ composer require krzysztof-gzocha/searcher

集成

SearcherBundle中完成与Symfony的集成

想法

  • CriteriaBuilder - 将为单个 Criteria 构建新的 条件
  • Criteria - 传递给 CriteriaBuilder 的模型。您只需以某种方式对其进行填充,使其变得有用。条件可以包含多个字段,并且所有(或某些)字段都可能在 CriteriaBuilder 中使用,
  • SearchingContext - 单次搜索的上下文。该服务应知道如何从构建的查询中检索结果,并持有名为 QueryBuilder 的东西,但它可以是任何对您有用的服务 - 任何服务。这是搜索和数据库之间的抽象层。Doctrine 的 ORM、ODM、Elastica、文件 等都有不同的上下文。如果您没有上下文,您可以实现一个 - 这不会很难。
  • Searcher - 持有 CriteriaBuilder 集合,并将 Criteria 传递给适当的 CriteriaBuilder

示例

假设我们想要搜索年龄在某个过滤范围内的 。在这个例子中,我们将使用 Doctrine 的 QueryBuilder,所以我们将使用 QueryBuilderSearchingContext,并在我们的 CriteriaBuidler 中指定它应仅与 Doctrine\ORM\QueryBuilder 交互,但请记住,我们不必只使用 Doctrine。

1. 条件

首先,我们需要创建 AgeRangeCriteria - 该类将保存最小和最大年龄的值。默认的 Criteria 已经在这里实现了。

class AgeRangeCriteria implements CriteriaInterface
{
    private $minimalAge;
    private $maximalAge;

    /**
    * Only required method.
    * If will return true, then it will be passed to some of the CriteriaBuilder(s)
    */
    public function shouldBeApplied(): bool
    {
        return null !== $this->minimalAge && null !== $this->maximalAge;
    }

    // getters, setters, whatever
}

2. 条件构建器

在第二步中,我们希望指定对该模型应施加的条件。这就是为什么我们需要创建 AgeRangeCriteriaBuilder

class AgeRangeCriteriaBuilder implements CriteriaBuilderInterface
{
    public function buildCriteria(
        CriteriaInterface $criteria,
        SearchingContextInterface $searchingContext
    ) {
        $searchingContext
            ->getQueryBuilder()
            ->andWhere('e.age >= :minimalAge')
            ->andWhere('e.age <= :maximalAge')
            ->setParameter('minimalAge', $criteria->getMinimalAge())
            ->setParameter('maximalAge', $criteria->getMaximalAge());
    }

    public function allowsCriteria(
        CriteriaInterface $criteria
    ): bool
    {
        return $criteria instanceof AgeRangeCriteria;
    }

    /**
    * You can skip this method if you will extend from AbstractORMCriteriaBuilder.
    */
    public function supportsSearchingContext(
        SearchingContextInterface $searchingContext
    ): bool
    {
        return $searchingContext instanceof QueryBuilderSearchingContext;
    }
}

3. 集合

在接下来的步骤中,我们需要为 CriteriaCriteriaBuidler 创建集合。

$builders = new CriteriaBuilderCollection();

$builders->addCriteriaBuilder(new AgeRangeCriteriaBuilder());
$builders->addCriteriaBuilder(/** rest of builders */);
$ageRangeCriteria = new AgeRangeCriteria();

// We have to populate the model before searching
$ageRangeCriteria->setMinimalAge(23);
$ageRangeCriteria->setMaximalAge(29);

$criteria = new CriteriaCollection();
$criteria->addCriteria($ageRangeCriteria);
$criteria->addCriteria(/** rest of criteria */);

4. 搜索上下文

现在,我们想要创建我们的 SearchingContext,并用从 Doctrine ORM 中获取的 QueryBuilder 来填充它。

$context  = new QueryBuilderSearchingContext($queryBuilder);

$searcher = new Searcher($builders, $context);
$searcher->search($criteriaCollection); // Yay, we have our results!

如果您在期望可遍历对象或数组时,QueryBuilder 返回 null 的可能性很小,那么您可以使用 WrappedResultsSearcher 而不是正常的 Searcher 类。它将像 Searcher 一样工作,但它将返回 ResultCollection,它仅适用于数组或 \Traversable,如果结果只是 null,则您的代码仍然可以工作。下面是这样做的示例

$searcher = new WrappedResultsSearcher(new Searcher($builders, $context));
$results = $searcher->search($criteriaCollection);  // instance of ResultCollection
foreach ($results as $result) {
    // will work!
}

foreach ($results->getResults() as $result) {
    // Since ResultCollection has method getResults() this will also work!
}

排序

为了对结果进行排序,您可以使用已经实现的 Criteria。您不需要从头开始实现它。请记住,您仍然需要实现您的 CriteriaBuilder 来实现它(该功能仍在开发中)。假设您想要对结果进行排序,并且您需要在您的 CriteriaBuidler 中使用 p.id 值来执行此操作,但您希望将其显示为 pid 以供最终用户查看。这很简单!这是创建 OrderByCriteria 的方法

$mappedFields = ['pid' => 'p.id', 'valueForUser' => 'valueForBuilder'];
$criteria = new MappedOrderByAdapter(
    new OrderByCriteria('pid'),
    $mappedFields
);
// $criteria->getMappedOrderBy() = 'p.id'
// $criteria->getOrderBy() = 'pid'

当然,您不需要使用 MappedOrderByAdapter - 您可以仅使用 OrderByCriteria,但这样用户就会知道正在使用哪些字段进行排序。

分页

分页的 Criteria 也已实现,您不需要这样做,但请记住,您仍然需要实现将使用它的 CriteriaBuilder 并执行实际分页(该功能仍在开发中)。假设您想要允许您的最终用户更改页面,但不更改每页的项目数。您可以使用以下示例代码

$criteria = new ImmutablePaginationAdapter(
  new PaginationCriteria($page = 1, $itemsPerPage = 50)
);
// $criteria->setItemsPerPage(250);    <- user can try to change it
// $criteria->getItemsPerPage() = 50   <- but he can't actualy do it
// $criteria->getPage() = 1

当然,如果允许用户更改每页的项目数,您也可以跳过 ImmutablePaginationAdapter 并仅使用 PaginationCriteria

贡献

所有想法和 pull 请求都受到欢迎并受到赞赏 :) 如果您在使用过程中遇到任何问题,请不要犹豫,创建一个问题,我们可以一起解决这个问题。

开发

运行测试的命令:composer test
所有单元测试都使用 padric/humbug 库进行突变测试,目标是保持 突变分数指标 等于或接近 100%。

要运行突变测试,您需要安装humbug并在主目录中运行:humbug。输出应存储在humbuglog.txt中。

感谢

按字母顺序排列

许可证

许可证:MIT
作者:Krzysztof Gzocha