Laravel 5.3 : les nouveautés

La version 5.3 est sortie et il est temps d’aller voir les nouveautés ! Elles sont nombreuses, certaines importantes et d’autres minimes, on va faire un peu le tour de tout ça sans toutefois épuiser le sujet !

J’ai actualisé mon exemple sur github avec un nouveau dépôt. J’en ai profité pour nettoyer le code et le réorganiser, j’ai utilisé les notifications pour l’envoi d’emails et j’ai aussi ajouté des tests !

Mais la première chose à savoir c’est que la version minimale de PHP est désormais la 5.6.4.

Ce passage de PHP 5.5.* à PHP 5.6.* nous donne la possibilité d’utiliser de nouvelles fonctionnalités. Vous en avez la liste sur cette page du manuel. En gros on a comme nouveautés :

  • des expressions scalaires dans l’affectation des constantes (bon, faut en avoir besoin…),
  • l’opérateur qui va faciliter l’utilisation des fonctions avec un nombre d’arguments variable,
  • le nouveau opérateur ** pour les puissances,
  • l’utilisation de use pour les constantes et les fonctions…

Structure

On va commencer par installer Laravel 5.3 :

    composer create-project laravel/laravel laravel5

Voici la structure que j’obtiens :

img13

Routes et middlewares

A première vue pas de grand changements, toutefois le dossier routes attire mon regard ! Regardons son contenu :

img14

On se retrouve avec deux fichiers : un pour le web et l’autre pour les api. J’en conclus que le fichier des routes dans le dossier app a disparu, voyons cela :

img14

Apparemment il n’y a pas que cela qui a disparu, ce dossier a fait une cure d’amaigrissement ! Mais pour le moment on va s’intéresser aux routes.

Voyons le contenu de web.php :

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| This file is where you may define all of the routes that are handled
| by your application. Just tell Laravel the URIs it should respond
| to using a Closure or controller method. Build something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Une simple route pour la racine qui renvoie la vue welcome.

Et pour api.php :

<?php

use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::get('/user', function (Request $request) {
    return $request->user();
});

Là aussi une simple route pour user qui renvoie l’utilisateur connecté.

La partie intéressante va se trouver au niveau des middlewares pour ces deux routes. Voyons un listing :

img15

On voit qu’on a :

  • pour la route web : le middleware web,
  • pour la route api : les middlewares api et auth:api. On voit également l’ajout automatique du préfixe api.

Voyons dans app/Http/Kernel.php de plus près ces middlewares :

/**
 * The application's route middleware groups.
 *
 * @var array
 */
protected $middlewareGroups = [
    'web' => [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    'api' => [
        'throttle:60,1',
        'bindings',
    ],
];

Pour le web on a les classiques : cookies, session, protection CSRF… et pour l’api un throttle et des bindings.

Mais où se fait l’affectation de ces middlewares pour nos deux fichiers de routes ? Pour ça il faut aller jeter un oeil dans le provider :

img16

On y trouve ce code pour le web :

/**
 * Define the "web" routes for the application.
 *
 * These routes all receive session state, CSRF protection, etc.
 *
 * @return void
 */
protected function mapWebRoutes()
{
    Route::group([
        'middleware' => 'web',
        'namespace' => $this->namespace,
    ], function ($router) {
        require base_path('routes/web.php');
    });
}

Un groupe avec le middleware web et un espace de nom qui est une propriété avec la valeur App\Http\Controllers. D’autre part on pointe le fichier routes/web.php.

Pour la partie api on a :

/**
 * Define the "api" routes for the application.
 *
 * These routes are typically stateless.
 *
 * @return void
 */
protected function mapApiRoutes()
{
    Route::group([
        'middleware' => ['api', 'auth:api'],
        'namespace' => $this->namespace,
        'prefix' => 'api',
    ], function ($router) {
        require base_path('routes/api.php');
    });
}

On déclare les deux middleware : api et auth:api. Pour l’espace de nom évidemment c’est le même que pour le web. On a aussi le préfixe api. Et évidemment on pointe sur le fichier routes/api.php.

Si on regarde maintenant le dossier des middlewares :

img17

On en trouve plus guère ici ! Il y en a comme auth qui ont basculé dans le framework.

Le dossier app

Revenons en à ce dossier app amaigri :

img14

On a perdu pas mal de dossiers : Events, Jobs, Listeners, Policies. Mais finalement ces dossiers au départ étaient vides. Le parti pris a été de ne pas les prévoir et de les créer dynamiquement dès qu’on en a besoin. Par exemple si je crée un événement :

img18

Voyons ce qu’il s’est passé :

img19

Le dossier a été créé en même temps que l’événement. Il en est de même pour les autres dossiers. On va dire que c’est un changement cosmétique qui évite d’avoir des dossiers vides.

Routes des ressources

Les paramètres des ressources seront maintenant au singulier par défaut. Autrement dit si vous avez cette ressource :

Route::resource('articles', 'ArticleController');

La route pour le show sera :

/articles/{article}

Au lieu de :

/articles/{articles}

Si vous voulez éviter ce comportement, par exemple pour une mise à niveau, il faut le préciser dans AppServiceProvider :

Route::singularResourceParameters(false);

 

Blade

Blade bénéficie d’une nouvelle variable $loop pour la directive @foreach. Cette variable pointe en fait une classe standard de PHP avec un lot de propriétés :

  • index : un index pour les éléments (commence à 0)
  • iteration : un index pour les éléments (commence à 1)
  • remaining : le nombre d’éléments restants
  • count : le nombre total d’éléments
  • first : un booléen qui indique si c’est le premier élément
  • last : un booléen qui indique si c’est le dernier élément
  • depth : un entier qui indique la profondeur de la boucle
  • parent : référence la variable $loop de l’éventuelle boucle englobante, sinon renvoie null

Faisons un petit essai avec une collection :

Route::get('/test', function () {
    $collection = collect([['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]);
    return view('test', compact('collection'));
});

Et dans la vue :

@foreach($collection as $col)
    @foreach($col as $c)
        @if($loop->first)
            {{ 'Boucle de profondeur ' . $loop->depth . ' avec parent itération ' . $loop->parent->iteration }}
        @endif
        <li>{{ $loop->iteration . '/' . $loop->count . ' et il en reste ' . $loop->remaining . ' -> ' . $c }}</li>
    @endforeach
@endforeach

Ce qui donne :

img20

Je pense que ça va être bien pratique dans les vues !

Validation

Une nouveauté dans les validations : on pouvait déjà vérifier qu’on avait une image avec image. On va pouvoir maintenant ajouter des contraintes sur les dimensions de cette image avec la règle dimensions qui accepte ces contraintes :

  • : largeur minimale en pixels
  • max_width : largeur maximale en pixels
  • min_height : hauteur minimale en pixels
  • max_height : hauteur maximale en pixels
  • width : largeur précise en pixels
  • height : hauteur précise en pixels
  • ratio : rapport largeur/hauteur

On va donc pouvoir écrire ce genre de règle :

'paysage' => 'dimensions:min_width=800,max_height=400'

Et pour le ratio :

'paysage' => 'dimensions:ratio=3/2'

Bien pratique tout ça !

Pagination personnalisée

Si vous avez déjà essayé de personnaliser la pagination avec la version 5 vous devez savoir que c’est loin d’être facile mais on va avoir désormais une solution simple !

Regardez dans le framework la partie qui concerne la pagination :

img21

On voit apparaître un dossier resources/views qui contient les gabarits pour la pagination. Au passage il est un peu surprenant de voir la référence à bootstrap 4 qui n’en est qu’à la version alpha…

Si on regarde le fichier bootstrap-4.blade.php on trouve la pagination classique avec bootstrap :

<ul class="pagination">
    <!-- Previous Page Link -->
    @if ($paginator->onFirstPage())
        <li class="page-item disabled"><span class="page-link">«</span></li>
    @else
        <li class="page-item"><a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">«</a></li>
    @endif

    <!-- Pagination Elements -->
    @foreach ($elements as $element)
        <!-- "Three Dots" Separator -->
        @if (is_string($element))
            <li class="page-item disabled"><span class="page-link">{{ $element }}</span></li>
        @endif

        <!-- Array Of Links -->
        @if (is_array($element))
            @foreach ($element as $page => $url)
                @if ($page == $paginator->currentPage())
                    <li class="page-item active"><span class="page-link">{{ $page }}</span></li>
                @else
                    <li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>
                @endif
            @endforeach
        @endif
    @endforeach

    <!-- Next Page Link -->
    @if ($paginator->hasMorePages())
        <li class="page-item"><a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">»</a></li>
    @else
        <li class="page-item disabled"><span class="page-link">»</span></li>
    @endif
</ul>

Comme ce fichier est dans le framework on ne va pas aller le modifier ici !

Par contre on peut le publier :

img22

Et on retrouve les fichiers dans les ressources de l’application :

img23

Là on peut les modifier sans problème et le tour est joué !

Si vous ne voulez pas les publier il reste la solution de renseigner la vue lors de la pagination :

{{ $articles->links('view.mapagination') }}

Mais pourquoi se compliquer la vie ?

Query Builder

Dans les versions précédentes le query builder retourne un tableau dont chaque élément est une instance de StdClass. Ce qui permet d’écrire :

foreach ($articles as $article) {
    echo $article->title;
}

Désormais le query builder va retourner une instance de Illuminate\Support\Collection, donc une collection. On pourra toujours écrire le code ci-dessus mais on pourra aussi utiliser toute la puissance des collections.

Si par exemple on a deux enregistrements dans la table users et qu’on les récupère on voit bien qu’on obtient une collection :

img24

On peut toujours transformer la collection en tableau en utilisant all(), mais une collection est quand même plus sympathique et utile, d’autant que ça introduit une homogénéité avec les résultats récupérés par Eloquent.

Si on veut par exemple le premier enregistrement il suffit d’utiliser first :

img25

Helpers

Au niveau des helpers on bénéficie d’un nouveau pour le cache. Pour mémoire j’ai rédigé sur ce blog un article concernant le cache mais évidemment sans utiliser l’helper qui n’existait pas encore !

Tous les helpers se trouvent dans le fichier helpers.php ici :

img26

On y trouve désormais celui pour le cache :

if (! function_exists('cache')) {
    /**
     * Get / set the specified cache value.
     *
     * If an array is passed, we'll assume you want to put to the cache.
     *
     * @param  dynamic  key|key,default|data,expiration|null
     * @return mixed
     *
     * @throws \Exception
     */
    function cache()
    {
        $arguments = func_get_args();

        if (empty($arguments)) {
            return app('cache');
        }

        if (is_string($arguments[0])) {
            return app('cache')->get($arguments[0], isset($arguments[1]) ? $arguments[1] : null);
        }

        if (is_array($arguments[0])) {
            if (! isset($arguments[1])) {
                throw new Exception(
                    'You must set an expiration time when putting to the cache.'
                );
            }

            return app('cache')->put(key($arguments[0]), reset($arguments[0]), $arguments[1]);
        }
    }
}

On retrouve toutes les possibillités du cache mais avec une syntaxe simplifiée :

img27

Pour ceux qui utilisent l’helper pour les sessions, ça fonctionne exactement pareil.

Mailable

Il y a aussi du changement pour l’envoi des email avec les classes « mailable » placée dans le dossier app/Mail. Pour respecter la cure d’amaigrissement ce dossier n’est pas prévu au départ et est généré dès la création de la première classe :

img28

On trouve la classe dans le dossier créé pour l’occasion :

img29Avec ce code :

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;

class EnvoiVoeux extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct()
    {
        //
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('view.name');
    }
}

Tout ce passe dans la méthode build. dans laquelle on va pouvoir utiliser from, subject, attach et view comme on le voit ci-dessus.

Peut-être qu’une bonne idée est de créer un dossier pour les vues des emails :

img30

On va configurer la classe pour envoyer des voeux à nos amis, ça pourrait donner quelque chose comme ça :

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use Services\Voeux;

class EnvoiVoeux extends Mailable
{
    use Queueable, SerializesModels;

    /**
     * The voeux instance.
     *
     * @var Voeux
     */
    public $voeux;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct(Voeux $voeux)
    {
        $this->voeux = $voeux;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
         return $this->from('cemoi@coucou.fr')
                ->view('emails.voeux')
                ->attach('/cartes/voeux.jpg');
    }
}

On aura automatiquement une variable $voeux dans la vue sans avoir besoin de le préciser.

Pour envoyer l’email on utilise la façade :

Mail::to($monami)
    ->cc($autreami)
    ->send(new EnvoiVoeux($voeux));

Et c’est parti !

Eloquent

Une petite modification : la méthode save désormais retourne false si le modèle n’a pas changé depuis le dernier accès, ce qui permet d’agir en conséquence.

Pour les tables pivot apparaît la nouvelle méthode toggle qui s’ajoute à attach et detach. Avec ces deux dernières méthodes il faut vérifier l’existence ou la non existence préalable avant de faire la mise à jour, avec toggle c’est automatique, on passe d’un état à un autre. Comme paramètre on peut transmettre des id, un tableau ou une collection.

Notifications

Voilà encore une nouveauté, on va avoir un système complet de notification ! Le système de notification de Laravel permet d’envoyer des informations par différents canaux : mail, SMS (avec Nexmo), ou Slack.Il y a déjà pas mal de drivers, vous pouvez tous les trouver ici.

Mais les notifications peuvent aussi être stockées dans une base en attendant d’être affichées sur le client.

C’est un vaste sujet que je ne vais pas développer dans cet article.

Par exemple pour les mails il y a un template dynamique qui permet d’écrire ce genre de code :

$this->line('Merci de votre participation')
    ->action('En savoir plus', 'http://parla.com')
    ->success()

On aura dans le mail le texte et pour l’action un bouton. Si c’est pour une erreur on change l’aspect du bouton avec error :

$this->line('Il y a apparemment un problème !')
    ->action('En savoir plus', 'http://parla.com')
    ->error()

Plutôt sympathique !

FrontEnd

Il y a aussi du nouveau du côté du frontend avec ce qu’on va appeler une forte suggestion d’utiliser vue.js (si vous ne connaissez pas cette superbe librairie aller jeter un coup d’oeil sur ma série d’initiation). Si vous regardez les assets vous aller y trouver un peu plus de choses que dans la version précédente :

img31

Et si vous regardez dans gulpfile.js :

elixir(mix => {
    mix.sass('app.scss')
       .webpack('app.js');
});

On voit l’utilisation de webpack (mais on pourrait aussi utiliser Rollup) pour la compilation du javascript à la mode elixir qui simplifie grandement les choses.

Pour faire fonctionner tout ça il faut commencer par installer les dépendances prévues dans package.json :

"devDependencies": {
  "bootstrap-sass": "^3.3.7",
  "gulp": "^3.9.1",
  "jquery": "^3.1.0",
  "laravel-elixir": "^6.0.0-9",
  "laravel-elixir-vue": "^0.1.4",
  "laravel-elixir-webpack-official": "^1.0.2",
  "lodash": "^4.14.0",
  "vue": "^1.0.26",
  "vue-resource": "^0.9.3"
}

On y trouve la version sass de bootstrap, mais aussi jquery, elixir, vue.js et son plugin pour les ressources. Bien entendu on n’est pas obligé d’utiliser tout ça, ni même de compiler nos assets mais on y est incité !

On va commencer par installer tout ça :

npm install

Il faut attendre un peu pour que le dossier node_modules se remplisse. Lorsque c’est fait il n’y a plus qu’à utiliser gulp :

gulp

Pour avoir aussi la minification il faudrait ajouter l’option –production.

Avec ce compte rendu des tâches :

img32

On voit que le css (bootstrap) est compilé dans public/css/app.css.

Pour le JavaScript tout va dans public/js/app.js. On a transformation de l’ES2015 en standard ES5. On a ici le JavaScript pour bootstrap et vue.js.

Tout est en place et on peut surveiller les changements avec :

gulp watch

Pour le moment tout ça ne sert à rien parce qu’aucune vue ne l’utilise…

Mais il suffit de mettre en oeuvre l’authentification pour avoir un layout qui l’utilise partiellement :

php artisan make:auth

Au passage vous remarquerez qu’il y a maintenant 4 contrôleurs plus légers pour l’authentification. D’autre part le verbe de la route du logout est passé de GET à POST pour être cohérent avec les normes. Donc attention aux mises à niveau !

Mais pour utiliser l’exemple de composant de vue.js il faut un peu de code…

On a un composant d’exemple qui se nomme example :

Vue.component('example', require('./components/Example.vue'));

Puisqu’on a un layout qui charge le css et le JavaScript grâce à l’authentification on va faire une simple vue test.blade.php :

@extends('layouts.app')
<example></example>

On ajoute une route :

Route::get('/test', function () { return view('test'); });

Et on voit appraître le composant au chargement :

img33

Si vous voulez utiliser vue.js toute l’infrastructure est déjà en place…

Je n’ai pas fait le tour complet de tous les changements et je n’ai pas vraiment approfondi mais cet article vous donne au moins une idée des nouveautés de cette version qui va bientôt débarquer !

9 réflexions sur “Laravel 5.3 : les nouveautés

  1. jerem dit :

    Bonjour,

    Toujours un très bon travail (j’avais déjà étudié le blog en version 5.2) !!

    J’ai un petit soucis, je n’arrive pas à faire fonctionner CKeditor, je n’arrive pas à avoir le panneau ‘parcourir le serveur’ en paramétrant avec : filebrowserBrowseUrl: ‘/elfinder/ckeditor’.

    moins important, pour les images uploadées dans /files dans le back il se dirrige bien vers public/files mais le front remonte sur le « / ». J’ai essayé de modifier un peu partout mais du coup ça oblige à donner le nom de l’appli, du coup c’est pas super pour des déploiement avec des noms différents….
    Du coup pour palier salement j’ai fait un lien /files => /public/files….

    Merci !!

  2. radamel17 dit :

    Bonjour,

    ma question pourra paraître stupide, mais en tant que débutant je me heurte à l’utilisation de la méthode POST des formulaires avec les routes. J’ai le message suivant TokenMismatch exception sous laravel 5.3.

    Voilà mon code :

    Titre de la page

    <input type="hidden" name="_token" value=" »>

    Quand je me rends sur la page (je suis en local), le input text ne s’affiche pas ou ne s’affiche que si je supprime <input type="hidden" name="_token" value=" »>.

    Voilà je suis un peu perdu…

    Merci d’avance pour les pistes.

  3. John Smith dit :

    Bonjour Momo et merci pour cette article.

    Après avoir lutté pour passer de la version 4.2 à la version 5.2, je me demande comment va se passer un éventuel passage à la version 5.3.

    Aussi, j’ai une question simple, est-ce que mon appli qui marche parfaitement en 5.2 marchera encore en 5.3 selon toi ?

Laisser un commentaire