Tutoriel pour apprendre les nouveautés de Laravel 5.3

Maurice Chavelli

Nous allons aborder dans ce tutoriel les nouveautés de la version 5.3 de Laravel ! 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, il faut en avoir besoin…) ;

- l'opérateur … qui va faciliter l'utilisation des fonctions avec un nombre d'arguments variable ;

- le nouvel opérateur ** pour les puissances ;

- l'utilisation de use pour les constantes et les fonctions…

Commentez

- Titre : Tutoriel pour apprendre les nouveautés de Laravel 5.3

- Auteur : Maurice Chavelli

- Parution : 11 décembre 2017

- Licence : Copyright ® 2017 Laravel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Structure

- I-A. Routes et middlewares

- I-B. Le dossier app

- II. Routes des ressources

- III. Blade

- IV. Validation

- V. Pagination personnalisée

- VI. Query Builder

- VII. Helpers

- VIII. Mailable

- IX. Eloquent

- X. Notifications

- XI. Front-End

- XII. Remerciements

I - Structure

On va commencer par installer Laravel 5.3 :

composer create-project laravel/laravel laravel5

Voici la structure que j'obtiens :

I-A - Routes et middlewares

À première vue, on n'a pas de grands changements, toutefois le dossier routes attire mon regard ! Regardons son contenu :

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 :

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 :

1. <?php

3. /*

4. |--------------------------------------------------------------------------

5. | Web Routes

6. |--------------------------------------------------------------------------

7. |

8. | This file is where you may define all of the routes that are handled

9. | by your application. Just tell Laravel the URIs it should respond

10. | to using a Closure or controller method. Build something great!

11. |

12. */

14. Route::get('/', function () {

15. return view('welcome');

16. });

C'est une simple route pour la racine, qui renvoie la vue welcome.

Et pour api.php :

1. <?php

3. use Illuminate\Http\Request;

5. /*

6. |--------------------------------------------------------------------------

7. | API Routes

8. |--------------------------------------------------------------------------

9. |

10. | Here is where you can register API routes for your application. These

11. | routes are loaded by the RouteServiceProvider within a group which

12. | is assigned the "api" middleware group. Enjoy building your API!

13. |

14. */

16. Route::get('/user', function (Request $request) {

17. return $request->user();

18. });

Là aussi on a une simple route pour user, qui renvoie l'utilisateur connecté.

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

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 :

1. /**

2. * The application's route middleware groups.

3. *

4. * @var array

5. */

6. protected $middlewareGroups = [

7. 'web' => [

8. \App\Http\Middleware\EncryptCookies::class,

9. \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,

10. \Illuminate\Session\Middleware\StartSession::class,

11. \Illuminate\View\Middleware\ShareErrorsFromSession::class,

12. \App\Http\Middleware\VerifyCsrfToken::class,

13. \Illuminate\Routing\Middleware\SubstituteBindings::class,

14. ],

16. 'api' => [

17. 'throttle:60,1',

18. 'bindings',

19. ],

20. ];

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 coup d'œil dans le provider :

On y trouve ce code pour le web :

1. /**

2. * Define the "web" routes for the application.

3. *

4. * These routes all receive session state, CSRF protection, etc.

5. *

6. * @return void

7. */

8. protected function mapWebRoutes()

9. {

10. Route::group([

11. 'middleware' => 'web',

12. 'namespace' => $this->namespace,

13. ], function ($router) {

14. require base_path('routes/web.php');

15. });

16. }

C'est 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 :

1. /**

2. * Define the "api" routes for the application.

3. *

4. * These routes are typically stateless.

5. *

6. * @return void

7. */

8. protected function mapApiRoutes()

9. {

10. Route::group([

11. 'middleware' => ['api', 'auth:api'],

12. 'namespace' => $this->namespace,

13. 'prefix' => 'api',

14. ], function ($router) {

15. require base_path('routes/api.php');

16. });

17. }

On déclare les deux middlewares : 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 :

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

I-B - Le dossier app

Revenons-en à ce dossier app amaigri :

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 :

Voyons ce qu'il s'est passé :

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.

II - 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);

III - 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 :

1. Route::get('/test', function () {

2. $collection = collect([['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]);

3. return view('test', compact('collection'));

4. });

Et dans la vue :

1. @foreach($collection as $col)

2. @foreach($col as $c)

3. @if($loop->first)

4. {{ 'Boucle de profondeur ' . $loop->depth . ' avec parent itération ' . $loop->parent->iteration }}

5. @endif

6. <li>{{ $loop->iteration . '/' . $loop->count . ' et il en reste ' . $loop->remaining . ' -> ' . $c }}</li>

7. @endforeach

8. @endforeach

Ce qui donne :

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

IV - 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 :

- min_width : 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 !

V - 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 :

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 :

1. <ul class="pagination">

2. <!-- Previous Page Link -->

3. @if ($paginator->onFirstPage())

4. <li class="page-item disabled"><span class="page-link">«</span></li>

5. @else

6. <li class="page-item"><a class="page-link" href="{{ $paginator->previousPageUrl() }}" rel="prev">«</a></li>

7. @endif

9. <!-- Pagination Elements -->

10. @foreach ($elements as $element)

11. <!-- "Three Dots" Separator -->

12. @if (is_string($element))

13. <li class="page-item disabled"><span class="page-link">{{ $element }}</span></li>

14. @endif

16. <!-- Array Of Links -->

17. @if (is_array($element))

18. @foreach ($element as $page => $url)

19. @if ($page == $paginator->currentPage())

20. <li class="page-item active"><span class="page-link">{{ $page }}</span></li>

21. @else

22. <li class="page-item"><a class="page-link" href="{{ $url }}">{{ $page }}</a></li>

23. @endif

24. @endforeach

25. @endif

26. @endforeach

28. <!-- Next Page Link -->

29. @if ($paginator->hasMorePages())

30. <li class="page-item"><a class="page-link" href="{{ $paginator->nextPageUrl() }}" rel="next">»</a></li>

31. @else

32. <li class="page-item disabled"><span class="page-link">»</span></li>

33. @endif

34. </ul>

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

Par contre on peut le publier :

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

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 ?

VI - 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 :

1. foreach ($articles as $article) {

2. echo $article->title;

3. }

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 :

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 , il suffit d'utiliser first :

VII - Helpers

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

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

On y trouve désormais le helper pour le cache :

1. if (! function_exists('cache')) {

2. /**

3. * Get / set the specified cache value.

4. *

5. * If an array is passed, we'll assume you want to put to the cache.

6. *

7. * @param  dynamic  key|key,default|data,expiration|null

8. * @return mixed

9. *

10. * @throws \Exception

11. */

12. function cache()

13. {

14. $arguments = func_get_args();

16. if (empty($arguments)) {

17. return app('cache');

18. }

20. if (is_string($arguments[0])) {

21. return app('cache')->get($arguments[0], isset($arguments[1]) ? $arguments[1] : null);

22. }

24. if (is_array($arguments[0])) {

25. if (! isset($arguments[1])) {

26. throw new Exception(

27. 'You must set an expiration time when putting to the cache.'

28. );

29. }

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

32. }

33. }

34. }

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

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

VIII - Mailable

Il y a aussi du changement pour l'envoi des emails avec les classes « mailable » placées 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 :

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

Avec ce code :

1. <?php

3. namespace App\Mail;

5. use Illuminate\Bus\Queueable;

6. use Illuminate\Mail\Mailable;

7. use Illuminate\Queue\SerializesModels;

8. use Illuminate\Contracts\Queue\ShouldQueue;

10. class EnvoiVoeux extends Mailable

11. {

12. use Queueable, SerializesModels;

14. /**

15. * Create a new message instance.

16. *

17. * @return void

18. */

19. public function __construct()

20. {

21. //

22. }

24. /**

25. * Build the message.

26. *

27. * @return $this

28. */

29. public function build()

30. {

31. return $this->view('view.name');

32. }

33. }

Tout se 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 :

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

1. <?php

3. namespace App\Mail;

5. use Illuminate\Bus\Queueable;

6. use Illuminate\Mail\Mailable;

7. use Illuminate\Queue\SerializesModels;

8. use Illuminate\Contracts\Queue\ShouldQueue;

9. use Services\Voeux;

11. class EnvoiVoeux extends Mailable

12. {

13. use Queueable, SerializesModels;

15. /**

16. * The voeux instance.

17. *

18. * @var Voeux

19. */

20. public $voeux;

22. /**

23. * Create a new message instance.

24. *

25. * @return void

26. */

27. public function __construct(Voeux $voeux)

28. {

29. $this->voeux = $voeux;

30. }

32. /**

33. * Build the message.

34. *

35. * @return $this

36. */

37. public function build()

38. {

39. return $this->from('cemoi@coucou.fr')

40. ->view('emails.voeux')

41. ->attach('/cartes/voeux.jpg');

42. }

43. }

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 :

1. Mail::to($monami)

2. ->cc($autreami)

3. ->send(new EnvoiVoeux($voeux));

Et c'est parti !

IX - 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 tableaux croisés dynamiques (pivot tables) apparaît la nouvelle méthode toggle qui s'ajoute à attach et detach. Avec ces deux dernières méthodes il faut en 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.

X - 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 :

1. $this->line('Merci de votre participation')

2. ->action('En savoir plus', 'http://parla.com')

3. ->success()

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

1. $this->line('Il y a apparemment un problème !')

2. ->action('En savoir plus', 'http://parla.com')

3. ->error()

Plutôt sympathique !

XI - Front-End

Il y a aussi du nouveau du côté du front-end (en français frontal) avec ce qu'on va appeler une forte incitation à utiliser Vue.js (si vous ne connaissez pas cette superbe librairie, aller jeter un coup d'œil sur ma série d'initiation). Si vous regardez les assets vous allez y trouver un peu plus de choses que dans la version précédente :

Et si vous regardez dans gulpfile.js :

1. elixir(mix => {

2. mix.sass('app.scss')

3. .webpack('app.js');

4. });

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 :

1. "devDependencies": {

2. "bootstrap-sass": "^3.3.7",

3. "gulp": "^3.9.1",

4. "jquery": "^3.1.0",

5. "laravel-elixir": "^6.0.0-9",

6. "laravel-elixir-vue": "^0.1.4",

7. "laravel-elixir-webpack-official": "^1.0.2",

8. "lodash": "^4.14.0",

9. "vue": "^1.0.26",

10. "vue-resource": "^0.9.3"

11. }

On y trouve la version SASS de Bootstrap, mais aussi jQuery, Elixir, Vue.js et son extension 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 :

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 une 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 œuvre 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 apparaître le composant au chargement :

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 !

XII - Remerciements

Nous remercions Maurice Chavelli qui nous autorise à publier ce tutoriel.

Nous tenons également à remercier Maxy35 pour la relecture orthographique et Winjerome pour la mise au gabarit.