Débugger un projet PHP Symfony en 2020

Si debugger, c’est supprimer des bugs, alors programmer ne peut être que les ajouter — Edsger Dijkstra

Vous l'avez compris, quel que soit votre niveau, vos années d'expérience, le language de programation que vous allez utiliser, une partie de votre quotidien consistera à débugger du code source.

Une anomalie peut être compliquée à trouver, comme parfois l'aguille dans une botte de foin : elle peut déprendre de nombreux paramètres, d'un contexte d'exécution particulier, de la présence (ou non) de certaines données, d'effets de bords, de «edges cases», et pourtant...

Pourtant il n'est pas rare (pas vous hein, votre voisin) de faire un echo "coucou;" die(); pour essayer d'y voir plus clair dans tout ce sac de noeud...

Bien entendu, il existe des outils, tels que Xdebug, qui permettent bien mieux qu'un dump() de tracer le comportement de votre projet PHP pour identifier et résoudre les anomalies.

Dans cet article, nous allons voir comment mettre en place un debug comme dans la vraie vie, avec #php, #symfony, (#symfony-cli | #docker), (#phpstom | #VSCode)

Première étape : installer Xdebug

Avec Docker

Pour disposer de Xdebug dans votre conteneur,
il vous suffira d'ajouter ces lignes à votre Dockerfile

FROM php:7.2-fpm # Version de PHP que vous voulez

RUN pecl install xdebug \  
	&& docker-php-ext-enable xdebug  

Sans Docker

Pour disposer de Xdebug avec PHP,
il vous suffira de l'installer vie pecl

# Avec Symfony CLI
$ symfony pecl install xdebug
# Sans Symfony CLI
$ pecl install xdebug

Server

Si vous faites du Web depuis un petit moment, vous pouvez constater l'évolution de la partie hébergement comme ceci. Au fil du temps, nous sommes passés :

  • D'un serveur physique
  • à un serveur virtuel
  • à des serveurs de plus en plus virtuels (cloud & co)
  • à des serveurs virtuels dans des serveurs virtuels (Docker / Kubernetes & co)...
  • Le site serverless-stack.com résume bien les choses :

    L’architecture serverless est un modèle dans lequel le fournisseur de services cloud (AWS, Azure ou Google Cloud) est responsable de l’exécution d’un morceau de code en allouant de manière dynamique les ressources.

    Et il ne facture que la quantité de ressources utilisées pour exécuter le code. Le code est généralement exécuté dans des conteneurs sans état pouvant être déclenchés par divers événements, notamment des requêtes http, des événements de base de données, des services de file d’attente, des alertes de surveillance, des téléchargements de fichiers, des événements planifiés (tâches cron), etc. Le code envoyé au fournisseur de cloud pour l’exécution est généralement sous la forme d’une fonction.

    Par conséquent, serverless est parfois appelé “Functions as a Service” ou “FaaS”.

    Déployons Symfony et API Platform en serverless

    Bien. Maintenant que nous savons tout cela, cet article va vous présenter comment déployer Symfony et API Platform sur le service Serverless d'Amazon, à savoir AWS Lambda.

    Qu'allons nous mettre en place ?

    Et pour faire de cet article un projet de la vraie vie, nous n'allons pas déployer un "Hello World". En cette période de confinement, où tout le monde est en télétravail, le bureau ne nous a jamais autant manqué. Alors, nous allons mettre en place une API qui nous permet de récupérer les meilleures citations de la célèbre série "The Office"

    Pour mener à bien ce projet, nous aurons donc besoin :

    • D'exécuter du code (AWS Lambda)
    • De servir des assets (AWS S3)
    • D'une base de données (AWS RDS)

    C'est parti !


    Mise en place du projet Symfony / API Platform

    Nous ne nous attarderons pas sur la mise en place du projet avec Symfony / API Platform, car le coeur de cet article ne se situe pas là. Vous pouvez jeter un oeil au code source que nous avons publié sur Github, accessible à la fin de cet article.
    $ symfony new the-office-api-quotes
    $ cd the-office-api-quotes
    $ composer req api debug maker  
    $ bin/console make:entity Quotes --api-resource
    $ symfony serve
    Et voilà ! Vous devriez avoir une API prête à servir des citations de The Office à l'URL https://127.0.0.1:8001/api/quotes

    Adaptons Symfony au serverless

    Symfony est presque prêt à être déployé tel quel. Il est nécessaire d'apporter une modification mineure pour faire en sorte que les fichiers de cache et de log soient générés dans le dossier /tmp, seul répertoire accessible par défaut en écriture

    
    	// src/Kernel.php
        public function getLogDir()
        {
            // When on the lambda only /tmp is writeable
            if (getenv('LAMBDA_TASK_ROOT') !== false) {
                return '/tmp/log/';
            }
    
            return parent::getLogDir();
        }
    
        public function getCacheDir()
        {
            // When on the lambda only /tmp is writeable
            if (getenv('LAMBDA_TASK_ROOT') !== false) {
                return '/tmp/cache/'.$this->environment;
            }
    
            return parent::getCacheDir();
        }
    

    Bref. Il faut déployer Symfony en Serverless

    Maintenant que notre API tourne en local, nous allons la déployer sur AWS Lambda, afin que celle-ci soit exécutée en tant que fonction.

    Nous allons pour cela nous appuyer sur trois outils : bref, serverless et aws.

    • serverless est un framework qui permet de déployer son code. Pour nous, il s'agira essentiellent d'un outil en ligne de commande et d'un fichier de configuration en yaml.
    • bref est une solution complète qui rend possible l'exécution de code PHP sur AWS Lambda (via un runtime PHP qui n'existe pas nativement). Bref est également un package installable via composer et qui permet entre autre de disposer un cli qui permet d'invoquer votre application.
    • aws est un outil en ligne de commande qui permet d'interragir avec les services d'AWS

    Premier déploiement avec Serverless / Bref

    Installation des outils
    $ npm install -g serverless # ou brew install serverless
    # récupérer vos clés AWS (https://bref.sh/docs/installation/aws-keys.html)
    $ serverless config credentials --provider aws --key  --secret 
    # BREF
    $ composer require bref/bref
    
    Mise en place du fichier serverless.yml

    Maintenant que les outils sont installés, nous allons créer un fichier serverless.yml à la racine du projet, qui va décrire ce dont nous avons besoin. Voici le fichier ci-dessous

    service: acseo-the-office-api
    
    provider:
        name: aws                             # Car nous utilisons AWS :)
        region: eu-west-3 			          # Paris
        runtime: provided			          # Car nous utilisons le runtime PHP de Bref
        environment:				          # Variables d'environement Symfony
            APP_ENV: prod 
    
    plugins:
        - ./vendor/bref/bref		          # Plugin bref installé avec composer
    
    package:
        exclude:					          # Fichier exclus car non nécessaires pour
            - node_modules/**		          # l'exécution de notre fonction
            - tests/**
    
    functions:
        website:                              # Première fonction exposée
            handler: public/index.php         # Point d'entrée
            timeout: 28 			          # en secondes (timeout de 29s chez AWS)
            layers:                           # Runtime utilisé
                - ${bref:layer.php-73-fpm}    # liste ici : https://bref.sh/docs/runtimes/index.html
            events:
                -   http: 'ANY /'             # Evènements écoutés
                -   http: 'ANY /{proxy+}'         
        console:                              # Bonus, la console est aussi exposée
            handler: bin/console
            timeout: 120 # in seconds
            layers:
                - ${bref:layer.php-73} # PHP
                - ${bref:layer.console} # The "console" layer
    

    Maintenant que ce fichier est créé, il ne nous reste plus qu'à déployer notre code !

    Déployer son code Symfony sur Lambda

    Le déploiement du code s'effectue très simplement :

    $ serverless deploy
    Serverless: Packaging service...
    Serverless: Excluding development dependencies...
    Serverless: Uploading CloudFormation file to S3...
    Serverless: Uploading artifacts...
    Serverless: Uploading service acseo-the-office-api.zip file to S3 (16.05 MB)...
    Serverless: Validating template...
    Serverless: Updating Stack...
    Serverless: Checking Stack update progress...
    ....................
    Serverless: Stack update finished...
    Service Information
    service: acseo-the-office-api
    stage: dev
    region: eu-west-3
    stack: acseo-the-office-api-dev
    resources: 15
    api keys:
      None
    endpoints:
      ANY - https://xxx.execute-api.eu-west-3.amazonaws.com/dev
      ANY - https://xxx.execute-api.eu-west-3.amazonaws.com/dev/{proxy+}
    functions:
      website: acseo-the-office-api-dev-website
      console: acseo-the-office-api-dev-console
    layers:
      None
    Serverless: Removing old service artifacts from S3...
    Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.
    

    Et voilà ! Vous avez déployé votre premier code PHP / Symfony sur Lambda. Mais ce n'est pas encore fini...

    En jetant un oeil à l'adresse du site Web sur https://xxx.execute-api.eu-west-3.amazonaws.com/dev/api, vous allez être cependant bien déçu : en effet, il manque les assets !

    Envoi des assets sur AWS S3

    Lambda n'est en effet pas fait pour servir des assets. En revanche, S3 est un parfait candidat pour stocker et rendre disponible ces derniers.

    Création d'un storage et envoi des assets

    Nous allons créer un espace de stockage puis synchroniser le dossier public dans celui-ci .

    $ aws s3 mb s3://acseo-the-office-api-assets --region eu-west-3
    $ aws s3 sync public s3://acseo-the-office-api-assets --delete --acl public-read
    

    Paramétrage de Symfony pour pointer sur S3

    Il ne nous reste plus qu'à éditer la configuration du projet pour pointer sur ces assets en production

    # config/prod/assets.yaml 
    framework:
        assets:
            base_urls:
                - '%env(PROD_ASSET_URL)%'
    


    Une fois le code redéployé avec serverless deploy,
    c'est maintenant beaucoup mieux !

    Mais il reste encore un problème : nous n'avons pas de données...

    Ajout et connexion à une base de données

    Dernière étape, et non des moindres, il faut maintenant ajouter une base de données reliée à notre Lambda.

    Pour rester sur les services AWS, nous allons donc instancier une base de données de type MariaDB sur les services AWS

    Une fois de plus, la documentation fournie par Bref à ce sujet est d'une excellente qualité et largement suffisante pour comprendre comment faire. A notre niveau, nous retiendrons que la bonne pratique consiste à ajouter notre fonction Lambda dans le même groupe de Virtual Private Cloud afin que fonction et base de données puissent se voir

    Lien entre la base de données et la fonction Lambda

    Nous éditerons donc notre fichier serverless.yml de la façon suivante :

    # serverless.yml
    functions:
        website:
            handler: public/index.php
            timeout: 28
            layers:
                - ${bref:layer.php-73-fpm}
            events:
                -   http: 'ANY /'
                -   http: 'ANY /{proxy+}'
            vpc:                             # Infos trouvées dans l'onglet
                securityGroupIds:            # Connectivité et sécurité
                    - sg-xxxx                # de votre instance RDS
                subnetIds:                   #
                    - subnet-xxxx            # 
                    - subnet-xxxx            #
                    - subnet-xxxx            #
    

    Activation de pdo_mysql dans le Layer Bref

    L'extension pdo_mysql n'est pas activée par défaut dans le Layer fourni par bref. Fort heureusement, il est très facile de pouvoir l'activer, en créant le fichier php/conf.d/php.ini à la racine de votre projet.

    # php/conf.d/php.ini
    extension=pdo_mysql
    

    Pour en savoir plus sur les extensions activées et activables : https://bref.sh/docs/environment/php.html

    Il ne nous reste plus qu'à éditer le fichier .env.prod pour configerer l'accès à notre base de données :

    Et pour finir...

    Pour résumer, nous avons :

    • Configuré serverless pour déployer notre code Symfony sur Lambda
    • Déployé les assets sur S3
    • Créé puis lié une base de données à notre fonction Lambda

    Une fois un dernier déploiement effectué, le résultat tant attendu apparait :

    Ca y est, nous disposons d'une API Symfony / API Platform déployée sur Lambda !

    Conclusion

    Comme le montre cet article, il n'est pas très compliqué de pouvoir utiliser Symfony tout en utilisant des solutions "modernes" ou en tout cas "hype". L'utilisation du framework serverless offre une solution pertinente dans un nombre de cas avérés, et il est toujours utile de la garder dans un coin de sa tête.

    Pour aller plus loin, il est important de rappeler que l'utilisation de solution serverless entraine une notion de coût variable, qu'il est important d'encadrer et de maitriser dès le départ. Un article écrit par nos amis de JoliCode aborde également ce sujet.

    Le code source de l'application Symfony de démo, ainsi que les fichiers de configuration, peuvent être consultés sur notre dépot Github : https://github.com/acseo/symfony-lambda-demo. Ce dépot contient également un dump SQL avec plus d'une centaines de citation de la série.

    Vous recherchez des experts Symfony, Contactez nous !

    Une idée, un projet ? Nous sommes à votre disposition pour discuter.