0
\$\begingroup\$

Recently I started playing a bit with Slim and what is making me confused is a necessity of container and middlewares hard coding. As because I am a staunch supporter of a dynamic loading I decided to write a piece of code for middlewares that suits my preferences. Considering my programing skills limitations - I am more a hobbyist than a professional programmer - would anybody be so kind and help me optimise what I have done ? Thanks in advance.

middleware.php

/** Middlewares : */ //================================================
//=====================================================================
$middleware = function ($request, $response, $next) use ($container){

    $method = $request->getMethod();

    /** Trigger "BEFORE" : */ //-------------------------------------------
    //--------------------------------------------------------------------- 
    if ($this->get('settings')['mw'.$method.'before']){
        $settings = $this->get('settings')['mw'.$method.'before'];

        foreach ($settings as $mw){
            $this[$mw['cIndex']] = function ($c) use($mw){
                return new $mw['nClass']();
            };
        };

        foreach ($settings as $mw)
            foreach ($mw['inject'] as $inject)
                $args[$mw['cIndex']][] = $this[$inject];

        $response = $this[$settings[0]['cIndex']]->run($request,$response,$args);
    }
    /** Trigger "ROUTE" : */ //--------------------------------------------
    //--------------------------------------------------------------------- 
    $response = $next($request, $response); 

    /** Trigger "AFTER" : */ //--------------------------------------------
    //--------------------------------------------------------------------- 
    if ($this->get('settings')['mw'.$method.'after']){
        $settings = $this->get('settings')['mw'.$method.'after'];

        foreach ($settings as $mw){
            $this[$mw['cIndex']] = function ($c) {
                return new $mw['nClass']();
            };
        };

        foreach ($settings as $mw)
            foreach ($mw['inject'] as $inject)
                $args[$mw['cIndex']][] = $this[$inject];

        $response = $this[$settings[0]['cIndex']]->run($reguest,$response,$args);
    }   

    return $response;
}

routes.php

/** Routes : */ //=====================================================
//=====================================================================
$app->get('/[{params:.*}]', function ( $request, $response, $args) use($container)
{
    // trigger controller ...

    return $response;
})->add($middleware);

settings.php

    // middlewares :
    //=============================================================================
    'mwGETbefore' => [
        [
            'cIndex'  => 'auth',
            'nClass'  => 'paneric\\engine\\middlewares\\Auth',
            'inject'  => ['session','access'],
        ],
        [
            'cIndex'  => 'access',                  
            'nClass'  => 'paneric\\engine\\middlewares\\Access',
            'inject'  => ['session','csrfForgery'],
        ],
        [
            'cIndex'  => 'csrfForgery',                 
            'nClass'  => 'paneric\\engine\\middlewares\\CsrfForgery',
            'inject'  => ['session','xssClean'],
        ],                  
        [
            'cIndex'  => 'xssClean',                    
            'nClass'  => 'paneric\\engine\\middlewares\\XssClean',
            'inject'  => [],
        ],                  
    ],

Auth.php (exemple middleware class)

//=====================================================================
/** CLASS Auth */ //===================================================
//=====================================================================
class Auth{

private $wrapped;

//=====================================================================
/** public function run() */
//=====================================================================  
    public function run($request, $response,  $args = array())
    {
        // class short name
        $cName = lcfirst((new \ReflectionClass($this))->getShortName());

        //pseudo injection
        $this->cSession = $args[lcfirst($cName)][0];
        // ...
        $this->wrapped = end($args[lcfirst($cName)]);
        unset($args[lcfirst($cName)]);

        // manipulate the request, maybe
        $request = $request->withAttribute($cName, true);

        // delegate to the middleware we wrap:
        $result = $this->wrapped->run($request, $response, $args);

        // see if we got a response
        if ($result instanceof ResponseInterface) {
            $response = $result;
        }

        // manipulate the response before returning
        return $response->withHeader('X-'.$cName, $cName);      
    }
}
\$\endgroup\$
0

1 Answer 1

2
\$\begingroup\$

I'm not 100% sure what you're trying to achieve as I tend to prefer explicit over magic configuration, but here's a few thoughts.

  1. Middleware in Slim and many other frameworks uses the function signature:

    function($request, $response, $next) : Response {}
    

    You should follow this for your middleware so that you can use other people's middleware and also so that other people can use yours.

  2. As middleware has a defined signature, it's typical to use constructor injection to inject dependencies. e.g. your Auth class would look more like this:

    namespace paneric\engine\middlewares;
    
    class Auth {
        function __construct($session, $access)
        {
            $this->session = $session;
            $this->access = $access;
        }
    
        public function __invoke($request, $response, $next)
        {
            // do your stuff before calling next middleware
    
            $response = $next($request, $response);
    
            // do stuff after next middleware has returned
    
            return $response;
        }
    }
    

    You'll need a DI factory for this to work:

    $container = $app->getContainer();
    $container['Auth'] = function ($c) {
        return new Auth($c->get('session'), $c->get('access'));
    };
    

    If you don't want to create this, then you could take advantage of the fact that Slim 3 will inject the container for you into the constructor. I don't see why you would instantiate it yourself when Slim already has this functionality built in.

    I strongly suggest that you don't configure the dependencies for a class in the same place where you decide wich middleware to add.

  3. As middleware gets to choose when to call the $next() callable, any given piece of middleware can choose whether to run code before or after the $next middleware is run, so it is strange to have the concept of "before" and "after" when adding middleware. Similarly, as any piece of middleware is passed the $request object, I would expect it to make a decision on whether the current HTTP method affects whether it needs to do any work or not.

If the goal is to define middleware in settings that you can then attach to routes, then you could do this:

settings.php:

return [
    'settings' => [
        'displayErrorDetails' => true
    ];
];

middleware_config.php:

return [
    'global' => [
        'paneric\\engine\\middlewares\\Auth',
        'paneric\\engine\\middlewares\\Access',
        'paneric\\engine\\middlewares\\CsrfForgery',
        'paneric\\engine\\middlewares\\XssClean',
    ],
    'routes' => [
        'home' => [
            'paneric\\engine\\middlewares\\Foo',
        ],
        'list' => [
            'paneric\\engine\\middlewares\\Bar',
        ],
    ],
];

index.php:

$settings = require('settings.php');
$app = new Slim\App($settings);

// register routes:
$app->get('/list/[{params:.*}]', 'ListAction')->setName('list');
$app->get('/[{params:.*}]', 'HomeAction')->setName('home');

// add middleware, either globally or per route
$middlewareConfig = require('middleware_config.php');

// register global middleware
foreach ($middlewareConfig['global'] as $middleware) {
    $app->add($middleware);
}

// register route middleware
$router = $app->get('router');
foreach ($middlewareConfig['routes'] as $name => $routeMiddleware) {
    try {
        $route = $router->getNamedRoute($name);
        foreach ($routeMiddleware as $middleware) {
            $route->add($middleware);
        }
    } catch (\RuntimeException $e) {
        if (stripos('Named route does not exist for name:', $e->getMessage()) !== 0) {
            throw $e;
        }
    }
}

$app->run();

This assumes that ListAction and HomeAction are classes with an __invoke method that has the signature: function __invoke($request, $response, $args) and that the middleware classes have an __invoke method with the signature __invoke($request, $response, $next).

It all depends on what problem you're trying to solve really.

\$\endgroup\$
1
  • \$\begingroup\$ Beautifoul piece of code indeed, thx \$\endgroup\$ Oct 15, 2017 at 17:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.