Création d'un projet sous Symfony 4 avec Vagrant

  • php
  • symfony
  • vagrant

Published at 2018-06-20

Symfony est le framework PHP le plus populaire en France. La version 4 est sortie en décembre et se veut plus légÚre et moins complexe que la précédente version. Voyons donc comment comment l'installer avec une machine virtuelle qui fera tourner un serveur Apache.

Vagrant

Installation

Vagrant est un outil qui permet de configurer et ainsi de reproduire facilement des machines virtuelles. L'environnement est exactement le mĂȘme pour tous les dĂ©veloppeurs, quel que soit le systĂšme d'exploitation de la machine hĂŽte.

Vagrant utilise Virtual Box. Il faut donc commencer par installer les deux outils via leurs sites officiels. Voici un exemple pour l'OS Ubuntu 18.04.

wget https://download.virtualbox.org/virtualbox/5.2.12/virtualbox-5.2_5.2.12-122591~Ubuntu~bionic_amd64.deb
dpkg -i virtualbox-5.2_5.2.12-122591~Ubuntu~bionic_amd64.deb
wget https://releases.hashicorp.com/vagrant/2.1.1/vagrant_2.1.1_x86_64.deb
dpkg -i vagrant_2.1.1_x86_64.deb

Une fois Vagrant & Virtual Box installés, on peut initialiser un nouveau projet avec la commande vagrant

mkdir RentMyRoom
cd RentMyRoom
vagrant init

Configuration

vagrant init a créé un nouveau fichier Vagrantfile qui contient toute la configuration de notre machine virtuelle. La directive

On met Ă  jour la configuration de Vagrant pour notre projet

# Vagrantfile
Vagrant.configure("2") do |config|
  # charge une machine virtuelle pré-configurée téléchargeable sur https://app.vagrantup.com/boxes/search
  # cette VM contient Ubuntu 18.04, PHP 7.2, MYSQL 5.7, memcached, Composer
  config.vm.box = "secalith/bionic64"
  # configure le réseaux
  config.vm.network "private_network", ip: "192.168.33.10"
  # on utilise un sous-dossier contiendra notre projet
  config.vm.synced_folder "./RentMyRoom/", "/var/www/html", owner: "www-data", group: "www-data", id: 'source'
  # on crée écrase la configuration par défault d'Apache
  config.vm.synced_folder "./apache2/", "/etc/apache2/sites-enabled", owner: "root", group: "root", id: 'vhost'
  # on force le redémarrage une fois la configuration d'Apache écrasée
  config.vm.provision :shell, run: "always", inline: "/etc/init.d/apache2 restart"
end

et notre Vhost Apache spécifique

# apache2/000-default.conf

<VirtualHost *:80>
    ServerName rent-my-room.local
    ServerAlias www.rent-my-room.local

    DocumentRoot /var/www/html/public
    <Directory /var/www/html/public>
        AllowOverride None
        Order Allow,Deny
        Allow from All

        <IfModule mod_rewrite.c>
            Options -MultiViews
            RewriteEngine On
            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteRule ^(.*)$ index.php [QSA,L]
        </IfModule>
    </Directory>

    <Directory /var/www/project/public/bundles>
        <IfModule mod_rewrite.c>
            RewriteEngine Off
        </IfModule>
    </Directory>
    ErrorLog /var/log/apache2/project_error.log
    CustomLog /var/log/apache2/project_access.log combined

</VirtualHost>

et on démarre notre machine virtuelle

vagrant up

Cette commande va télécharger notre machine virtuelle et la démarrer.

Symfony

On initialise le projet avec composer. Pour cela, on se connecte Ă  la machine virtuelle avec vagrant ssh

vagrant ssh
cd html

et on installe les dépendances

composer install

Il suffit ensuite de se rendre à l'adresse 192.168.33.10 et la page d’accueil s'affiche!!

Prise en main de Symfony

Imaginons une application web permettant de louer des salles Room Ă  des utilisateurs User.

Gestion des utilisateurs

Symfony utilise des bundle qui sont des "morceaux d'applications" utilisables dans d'autres projets. Dans notre cas, au lieu de partir de zéro, nous utiliserons FOSUserBundle. Ce Bundle gÚre

  • la crĂ©ation d'utilisateur
  • la confirmation de son adresse mail
  • la connexion de l'utilisateur

Pour l'installer il suffit de suivre les indications de la documentation officielle.

Création de notre entité

Création du modÚle

Nous allons ici créer notre modÚle Room et créer les actions CRUD. Dans un premier temps, il faut créer notre entité. Pour cela on utilise la commande doctrine:generate:entities

php bin/console doctrine:generate:entities Room

Cette commande va créer:

  • RentMyRoom/src/Entity/Room.php
  • RentMyRoom/src/Repository/RoomRepository.php

Pour mettre à jour notre base de données, deux options existent:

  • Mettre Ă  jour la base de donnĂ©e automatiquement
php bin/console doctrine:schema:update
  • CrĂ©er une migration contenant les diffĂ©rences depuis la derniĂšre migration
php bin/console doctrine:migration:diff

Et pour lancer toutes les migrations non-jouées, on utilise

php bin/console doctrine:migration:migrate

Création de tout le reste

Il suffit ensuite d'utiliser make:crud afin de générer automatiquement le controller, le formulaire et les vues en fonction de notre modÚle:

php bin/console make:crud Room

Cette commande va créer ces fichiers

  • src/Controller/RoomController.php contenant le controller et toutes les actions CRUD
  • src/Form/RoomType.php contenant le formulaire
  • templates/room/ contenant les vues CRUD

Utiliser Bootstrap pour le formulaire

Il suffit d'éditer le formulaire contenu dans RentMyRoom/src/Form/RoomType.php. La fonction FormBuilderInterface::add permet de spécifier le type du champ et de spécifier la classe CSS.

// RentMyRoom/src/Form/RoomType.php
<?php

namespace App\Form;

// uses ..

class RoomType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('address', TextType::class, ['attr'   => ['class' => 'form-control']])
            ->add('start_at', DateTimeType::class, ['widget' => 'single_text', 'attr'   => ['class' => 'form-control']])
            ->add('end_at', DateTimeType::class, ['widget' => 'single_text', 'attr'   => ['class' => 'form-control']])
            ->add('price', NumberType::class, ['attr'   => ['class' => 'form-control']])
            ->add('size', NumberType::class, ['attr'   => ['class' => 'form-control']])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(['data_class' => Room::class,]);
    }
}

Ne pas oublier d'ajouter Bootstrap au fichier Template/base.html.twig

Créer la relation User has many Rooms

Il suffit de se rendre dans notre entité Room et d'ajouter la liaison

<?php
// Entity/Room.php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\RoomRepository")
 */
class Room
{
    // ...

    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\User")
     */
    private $user;

    public function getUser() : User
    {
        return $this->user;
    }

    public function setUser(User $user): void
    {
        $this->user = $user;
    }
}

On génÚre ensuite la migration

php bin/console doctrine:migrations:diff

Cette commande va nous générer automatiquement un migration qui va ajouter

  • la colonne user_id dans la table room
  • la clĂ© primaire afin de vĂ©rifier l'existence de l'user
<?php
// src/Migrations/Version20180619075756.php

// ..

public function up(Schema $schema) : void
{
    // this up() migration is auto-generated, please modify it to your needs
    $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');

    $this->addSql('ALTER TABLE room ADD user_id INT DEFAULT NULL');
    $this->addSql('ALTER TABLE room ADD CONSTRAINT FK_729F519BA76ED395 FOREIGN KEY (user_id) REFERENCES users (id)');
    $this->addSql('CREATE INDEX IDX_729F519BA76ED395 ON room (user_id)');
}

Pour modifier notre base de données, il faut lancer la commande

php bin/console doctrine:migrations:migrate

Modifier le controller

On modifie ensuite notre controller afin d'ajouter l'user connecté à la room créée

<?php
// src/Controller/RoomController.php

/**
 * @Route("/room")
 */
class RoomController extends Controller
{
    // ..

    /**
     * @Route("/new", name="room_new", methods="GET|POST")
     */
    public function new(Request $request): Response
    {
        $room = new Room();
        $form = $this->createForm(RoomType::class, $room);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $em = $this->getDoctrine()->getManager();
            $room->setUser($this->getUser());
            $em->persist($room);
            $em->flush();

            return $this->redirectToRoute('room_index');
        }

        return $this->render('room/new.html.twig', [
            'room' => $room,
            'form' => $form->createView(),
        ]);
    }

    // ...

}

Afin de restreindre les actions edit et destroy Ă  l'utilisateur qui possĂšde

<?php
// src/Controller/RoomController.php

/**
 * @Route("/room")
 */
class RoomController extends Controller
{

    // ...

    /**
     * @Route("/{id}/edit", name="room_edit", methods="GET|POST")
     */
    public function edit(Request $request, Room $room): Response
    {
        $this->checkRoomOwner($room);
        // ...
    }

    /**
     * @Route("/{id}", name="room_delete", methods="DELETE")
     */
    public function delete(Request $request, Room $room): Response
    {
        $this->checkRoomOwner($room);
        // ...
    }


    private function checkRoomOwner(Room $room)
    {
        $currentUser = $this->getUser();
        if(is_null($currentUser)) {
            // should not happens because of security bundle
            throw $this->createAccessDeniedException('You must be connected to access on this pas');
        }elseif($currentUser != $room->getUser()){
            // user should own this room
            throw $this->createAccessDeniedException('You do not own this room');
        }
    }
}