Coder Social home page Coder Social logo

Comments (10)

geerteltink avatar geerteltink commented on July 19, 2024

// 1. We have only 1 route and we have to add a subst param ['action' => 'create'] in UrlHelper

IIRC, the url helper re-uses the current request value if none is given.

// 2. We to examine the request action-attribute in the controller(handler) handle method to reroute the request to internal method

What's wrong with an abstract controller class which handles the crud logic detection and redirects to an action method if it exists?

from mezzio.

pine3ree avatar pine3ree commented on July 19, 2024

Hello @geerteltink,

  1. But the url-helper is used to build different uris, not just for the current route.
    $url('/news/article/index') vs $url('/news/article', ['action' => 'index'])
    $url('/news/article/create') vs $url('/news/article', ['action' => 'create'])
    $url('/news/article/update', ['id' => 123]) vs $url('/news/article', ['action' => 'update', 'id' => 123])
    For me atomic route-names are clearer.

  2. Nothing wrong about it, that's how we do it right now, but, repeating briefly my opening comment:

    • We'd have 2 alternative middleware/handlers definitions (that other routers/frameworks already support)
    • route definitions looks nicer too...
    • the actual callable middleware is now actually intercepting those 2 types of definitions but is unable to resolve them
  3. Being an addon, one can still continue to use an abstract controller (either a middleware or a request-handler or both....I use a middleware to avoid instantiating another wrapper (lazy-middleware) and to pass down to the next middleware when the action is not matched by any internal method)

kind regards

from mezzio.

pine3ree avatar pine3ree commented on July 19, 2024

@geerteltink
There is also another benefits for atomic routes:

  1. define per-route supported http-verbs
  2. define per-route middleware pipelines
ROUTE               VERBS    ROUTE-MIDDLEWARES
news/article/create POST     [BodyParserMiddleware, ArticleValidatorMiddleware, News\ArticleController::create]
news/article/edit   GET      [News\ArticleController::edit]
news/article/update PUT|PATCH|POST  [BodyParserMiddleware, ArticleValidatorMiddleware, News\ArticleController::update]
news/article/delete DELETE|POST  [News\ArticleController::delete]

All previous routes expect an handler with the same-dependenies (article persistence model/orm) but they require different http-methods and may require additional middlewares.

from mezzio.

geerteltink avatar geerteltink commented on July 19, 2024

I'm trying to understand what the underlying idea is. I'm guessing it is the generation of factories, or having too many of them.

There are factory generators available with mezzio/mezzio-tooling and laminas/laminas-servicemanager. You can also reuse a factory for different handlers to inject the same dependencies.

Your proposal might be a nice solution, but it might complicate the configuration with too many options.

I would suggest not to use this $app->route('/news/article/update/{id}', ['News\ArticleController::update']);. You loose the FQCN completion.

I guess you end up with something like this:

$app->route('/news/article/create', [News\ArticleController::class, 'create']);

However that might give issues because middleware can be an array, so you would get something like this:

$app->route('/news/article/create', [
    [News\ArticleController::class, 'create']
]);

Something else I'm thinking about is that you create your own MiddlewareInterface. I think you can wrap your controller class in that and pass it directly. A side effect is that you can't cache the config.

from mezzio.

pine3ree avatar pine3ree commented on July 19, 2024

Hello @geerteltink,

I would like to be able to handle both cases ("fqcn::method" and ["fqcn", "method"]) while keeping atomic routes for the reasons I stated earlier. Both the array version ["fqcn", "method"] and the string version are actually intercepted by the callable-handler case: https://github.com/mezzio/mezzio/blob/master/src/MiddlewareFactory.php#L78 so in this example:

$app->route('/news/article/create', [News\ArticleController::class, 'create']);

the array is not treated as a mw-pipeline, if the class exists and the specified method is callable.

My idea is to intercept the is_callable() case and add a sub case with a $this->controller() call, where the controller method would handle 3 sub cases:

string "FQCN::method" => tranform into [FQCN::class, 'method'] array case
array [FQCN::class, 'method'] => create a LazyControllerMIddleware
array [$obj, 'method'] => create a ControllerMIddleware

I also prefer FQCN completion, but it would be nice to handle the "class::method" callable string case, like other routers do. It may be also visually clearer than the array case, even if I'd most probably use the last mentioned.

pseudo-code

//...
class LazyControllerMIddleware implements MiddlewareInterface 
{
    public function __construct(
        Container $container,
        $middleware
    ) {
        $this->container = $container;
        $this->middleware = $middleware;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
    {
        list($fcqn, $method) = $this->resolve($this->middleware);

        // if we want to handle the [$instance, 'method'] case
        if (is_object($fcqn)) {
            return $fcqn->{$method}($request);
        }

        if ($this->container->has($fcqn)) {
            $controller = $this->container->get($fcqn);
            return $controller->{$method}($request);
        }

        return $handler->handle($request);
    }

    private function resolve($middleware): array
    {
        if (is_array($middleware)) {
            return $middleware;
        }
        if (is_string($middleware) && 0 < strpos($middleware, '::')) {
            return explode('::', $middleware);
        }
        ///throw ...
    }
}
//...

kind regards

from mezzio.

pine3ree avatar pine3ree commented on July 19, 2024

I started implementing this, but I found out that I need the root-container (to pull controller-instances if defined as services, I updated my example above) not the middle-ware controller. So i need the MiddlewareFactory to have access to the root-container i.e. I need to make MiddlewareFactoryFactory pass down the root-container as well...

from mezzio.

pine3ree avatar pine3ree commented on July 19, 2024

I added a branch (against develop) with a possible implementation:
https://github.com/pine3ree/mezzio/tree/add-controller-middleware

  • added access to the root-container in MiddlewareFactory

  • added controller-middleware (pulling the controller from the cntainer or just creating a new instance)

  • many unrelated tests are failing (even in the original master) some also complaining about missing psr stream interface. I can make test/Middleware tests pass with php>=7.2. php-7.1 seems not to work in all branches...

pinging @geerteltink (and @michalbundyra @weierophinney if interested in this matter)
kind regards

from mezzio.

pine3ree avatar pine3ree commented on July 19, 2024

Simple test with skeleton:

<?php
// file: src/App/Controller/Home
declare(strict_types=1);

namespace App\Controller;

use Laminas\Diactoros\Response\TextResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class Home
{
    public function door(ServerRequestInterface $request) : ResponseInterface
    {
        return new TextResponse(__METHOD__);
    }

    public function kitchen(ServerRequestInterface $request) : ResponseInterface
    {
        return new TextResponse(__METHOD__);
    }

    public function bedroom(ServerRequestInterface $request) : ResponseInterface
    {
        return new TextResponse(__METHOD__);
    }
}
<?php
// file: config/routes.php

declare(strict_types=1);

use App\Controller\Home;
use Mezzio\Application;
use Mezzio\MiddlewareFactory;
use Psr\Container\ContainerInterface;

return static function (Application $app, MiddlewareFactory $factory, ContainerInterface $container) : void {
    $app->get('/', App\Handler\HomePageHandler::class, 'home');
    $app->get('/api/ping', App\Handler\PingHandler::class, 'api.ping');
    $app->get('/home/door', [App\Controller\Home::class, 'door'], 'home/door');
    $app->get('/home/kitchen', 'App\Controller\Home::kitchen', 'home/kitchen');
    $app->get('/home/bedroom', [
        // test mw-pipeline including a callable-array-controller definition
        function ($request, $next) {
            $response = $next->handle($request);
            $text = $response->getBody()->getContents();
            $response->getBody()->write("{$text}\nI did not do anything!");
            return $response;
        },
        [App\Controller\Home::class, 'bedroom'],
    ], 'home/bedroom');
};

from mezzio.

weierophinney avatar weierophinney commented on July 19, 2024

This is not something we really want to enable. A big part of the reason we removed the handler-as-controller documentation you linked for version 3 was because we feel the paradigm of one route, one handler, helps tremendously with preventing the "fat controller" tendency of MVC systems. (One thing to note, however: we also consider a route to consist not just of the path, but also the HTTP method.)

More importantly, we feel that handlers should implement the RequestHandlerInterface. This makes it clear what their purpose is.

What you want to achieve can be done in userland easily already, and would consist of the following parts:

  1. A special RequestHandlerInterface implementation that would receive the PSR-11 container and the middleware specification as constructor arguments. Internally, it would split them on the :: string, retrieve the left side from the container, and call the method from the right side:

    [$class, $method] = explode('::', $this->specification);
    $instance = $this->container->get($class);
    return $instance->$method($request);

    Alternately, you could have it handle arrays in the format [$className, $methodName], or even handle both scenarios.

  2. An extension to the Mezzio\MiddlewareFactory that adds a method with a name like controller() or similar. This method would create an instance of the class defined in 1. above. (If you look in this class, you'll see another reason why we do not want to support syntax like what you propose: it means strings and/or arrays now have two possible resolutions, and it becomes far more difficult to understand which is being requested!)

  3. Override the Mezzio\MiddlewareFactory service in your application to return your extension instead.

  4. In your routes.php, you could then do:

    $app->route('/home/bedroom', [
        // some middleware, and then...
        $factory->controller(App\Controller\Home::class . '::bedroom')
    ], 'home/bedroom');

Again, this is not something we want to add at this time, as it is yet one more way to define handlers, and we want to encourage defining RequestHandlerInterface implementations, versus having a convention-driven approach.

from mezzio.

pine3ree avatar pine3ree commented on July 19, 2024

@weierophinney,

just wanted to give it a try :-)

  1. Override the Mezzio\MiddlewareFactory service in your application to return your extension instead.

Yes, this is what I have been doing, and ported that code into my proposed branch...the nice thing is that the callable case is already intercepting these 2 cases ensuring the existence of class/method combination....anyway...it's fine...I understand the will to encourage atomic req-handlers in these repos!

kind regards

from mezzio.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.