demos-europe/edt-queries

简化通过属性路径查询实体。


README

此Composer库提供了PHP类,以简化对常用用例中所需实体列表的访问,同时不受实体来源的影响。

概述

基于属性路径的方法

获取实体列表时,有两个常见的需求:1) 限制它只包含满足特定条件的实体;2) 使用特定的排序方法对其进行排序。

不同的查询语言在不同的范围和方式中处理这些需求。EDT-Queries允许在访问实体属性时使用路径。这简化了查询的创建,因为不需要像SQL中那样定义手动连接,并且允许关注实际意图而不是数据源实现细节。另一方面,这也增加了一些关于实体之间关系的要求:要按作者名称获取书籍,书籍实体必须引用作者实体。同样,要按书籍标题获取作者,作者实体必须引用书籍实体。

虽然书籍和作者之间的双向关系似乎是合理的,但这种缺点在通常只需要单向关系的情况下更为明显。假设您有一个独立的电子邮件地址实体,它被不同的实体如“组织”、“人员”和“阻止列表”引用。使用EDT-Queries简单地获取所有仍在使用的电子邮件地址将需要电子邮件地址实体与这三个实体中的每个实体都有关系。

数据源无关性

使用EDT-Queries创建查询时,思维模型基于具有属性和与其他实体关系的实体。如果这种思维模型可以转换为后端数据源的模型,那么就可以实现后端数据源的支持。

例如,当通过作者名称获取书籍时,我们可以使用等价于 book.getAuthor().getName() === $authorName 的条件(稍后显示创建条件的实际语法)。如果 bookauthor 作为关系数据库中的表存在,那么原则上可以将此表达式转换为包含 bookauthor 之间 JOIN 的SQL查询以及比较实际作者名称与给定作者名称的 WHERE 条件。

特定数据源的实施大多与该项目分开。有关更多信息,请参阅提供者和工厂设置

使用示例

在以下示例中,我们获取所有由出生于1800年后的作者所著、姓氏以'A'开头的书籍,主要按出版公司名称排序,次要按出版日期排序。

由于此示例仅关注使用,我们使用占位符代替实际初始化工厂和提供者。一个可能的设置在提供者和工厂设置中显示。

use EDT\ConditionFactory\ConditionFactoryInterface;
use EDT\Querying\Contracts\ObjectProviderInterface;
use EDT\Querying\Contracts\SortMethodFactoryInterface;

// Placeholder setup: The actual factory and provider instances depend on the type of data source.
/** @var ConditionFactoryInterface $conditionFactory */
/** @var SortMethodFactoryInterface $sortMethodFactory */
/** @var ObjectProviderInterface $bookProvider */

// Query definition
$nameCondition = $conditionFactory->propertyStartsWithCaseInsensitive('A', 'author', 'lastName');
$birthDateCondition = $conditionFactory->valueGreaterThan(1800, 'author', 'birth', 'year');
$primarySortMethod = $sortMethodFactory->propertyAscending('publisher', 'name');
$secondarySortMethod = $sortMethodFactory->propertyDescending('publishDate');
$conditions = [$nameCondition, $birthDateCondition];
$sortMethods = [$primarySortMethod, $secondarySortMethod];

// Executing the query
$booksResultList = $bookProvider->getObjects($conditions, $sortMethods);

作为替代,上面的内容可以写成如下所示的流畅查询。

use EDT\ConditionFactory\ConditionFactoryInterface;
use EDT\Querying\Contracts\ObjectProviderInterface;
use EDT\Querying\Contracts\SortMethodFactoryInterface;
use EDT\Querying\FluentQueries\FluentQuery;
use EDT\Querying\FluentQueries\ConditionDefinition;
use EDT\Querying\FluentQueries\SortDefinition;
use EDT\Querying\FluentQueries\SliceDefinition;

// Query definition
$query = create_book_query();
$query->getConditionDefinition()
    ->propertyStartsWithCaseInsensitive('A', 'author', 'lastName')
    ->valueGreaterThan(1800, 'author', 'birth', 'year');
$query->getSortDefinition()
    ->propertyAscending('publisher', 'name')
    ->propertyDescending('publishDate');

// Executing the query
$bookResultList = $query->getEntities();

// Placeholder setup: The actual factory and provider instances depend on the type of data source.
function create_book_query(): FluentQuery
{
    /** @var ConditionFactoryInterface $conditionFactory */
    /** @var SortMethodFactoryInterface $sortMethodFactory */
    /** @var ObjectProviderInterface $bookProvider */

    return new FluentQuery(
        $bookProvider,
        new ConditionDefinition($conditionFactory),
        new SortDefinition($sortMethodFactory),
        new SliceDefinition()
    );
}

提供者和工厂设置

对于 ConditionFactoryInterfaceSortMethodFactoryInterfaceObjectProviderInterface 应使用哪些实现取决于您实际的数据源。

请注意,在特定要求下,不同数据源的工厂和对象提供者只能混合使用。具体来说,要通过对象提供者访问数据源,不仅对象提供者必须支持该数据源,而且创建条件和排序方法所使用的工厂也必须支持。

为了保持该项目的依赖项较小,EDT-Queries 只支持单个数据源,即已经加载的 PHP array,它提供实体对象。

已经编写了针对 Doctrine ORM 的实现,并作为 EDT-DQL 提供。它提供了条件和排序方法的工厂,这些方法可以自动转换为 DQL,而 DQL 已经支持多种不同的数据源。

PHP 数组数据源

尽管它们在现实世界的使用案例有限,但基于 array 的方法仍然可以在可以加载到数组中的小数据集上使用。为了完成上面的示例,你可以实例化一个 PhpConditionFactory 和一个 PhpSortMethodFactory 来创建相应的实例。在创建 PrefilledObjectProvider 时,需要注入 PropertyAccessorInterface 依赖项,该依赖项确定在应用条件和排序方法时如何从实体中读取值。这允许调整提供者的行为,而无需完全扩展它并重写其方法。

// The books to be filtered, preloaded into an array
/** @var list<object> $books */

// Setting up the factories and provider
$conditionFactory = new \EDT\Querying\ConditionFactories\PhpConditionFactory();
$sortMethodFactory = new EDT\Querying\SortMethodFactories\PhpSortMethodFactory();
$bookProvider = new \EDT\Querying\ObjectProviders\PrefilledObjectProvider(
    new \EDT\Querying\PropertyAccessors\ReflectionPropertyAccessor(),
    $books
);

// Filtering books by the name of the auther's children and sorting them by their title
$bookProvider->getObjects(
    [$conditionFactory->propertyHasValue('Christopher Tolkien', 'author', 'children' 'name')],
    [$sortMethodFactory->propertyAscending('title')]
);

致谢

由 Christian Dressler 设计和实现,感谢 eFrane