Archives mensuelles : janvier 2018

[Symfony 4] Création de fixtures aléatoires – Faker

Rien de plus ennuyant que de créer des jeux de données afin de tester votre application, d’autant qu’il existe des scripts pour ça, comme Faker qui fait très bien le boulot.
Ces données s’appellent plus communément des « fixtures ».

On va commencer par créer un nouveau projet Symfony 4 :

composer create-project symfony/skeleton sf4-faker

Et y ajouter quelques librairies qui nous serons utiles :

composer req orm
composer req --dev make doctrine/doctrine-fixtures-bundle

Puis tout de suite ajouter le composant qui nous intéressent : Faker (https://github.com/fzaninotto/Faker) :

composer req --dev fzaninotto/faker

Ensuite on va créer une nouvelle entité appelée « Personne » :

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\PersonneRepository")
 */
class Personne
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=100)
     */
    private $nom;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $adresse;

    /**
     * @ORM\Column(type="string", length=100)
     */
    private $ville;

    /**
     * @ORM\Column(type="string", length=15)
     */
    private $codePostal;

    /**
     * @ORM\Column(type="text")
     */
    private $description;

    /**
     * @ORM\Column(type="string", length=100)
     */
    private $email;

    /**
     * Get the value of id
     */ 
    public function getId()
    {
        return $this->id;
    }

    /**
     * Get the value of nom
     */ 
    public function getNom()
    {
        return $this->nom;
    }

    /**
     * Set the value of nom
     *
     * @return  self
     */ 
    public function setNom($nom)
    {
        $this->nom = $nom;

        return $this;
    }

    /**
     * Get the value of adresse
     */ 
    public function getAdresse()
    {
        return $this->adresse;
    }

    /**
     * Set the value of adresse
     *
     * @return  self
     */ 
    public function setAdresse($adresse)
    {
        $this->adresse = $adresse;

        return $this;
    }

    /**
     * Get the value of ville
     */ 
    public function getVille()
    {
        return $this->ville;
    }

    /**
     * Set the value of ville
     *
     * @return  self
     */ 
    public function setVille($ville)
    {
        $this->ville = $ville;

        return $this;
    }

    /**
     * Get the value of codePostal
     */ 
    public function getCodePostal()
    {
        return $this->codePostal;
    }

    /**
     * Set the value of codePostal
     *
     * @return  self
     */ 
    public function setCodePostal($codePostal)
    {
        $this->codePostal = $codePostal;

        return $this;
    }

    /**
     * Get the value of description
     */ 
    public function getDescription()
    {
        return $this->description;
    }

    /**
     * Set the value of description
     *
     * @return  self
     */ 
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Get the value of email
     */ 
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * Set the value of email
     *
     * @return  self
     */ 
    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }
}

lors de l’installation du composant fixtures ce dernier a installé un nouveau répertoire : DataFixtures, c’est dans ce dernier que nous allons générer nos fixtures.

Vous trouverez ici tous les types de données que Faker peut générer : https://github.com/fzaninotto/Faker#formatters

Voilà notre fichier FakerFixtures :

<?php
// src/DataFixtures/FakerFixtures.php
namespace App\DataFixtures;

use App\Entity\Personne;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Faker;

class FakerFixtures extends Fixture
{
    public function load(ObjectManager $manager)
    {

        // On configure dans quelles langues nous voulons nos données
        $faker = Faker\Factory::create('fr_FR');

        // on créé 10 personnes
        for ($i = 0; $i < 10; $i++) {
            $personne = new Personne();
            $personne->setNom($faker->name);
            $personne->setAdresse($faker->streetAddress);
            $personne->setVille($faker->city);
            $personne->setCodePostal($faker->postcode);
            $personne->setDescription($faker->text);
            $personne->setEmail($faker->email);
            $manager->persist($personne);
        }

        $manager->flush();
    }
}

Un petit :

php bin/console doctrine:fixtures:load

Et voilà notre base remplie de 10 personnes :

Vous pouvez retrouver les sources ici :

https://github.com/gponty/sf4-faker

[Symfony 4] Utiliser vue.js

Dans cet article nous allons nous attaquer à la mise en place de vue.js avec Symfony 4.

Vue.js est un framework javascript que j’apprécie particulièrement pour sa simplicité.

Avant de commencer assurez-vous d’avoir bien nodeJS sur votre poste.

On va tout d’abord créer un nouveau projet symfony :

composer create-project symfony/skeleton symfony-vue

Puis ajouter quelques librairies :

composer require annotations twig encore asset
composer require server --dev

Et enfin installer vue.js :

npm install --dev vue vue-loader vue-template-compiler

Afin d’initialiser le projet, il suffit de faire :

npm install

Ces commandes vont créer les répertoires assets, node_module ainsi que le fichier webpack.config.js

C’est ce dernier que nous allons tout de suite modifier :

var Encore = require('@symfony/webpack-encore');

Encore
    .setOutputPath('public/build/')
    .setPublicPath('/build')
    .cleanupOutputBeforeBuild()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning(Encore.isProduction())
    .addEntry('app', './assets/js/main.js')
    .enableVueLoader()
;

module.exports = Encore.getWebpackConfig();

Et il faut enfin donner le chemin à symfony du manifest.json afin qu’il trouve les différents assets (le manifet.json sera créé au moment du build)

    # config/packages/framework.yaml
    assets:
        json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'

Voilà pour l’installation, on va maintenant passer aux choses sérieuses en créant nos pages.

On va d’abord créer notre controller, rien de compliqué ici :

<?php
// src/Controller/DefaultController.php

namespace App\Controller;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DefaultController extends Controller
{
    /**
     * @Route("/")
     */
    public function default()
    {
        return $this->render('base.html.twig');
    }
}

Puis le fichier twig templates/base.html.twig :

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}{% endblock %}
    </head>
    <body>
        {% block body %}
        <div id="app">Oups... Problème</div>
        {% endblock %}

        {% block javascripts %}
        <script src="{{ asset('build/app.js') }}"></script>
        {% endblock %}
    </body>
</html>

Le composant vue.js dans assets/js/App.vue :

<template>
  <div id="app">
    <hello></hello>
  </div>
</template>

<script>
import Hello from './components/Hello'

class TestClassSyntax {

}

export default {
  name: 'app',
  components: {
    Hello
  }
}
</script>

Le fichier main de vue dans assets/js/main.js :

import Vue from 'vue'
import App from './App'

new Vue({
  el: '#app',
  template: '<App/>',
  components: { App }
})

Et enfin le fichier assets/js/components/Hello.vue :

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
  </div>
</template>

<script>
export default {
  name: 'hello',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>

Si vous lancer votre serveur à ce moment :

php bin/console server:run

vous allez avoir une erreur 404 vous disant qu’il ne trouve pas le fichier build/app.js, c’est tout à faire normal puisqu’il faut que l’on lance la génération de ce fichier en faisant :

./node_modules/.bin/encore dev

Si tout se passe bien, en allant sur http://127.0.0.1:8000 vous devez avoir un « Welcome to Your Vue.js App » s’afficher, et un « Oups… Problème » si il y a un… problème.

Petite astuce si voulez voir vos modifs en direct sans à chaque fois refaire une génération, à la place de la commande ci-dessus il faut faire :

./node_modules/.bin/encore dev-server --hot

Vous pouvez retrouver les sources ici : https://github.com/gponty/symfony-vue

[Symfony 4] – Gestion d’événements

On continue notre série de gestion des utilisateurs sans FOSUserBundle, cette fois on va mettre en place un EventListener, c’est à dire un événement qui va se déclencher là où on le voudra.

On va simplement envoyer un mail à l’utilisateur qui vient de s’inscrire afin de lui souhaiter la bienvenue.

Pour cela on va déjà ajouter la librairie qui permet de gérer l’envoi des mails :

composer req symfony/swiftmailer-bundle

Suite à ça vous devez avoir une nouvelle ligne dans votre fichier .env, qu’il faut modifier selon votre serveur, pour moi c’est :

MAILER_URL=smtp://localhost:25?encryption=&auth_mode=

(vous avez aussi la même ligne dans le fichier phpunit.xml.dist si vous voulez envoyer des mails durant vos tests)

On va aussi ajouter quelques paramètres dans nos fichiers de config, comme l’adresse expéditeur :

# services.yaml
parameters:
    locale: 'en'
    app.notifications.email_sender: g.ponty@dev-web.io

services:
   # le nom de votre service
   App\EventSubscriber\RegistrationNotifySubscriber:
        # le nom de la variable que l'on utilisera dans le service
        $sender: '%app.notifications.email_sender%'

Pour mettre en place de bonnes pratiques, on va créer un fichier qui regroupera tous nos events, ça permettra de les avoir tous au même endroit :

// App/Events.php
namespace App;

/**
 * This class defines the names of all the events dispatched in
 * our project. It's not mandatory to create a
 * class like this, but it's considered a good practice.
 *
 */
final class Events
{
    /**
     * For the event naming conventions, see:
     * https://symfony.com/doc/current/components/event_dispatcher.html#naming-conventions.
     *
     * @Event("Symfony\Component\EventDispatcher\GenericEvent")
     *
     * @var string
     */
    const USER_REGISTERED = 'user.registered';
}

On créé maintenant notre Listener, il n’y a rien de compliqué :

<?php
// App\EventSubscriber\RegistrationNotifySubscriber.php
namespace App\EventSubscriber;

use App\Entity\User;
use App\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\EventDispatcher\GenericEvent;

/**
 * Envoi un mail de bienvenue à chaque creation d'un utilisateur
 *
 */
class RegistrationNotifySubscriber implements EventSubscriberInterface
{
    private $mailer;
    private $sender;

    public function __construct(\Swift_Mailer $mailer, $sender)
    {
        // On injecte notre expediteur et la classe pour envoyer des mails
        $this->mailer = $mailer;
        $this->sender = $sender;
    }

    public static function getSubscribedEvents(): array
    {
        return [
            // le nom de l'event et le nom de la fonction qui sera déclenché
            Events::USER_REGISTERED => 'onUserRegistrated',
        ];
    }

    public function onUserRegistrated(GenericEvent $event): void
    {
        /** @var User $user */
        $user = $event->getSubject();

        $subject = "Bienvenue";
        $body = "Bienvenue mon ami.e sur ce tutorial";

        $message = (new \Swift_Message())
            ->setSubject($subject)
            ->setTo($user->getEmail())
            ->setFrom($this->sender)
            ->setBody($body, 'text/html')
        ;

        $this->mailer->send($message);
    }
}

Et enfin on modifie notre Controller pour juste y ajouter le déclenchement de notre event :

<?php
// src/Controller/RegistrationController.php
namespace App\Controller;

use App\Form\UserType;
use App\Entity\User;
use App\Events;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;

class RegistrationController extends Controller
{
    /**
     * @Route("/register", name="user_registration")
     */
    public function registerAction(Request $request, UserPasswordEncoderInterface $passwordEncoder, EventDispatcherInterface $eventDispatcher)
    {
    
        $user = new User();
        $form = $this->createForm(UserType::class, $user);

        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {

            $password = $passwordEncoder->encodePassword($user, $user->getPassword());
            $user->setPassword($password);

            // Par defaut l'utilisateur aura toujours le rôle ROLE_USER
            $user->setRoles(['ROLE_USER']);

            // On enregistre l'utilisateur dans la base
            $em = $this->getDoctrine()->getManager();
            $em->persist($user);
            $em->flush();

            //On déclenche l'event
            $event = new GenericEvent($user);
            $eventDispatcher->dispatch(Events::USER_REGISTERED, $event);

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

        return $this->render(
            'register.html.twig',
            array('form' => $form->createView())
        );
    }
}

and voilà !

Vous pouvez retrouver toutes les sources ici :

https://github.com/gponty/userDemo

[Symfony 4] – Tests unitaires

Rien à voir avecPhpUnit mais on va installer un serveur web pour exécuter notre site :

composer require --dev symfony/web-server-bundle

Puis pour lancer le serveur :

php bin/console server:run

Vous pouvez comme ça accéder à votre application avec : http://127.0.0.1:8000

Ensuite pour les tests, il faut le composant PHPUnit :

composer req symfony/phpunit-bridge

Et pour les tests fonctionnels on va avoir besoin de :

composer req --dev browser-kit
composer req --dev symfony/css-selector

Il faut aussi créer le répertoire (si cela n’a pas était fait automatiquement) qui contiendra tout nos tests, on l’appellera pour être original : « tests »

Et comme on est feignant on va faire appelle au maker pour nous créer nos classes :

php bin/console make:unit-test
php bin/console make:functional-test

On va maintenant faire un peu de config, dans le fichier config/packages/test/framework.yaml, nous allons lui dire que nous allons utiliser les sessions (juste décommenter les 2 dernières lignes) :

framework:
    test: ~
    # Uncomment this section if you're using sessions
    session:
        storage_id: session.storage.mock_file

Et surtout ajouter cette ligne dans le fichier phpunit.xml.dist, c’est sur cette base de données que va se connecter phpunit pour effectuer qui ont besoin de votre base :

<!-- define your env variables for the test env here -->
<env name="DATABASE_URL" value="mysql://root:@127.0.0.1/userDemo" />

ATTENTION : il faut que la base de données soit la même que ce qu’il y a dans votre fichier env.
Ceci implique que vos tests seront réalisés sur la base réelle, l’idéal serait de remplacer userDemo par userDemoTest et utiliser une base dédiée aux tests.
Le problème c’est que je n’ai pas trouvé les commandes en symfony 4 pour créer la base test avec son schéma.

Vous pouvez déjà exécuter les tests par défaut en faisant simplement :

php bin/phpunit

Voici le résultat attendu :

PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

Testing Project Test Suite
..                                                                  2 / 2 (100%)

Time: 12.97 seconds, Memory: 26.00MB

OK (2 tests, 3 assertions)

On va maintenant créer un 1er test qui consiste à vérifier qu’un message d’erreur s’affiche bien lorsque l’utilisateur s’inscrit en ne saisissant pas les 2 mêmes mot de passe :

public function testCheckPassword(){
        $client = static::createClient();

        $crawler = $client->request(
            'GET',
            '/register'
        );

        $form = $crawler->selectButton('S\'inscrire')->form();

        $form['user[email]'] = 'toto@email.com';
        $form['user[username]'] = 'usernametest';
        $form['user[fullName]'] = 'John Doe';
        $form['user[password][first]'] = 'pass1';
        $form['user[password][second]'] = 'pass2';

        $crawler = $client->submit($form);

        //echo $client->getResponse()->getContent();


        $this->assertEquals(1,
            $crawler->filter('li:contains("This value is not valid.")')->count()
        );
    }

Si tout va bien vous devriez obtenir :

#!/usr/bin/env php
PHPUnit 6.5.5 by Sebastian Bergmann and contributors.

Testing Project Test Suite
...                                                                 3 / 3 (100%)

Time: 158 ms, Memory: 22.00MB

OK (3 tests, 4 assertions)