mehr-it/eli_dispatcher

Flexible and lightweight request dispatcher (and emitter) for PHP applications based on PSR-7 and PSR-15 standards

1.0.1 2020-05-25 20:23 UTC

This package is auto-updated.

Last update: 2024-03-21 22:32:55 UTC


README

Fast and lightweight request dispatcher (and emitter) for PHP applications using PSR-7 and PSR-15.

Basic concepts

The dispatcher can be configured with a custom PSR-15 request handler to resolve requests. Any PSR-15 middleware (or closures) may be added to the processing chain.

The request is created from globals (or passed as argument), processed by middleware, handled over to the request handler and then emitted (or returned).

Basic usage

Following example shows the basic and most common way to use the dispatcher:

(new Dispatcher())
	->middleware(new SessionMiddleware())
	->middleware(function($request, $next) {
		// implement middleware code here
	})
	->middleware(function($request) {
		// return response
	})
	->dispatch();

A dispatcher instance is created, middleware is added and the request is dispatched. You might miss a request handler in the example. In fact you don't need one if you implement a middleware which does not call the "next" but instead return a response. This means any middleware can act as request handler.

Adding middleware

If you want to add middleware for request processing, you may either pass a PSR-15 middleware

$dispatcher->middleware(new MyMiddleware())

or a callable, eg. a closure:

$dispatcher->middleware(function(ServerRequestInterface $request, RequestHandlerInterface $next) {

	// this middleware does nothing, only return the 
	// response generated by the next handler
	return $next->handle();
 
})

Middleware, even closures must always return a response. But it's up to the middleware if it invokes the next handler and processes it's response or generates a response without invoking any further handlers.

Request handlers

As mentioned above, you don't have to use request handlers. In fact a request handler is a middleware without the ability to be chained. So there is nothing you can't do with a middleware instead.

However the request processing chain needs a end. By default a request handler returning a 404 response without body is appended to the chain.

If you want to change this behaviour, you can set your own request handler:

$dispatcher->handler(new RequestHandler());

Passing requests

If calling the dispatch() method without any parameters, the request is captured from PHP's globals.

$dispatcher->dispatch();

However you may also pass a custom PSR-7 server request instance to be dispatched:

$dispatcher->dispatch($serverRequest);

Returning responses

By default the dispatcher emits the generated response. If you don't want the response to be emitted, but only to be returned you can set the second parameter to false:

$response = dispatcher->dispatch(null, false); 

Termination handlers

Sometimes there is some work to do after the request has been emitted. Common examples are writing log entries or persisting session data.

Performing these actions after sending the request to the client can improve response time a lot.

To register a handler to be called after request is sent, you can use the onTerminate method:

$dispatcher->onTerminate(function($request, $response) {
	
	// implement your code here
	 
});

Often middleware needs to add termination handlers. Since middleware cannot access the current dispatcher instance on its own, the onTerminate() method can be called statically:

Dispatcher::onTerminate($handler, true);

Static calls are only possible while dispatching and are forwarded to the dispatcher instance currently dispatching.

Typically middleware would set the second parameter to true, which causes the handler to be only invoked once. This allows to reuse the dispatcher and add termination handlers per request only.

Streaming responses

By default the dispatcher "streams" the response in chunks to the client. You may configure the chunk size using the sendBuffer() method and even disable streaming by passing send buffer size 0:

$dispatcher->sendBuffer(1024 * 1024);

or

Dispatcher::sendBuffer(0);

When streaming is enabled, the client my request only a certain range of the response.

However you should add the accept-ranges: bytes header to responses, where you want to indicate this feature to clients.

Content length

If the response body size is known, the dispatcher will automatically add the content-length header, if yet not existing.

Boot scripts

If you need to configure different middleware based on the execution environment, boot scripts are very handy:

You can pass in the boot script for the current environment to the dispatcher constructor. Within the boot script you can configure the dispatcher:

// index.php

if (/* condition* /)
	$bootScript = 'local.php';
else
	$bootScript = 'production.php';
	
(new Dispatcher($bootScript))->disptach();

The boot script can access the dispatcher instance via the $dispatcher variable and configure it:

// local.php

$dispatcher->middleware(/* ... */);

Chains

Sometimes middleware needs to inject other middleware dynamically. You can use the Chain class to easily create a middleware processing chain. See following example:

$dispatcher->middleware(function($request, $next) {

	if (/* ... */) {
		$chain = new Chain([
			new MiddlewareA(),
			new MiddlewareB(),
		], $next);
	}
	else {
		$chain = new Chain([
			new MiddlewareC(),
			function($request, $next) {
				/* ... */
			},
		], $next);
	}
	
	return $chain->handle($request);
});