phpmodel-view-controllermiddlewarezend-framework3mezzio

How to pass a Request to Controller through middleware Zend Framework 3


I'm developing a REST API with ZendFramework 3 components. I decided to validate (Authenticate) every request using a middleware and if the request is valid then pass to an ordinary controller action to retrieve resources and send the response back.

  1. Is this concept even correct? using a middleware to wrap controller?

  2. How to pass the request to controller with below code and configuration? Middleware of

//file: myapp/module/Auth/src/Middleware/Authorize.php

namespace Auth\Middleware;

use Interop\Http\ServerMiddleware\DelegateInterface;
use Interop\Http\ServerMiddleware\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Json\Json;
use Zend\Http\Response;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Diactoros\Response\RedirectResponse;

class Authorize
{
    public function __invoke($request, $response)
    {
        // Validate $request code goes here ...

        $response->getBody()->write('Hello World!');
        return $response;
    }
}

router configuration

//file: myapp/module/Auth/config/module.config.php
namespace Auth;

use Zend\Router\Http\Segment;
use Zend\ServiceManager\Factory\InvokableFactory;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;

return [
    'controllers' => [
        'factories' => [
            Controller\AuthController::class =>Controller\Factory\AuthControllerFactory::class,
        ],

    ],

    'service_manager' => [
        'factories' => [
            \Zend\Authentication\AuthenticationService::class => Service\Factory\AuthenticationServiceFactory::class,
            Service\AuthAdapter::class => Service\Factory\AuthAdapterFactory::class,
            Service\AuthManager::class => Service\Factory\AuthManagerFactory::class,
            Middleware\Authorize::class => InvokableFactory::class,
        ],
    ],

    'router' => [
        'routes' => [
            'auth' => [
                'type'    => Segment::class,
                'options' => [
                    'route' => '/auth[/:action[/:id]]',
                    'constraints' => [
                        'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
                        'id'     => '[0-9]+',
                    ],
                    'defaults' => [
                        'controller' => Controller\AuthController::class,
                        'action'     => 'index',
                    ],
                ],
            ],
            'user' => [
                'type'    => Segment::class,
                'options' => [
                    'route' => '/user[/:action[/:id]]',
                    'constraints' => [
                        'action' => '[a-zA-Z][a-zA-Z0-9_-]*',
                        'id'     => '[0-9]+',
                    ],
                    'defaults' => [
                        'middleware' => [
                            Middleware\Authorize::class,
                            Controller\UserController::class,
                        ],
                        'controller' => Controller\UserController::class,
                        'action'     => 'index',
                    ],
                ],
            ],
        ],
    ],

    'view_manager' => [
        'template_path_stack' => [
            'auth' => __DIR__ . '/../view',
        ],
    ],

    'doctrine' => [
        'driver' => [
            __NAMESPACE__ . '_driver' => [
                'class' => AnnotationDriver::class,
                'cache' => 'array',
                'paths' => [__DIR__ . '/../src/Entity']
            ],
            'orm_default' => [
                'drivers' => [
                    __NAMESPACE__ . '\Entity' => __NAMESPACE__ . '_driver'
                ]
            ]
        ]
    ],
];

Solution

  • So, looking at your code, what strikes me is the following config:

    'defaults' => [
        'middleware' => [
            Middleware\Authorize::class,
            Controller\UserController::class,
        ],
        'controller' => Controller\UserController::class,
        'action'     => 'index',
    ],
    

    You can only use one of these options, not both, and that's because in ZF3 middlewares are dispatched from an event listener, therefore disabling the rest of the traditional dispatch loop.

    You can therefore remove both the controller and action lines.

    For the rest of it, the concept is right, and you definitely can add the Authentication this way. If you're just starting out with your project, I would recommend you use Zend Expressive. For you, the difference would be that you could apply you Authentication action before any other middleware, while in ZF3 you will have to add it to every route manually.