如何为通用的、可插入的集合设计过滤器?
How to design filters for a generic, pluggable collection?
我正在开发一个具有时间线功能的 Web 应用程序,很像 Facebook 时间线。时间线本身是完全通用和可插入的。它只是一个通用的项目集合。任何具有适当界面 (Dateable
) 的内容都可以添加到集合中并显示在时间线上。
其他组件(Symfony 捆绑包)定义了实现 Dateable
接口的模型,并设置了可以找到和 return 这些模型的提供者。代码大致是这样的:
class Timeline
{
private $providers = []; // Filled by DI
public function find(/* ... */)
{
$result = [];
foreach ($this->providers as $provider) {
$result = array_merge($result, $provider->find(/* ... */));
}
return $result;
}
}
问题是时间轴旁边需要有一组过滤器。一些过滤器选项(如 date
)适用于所有提供商。但大多数选项没有。例如,大多数提供商都可以使用 author
过滤器选项,但不是全部。一些通知项目是动态生成的,没有作者。
一些过滤器选项仅适用于单个提供商。例如,只有事件项具有 location
属性.
我不知道如何设计一个与时间轴本身一样模块化的过滤器表单。应该在哪里定义可用的过滤器选项?特定于捆绑包的过滤器选项可能来自捆绑包本身,但是可以由多个捆绑包使用的过滤器选项(如 user
)怎么样?如果一些过滤器选项后来可以被多个包使用怎么办?例如,现在只有事件有 location
,但如果添加另一个模块也有带位置的项目怎么办?
每个提供商如何确定提交的过滤器表单是否只包含它理解的选项?如果我在过滤器中设置位置,那么 BlogPostProvider
不应 return 任何消息,因为博文没有位置。但我无法在过滤器中检查 location
,因为 BlogPostBundle 不应该知道其他提供商及其过滤选项。
关于如何设计这样的过滤器表单有什么想法吗?
添加一个中心 FilterHandler
,可以在其中注册每个可用的过滤器。通用过滤器可以与处理程序保存在同一个包中并从那里注册,并且包也可以注册过滤器。
所有提供者都应该知道他们是否以及何时使用了哪些过滤器(通过过滤器名称)。还要在其中 DI 处理程序。
从处理程序中,您可以获得已注册过滤器的完整列表,然后在此基础上构建您的过滤器表单。
过滤调用时 $provider->filter($requestedFiltersWithValues)
将检查它使用的过滤器是否实际被请求和注册(通过注入的处理程序)并且 return 根据需要得到结果。
最后我是这样解决的
首先,我有一个 FilterRegistry
。任何包都可以使用 Symfony DI 标签添加过滤器。过滤器只是一种表单类型。过滤器示例:
class LocationFilterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('location', 'choice', [ /* ... */ ]);
}
}
DI 配置:
<service id="filter.location" class="My\Bundle\Form\LocationFilterType">
<tag name="form.type" alias="filter_location" />
<tag name="filter.type" alias="location" />
</service>
FilterRegistry
知道如何从 DI 容器中获取那些表单类型:
class FilterRegistry
{
public function getFormType($name)
{
if (!isset($this->types[$name])) {
throw new \InvalidArgumentException(sprintf('Unknown filter type "%s"', $name));
}
return $this->container->get($this->types[$name]);
}
}
Timeline
class 和提供者使用 FilterBuilder
向过滤器表单添加新过滤器。生成器看起来像这样:
class FilterBuilder
{
public function __construct(FilterRegistry $filterRegistry, FormBuilderInterface $formBuilder)
{
$this->filterRegistry = $filterRegistry;
$this->formBuilder = $formBuilder;
}
public function add($name)
{
if ($this->formBuilder->has($name)) {
return;
}
$type = $this->filterRegistry->getFormType($name);
$type->buildForm($this->formBuilder, $this->formBuilder->getOptions());
return $this;
}
}
为了显示表单,使用所有提供程序的选项构建了一个过滤器。这发生在 Timeline->getFilterForm()
。请注意,没有绑定到表单的数据对象:
class Timeline
{
public function getFilterForm()
{
$formBuilder = $this->formFactory->createNamedBuilder('', 'base_filter_type');
foreach ($this->providers as $provider) {
$provider->configureFilter(new FilterBuilder($this->filterRegistry, $formBuilder));
}
return $formBuilder->getForm();
}
}
每个提供者都实现 configureFilter
方法:
class EventProvider
{
public function configureFilter(FilterBuilder $builder)
{
$builder
->add('location')
->add('author')
;
}
}
时间轴class的find
方法也进行了修改。它不是构建一个包含所有选项的过滤器,而是构建一个仅包含该提供者选项的新过滤器表单。如果表单验证失败,则提供者无法处理当前提交的过滤器组合。通常这是因为设置了提供者不理解的过滤器选项。在这种情况下,由于设置了额外的数据,表单验证失败。
class Timeline
{
public function find(Request $request)
{
$result = [];
foreach ($this->providers as $provider) {
$filter = $provider->createFilter();
$formBuilder = $this->formFactory->createNamedBuilder('', 'base_filter_type', $filter);
$provider->configureFilter(new FilterBuilder($this->filterRegistry, $formBuilder));
$form = $formBuilder->getForm();
$form->handleRequest($request);
if (!$form->isSubmitted() || $form->isValid()) {
$result = array_merge($result, $provider->find($filter));
}
}
return $result;
}
}
在这种情况下,是一个数据class绑定到表单。 $provider->createFilter()
只是 returns 一个具有与过滤器匹配的属性的对象。然后将填充和验证的过滤器对象传递给提供者的 find()
方法。例如:
class EventProvider
{
public function createFilter()
{
return new EventFilter();
}
public function find(EventFilter $filter)
{
// Do something with $filter and return events
}
}
class EventFilter
{
public $location;
public $author;
}
这一切使管理过滤器变得非常容易。
添加新类型的过滤器:
- 实现 FormType
- 在 DI 中将其标记为
form.type
和 作为 filter.type
要开始使用过滤器:
- 将其添加到
configureFilters()
中的 FilterBuilder
- 在过滤器模型中添加一个属性
- 处理
find()
方法中的属性
我正在开发一个具有时间线功能的 Web 应用程序,很像 Facebook 时间线。时间线本身是完全通用和可插入的。它只是一个通用的项目集合。任何具有适当界面 (Dateable
) 的内容都可以添加到集合中并显示在时间线上。
其他组件(Symfony 捆绑包)定义了实现 Dateable
接口的模型,并设置了可以找到和 return 这些模型的提供者。代码大致是这样的:
class Timeline
{
private $providers = []; // Filled by DI
public function find(/* ... */)
{
$result = [];
foreach ($this->providers as $provider) {
$result = array_merge($result, $provider->find(/* ... */));
}
return $result;
}
}
问题是时间轴旁边需要有一组过滤器。一些过滤器选项(如 date
)适用于所有提供商。但大多数选项没有。例如,大多数提供商都可以使用 author
过滤器选项,但不是全部。一些通知项目是动态生成的,没有作者。
一些过滤器选项仅适用于单个提供商。例如,只有事件项具有 location
属性.
我不知道如何设计一个与时间轴本身一样模块化的过滤器表单。应该在哪里定义可用的过滤器选项?特定于捆绑包的过滤器选项可能来自捆绑包本身,但是可以由多个捆绑包使用的过滤器选项(如 user
)怎么样?如果一些过滤器选项后来可以被多个包使用怎么办?例如,现在只有事件有 location
,但如果添加另一个模块也有带位置的项目怎么办?
每个提供商如何确定提交的过滤器表单是否只包含它理解的选项?如果我在过滤器中设置位置,那么 BlogPostProvider
不应 return 任何消息,因为博文没有位置。但我无法在过滤器中检查 location
,因为 BlogPostBundle 不应该知道其他提供商及其过滤选项。
关于如何设计这样的过滤器表单有什么想法吗?
添加一个中心 FilterHandler
,可以在其中注册每个可用的过滤器。通用过滤器可以与处理程序保存在同一个包中并从那里注册,并且包也可以注册过滤器。
所有提供者都应该知道他们是否以及何时使用了哪些过滤器(通过过滤器名称)。还要在其中 DI 处理程序。
从处理程序中,您可以获得已注册过滤器的完整列表,然后在此基础上构建您的过滤器表单。
过滤调用时 $provider->filter($requestedFiltersWithValues)
将检查它使用的过滤器是否实际被请求和注册(通过注入的处理程序)并且 return 根据需要得到结果。
最后我是这样解决的
首先,我有一个 FilterRegistry
。任何包都可以使用 Symfony DI 标签添加过滤器。过滤器只是一种表单类型。过滤器示例:
class LocationFilterType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('location', 'choice', [ /* ... */ ]);
}
}
DI 配置:
<service id="filter.location" class="My\Bundle\Form\LocationFilterType">
<tag name="form.type" alias="filter_location" />
<tag name="filter.type" alias="location" />
</service>
FilterRegistry
知道如何从 DI 容器中获取那些表单类型:
class FilterRegistry
{
public function getFormType($name)
{
if (!isset($this->types[$name])) {
throw new \InvalidArgumentException(sprintf('Unknown filter type "%s"', $name));
}
return $this->container->get($this->types[$name]);
}
}
Timeline
class 和提供者使用 FilterBuilder
向过滤器表单添加新过滤器。生成器看起来像这样:
class FilterBuilder
{
public function __construct(FilterRegistry $filterRegistry, FormBuilderInterface $formBuilder)
{
$this->filterRegistry = $filterRegistry;
$this->formBuilder = $formBuilder;
}
public function add($name)
{
if ($this->formBuilder->has($name)) {
return;
}
$type = $this->filterRegistry->getFormType($name);
$type->buildForm($this->formBuilder, $this->formBuilder->getOptions());
return $this;
}
}
为了显示表单,使用所有提供程序的选项构建了一个过滤器。这发生在 Timeline->getFilterForm()
。请注意,没有绑定到表单的数据对象:
class Timeline
{
public function getFilterForm()
{
$formBuilder = $this->formFactory->createNamedBuilder('', 'base_filter_type');
foreach ($this->providers as $provider) {
$provider->configureFilter(new FilterBuilder($this->filterRegistry, $formBuilder));
}
return $formBuilder->getForm();
}
}
每个提供者都实现 configureFilter
方法:
class EventProvider
{
public function configureFilter(FilterBuilder $builder)
{
$builder
->add('location')
->add('author')
;
}
}
时间轴class的find
方法也进行了修改。它不是构建一个包含所有选项的过滤器,而是构建一个仅包含该提供者选项的新过滤器表单。如果表单验证失败,则提供者无法处理当前提交的过滤器组合。通常这是因为设置了提供者不理解的过滤器选项。在这种情况下,由于设置了额外的数据,表单验证失败。
class Timeline
{
public function find(Request $request)
{
$result = [];
foreach ($this->providers as $provider) {
$filter = $provider->createFilter();
$formBuilder = $this->formFactory->createNamedBuilder('', 'base_filter_type', $filter);
$provider->configureFilter(new FilterBuilder($this->filterRegistry, $formBuilder));
$form = $formBuilder->getForm();
$form->handleRequest($request);
if (!$form->isSubmitted() || $form->isValid()) {
$result = array_merge($result, $provider->find($filter));
}
}
return $result;
}
}
在这种情况下,是一个数据class绑定到表单。 $provider->createFilter()
只是 returns 一个具有与过滤器匹配的属性的对象。然后将填充和验证的过滤器对象传递给提供者的 find()
方法。例如:
class EventProvider
{
public function createFilter()
{
return new EventFilter();
}
public function find(EventFilter $filter)
{
// Do something with $filter and return events
}
}
class EventFilter
{
public $location;
public $author;
}
这一切使管理过滤器变得非常容易。
添加新类型的过滤器:
- 实现 FormType
- 在 DI 中将其标记为
form.type
和 作为filter.type
要开始使用过滤器:
- 将其添加到
configureFilters()
中的 FilterBuilder
- 在过滤器模型中添加一个属性
- 处理
find()
方法中的属性