Tuto : Installer Symfony 3 sous Debian, gérer les droits, utiliser Composer et Bower
Par SuperJohnson le
Bien installer Symfony n'est pas si compliqué mais peut quand même poser quelques problèmes, notamment au niveau des droits d'exécution pour l'écriture du cache via le chargement dans le navigateur ou dans la console, ou de la gestion des dépendances. Je vais essayer de vous montrer comment s'y prendre de A à Z.
Symfony est un framework PHP orienté objet développé par le français SensioLabs. Actuellement en version 3.2.4, c'est un outil particulièrement souple et impressionnant en terme de performances et d'adaptabilité qui a su s'imposer comme l'un des meilleurs sur le marché, notamment parce qu'il a eu l'intelligence d'intégrer nativement des librairies très utiles. Il compte par exemple parmi celles-ci le génial moteur de template Twig pour le rendu des vues, l'ORM Doctrine, qu'on ne présente plus, doté d'une couche d'abstraction particulièrement puissante, ou encore la librairie Swift Mailer pour envoyer des email via PHP le plus simplement du monde. Vous pouvez également installer autant de Bundle supplémentaires que vous le souhaitez ou en développer vous-même au besoin.
Le but ici n'est pas forcément de faire de vous des champions dudit framework, mais simplement de vous permettre d'installer la bête proprement pour bien démarrer. Je tourne personnellement sur une VM Debian 8 "Jessie" avec MySQL, PHP7.1 et Nginx configurés, et je possède déjà un Vhost Nginx pour un site de développement qu'on prendra pour exemple, www.site1.dev. Toute mon installation "from scratch" est décrite en détail dans le précédent tutoriel sur l'installation d'un serveur de développement sous Debian.
Installer Symfony
Je suis logué sous root. J'ai déjà les extensions curl et intl pour PHP 7.1 sur ma machine, voir le tuto précédent. Le chmod sert à s'assurer que tous les utilisateurs pourront exécuter Symfony.
curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony
chmod a+x /usr/local/bin/symfony
Notez qu'en plaçant l'exécutable dans /usr/local/bin/, la commande symfony sera accessible de partout. Placez-vous dans le dossier /var/www/. Vous pouvez renommer votre dossier site1 en old, on va l'utiliser pour l'exemple, sachant qu'on a déjà un Vhost Nginx de configuré pour ce répertoire, ça évite trop de manip :
mv site1 old
Créez votre projet Symfony à la place. Il est important de passer sous l'utilisateur dev ici. En exécutant Symfony sous dev, les fichiers créés lui appartiendront, or c'est sous cet utilisateur qu'on est destiné à les modifier lorsqu'on codera. Je ne précise pas de version particulière, donc c'est la version la plus récente qui sera installée pour ce projet.
su dev
symfony new site1
Vous pouvez vérifier que tout va bien en lancant :
site1/bin/symfony_requirements
A part une petite notice concernant votre version trop ancienne d'intl ICU, dont on se fout un peu, tout devrait être OK. Cette notice, je le précise, ne vous embêtera pas. La régler nécessiterait de compiler soi-même intl si ça vous intéresse.
Editez votre Vhost pour site1, on va adapter un peu notre config pour Symfony.
nano /etc/nginx/sites-available/site1
Voici une config adaptée à Symfony, avec quelques changements pour les paramètres fastCGI puisque nous les avons factorisés. Vous pouvez également consulter la documentation officielle de Symfony pour les Vhost, que vous utilisiez Nginx ou Apache. Attention aux limites de nombre de connexion par client, à commenter en développement puisque la toolbar de Symfony va atteindre ces limites!
# site1 redirection vers www
server {
listen 80;
listen [::]:80;
server_name site1.dev;
return 301 http://www.site1.dev$request_uri;
}
# site1
server {
# listen on
listen 80 default_server;
listen [::]:80 default_server;
# root directory
root /var/www/site1/web;
# server name
server_name www.site1.dev;
# php params, will include snippets if php is required
include php.conf;
# custom log files
access_log /var/log/nginx/site1.acc.log;
error_log /var/log/nginx/site1.err.log;
# deny access to .htaccess files
location ~ /\.ht {
deny all;
}
# deny access for extensions that we don't want to load
location ~ \.(ht|sql|bat|git|ini|sh|svn[^.]*|tpl)$ {
deny all;
}
# basic location
location / {
# try to serve file directly, fallback to app_dev.php
try_files $uri /app_dev.php$is_args$args;
# production params will be :
# try_files $uri /app.php$is_args$args;
# limit max connections per client
# comment it on dev mode so symf toolbar will be ok
# limit_req zone=req_limit_per_ip burst=5 nodelay;
# limit_conn conn_limit_per_ip 30;
}
# return 404 for all other php files not matching the front controller
# this prevents access to other php files you don't want to be accessible.
location ~ \.php$ {
return 404;
}
}
Notez qu'en production, vous pointerez bien évidemment vers app.php et non app_dev.php. Redémarrez Nginx pour qu'il prenne en compte les changements.
service nginx restart
Petite modif pour que tout ça fonctionne, éditez votre fichier site1/web/app_dev.php et commentez les lignes concernant la désactivation du mode développement non local. Commentez également la mise en cache, inutile en dév.
<?php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Debug\Debug;
// If you don't want to setup permissions the proper way, just uncomment the following PHP line
// read http://symfony.com/doc/current/setup.html#checking-symfony-application-configuration-and-setup
// for more information
// This check prevents access to debug front controllers that are deployed by accident to production servers.
// Feel free to remove this, extend it, or make something more sophisticated.
// if (isset($_SERVER['HTTP_CLIENT_IP'])
// || isset($_SERVER['HTTP_X_FORWARDED_FOR'])
// || !(in_array(@$_SERVER['REMOTE_ADDR'], ['127.0.0.1', '::1']) || php_sapi_name() === 'cli-server')
// ) {
// header('HTTP/1.0 403 Forbidden');
// exit('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
// }
/** @var \Composer\Autoload\ClassLoader $loader */
$loader = require __DIR__.'/../app/autoload.php';
Debug::enable();
$kernel = new AppKernel('dev', true);
// $kernel->loadClassCache();
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);
Gérer les droits
Il reste une partie très importante à régler : les droits sur les fichiers. En effet, lorsqu'on éditait tous nos fichiers nous-même via dev, il n'y avait pas de problème. Tous nos fichiers étaient créés en 0777 et tous les processus pouvaient les lire, les exécuter et les écrire. Le problème ici, c'est que Symfony, lorsqu'il nous a créé notre dossier site1, pour des raisons de sécurité, n'a pas donné les mêmes droits à tous les dossiers/fichiers de site1. Vous pouvez vous en rendre compte en listant les droits du contenu du dossier.
cd site1
ls -l
Le résultat est clair : les dossiers et fichiers du site n'ont pas les droits 0777.
drwxrwxr-x 4 dev dev 4096 févr. 26 22:26 app
drwxrwxr-x 2 dev dev 4096 févr. 26 22:53 bin
-rw-rw-rw- 1 dev dev 2102 févr. 26 22:53 composer.json
-rw-rw-rw- 1 dev dev 75700 févr. 26 22:53 composer.lock
-rw-rw-r-- 1 dev dev 978 févr. 17 01:42 phpunit.xml.dist
-rw-rw-rw- 1 dev dev 71 févr. 26 22:26 README.md
drwxrwxr-x 3 dev dev 4096 févr. 26 22:26 src
drwxrwxr-x 3 dev dev 4096 févr. 26 22:26 tests
drwxrwxr-x 5 dev dev 4096 févr. 26 22:53 var
drwxrwxr-x 15 dev dev 4096 févr. 26 22:53 vendor
drwxrwxr-x 4 dev dev 4096 févr. 26 23:19 web
Allez voir par exemple l'url de votre site : http://www.site1.dev/. Vous devriez voir ceci :
Eh oui. L'utilisateur qui exécute PHP ici, c'est le serveur web Nginx. Or son utilisateur par défaut (qu'on a gardé), c'est www-data. Et il n'a pas les droits d'écriture dans le dossier /var/www/site1/var/cache puisqu'il ne fait partie ni du groupe propriétaire, ni de l'utilisateur propriétaire. En fait comme on est en mode dev (on passe par app_dev.php), Symfony a besoin de créer un dossier profiler dedans pour pouvoir générer son profiler, sa fameuse barre de débug. En effet, même si on a désactivé la mise en cache, il a besoin d'écrire dans le cache pour le profiler, mais il n'y arrive pas. Dès le dossier var ça coince, car on a bien vu que all (qui représente ceux qui ne font pas partie des propriétaires) n'avait pas les droits d'écriture dessus.
Plus embêtant : Vous n'êtes sans doute pas sans savoir que par défaut, les fichiers créés via le shell appartiennent à l'utilisateur qui lance la commande. C'est valable par exemple pour les fichiers créés via la commande Symfony ou même via celle de Composer. Maintenant, imaginez que vous soyez un peu dans le cul un matin. Comme vous êtes à la ramasse, vous faites un sudo composer update par exemple. Ou alors vous le lancez en root. Les fichiers créés appartiendront à root, et vous ne pourrez plus écrire dessus. Vous seriez obligés de faire systématiquement des sudo pour update de nouveau les composants concernés avec Composer... ou de vous réattribuer le dossier complet avec un chmod.
Il y a pas mal de manières de régler ce souci. Perso, je vous en propose une dérivée de la documentation officielle : utiliser setfacl. Je trouve ça plutôt élégant et pratique, même si il y a évidemment d'autres façons de gérer cette problématique.
Pour éviter de se prendre le chou avec ces histoires de droits, l'idée est la suivante : On veut que www-data et dev aient toujours tous les droits sur tout le dossier site1. Le problème, c'est que même en réglant ça avec un chmod, ça ne fonctionnera pas pour les prochains fichiers créés. Et moi j'ai pas spécialement que ça à faire de faire des chmod toutes les semaines.
Pour pallier à ça, on va donc utiliser setafcl, comme conseillé dans la doc, pour ajouter "à la volée" et récursivement tous les droits à ces deux utilisateurs sur tout le répertoire site1:
cd /var/www/
setfacl -R -m u:www-data:rwX -m u:dev:rwX site1/
Puis on utilise ces nouveau droits par défaut plutôt que les droits réguliers :
setfacl -dR -m u:www-data:rwX -m u:dev:rwX site1/
De cette manière, à l'avenir, vous êtes sûrs de ne plus avoir de problème de droits d'écriture avec ce site. Notez que vous pouvez toujours revenir en arrière et retirer ces droits supplémentaires avec :
sudo setfacl -b --remove-all -R site1/
Enfin, je trouvais un peu cracra de faire ça sur tout le dossier var comme conseillé dans la doc, donc j'ai préféré mettre ça en place uniquement pour le dossier du site. Maintenant, vous faites ce que vous voulez chez vous.
Vous pouvez à nouveau visiter votre url, et cette fois, tout est rentré dans l'ordre :
Vous pouvez également vérifiez que tout va bien en console, en essayant par exemple d'effacer le cache.
bin/console cache:clear
Composer
J'installe le désormais célèbre gestionnaire de paquets Composer qui nous sera indispensable.
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php composer-setup.php
mv composer.phar /usr/local/bin/composer
php -r "unlink('composer-setup.php');"
composer -V
La version retournée devrait être au moins 1.3.2. On pourra appeler Composer quelque-soit l'endroit où vous vous trouvez sur la machine, de la même manière que pour l'exécutable de Symfony. Si vous avez un problème de droits, il vous suffira d'autoriser l'exécution de Composer pour tout le monde.
chmod a+x /usr/local/bin/composer
On peut mettre à jour les composants dépendants du projet grâce à Composer. Là encore, pour des questions de droit d'écriture des fichiers, il est mieux de le faire sous dev. Sans rien spécifier comme option, Composer me remontera les dernières versions stables plus à jour que les miennes, dans la limite des restrictions définies dans le fichier composer.json original de Symfony 3.2.4, mais on y viendra juste après.
composer update
Chez moi, la commande a mis à jour Twig, Doctrine et Security-checker. Pour l'installation basique de bundle via Composer et son usage, je vous invite à consulter la doc.
Et pour aller un peu plus loin, jetez un oeil à votre fichier composer.json à la racine du projet :
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.2.*",
"doctrine/orm": "^2.5",
"doctrine/doctrine-bundle": "^1.6",
"doctrine/doctrine-cache-bundle": "^1.2",
"symfony/swiftmailer-bundle": "^2.3.10",
"symfony/monolog-bundle": "^3.0.2",
"symfony/polyfill-apcu": "^1.0",
"sensio/distribution-bundle": "^5.0",
"sensio/framework-extra-bundle": "^3.0.2",
"incenteev/composer-parameter-handler": "^2.0",
"twig/twig": "^1.0||^2.0"
},
"require-dev": {
"sensio/generator-bundle": "^3.0",
"symfony/phpunit-bridge": "^3.0"
},
La partie require-dev liste les versions des dépendances possiblement installées/mises à jour par Composer, mais utiles seulement pour développer. Typiquement ici, c'est assez logique. PHPUnit sert à faire des tests unitaires, et vous n'avez pas besoin de générer de Bundle en production. Cette liste sera exclue si vous spécifiez --no-dev à votre commande. Par exemple...
composer update --no-dev
...ne cherchera pas à mettre à jour le generator-bundle ni le unit-bridge. Il est recommandé d'utiliser cette commande en production.
La partie require, elle, spécifie les versions des composants nécessaires à votre projet :
- >= a.b.c demande simplement une version minimum, sans spécifier de limite supérieure.
- a.b.* utilise le jocker pour dire à Composer de ne pas dépasser la version a.b.
- ^a.b demande au minimum la version a.b et s'arrêtera avant le changement de la branche principale. Il pourrait mettre à jour a.b.c vers a.x.x mais n'ira jamais récupérer b.x.x.
Il existe encore des subtilités avec la précision des versions stables et de développement, que je vous invite à lire ici. Et enfin, la liste complète des commandes et de leurs options se trouve ici.
Pour la liste des bundle déjà codés et prêts à l'emploi, vous en trouverez ici ou encore ici.
Bower (optionnel)
Composer est utile pour gérer tous les composants de backend. Mais pour les librairies statiques comme Bootstrap ou jQuery, ça devient compliqué puisqu'il va les installer par défaut dans le dossier vendor, extérieur au dossier root de Symfony qui est web. Du coup, si vous voulez gérer les versions des fichiers statiques de la même manière que Composer le fait pour les bundle PHP, il est plus pratique d'utiliser Bower. En fait, je pense qu'il est possible de pallier à ce problème en spécifiant à Composer d'installer certains trucs ailleurs, ou encore en disant à Twig de "sortir" du dossier web, mais ça me paraît un peu lourd voire carrément dangereux pour la deuxième option.
Je précise quand même une chose : Le versionnage de ces composants statiques, qu'on appelle assets, n'a pas forcément d'intérêt. Se prendre la tête pour obtenir la dernière version stable de Bootstrap tous les 3 mois relève, selon certains, de la masturbation. Mon avis là-dessus n'est pas encore établi, mais je pense qu'il est toujours d'actualité de télécharger et de dézipper "à la main" vos assets où vous le souhaitez dans le document root, et que l'utilisation de Bower est loin d'être indispensable. Il peut juste être de bon goût de l'utiliser si vous souhaitez partager un projet sur GitHub par exemple, pour permettre à vos utilisateurs qui utilisent Bower de s'en servir. Bref, cette section n'est pas du tout obligatoire, et vous pouvez parfaitement passer votre chemin.
Bower nécessite npm, Git et Node.js pour fonctionner. C'est parti pour npm.
apt-get install npm
Puis on installe Git.
apt-get install git-all
Puis NodeJS.
curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -
sudo apt-get install -y nodejs
Pour installer Bower, on est obligés de passer root, même un sudo ne marchera pas (je vois pas pourquoi d'ailleurs, c'est un peu n'imp). Je repasse sous dev juste après.
su root
npm install -g bower
su dev
On crée un ficher de config à la racine de site1 pour dire à Bower quel répertoire utiliser pour stocker les assets. Si on ne le fait pas, ils seront installés dans bower_components, hors du document root, ce qui est bien balot puisque nous on a besoin que nos assets soient dans le dossier web.
nano .bowerrc
On y place ceci pour renseigner le chemin de notre prochain dossier assets. J'ai repris la suggestion de la documentation Symfony.
{
"directory": "web/assets/vendor/"
}
Je m'installe Bootstrap, et jQuery viendra avec.
bower install bootstrap
Vous pouvez constater que vos fichiers ont bien atterri où vous aviez demandé qu'ils arrivent. Outre la gestion des dépendances qu'on va voir bientôt, Bower vous a renommé les dossiers en enlevant leur numéro de version, et compilé les fichiers de Bootstrap en Less. Un plus pour le coup, même si personnellement je ne m'y suis pas encore mis.
Ici je n'ai pas spécifié de version particulière, mais vous pouvez le faire en demandant explicitement un numéro de version précédé de # à l'installation d'un composant. Vous pouvez également demander à ce que votre asset soit compris entre deux numéros de version, comme avec Composer. Vous trouverez les principales commandes et options décrites sur le site officiel de l'outil.
Maintenant, comme pour Composer, on va se créer dynamiquement un fichier qui liste les dépendances de notre projet avec :
bower init
Je ne renseigne pas grand chose en réponse aux questions posées à part le nom du projet et le fait qu'il s'agisse d'un dossier privé. Je réponds que oui, les composants Bower sont des dépendances à lister.
? name site1
? description
? main file
? keywords
? authors
? license MIT
? homepage
? set currently installed components as dependencies? Yes
? add commonly ignored files to ignore list? Yes
? would you like to mark this package as private which prevents it from being accidentally published to the registry? Yes
{
name: 'site1',
description: '',
main: '',
license: 'MIT',
homepage: '',
private: true,
ignore: [
'**/.*',
'node_modules',
'bower_components',
'web/assets/vendor/',
'test',
'tests'
],
dependencies: {
bootstrap: '^3.3.7'
}
}
? Looks good? Yes
Comme vous pouvez le voir, Bower a compris que Bootstrap était déjà installé et a automatiquement listé sa branche majeure comme dépendance de mon projet, avec la même syntaxe que Composer. Vous pouvez retrouver et modifier ces dépendances dans le fichier bower.json qu'il a généré pour vous.
Maintenant je vais me rajouter Font Awesome et jQuery UI. L'option --save les ajoutera au passage à mon bower.json, ce qui en fera des dépendances de mon projet.
bower install jquery-ui --save
bower install fontawesome --save
Paramétrer Symfony et inclure ses assets
Les paramètres de connexion à la base de données et de connexion smtp sont stockés dans app/config/parameters.yml. Allons les éditer pour renseigner les données qui correspondent à notre configuration. Ici je vais rentrer les infos qui correspondent à mon installation du tuto précédent.
# This file is auto-generated during the composer install
parameters:
database_host: 127.0.0.1
database_port: null
database_name: site1
database_user: site1
database_password: site1
mailer_transport: smtp
mailer_host: smtp.gmail.com
mailer_user: devserver666@gmail.com
mailer_password: devserver666dev
secret: 1009668c02bd77254dr52748bc07v001d62552c8
Que vous ayez ou non utilisé Bower pour installer vos assets, il faut à présent les appeler pour que notre projet puisse les utiliser. Pour ça, rien de compliqué. Le template Twig utilisé par défaut sur la page d'accueil est app/Ressources/views/default/index.html.twig, mais il étend (extends) app/Ressources/views/base.html.twig. Voici ce que donne le fichier une fois les assets inclus dans base.html.twig :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>{% block title %}Welcome!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
{# Bootstrap #}
<link rel="stylesheet" href="{{ asset('assets/vendor/bootstrap/dist/css/bootstrap.min.css') }}">
{# jQuery UI #}
<link rel="stylesheet" href="{{ asset('assets/vendor/jquery-ui/themes/base/jquery-ui.min.css') }}">
{# Font Awesome #}
<link rel="stylesheet" href="{{ asset('assets/vendor/font-awesome/css/font-awesome.min.css') }}">
{# Custom CSS #}
{# Appelez ici votre propre fichier CSS, par exemple : #}
{# <link rel="stylesheet" href="{{ asset('assets/custom.css') }}"> #}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}
{# jQuery #}
<script type="text/javascript" src="{{ asset('assets/vendor/jquery/dist/jquery.min.js') }}"></script>
{# Bootstrap #}
<script type="text/javascript" src="{{ asset('assets/vendor/bootstrap/dist/js/bootstrap.min.js') }}"></script>
{# jQuery UI #}
<script type="text/javascript" src="{{ asset('assets/vendor/jquery-ui/jquery-ui.min.js') }}"></script>
{# Custom JS #}
{# Appelez ici votre propre fichier JS, par exemple : #}
{# <script type="text/javascript" src="{{ asset('assets/custom.js') }}"></script> #}
{% endblock %}
</body>
</html>
Voilà, vous pouvez vérifier dans le code source de la page d'index que tout est opérationnel.
Votre premier bundle
Juste pour vérifier que tout va bien, on va très rapidement se faire un nouveau bundle.
En console, placez-vous à la racine du site et générez-en un avec la commande adéquate.
bin/console generate:bundle
J'ai choisi comme nom IndexBundle, et j'ai tout laissé par défaut. Il y a beaucoup de moyens de gérer des routes, mais je trouve les annotations beaucoup plus pratiques que les autres. Le squelette de votre bundle est créé dans src/IndexBundle, et il a été enregistré automatiquement dans l'autoloader situé dans app/AppKernel.php.
Dans src/IndexBundle/Controller/DefaultController.php, vous pouvez voir que la méthode par défaut a été créée. Elle appelle une vue, elle aussi créée par la commande symfony. Editez votre route comme suit pour rendre cette méthode accessible via l'url de votre choix et passer une petite variable à votre nouvelle vue Twig :
<?php
namespace IndexBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
class DefaultController extends Controller
{
/**
* @Route("/index", name="index_action")
*/
public function indexAction()
{
// Juste un petit test de variable à transmettre à la vue
$test = ['un' => 1, 'deux' => 2, 'trois' => 3, 'soleil' => 'what?'];
// Passage à la vue de $test
return $this->render('IndexBundle:Default:index.html.twig', ['test' => $test]);
}
}
Il ne reste plus qu'à éditer votre vue src/IndexBundle/Resources/views/Default/index.html.twig pour étendre le template par défaut auquel on avait inclus nos assets, et faire une sorte de var_dump amélioré du tableau qu'on lui a transmis.
{% extends "base.html.twig" %}
{% block body %}
Hello World!
{{ dump(test) }}
{% endblock %}
Vous pouvez maintenant visiter l'url de la route nouvellement créée, et normalement tout roule. En prime, Twig vous a généré un superbe var_dump() boosté aux hormones : plus lisible, déroulant et sexy!
Vous êtes prêts pour entrer dans le bain pour apprendre à créer votre premier site sous Symfony. Bien entendu, la documentation est à lire, et le profiler est à utiliser sans restriciton. Je vous invite aussi à aller voir le tutoriel d'OpenClassrooms, rédigé en partenariat avec SensioLabs. Si ça vous intéresse, SensioLabs propose aussi d'installer une version démo d'un projet type, qui illustre les bonnes pratiques à respecter et des cas concrets d'utilisation. Il s'agit de Symfony Demo Application. Enjoy! ;)