espresso / app
PHP with a cup of coffee
Requires
- php: >=7.2
- espresso/http-module: ^3.1
- http-interop/response-sender: ^1.0
- middlewares/payload: ^2.1
- middlewares/whoops: ^1.2
- nyholm/psr7: ^1.1
- nyholm/psr7-server: ^0.3.0
Requires (Dev)
- codacy/coverage: ^1.4
- friendsofphp/php-cs-fixer: ^2.15
- league/container: ^3.3
- phpunit/phpunit: 7.5
README
PHP with a cup of coffee
Introduction
Espresso is an HTTP micro framework for your web application built with PHP. It is heavily inspired in Node's Express.
We aim to make PHP development as simple as writing a Node Js app, while using PHP awesome object oriented features, the best patterns and the FIG standards for maximum interoperability.
Installation
To install Espresso, simply run:
composer require espresso/app
Or, if you prefer an skeleton project with some directory structure you can use:
composer create-project espresso/skeleton <folder>
Please refer to the skeleton documentation to know how to use it.
NOTE: Espresso is an HTTP framework that comes with some tooling out of the box to make your life easier. If you don't need all that tooling, you might be fine just by using
espresso/http-module
, which is the base class for Espresso.
Quick Start
Bootstrapping Espresso is simple. To create a hello world application, simply
create your front controller (index.php
) in a public directory, and then
write:
<?php use Espresso\App\Espresso; use Zend\Diactoros\Response\HtmlResponse; // We create the application $app = Espresso::createApp(); // We define a single get route with a callable handler $app->get('/', static function () { return new HtmlResponse('Hello World'); }); // We run the app, processing the request and emitting a response. $app->run();
Architecture Overview
Espresso's key word is middleware. If you don't understand what middleware is and how it works, you'll have a hard time using Espresso. I recommend you to read the following resources to learn more about the pattern:
- Slim framework docs on what is middleware
- Matthew Weier O'Phinney on the state of the PHP ecosystem before middleware
- Phil Sturgeon on why middleware is important
- Anthony Ferrara on what is the best way of implementing middleware
In Espresso everything is a middleware (Routes, Paths, Handlers, Execution Pipelines, you name it). Even the main Espresso instance is a middleware itself. A well structured Espresso app is no more than a tree of middleware. The idea behind Espresso is that you don't have to compose this tree manually, but instead use the simple Api provided by Espresso, which is very similar to the one of Express JS.
Using Espresso
There are two main actions you can do in Espresso:
- Append middleware
- Register a route
NOTE: Really, there's one action Espresso does, and that is to append middleware to the middleware queue. Routes are just middleware too, as well as paths.
Appending middleware
To append middleware, you must call the use
method on the Espresso instance.
For example, to append a middleware that adds the header X-Powered-By
to the
response.
<?php use Psr\Http\Message\ServerRequestInterface as Req; use Psr\Http\Message\ResponseInterface as Res; use Psr\Http\Server\RequestHandlerInterface as Next; $app->use(static function (Req $req, Next $next): Res { return $next->handle($req) ->withAddedHeader('X-Powered-By', 'Espresso'); });
Registering routes
To register routes, you have the get
, post
, put
, patch
, delete
and route
methods. All of them take a route path and one or more handlers.
<?php use Psr\Http\Message\ServerRequestInterface as Req; use Psr\Http\Message\ResponseInterface as Res; use Zend\Diactoros\Response\JsonResponse; $app->get('/users/:id', static function (Req $req): Res { // We get the user id from the request $userId = $req->getAttribute('id'); return new JsonResponse(['userId' => $userId]); });
Supported types for function calls
As you have seen in the examples, we have been passing closures to these methods. But that is not very convenient for larger applications. Ideally, we need to encapsulate all that logic in their own classes.
So in Espresso, you can pass psr-15 middleware instances instead of closures.
Let's implement the same previous example but with a class.
<?php use Psr\Http\Message\ServerRequestInterface as Req; use Psr\Http\Message\ResponseInterface as Res; use Zend\Diactoros\Response\JsonResponse; use Psr\Http\Server\RequestHandlerInterface as Next; use Psr\Http\Server\MiddlewareInterface; class UserIdHandler implements MiddlewareInterface { public function process(Req $req, Next $next) : Res { $userId = $req->getAttribute('id'); return new JsonResponse(['userId' => $userId]); } } $app->get('/users/:id', new UserIdHandler());
The Middleware Resolver
Espresso allows you to register both objects and closures, and much more types of
arguments thanks to it's MiddlewareResolver
.
The Middleware Resolver is an interface whose sole purpose is to take any type
of argument and return an object implementing MiddlewareInterface
.
Espresso uses the default espresso/http-module
implementation which
can resolve the following:
- arrays: will recursively resolve each element and put it into a
EspressoPipeline
, which is a middleware that executes a chain of middleware. - middleware instances: It just returns it as it is.
- request handler instances: Decorates them in a Request Handler Middleware.
- closures: Decorates them in a Callable Middleware, and optionally binds an object to
$this
. - strings: If you provide a DI Container and a service with the string passed is available, then creates a Lazy Middleware wrapping that service.
- strings containing @: Will split the parts, and find a service and then call a method on it. This is so you can use the Controller pattern.
Using a DI Container
Espresso can use a DI Container to resolve service names. Just pass it to the factory method and it will resolve every service that encounters.
Espresso comes with a ready-to-use Service Provider for league/container
, which
is the preferred choice for the skeleton.