zendframework / zend-expressive

PSR-15 middleware in minutes!
BSD 3-Clause "New" or "Revised" License
711 stars 196 forks source link

Path segregation - pipeline middleware - config-driven #652

Open nuxwin opened 5 years ago

nuxwin commented 5 years ago

@weierophinney

Good morning,

I've an expressive application with a config-driven pipeline. That application exposes different UI levels such as /admin, /reseller and /client, among others...

Right now, I've a specific pipeline middleware that I want to execute only for specific paths:

...
protected function getPipeline()
{
    return [
        [
            'path' => '/admin',
            'middleware' => \Zend\Expressive\Navigation\Middleware\NavigationMiddleware::class
        ],
        [
            'path' => 'reseller',
            'middleware' => \Zend\Expressive\Navigation\Middleware\NavigationMiddleware::class
        ],
        [
            'path' => '/client',
            'middleware' => \Zend\Expressive\Navigation\Middleware\NavigationMiddleware::class
        ],
    ];
}
...

This is working but, I would rather be able to do something like this:

...
protected function getPipeline()
{
    return [
        [
            'paths' => ['/admin', '/reseller', '/client'],
            'middleware' => \Zend\Expressive\Navigation\Middleware\NavigationMiddleware::class
        ],
    }
}
...

Of course, I could use a custom delegator but I think this could be integrated in the default \Zend\Expressive\Container\ApplicationConfigInjectionDelegator delegator as follows:

...
    public static function injectPipelineFromConfig(Application $application, array $config) : void
    {
        if (empty($config['middleware_pipeline'])) {
            return;
        }

        // Create a priority queue from the specifications
        $queue = array_reduce(
            array_map(self::createCollectionMapper(), $config['middleware_pipeline']),
            self::createPriorityQueueReducer(),
            new SplPriorityQueue()
        );

        foreach ($queue as $spec) {
            $paths = $spec['path'] ?? ($spec['paths'] ?? '/');
            foreach((array) $paths as $path) {
                $application->pipe($path, $spec['middleware']);
            }
        }
    }
...

instead of

....
    public static function injectPipelineFromConfig(Application $application, array $config) : void
    {
        if (empty($config['middleware_pipeline'])) {
            return;
        }

        // Create a priority queue from the specifications
        $queue = array_reduce(
            array_map(self::createCollectionMapper(), $config['middleware_pipeline']),
            self::createPriorityQueueReducer(),
            new SplPriorityQueue()
        );

        foreach ($queue as $spec) {
            $path = $spec['path'] ?? '/';
            $application->pipe($path, $spec['middleware']);
        }
    }
....

Thank you.

weierophinney commented 5 years ago

We don't recommend usage of config-driven routes or pipelines at this point; starting in Expressive 2, we began moving away from using them in examples, and we've discussed deprecating the feature. I know some people swear by them, but it's a complicated feature, and easy to misconfigure. However, if you want to submit a pull request (which will require tests and documentation), I'll review. (We may separate the feature into a standalone package later, so new features will continue to live.)

nuxwin commented 5 years ago

@weierophinney

some people swear by them

We are one of them. You should really avoid opinionated implementations. This was the mistake you have made with your MVC implementation. Zend Expressive is really cool as micro-framework and a great alternative to symfony but if you start forcing developers to follow a single road, that will make it unusable or a bad choice for some developers.

We prefer a config-driven pipeline rather than a pipeline programmatically configured. This is a choice we've made because we want make the configuration close to our modules.

Please don't force us to configure our pipelines and/or routes programatically by deprecating the feature, even through, we could always create our own delegator. If you deprecate that feature which is not so complicated to understand, you will really make some of your supporters unhappy.

The major problem with the programmatically approach is that the configuration is not close to the modules. When we setup our OPTIONAL modules, which are close to our application, we want just have to inject their configuration providers, nothing helse. Having to add each module's pipeline/route configurations programatically is really a pain for us and for our end-users.

I'll see if I can contribute a bit more. Right now, I'm developing a Zend helper extension for Plates, extension that composes an helper plugin manager to provide Zend view helpers-like (doctype, navigation ...) in Plates templates. As you surely know already, the major drawback with Plates extensions is that those need to be created on registration. Registering a single helper extension that composes an helper plugin manager solve that issue as the helpers are lazy-created.

Thank you.

weierophinney commented 5 years ago

You should really avoid opinionated implementations. This was the mistake you have made with your MVC implementation. Zend Expressive is really cool as micro-framework and a great alternative to symfony but if you start forcing developers to follow a single road, that will make it unusable or a bad choice for some developers.

We have to choose carefully where we spend our time maintaining code and educating users. While I appreciate that the config-driven approach is useful to a number of people, the number of support questions we had to field based on them was disproportionate, which was why we moved away from the feature.

This sort of feature is quite easy for us to separate into its own package, however, and we could hand off maintenance of that to interested users. This would be the best of both worlds, I feel, as those who want the feature can install the package and use the support, and we can still direct folks to the explicit routing/pipeline approach we document initially.

weierophinney commented 4 years ago

This repository has been closed and moved to mezzio/mezzio; a new issue has been opened at https://github.com/mezzio/mezzio/issues/4.