Solicitud HTTP

Les aplicacions web del costat servidor es caracterizen perquè utilitzen el protocol HTTP per interactuar amb les aplicacions client (com pot ser el navegador web).

Aquest protocol és flexible, i s'assembla a cridar funcions remotes. Aquestes "funcions" es crident mitjançant una línea en la capçalera del missatge que envia el client, i que s'anomena línia de solicitud.

GET /hello/david HTTP/1.1

La línia de sol·licitud comença especificant el mètode que es vol utilitzar, seguit del identificador del recurs que es solicita (una paǵina HTML, una imatge, etc.) i la versió del protocol.

Ejemplo

Aquest és un exemple molt senzill que et permetrà entendre com gestiona les sol.licitus una aplicació web en entorn PHP.

Copia aquest fitxer al teu directori arrel index.php:

<?php

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

require __DIR__ . '/vendor/autoload.php';

$app = AppFactory::create();

$app->get("/", function (Request $request, Response $response) {
  $response->getBody()->write("Hello, World!");
  return $response;
});

$app->get('/hello/{name}', function (Request $request, Response $response, $args) {
  $name = $args['name'];
  $response->getBody()->write("Hello, $name");
  return $response;
});

$app->run();

Importa las librerías requeridas con composer:

$ composer require slim/slim slim/psr7
Using version ^4.13 for slim/slim
...

composer crea un fichero composer.json donde guarda la configuración de tu proyecto:

{
    "require": {
        "slim/slim": "^4.13",
        "slim/psr7": "^1.6"
    }
}

También descarga las librerías que has solicitatdo, y todas sus dependencias en el directorio vendor:

$ ls vendor 
autoload.php  composer  fig  nikic  psr  ralouphie  slim  symfony

Inicia un servidor web:

php -S 0.0.0.0:3000

Si hago una petición HTTP a /, el servidor ejecuta la primera función y devuelve Hello, World!:

$ curl localhost:3000
Hello, World!

Si hago una petición HTTP a /hello/david, el servidor ejecuta la segunda función y , en este caso, devuelve Hello, david!:

$ curl localhost:3000/hello/david
Hello, david!

Slim

Per desenvolupar un projecte d'aplicació web necessites utilitzar paquets (el que és coneix com a llibreries), ja que encara que PHP et permet crear aplicacions web (és un llenguatge que es va crear específicament per això), el que t'ofereix són primitives molt senzilles.

Per tant necessites per començar utilizar un micro framework com slim i el paquet PSR-7 (contè les interfícies de missatges PHP).

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs. At its core, Slim is a dispatcher that receives an HTTP request, invokes an appropriate callback routine, and returns an HTTP response. That’s it.

Request and response

When you build a Slim app, you are often working directly with Request and Response objects. These objects represent the actual HTTP request received by the web server and the eventual HTTP response returned to the client.

Every Slim app route is given the current Request and Response objects as arguments to its callback routine. These objects implement the popular PSR-7 interfaces. The Slim app route can inspect or manipulate these objects as necessary. Ultimately, each Slim app route MUST return a PSR-7 Response object.

Bring your own components

Slim is designed to play well with other PHP components, too. You can register additional first-party components such as Slim-Csrf, Slim-HttpCache, or Slim-Flash that build upon Slim’s default functionality. It’s also easy to integrate third-party components found on Packagist.

require

Per utilitzar els paquets necessites incluirlos en el fitxer, i per això necessites utilizar l'estructura de control require

require __DIR__ . '/vendor/autoload.php';

Aplicació

El primer que has de fer és crear un objecte Slim\App mitjançant un mètode de factoria, i guardar l'objecte en una variable $app per poder ser utilizat amb posterioritat per afegit rutes a l'aplicació. Finalment inicies l'aplicació amb la crida d'el métode run()

Rutes

Una de les decisions de disseny més importants en un servidor web és la definició de les rutes que permetran accedir als diferents recursos de la teva aplicació.

Encara que PHP tè un sistema per defecte ..., aquest no és flexible, a més de ser complexe i no permetre dividir fàcilment una aplicació en diferents components.

Això no només passa amb PHP, sinò tambè amb Java, Go, Rust, etc.

Per tant, totes les aplicacions web modernes utilizant paquets (o llibreries) que gestionen l'encaminament.

Slim utilitza la librería Fast Route.

$app->get("/", function (Request $request, Response $response) {
    $response->getBody()->write("Hello, World!");
    return $response;
});

How to create routes

You can add a route that handles only one, all, or only a set of http request methods with Slim application specific methods. They accepts two arguments:

  • The route pattern (with optional named placeholders)
  • The route callback
$app->get('/books/{id}', function ($request, $response) {
    // Show book identified by $args['id']
});

$app->put('/books/{id}', function ($request, $response) {
    // Update book identified by $args['id']
});

$app->delete('/books/{id}', function ($request, $response) {
    // Delete book identified by $args['id']
});

$app->options('/books/{id}', function ($request, $response) {
    // Return response headers
});

$app->patch('/books/{id}', function ($request, $response) {
    // Apply changes to book identified by $args['id']
});

$app->any('/books/[{id}]', function ($request, $response) {
    // Apply changes to books or book identified by $args['id'] if specified.
    // To check which method is used:
    $method = $request->getMethod();
});

$app->map(['GET', 'POST'], '/books', function ($request, $response) {
    // Create new book or list all books
    $method = $request->getMethod();
});

Route callbacks

$app->get("/", function (Request $request, Response $response, array $args) {
    $response->getBody()->write("Hello, World!");
    return $response;
});

Each routing method described above accepts a callback routine as its final argument. This argument can be any PHP callable, and by default it accepts three arguments.

  • Request The first argument is a Psr\Http\Message\ServerRequestInterface object that represents the current HTTP request.
  • Response The second argument is a Psr\Http\Message\ResponseInterface object that represents the current HTTP response.
  • Arguments The third argument is an associative array that contains values for the current route’s named placeholders.

To write content to the response, first get the $response body and then write to it. This content will be appended to the HTTP $response object.

You MUST return a PSR-7 Response object.

Route placeholders

Each routing method accepts a URL pattern that is matched against the current HTTP request URI. Route patterns may use named placeholders to dynamically match HTTP request URI segments.

A route pattern placeholder starts with a {, followed by the placeholder name, ending with a }.


$app->get('/hello/{name}', function ($request, $response) {
    $name = $args['name'];
    $response->getBody()->write("Hello, $name");
    
    return $response;
});

To make a section optional, simply wrap in square brackets:

$app->get('/users/[{id}]', function ($request, $response, array $args) {
    // responds to both `/users/` and `/users/123`
    // but not to `/users`
    
    return $response;
});

Multiple optional parameters are supported by nesting:

$app->get('/news/[{year/}[{month/}]]', function ($request, $response, array $args) {
    // reponds to `/news/`, `/news/2016/` and `/news/2016/03/`
    // ...
    
    return $response;
});

For "Unlimited" optional parameters, you can do this:

$app->get('/news/[{params:.*}]', function ($request, $response, array $args) {
    // $params is an array of all the optional segments
    $params = explode('/', $args['params']);
    // ...
    
    return $response;
});

In this example, a URI of /news/2016/03/20 would result in the $params array containing three elements: ['2016', '03', '20'].

Regular expression matching

By default the placeholders are written inside {} and can accept any values. However, placeholders can also require the HTTP request URI to match a particular regular expression. If the current HTTP request URI does not match a placeholder regular expression, the route is not invoked. This is an example placeholder named id that requires one or more digits.

$app->get('/users/{id:[0-9]+}', function ($request, $response, array $args) {
    // Find user identified by $args['id']
    // ...
    
    return $response;
});

Route names

Application routes can be assigned a name. This is useful if you want to programmatically generate a URL to a specific route with the RouteParser’s urlFor() method. Each routing method described above returns a Slim\Route object, and this object exposes a setName() method.

$app->get('/hello/{name}', function ($request, $response, array $args) {
    $response->getBody()->write("Hello, " . $args['name']);
    return $response;
})->setName('hello');

You can generate a URL for this named route with the application RouteParser’s urlFor() method.

$routeParser = $app->getRouteCollector()->getRouteParser();
echo $routeParser->urlFor('hello', ['name' => 'Josh'], ['example' => 'name']);

// Outputs "/hello/Josh?example=name"

The RouteParser’s urlFor() method accepts three arguments:

  • $routeName The route name. A route’s name can be set via $route->setName('name'). Route mapping methods return an instance of Route so you can set the name directly after mapping the route. e.g.: $app->get('/', function () {...})->setName('name')
  • $data Associative array of route pattern placeholders and replacement values.
  • $queryParams Associative array of query parameters to be appended to the generated url.

Route groups

To help organize routes into logical groups, the Slim\App also provides a group() method. Each group’s route pattern is prepended to the routes or groups contained within it, and any placeholder arguments in the group pattern are ultimately made available to the nested routes:

use Slim\Routing\RouteCollectorProxy;
// ...

$app->group('/users/{id:[0-9]+}', function (RouteCollectorProxy $group) {
    $group->map(['GET', 'DELETE', 'PATCH', 'PUT'], '', function ($request, $response, array $args) {
        // Find, delete, patch or replace user identified by $args['id']
        // ...
        
        return $response;
    })->setName('user');
    
    $group->get('/reset-password', function ($request, $response, array $args) {
        // Route for /users/{id:[0-9]+}/reset-password
        // Reset the password for user identified by $args['id']
        // ...
        
        return $response;
    })->setName('user-password-reset');
});

The group pattern can be empty, enabling the logical grouping of routes that do not share a common pattern.

use Slim\Routing\RouteCollectorProxy;
// ...

$app->group('', function (RouteCollectorProxy $group) {
    $group->get('/billing', function ($request, $response, array $args) {
        // Route for /billing
        return $response;
    });
    
    $group->get('/invoice/{id:[0-9]+}', function ($request, $response, array $args) {
        // Route for /invoice/{id:[0-9]+}
        return $response;
    });
})->add(new GroupMiddleware());

Note inside the group closure, Slim binds the closure to the container instance.

inside route closure, $this is bound to the instance of Psr\Container\ContainerInterface

Route middleware

You can also attach middleware to any route or route group.

use Slim\Routing\RouteCollectorProxy;
// ...

$app->group('/foo', function (RouteCollectorProxy $group) {
    $group->get('/bar', function ($request, $response, array $args) {
        // ...
        return $response;
    })->add(new RouteMiddleware());
})->add(new GroupMiddleware());

PSR7

El document PSR-7 descriu les interfícies habituals per representar missatges HTTP.

Els missatges HTTP són la base del desenvolupament web. Els navegadors web i els clients HTTP, com ara cURL, creen missatges de sol·licitud HTTP que s’envien a un servidor web, que proporciona un missatge de resposta HTTP.

Capçaleres

Case-insensitive header field names

HTTP messages include case-insensitive header field names. Headers are retrieved by name from classes implementing the MessageInterface in a case-insensitive manner. For example, retrieving the foo header will return the same result as retrieving the FoO header. Similarly, setting the Foo header will overwrite any previously set foo header value.

$message = $message->withHeader('foo', 'bar');

echo $message->getHeaderLine('foo');
// Outputs: bar

echo $message->getHeaderLine('FOO');
// Outputs: bar

$message = $message->withHeader('fOO', 'baz');
echo $message->getHeaderLine('foo');
// Outputs: baz

Despite that headers may be retrieved case-insensitively, the original case MUST be preserved by the implementation, in particular when retrieved with getHeaders().

Non-conforming HTTP applications may depend on a certain case, so it is useful for a user to be able to dictate the case of the HTTP headers when creating a request or response.

Headers

Every HTTP response has headers. These are metadata that describe the HTTP response but are not visible in the response’s body.

Set Header

You can set a header value with the PSR-7 Response object’s withHeader($name, $value) method.

$response = $response->withHeader('Content-type', 'application/json');

Reminder The Response object is immutable. This method returns a copy of the Response object that has the new header value. This method is destructive, and it replaces existing header values already associated with the same header name.

Append Header

You can append a header value with the PSR-7 Response object’s withAddedHeader($name, $value) method.

$response = $response->withAddedHeader('Allow', 'PUT');

Reminder Unlike the withHeader() method, this method appends the new value to the set of values that already exist for the same header name. The Response object is immutable. This method returns a copy of the Response object that has the appended header value.

Remove Header

You can remove a header with the Response object’s withoutHeader($name) method.

$response = $response->withoutHeader('Allow');

Reminder The Response object is immutable. This method returns a copy of the Response object without the specified header.

Body

An HTTP response typically has a body.

Just like the PSR-7 Request object, the PSR-7 Response object implements the body as an instance of Psr\Http\Message\StreamInterface. You can get the HTTP response body StreamInterface instance with the PSR-7 Response object’s getBody() method. The getBody() method is preferable if the outgoing HTTP response length is unknown or too large for available memory.

$body = $response->getBody();

The resultant Psr\Http\Message\StreamInterface instance provides the following methods to read from, iterate, and write to its underlying PHP resource.

  • getSize()
  • tell()
  • eof()
  • isSeekable()
  • seek()
  • rewind()
  • isWritable()
  • write($string)
  • isReadable()
  • read($length)
  • getContents()
  • getMetadata($key = null)

Most often, you’ll need to write to the PSR-7 Response object. You can write content to the StreamInterface instance with its write() method like this:

$body = $response->getBody();
$body->write('Hello');