Je suis développeur web freelance et propose des formations à Symfony2 ! Contactez-moi pour en discuter.

Cet article est la traduction d’un article original de Fabien Potencier, à l’origine de Symfony2, disponible ici.

Avant de plonger dans le refactoring du code, je voudrais revenir en sur les raisons pour lesquels vous pourriez vouloir utiliser un framework plutôt qu’une bonne vieille application PHP. Pour parler de pourquoi l’utilisation d’un framework utilisant les composants Symfony2 est une meilleure idée que celle d’en créer un de zéro.

Je ne parlerais pas des bénéfices évidents de l’utilisation d’un framework lorsque l’on travaille sur de grosses applications avec beaucoup de développeurs; Internet propose déjà beaucoup de ressources à ce sujet.

Même si l' »application » que nous avons écrit hier était très simple, elle souffre de quelques problèmes :


Tout d'abord, si le paramètre de la requête name ne se trouve pas dans la chaine de l'URL de requête, il va y avoir un warning PHP; corrigeons donc ce problème :

Ensuite, cette application n'est pas sécurisée. C'est dingue non ? Même ce simple bout de code PHP est vulnérable à l'un des problèmes de sécurité les plus répandus sur Internet : XSS (Cross-Site Scripting). Voici une version plus sûre :


Comme vous l'avez peut-être remarqué, sécuriser son code avec htmlspecialchars est pénible et source d'erreurs. C'est l'une des raisons pour laquelle utiliser un moteur de template tel que Twig, qui propose un échappement automatique par défaut, peut être une bonne idée; l'échappement explicite y est d'ailleurs moins pénible, grâce à l'usage plus simple du filtre e.

Comme vous pouvez vous en rendre compte, le code tout simple que nous avions écrit au départ n'est plus si simple si l'on veut éviter les erreurs PHP et rendre le code sécurisé.

En plus de la sécurité, ce code n'est même pas facilement testable. Même s'il n'y a pas grand chose à tester, cela me dérange que l'écriture de tests unitaires pour le plus simple bout de PHP ne soit pas naturel et même plutôt laid. Voici une tentative de test unitaire PHPUnit du code précédent :


assertEquals('Hello Fabien', $content);
}
}

Si votre application était un peu plus grosse, nous aurons aurions pu trouver encore plus de problèmes. Si cela vous intéresse, lisez l'article Symfony2 versus Flat PHP de la documentation de Symfony2.

A ce stade, si vous n'êtes pas convaincus que la sécurité et les tests sont en effet 2 très bonnes raisons pour arrêter d'écrire du code d'une ancienne manière et plutôt adopter un framework (quoi que cela puisse signifier dans ce contexte), vous pouvez arrêter la lecture de cette série d'articles et retourner à ce que vous étiez en train de coder avant.

Bien sûr, utiliser un framework devrait vous apporter plus que de la sécurité et de la testabilité, mais la chose la plus importante à garder à l'esprit c'est qu'un framework doit vous permettre d'écrire du code de meilleure qualité et plus rapidement.

Débuts de POO avec le composant HttpFoundation

Écrire du code pour le web, cela consiste principalement à intéragir avec de l'HTTP. Les principes fondamentaux d'un framework doivent donc être liés aux spécifications HTTP.

Les spécifications HTPS décrivent comment un client (un navigateur, par exemple) intéragissent avec un serveur. (notre application, via un serveur web). Le dialogue entre un client et un serveur est spécifié par des messages bien définis, des requêtes et des réponses : le client envoit une requ^ête au serveur, et à partir de cette requête le serveur envoit une réponse..

En PHP, la requête est représentée par les variables globales ($_GET, $_POST,
$_FILE, $_COOKIE, $_SESSION...) et la réponse est générée par des fonctions (echo, header, setcookie, ...).

La première étape vers un meilleur code se trouve probablement dans l'approche objet; c'est le principal but du composant HttpFoundation de Symfony2 : remplacer les variables globales et les fonctions de PHP par une couche objet.

Pour utiliser ce composant, ouvrez le fichier composer.json et ajoutez-y une dépendance :

{
"require": {
"symfony/class-loader": "2.1.*",
"symfony/http-foundation": "2.1.*"
}
}

Ensuite, lancez la commande update de composer:


$ php composer.phar update

Enfin, ajoutez le code nécessaire à l'autoloading du composant à la fin du fichier autoload.php :


registerNamespace('Symfony\\Component\\HttpFoundation', __DIR__.'/vendor/symfony/http-foundation');

Maintenant, réécrivons notre application pour utiliser les classes Request et Response :


get('name', 'World');

$response = new Response(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));

$response->send();

La méthode createFromGlobals() crée un objet Request à partir des valeurs actuelles des variables globales de PHP.

la méthode send() renvoit l'objet Response au client (d'abord l'entête HTTP, puis le contenu).

Avant l'appel à send(), nous aurions dû ajouter un appel à la méthode prepare() (en faisant $response->prepare($request);) pour nous assurer que notre Response est bien conforme aux spécifications HTTP. Par exemple, si nous devions appeler la page avec la méthode HEAD, elle aurait enlevé le contenu de la réponse.

La différence principale avec le code précédent est que vous avez un contrôle total des messages HTTP. Vous pouvez créer n'importe quel requête, et vous êtes en charge d'envoyer la réponse que vous voulez quand vous le voulez.

Nous n'avons pas explicitement précisé le header Content-Type dans le code réécrit, car le charset par défaut de l'objet Response est UTF-8.

Avec la classe Request, vous avez toutes les informations sur la requête au bout des doigts grâce à cette simple API :


getPathInfo();

// Récupérer les variables GET et POST respectivement
$request->query->get('foo');
$request->request->get('bar', 'valeur par defaut si bar n existe pas');

// Récupérer la variable SERVER
$request->server->get('HTTP_HOST');

// Récupérer une instance d'un objet UploadedFile identifié par foo
$request->files->get('foo');

// Récupérer la valeur d'un COOKIE
$request->cookies->get('PHPSESSID');

// Récupérer le header d'une requête HTTP, avec des clés normalisées, en minusucule
$request->headers->get('host');
$request->headers->get('content_type');

$request->getMethod(); // GET, POST, PUT, DELETE, HEAD
$request->getLanguages(); // Un tableau des languages que le client accepte

Vous pouvez également simuler une requête :


$request = Request::create('/index.php?name=Fabien');

Avec la classe Response, vous pouvez facilement personnaliser la réponse :


setContent('Hello world!');
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/html');

// configuration du header du cache HTTP
$response->setMaxAge(10);

Pour débugguer une réponse, faites-en un cast vers un string; celà va vous donner la représentation HTTP de la réponse (header et contenu).

Pour finir, ces classes, comme toutes celles du code de Symfony2, ont été auditées d'un point de vue sécurité par une société indépendante. De plus, être un projet open-source signifie également que d'autres programmeurs partout dans le monde en ont lu le code et ont déjà résolu des problèmes de sécurité potentiels. C'est quand la dernière fois que vous avez eu un audit de code professionnel pour du code maison ?

Même quelque chose d'aussi simple que l'obtention de l'adresse IP du client peut ne pas être sécurisé :


Cela marche parfaitement jusqu'à ce que vous ayiez un proxy inverse devant le serveur de production; à ce moment là, vous devrez changer votre code pour qu'il marche à la fois sur la machine de développement (où il n'y a pas de proxy) et sur votre serveur :


Utiliser la méthode Request::getClientIp() vous aurait fourni le comportement attendu dès le premier jour, et aurait couvert le cas de proxy qui s'enchainent :


getClientIp()) {
// Le client est connu, donc on lui accorde quelques privilièges supplémentaires
}

Et il y a un bénéfice de plus : c'est sécurisé par défaut. Qu'est ce que j'entends par sécurisé ? On ne peut pas faire confiance à la valeur de $_SERVER['HTTP_X_FORWARDED_FOR'] car elle peut être manipulée par l'utilisateur lorsqu'il n'y a pas de proxy. Du coup, si vous utilisez ce code en production sans proxy, il est très facile d'abuser votre système. Ce n'est pas le cas avec la méthode getClientIp() car vous devez explicitement faire confiance à ce header en appelant trustProxyData() :

getClientIp(true)) {
// Le client est connu, donc on lui accorde quelques privilièges supplémentaires
}

La méthode getClientIp() marche donc de manière sûre dans toutes les circonstances. Vous pouvez l'utiliser dans vos projets, quelle qu'en soit la configuration, et elle se comportera correctement. C'est l'un des buts de l'utilisation d'un framework. Si vous deviez écrire un framework de zéro, vous devriez penser à toutes ces choses par vous même. Pourquoi ne pas plutôt utiliser une technologie qui marche déjà ?

Si vous voulez en apprendre plus sur le composant HttpFoundation, vous pouvez jeter un oeil à l'API ou lire sa documentation détaillée sur le site web de Symfony.

Croyez le ou non, mais nous avons notre premier framework. Vous pouvez vous arrêter là si vous le souhaitez. En général, simplement utiliser le composant HttpFoundation de Symfony2 vous permet déjà d'écrire du code meilleur et plus testable. Cela vous permet également d'écrire plus rapidement car beaucoup de problèmes que vous rencontrez quotidiennement on déjà été résolus pour vous.

En fait, des projets tels que Drupal on déjà adopté (pour la version 8 à venir) le composant HttpFoundation; s'il marche pour eux, il marchera probablement pour vous. Ne réinventez pas la roue.

J'ai presque failli oublier de vous parler d'un des bénéfices ajoutés: utiliser le composant HttpFoundation est le début d'une meilleure interopérabilité entre les framework et les applications qui les utilisent (à ce jour Symfony2, Drupal 8, phpBB 4, Silex, Midgard CMS, Zikula ...).gard CMS, Zikula ...).

2 Réponses à “Créez votre propre framework… avec les composants Symfony2 (partie 2)”

  1. Youpi Dit:

    Article intéressant, Fabien Potencier aurait pu se passer de prendre les gens pour cons avec l’exemple boiteux du framework perso qui n’en est pas un et pas optimisé du tout.

    // framework/index.php

    Les développeurs qui se font un framework perso ne mettent pas la config direct dans le code mais dans un ou plusieurs fichiers de config.
    De même ils optimisent un minimum le code.

    $input = isset($_GET[‘name’]) ? htmlspecialchars($_GET[‘name’]) : ‘World’;

    Et si il s’agit d’un framework perso, il y a déjà ce qu’il faut dès la création du framework :

    $input = REQUEST::get(‘name’,’World’);

  2. Keirua Dit:

    Même constat que toi.
    Ce qui m’a motivé à traduire les articles, c’était ma connaissance assez partielle du fonctionnement de pas mal de choses. Mais au final, l’approche n’est ni perso (hey ! on recode Silex !) ni optimisée.

    Ce que je trouve par contre très intéressant, c’est la structuration de la pensée et la mise en place de bonnes pratiques, un truc qui manque beaucoup dans le domaine du web je trouve (même si ça s’améliore).

Répondre