Skip to content

Dependency Injection and Middleware

This chapter describes dependency injection (DI) and middleware in the DotApp PHP Framework 2.0. Controllers, middleware, and routes belong in a module. Call a controller with "Module:Controller@method!" (the trailing ! turns DI off for that method). Attach module middleware with ->before("#Module:AuthGate@check!").

1. Dependency Injection and Middleware in DotApp

The DI container lives on the DotApp instance (bind, singleton, resolve). Middleware is either a route before/after hook, a named Middleware::register pipeline, or a module class under app/modules/{Module}/Middleware/.

1.1. What is Dependency Injection and Middleware?

Dependency Injection (DI) inserts registered services into callbacks instead of constructing them by hand. In DotApp the container is the DotApp class.

Middleware runs before or after the route handler. Attach a module middleware class with ->before("#Module:Class@method!"). Named pipelines use Middleware::register plus Middleware::use()->group().

1.2. The DI container

Three methods on the kernel you receive as initialize($dotApp):

1.2.1. bind(string $key, callable $resolver)

Registers a factory. Each resolve call creates a new instance.


$dotApp->bind('logger', function () {
    return new Logger();
});
$logger = $dotApp->resolve('logger');
$logger2 = $dotApp->resolve('logger'); // different instance
        
1.2.2. singleton(string $key, callable $resolver)

Created once, then reused. The framework already registers DotApp::class as a singleton.


$dotApp->singleton('cache', function () {
    return new CacheService();
});
$cache1 = $dotApp->resolve('cache');
$cache2 = $dotApp->resolve('cache'); // same instance
        
1.2.3. resolve(string $key)

Returns the bound value. Throws if the key is unknown.


$cache = $dotApp->resolve('cache');
        
1.2.4. How DI is used in practice

Register services in a module’s initialize($dotApp). Controller methods used from routes are public static and take $request. The trailing ! on the callable string turns DI off for that method so the handler receives $request only.


public function initialize($dotApp) {
    $dotApp->singleton('cache', function () {
        return new CacheService();
    });
}
        

1.3. Calling controllers

All of the following belong in app/modules/{Module}/module.init.php inside initialize($dotApp).

1.3.1. Module controller string

Syntax: "Module:Controller@method!". Live Users demo: Users:Login@page!.


Router::get('/documentation/examples/run/users/login', "Users:Login@page!", Router::STATIC_ROUTE);
        
1.3.2. Trailing !

The ! disables DI for that method. Use it on almost every route handler.


Router::post('/documentation/examples/run/users/login', "Users:Login@save!", Router::STATIC_ROUTE);
        
1.3.3. Closures

The first argument is $request.


Router::get('/status', function ($request) {
    return "Current method: " . $request->getMethod();
}, Router::STATIC_ROUTE);
        
1.3.4. DotApp::call

Call a module controller or middleware string from PHP:


DotApp::call("Users:Login@page!", $request);
        

1.4. Middleware

1.4.1. Module middleware on a route

Put the class in app/modules/Users/Middleware/AuthGate.php and attach it with before. The route handler remains the controller method.


Router::get('/documentation/examples/run/users/app', "Users:Login@app!", Router::STATIC_ROUTE)
    ->before("#Users:AuthGate@check!");
        

namespace Dotsystems\App\Modules\Users\Middleware;

use Dotsystems\App\Parts\Auth;
use Dotsystems\App\Parts\Config;
use Dotsystems\App\Parts\Response;

class AuthGate extends \Dotsystems\App\Parts\ModuleMiddleware
{
    public static function check($request, array $rights = [])
    {
        if (!Auth::isLogged()) {
            $p = rtrim((string) Config::module('Users', 'prefix'), '/');
            return Response::redirect($p . '/login', 302);
        }
        if (!empty($rights) && !Auth::can($rights)) {
            return new Response(403, 'Forbidden');
        }
    }
}
        
1.4.2. Named middleware pipeline

Register with Middleware::register, then wrap routes with Middleware::use()->group(). The callback receives ($request, $next) and must call $next($request).


use Dotsystems\App\Parts\Middleware;
use Dotsystems\App\Parts\Response;

Middleware::register('is_admin', function ($request, $next) {
    if (!\Dotsystems\App\Parts\Auth::can('Users.admin')) {
        return new Response(403, 'Forbidden');
    }
    return $next($request);
});

Middleware::use('is_admin')->group(function () {
    Router::get('/admin/users', 'Users:Login@app!');
});
        
1.4.3. Global before hook

A closure used as Router::before must accept $request. Returning a Response short-circuits the route.


Router::before(['POST'], ['/documentation/examples/run/users/*'], function ($request) {
    // e.g. extra CRC or logging
});
        
1.4.4. Route-chain middleware()

On a route chain, Router::middleware() is an alias of before(). Named pipelines are registered with Middleware::register and applied with Middleware::use()->group(). The second argument of Router::get (and the other verb methods) is the route handler: a controller string or a callable.

1.5. Practical examples

1. Singleton registration in initialize($dotApp):


$dotApp->singleton('cache', function () {
    return new CacheService();
});
$cache1 = $dotApp->resolve('cache');
$cache2 = $dotApp->resolve('cache');
echo ($cache1 === $cache2) ? "Same instance" : "Different instances";
        

2. Protected page:


Router::get('/documentation/examples/run/users/app', "Users:Login@app!", Router::STATIC_ROUTE)
    ->before("#Users:AuthGate@check!");
        

3. Bind plus a closure that reads the request:


$dotApp->bind('logger', function () {
    return new Logger();
});
Router::get('/log', function ($request) use ($dotApp) {
    $logger = $dotApp->resolve('logger');
    return "Method: " . $request->getMethod();
}, Router::STATIC_ROUTE);
        

1.6. Notes

  • Singleton vs bind: singleton for shared services, bind for a new instance each time.
  • Controllers: public static, first argument $request, route string 'Module:Ctrl@method!'.
  • Middleware: module class + ->before('#Module:Class@method!'), or Middleware::register + use()->group().
  • Named pipelines: register with Middleware::register, then wrap routes in Middleware::use('name')->group(). On a route chain, ->middleware() is an alias of ->before(), not a registrar.