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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
<?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'
);
}
);
C'est une simple route pour la racine, qui renvoie la vue welcome.
Et pour api.php :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
<?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 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
/**
* 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 coup d'œil dans le provider :
On y trouve ce code pour le web :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
/**
* 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
'
);
}
);
}
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
/**
* 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 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 :
Et dans la vue :
2.
3.
4.
5.
6.
7.
8.
@
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 :
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
<
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 :
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 :
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
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 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
<?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 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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
<?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 :
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 :
2.
3.
$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 corriger une erreur on change l'aspect du bouton avec error :
2.
3.
$this
->
line('
Il y a apparemment un problème !
'
)
->
action('
En savoir plus
'
,
'
http://parla.com
'
)
->
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 :
2.
3.
4.
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
"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 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 :
On ajoute une route :
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 !