laminas-api-tools / api-tools

Laminas API Tools module for Laminas
https://api-tools.getlaminas.org/documentation
BSD 3-Clause "New" or "Revised" License
37 stars 19 forks source link

Configurable Rest Collection Filters #3

Open weierophinney opened 4 years ago

weierophinney commented 4 years ago

Hi ,

I wonder, in apigility is there a way to configure filters that to be injected into your rest collection based on configuration ?

At the moment I'm doing it programmatically like

public function fetchAll($criteria = [])
    {
        /** @var Paginator $paginator */
        $paginator = parent::fetchAll($criteria);

        $paginator->setFilter(
            $this->getServiceLocator()->get(PasswordCollectionFilter::class)
        );

        return $paginator;
    }

This filter injection can be done by configuration , smth like :

'zf-rest' => array(
 'Some\\Controller' => array(
    other configs ...
    'collection_class' => 'Some\\KerioEmailsCollection',
    'collection_filters' => [
          ExampleFilterCapitalLetters,
          AnotherExampleFilter
      ]
    other configs ..
)

Originally posted by @karborator at https://github.com/zfcampus/zf-apigility/issues/192

weierophinney commented 4 years ago

There is, but not the way you're doing it!

First off, all filters are configured via the filters configuration key, which itself follows the semantics of a service manager: it has subkeys for services, aliases, invokables, factories, etc. You access filters via the Zend\Filter\FilterPluginManager, which is stored as the service FilterManager. In your example above, you'd do the following instead:

// This line:
$this->getServiceLocator()->get(PasswordCollectionFilter::class)

// becomes:
$this->getServiceLocator()->get('FilterManager')->get(PasswordCollectionFilter::class)

However, I don't recommend that! We have deprecated the usage of the ServiceLocatorAwareInterface, and, if you upgrade to the latest zend-mvc versions, it's not available at all! There's a good reason for that: pulling dependencies from the service locator is a form of dependency hiding, and makes understanding the requirements for a class harder to determine, and harder to test.

Instead, update your resource class to instead accept the filter(s) you want via its constructor, and then update the factory for your resource class to inject it. This allows you then to use any configuration scheme you want. As an example, based on your examples above:

<?php
// Your resource class would accept a filter to the constructor:
use Zend\Filter\FilterInterface;

class SomeResource extends AbstractResource
{
    private $filter;

    public function __construct(FilterInterface $filter)
    {
        $this->filter = $filter;
    }

    /* ... */

    public function fetchAll($criteria = [])
    {
        /** @var Paginator $paginator */
        $paginator = parent::fetchAll($criteria);
        $paginator->setFilter($this->filter);
        return $paginator;
    }

    /* ... */
}

// Your factory would inject the filter:
use Zend\Filter\FilterChain;

class SomeResourceFactory
{
    public function __invoke(ContainerInterface $container)
    {
        $config = $container->get('config');
        $filters = $config['zf-rest'][Some\\Controller::class]['collection_filters'];

        $chain = new FilterChain();
        $chain->setPluginManager($container->get('FilterManager'));
        array_walk($filters, function ($filter) use ($chain) {
            $chain->attachByName($filter);
        });

        return new SomeResource($chain);
    }
}

Originally posted by @weierophinney at https://github.com/zfcampus/zf-apigility/issues/192#issuecomment-308449263

weierophinney commented 4 years ago

Is that means that currently Apigility haven't native support for filters injection into the resource-collection based on zf-rest configs?

I dont want to overwrite the method "fetchAll" every time , instead I want to place a array config with filters that I wan to be injected to the collection/paginator, and to be able to leave the resource clean.


Originally posted by @karborator at https://github.com/zfcampus/zf-apigility/issues/192#issuecomment-308452469

weierophinney commented 4 years ago

Is that means that currently Apigility haven't native support for filters injection into the resource-collection based on zf-rest configs?

Yes; this has never been supported, other than via the zf-doctrine-querybuilder-filter, which only works for Doctrine-connected services, and is highly specific to how the ORM supports querying data.

Additionally, your code $paginator->setFilter() is non-standard; zend-paginator instances do not have filters, nor do our shipped Collection classes.

My point is: you are talking about application-specific features. Apigility can accommodate these features, but you need to write such functionality yourself. One way you can accomplish it is to write your own base Resource class that overrides fetchAll(), your own base Collection class that allows injection of a filter, and use either initializers or delegator factories to inject the filters into these resources.


Originally posted by @weierophinney at https://github.com/zfcampus/zf-apigility/issues/192#issuecomment-308457320

weierophinney commented 4 years ago

https://github.com/zendframework/zend-paginator/blob/master/src/Paginator.php (486)

getItemsByPage ( 608 ) is using filters to filter the items getIterator -> getCurrentItems -> getItemsByPage ( 608 )


Originally posted by @karborator at https://github.com/zfcampus/zf-apigility/issues/192#issuecomment-308531847