XXII. Chapitre 22 : Installer des générateurs▲
Il commence à apparaître des aides dans l'utilisation de Laravel 4. Il y en a une qui me plaît bien qui a été créée par Jeffrey Way, c'est un ensemble de générateurs qui accélèrent le codage.
XXII-A. Installation du paquetage▲
Vous pouvez trouver le paquetage ici :
Vous trouvez la procédure d'installation sur Github. La première chose à faire est d'ajouter la référence au fichier composer.json :
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.
{
"require"
:
{
"laravel/framework"
:
"4.0.*"
"way/generators"
:
"dev-master"
},
"autoload"
:
{
"classmap"
:
[
"app/commands"
,
"app/controllers"
,
"app/models"
,
"app/database/migrations"
,
"app/database/seeds"
,
"app/tests/TestCase.php"
]
},
"scripts"
:
{
"pre-update-cmd"
:
[
"php artisan clear-compiled"
],
"post-install-cmd"
:
[
"php artisan optimize"
],
"post-update-cmd"
:
[
"php artisan optimize"
]
},
"config"
:
{
"preferred-install"
:
"dist"
},
"minimum-stability"
:
"dev"
}
Il serait d'ailleurs plus judicieux de le placer dans une rubrique require-dev :
"require-dev"
:
{
"way/generators"
:
"dev-master"
},
Lors de la mise à jour, les paquetages situés dans cette rubrique sont chargés par défaut (mettez bien à jour votre composer s'il vous le demande !). Lorsque vous voulez avoir ensuite seulement les paquetages utiles à la production vous ajoutez -no-dev à la commande update.
Faites ensuite une mise à jour avec composer :
Vous attendez que tout soit en place. Il ne reste alors plus qu'à ajouter le service provider (fourniseur) dans le fichier app/config/app.php :
2.
3.
4.
5.
'
Illuminate\Validation\ValidationServiceProvider
'
,
'
Illuminate\View\ViewServiceProvider
'
,
'
Illuminate\Workbench\WorkbenchServiceProvider
'
,
'
Way\Generators\GeneratorsServiceProvider
'
),
Maintenant si vous utilisez la commande php artisan vous remarquez l'apparition de nouvelles commandes :
Si vous utilisez Sublime Text il existe un plugin dédié pour ces générateurs.
XXII-A-1. Créer une migration▲
XXII-A-1-a. Créer une table▲
Voyons ce que donne la génération d'une migration avec cet outil :
J'ai demandé la création d'une migration pour une table nommée photos. Voici le résultat dans le fichier 2013_05_31_132938_create_photos_table.php (évidemment la date dépend du moment où vous lancez la commande ) :
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.
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreatePhotoTable extends
Migration {
/**
* Run the migrations.
*
*
@return
void
*/
public
function
up()
{
Schema::
create('photos'
,
function
(Blueprint $table
) {
$table
->
increments('id'
);
$table
->
timestamps();
}
);
}
/**
* Reverse the migrations.
*
*
@return
void
*/
public
function
down()
{
Schema::
drop('photos'
);
}
}
J'obtiens bien la création de la table avec déjà en place la déclaration de l'index et du timestamp. J'ai aussi la suppression de la table dans la fonction down ! On supprime ce fichier et on va plus loin en déclarant aussi des champs :
J'obtiens le même code avec en complément les deux déclarations de champ :
XXII-A-1-b. Ajouter un champ▲
Allons encore plus loin, imaginons que je veux ajouter un champ style_id à ma table :
Voyons le contenu de ce nouveau fichier de migration :
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.
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class AddStyleIdToPhotosTable extends
Migration {
/**
* Run the migrations.
*
*
@return
void
*/
public
function
up()
{
Schema::
table('photos'
,
function
(Blueprint $table
) {
$table
->
integer('style_id'
);
}
);
}
/**
* Reverse the migrations.
*
*
@return
void
*/
public
function
down()
{
Schema::
table('photos'
,
function
(Blueprint $table
) {
$table
->
dropColumn('style_id'
);
}
);
}
}
On a bien la création du champ et aussi dans la fonction down la suppression de ce champ. Tout semble bien se passer
XXII-A-1-c. Supprimer un champ▲
Voyons maintenant comment supprimer un champ :
Résultat :
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.
use Illuminate\Database\Schema\Blueprint;
class RemoveCategorieFromPhotosTable extends
Migration {
/**
* Run the migrations.
*
*
@return
void
*/
public
function
up()
{
Schema::
table('photos'
,
function
(Blueprint $table
) {
$table
->
dropColumn('categorie'
);
}
);
}
/**
* Reverse the migrations.
*
*
@return
void
*/
public
function
down()
{
Schema::
table('photos'
,
function
(Blueprint $table
) {
$table
->
integer('categorie'
);
}
);
}
}
On a bien la suppression de la colonne categorie en up et sa recréation en down.
XXII-A-2. Créer un modèle▲
On peut facilement créer un modèle :
On retrouve bien le fichier app/models/Photo.php :
2.
3.
4.
class Photo extends
Eloquent {
protected
$guarded
=
array
();
public
static
$rules
=
array
();
}
Les deux propriétés ajoutées ne vous seront utiles que si vous utilisez certaines fonctionnalités des paquetages de Jeffrey Way.
XXII-A-3. Créer des seeds▲
Il est aussi facile de créer des seeds, autrement dit du code pour remplir les tables :
Résultat avec le fichier app/database/seeds/PhotosTableSeeder.php :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
class PhotosTableSeeder extends
Seeder {
public
function
run()
{
// Uncomment the below to wipe the table clean before populating
// DB::table('photos')->delete();
$photos
=
array
(
);
// Uncomment the below to run the seeder
// DB::table('photos')->insert($photos);
}
}
Le générateur est suffisamment gentil pour mettre à jour aussi le fichier app/database/seeds/DatabaseSeeder.php :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
class DatabaseSeeder extends
Seeder {
/**
* Run the database seeds.
*
*
@return
void
*/
public
function
run()
{
Eloquent::
unguard();
// $this->call('UserTableSeeder');
$this
->
call('PhotosTableSeeder'
);
}
}
Il ne vous reste plus qu'à compléter le tableau. Par exemple :
En utilisant Artisan (ça va marcher si vous avez effectivement créé la table avec une migration auparavant) :
XXII-A-4. Créer une vue▲
On peut créer une vue :
Mais là on ne crée pas grand-chose, juste le fichier app/views/photo.blade.php :
photo.blade
Évidemment le générateur ne peut pas aller plus loin .
XXII-A-5. Créer une ressource▲
Ici le générateur est particulièrement puissant. Imaginez que vous voulez créer une ressource pour des livres. Voilà la syntaxe si on veut une table livres avec les champs titre et auteur :
On voit que le générateur crée pas mal de choses !
D'abord le modèle app/models/Livre.php :
2.
3.
4.
5.
class Livre extends
Eloquent {
protected
$guarded
=
array
();
public
static
$rules
=
array
();
}
Ensuite la migration app/database/migrations/2013_05_31_132938_create_photo_table.php :
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.
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateLivresTable extends
Migration {
/**
* Run the migrations.
*
*
@return
void
*/
public
function
up()
{
Schema::
create('livres'
,
function
(Blueprint $table
) {
$table
->
increments('id'
);
$table
->
string('titre'
);
$table
->
string('auteur'
);
$table
->
timestamps();
}
);
}
/**
* Reverse the migrations.
*
*
@return
void
*/
public
function
down()
{
Schema::
drop('livres'
);
}
}
Ensuite quatre vues dans un dossier app/views/livres :
Et pour terminer un seed pour remplir la table app/database/seeds/LivresTableSeeder.php :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
class LivresTableSeeder extends
Seeder {
public
function
run()
{
// Uncomment the below to wipe the table clean before populating
// DB::table('livres')->delete();
$livres
=
array
(
);
// Uncomment the below to run the seeder
// DB::table('livres')->insert($livres);
}
}
On trouve aussi la route :
Route::
resource('
livres
'
,
'
LivresController
'
);
Et le contrôleur app/controllers/LivresController.php :
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.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
class LivresController extends
BaseController {
/**
* Display a listing of the resource.
*
*
@return
Response
*/
public
function
index()
{
//
}
/**
* Show the form for creating a new resource.
*
*
@return
Response
*/
public
function
create()
{
//
}
/**
* Store a newly created resource in storage.
*
*
@return
Response
*/
public
function
store()
{
//
}
/**
* Display the specified resource.
*
*
@param
int
$id
*
@return
Response
*/
public
function
show($id
)
{
//
}
/**
* Show the form for editing the specified resource.
*
*
@param
int
$id
*
@return
Response
*/
public
function
edit($id
)
{
//
}
/**
* Update the specified resource in storage.
*
*
@param
int
$id
*
@return
Response
*/
public
function
update($id
)
{
//
}
/**
* Remove the specified resource from storage.
*
*
@param
int
$id
*
@return
Response
*/
public
function
destroy($id
)
{
//
}
}
XXII-A-6. Utilisation de la ressource▲
On ne va pas rester en si bon chemin et on va maintenant utiliser la ressource que nous venons de créer. D'abord la migration :
Et l'insertion de quelques livres :
XXII-A-6-a. Index▲
Voyons à présent si tout ça fonctionne. On va renseigner la méthode index du contrôleur :
Et évidemment renseigner aussi la vue app/view/livres/index.blade.php :
Et voilà le résultat avec l'URL http://localhost/laravel/public/livres :
2.
3.
4.
5.
6.
7.
Titre du livre : La grande barrique
Auteur : Le Gnouf
Titre du livre : Ma mer
Auteur : Jean Veut
XXII-A-6-b. Show▲
Puisqu'on est bien parti, renseignons aussi la fonction show :
Et la vue app/view/livres/show.blade.php :
Et voilà le résultat avec l'URL http://localhost/laravel/public/livres/1 :
Titre du livre : La grande barrique
Auteur : Le Gnouf
XXII-A-6-c. Create▲
Voyons à présent la fonction create, renseignons le contrôleur :
Et la vue app/view/livres/create.blade.php :
2.
3.
4.
5.
6.
7.
{{
Form::
open(array
('url'
=>
'livres'
,
'method'
=>
'POST'
)) }}
{{
Form::
label('titre'
,
'Titre :'
)}}
{{
Form::
text('titre'
) }}
{{
Form::
label('auteur'
,
'Auteur :'
)}}
{{
Form::
text('auteur'
) }}
{{
Form::
submit('Envoyer'
) }}
{{
Form::
close() }}
Et voilà le résultat avec l'URL http://localhost/laravel/public/livres/create :
On doit utiliser la méthode POST. Au retour on tombe sur la fonction store. Pour simplifier on ne va pas prévoir de validation, le but étant juste de voir le mécanisme de la ressource REST :
Faisons un essai :
XXII-A-6-d. Edit▲
Le résultat est satisfaisant. Voyons maintenant l'édition d'un livre avec la fonction edit :
Et la vue app/view/livres/edit.blade.php :
2.
3.
4.
5.
6.
7.
{{
Form::
open(array('
url
'
=>
'
livres/
'
.
$livre
->
id,
'
method
'
=>
'
PUT
'
)) }}
{{
Form::
label('
titre
'
,
'
Titre :
'
)}}
{{
Form::
text('
titre
'
,
$livre
->
titre) }}
{{
Form::
label('
auteur
'
,
'
Auteur :
'
)}}
{{
Form::
text('
auteur
'
,
$livre
->
auteur) }}
{{
Form::
submit('
Envoyer
'
) }}
{{
Form::
close
() }}
Et voilà le résultat avec l'URL http://localhost/laravel/public/livres/1/edit :
On doit utiliser la méthode PUT. Remarquez dans le code généré que Laravel ajoute ce contrôle caché pour que la méthode PUT soit prise en compte (nos navigateurs ne connaissent que POST et GET) :
<input name
=
"_method"
type
=
"hidden"
value
=
"PUT"
>
Au retour on tombe sur la fonction update. Pour simplifier encore, on ne va pas prévoir de validation :
On va juste changer l'orthographe du nom :
XXII-A-6-e. Destroy▲
Tout a encore l'air de bien se passer . Il ne nous reste plus qu'à voir la suppression d'un livre avec la fonction destroy :
Maintenant, si on utilise l'URL http://localhost/laravel/public/livres/4 avec la méthode DELETE, on va supprimer le livre avec l'id 4. Mais la question est : comment avoir cette méthode ? On peut envisager diverses possibilités, je vous en propose une qui est simple pour tester le contrôleur en modifiant un peu la vue app/view/livres/index.blade.php :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
@foreach
($livres
as
$livre
)
<
p>
Titre du livre : {{
$livre
->
titre }}
<
/p
>
<
p>
Auteur : {{
$livre
->
auteur }}
<
/p
>
@endforeach
<
p>
Choisissez le livre à supprimer :<
/p
>
@foreach
($livres
as
$livre
)
{{
Form::
open(array
('url'
=>
'livres/'
.
$livre
->
id,
'method'
=>
'DELETE'
)) }}
{{
Form::
submit($livre
->
titre.
' de '
.
$livre
->
auteur) }}
{{
Form::
close() }}
@endforeach
Vous obtenez des boutons pour supprimer les livres avec l'URL http://localhost/laravel/public/livres :
On peut évidemment utiliser AJAX pour créer des solutions plus efficaces .
XXII-A-6-f. Synthèse▲
Faisons une petite synthèse en modifiant encore une fois la vue app/view/livres/index.blade.php :
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.
<style type
=
"
text/css
"
>
form {
margin:
0
}
a {
text-decoration:
none
}
table {
border:
medium
solid
#6495ed
;
border-collapse:
collapse
;
}
th,
td {
border:
thin
solid
#6495ed
;
padding:
5
px;
}
th {
background-color:
#D0E3FA
}
td {
background-color:
#ffffff
}
</
style>
@if
(Session::
has('flash_notice'
))
<
p>
{{
Session::
get('flash_notice'
) }}
<
/p
>
@endif
<
table>
<
caption><h2>
Livres en stock<
/h2
></caption
>
<
tr>
<
th>
Titre<
/th
>
<
th>
Auteur<
/th
>
<
th colspan
=
3><a href
=
"
{{
url('livres/create'
)}}
"
>
<
input type
=
"
button
"
value
=
"
Ajouter un livre
"
>
<
/a
></th
>
<
/tr
>
@foreach
($livres
as
$livre
)
<
tr>
<
td>
{{
$livre
->
titre }}
<
/td
>
<
td>
{{
$livre
->
auteur }}
<
/td
>
<
td>
<
a href
=
"
{{
url('livres/'
.
$livre
->
id)}}
"
>
<
input type
=
"
button
"
value
=
"
Voir
"
>
<
/a
>
<
/td
>
<
td>
<
a href
=
"
{{
url('livres/'
.
$livre
->
id.
'/edit'
)}}
"
>
<
input type
=
"
button
"
value
=
"
Modifier
"
>
<
/a
>
<
/td
>
<
td>
{{
Form::
open(array
('url'
=>
'livres/'
.
$livre
->
id,
'method'
=>
'DELETE'
)) }}
{{
Form::
submit('Supprimer'
,
array
('onclick'
=>
'return confirm(
\'
Vraiment supprimer ce livre ?
\'
)'
)) }}
{{
Form::
close() }}
<
/td
>
<
/tr
>
@endforeach
<
/table
>
Et voilà le résultat avec l'URL http://localhost/laravel/public/livres :
Cette fois j'ai fait un petit effort de présentation . Vous voyez qu'on peut obtenir quelque chose de fonctionnel assez rapidement avec ce générateur. Pour boucler l'application on modifie le contrôleur pour renvoyer à cette page après chaque opération :
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.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
class LivresController extends
BaseController {
/**
* Display a listing of the resource.
*
*
@return
Response
*/
public
function
index()
{
return
View::
make('livres.index'
)->
with('livres'
,
Livre::
all());
}
/**
* Show the form for creating a new resource.
*
*
@return
Response
*/
public
function
create()
{
return
View::
make('livres.create'
);
}
/**
* Store a newly created resource in storage.
*
*
@return
Response
*/
public
function
store()
{
$livre
=
new
Livre;
$livre
->
create(Input::
all());
return
Redirect::
to('livres'
)->
with('flash_notice'
,
'Livre bien ajouté.'
);
}
/**
* Display the specified resource.
*
*
@param
int
$id
*
@return
Response
*/
public
function
show($id
)
{
return
View::
make('livres.show'
)->
with('livre'
,
Livre::
find($id
));
}
/**
* Show the form for editing the specified resource.
*
*
@param
int
$id
*
@return
Response
*/
public
function
edit($id
)
{
return
View::
make('livres.edit'
)->
with('livre'
,
Livre::
find($id
));
}
/**
* Update the specified resource in storage.
*
*
@param
int
$id
*
@return
Response
*/
public
function
update($id
)
{
$livre
=
Livre::
find($id
);
$livre
->
update(Input::
all());
return
Redirect::
to('livres'
)->
with('flash_notice'
,
'Livre bien modifié.'
);
}
/**
* Remove the specified resource from storage.
*
*
@param
int
$id
*
@return
Response
*/
public
function
destroy($id
)
{
$livre
=
Livre::
find($id
);
$livre
->
delete();
return
Redirect::
to('livres'
)->
with('flash_notice'
,
'Livre bien supprimé.'
);
}
}
Notez qu'on peut faire la redirection en désignant aussi le contrôleur, par exemple pour la fonction update :
return Redirect::
action('
LivresController@index
'
)->
with('
flash_notice
'
,
'
Livre bien modifié.
'
);
XXII-A-7. Créer un « scaffold »▲
Vous pouvez aller encore plus loin avec ces générateurs et utiliser la commande scaffold. C'est quoi ? Tout simplement le générateur qui va non seulement créer tout ce qu'on a vu précédemment, mais en plus les vues seront complètes et fonctionnelles ! La syntaxe est la même que pour les ressources. Voyons un exemple :
Au niveau de la génération, très peu de différences si ce n'est un layout et un fichier de tests (on peut se demander d'ailleurs pourquoi la génération d'une ressource ne crée plus le fichier de test ). Lançons la migration pour créer la table :
Que se passe-t-il maintenant avec l'URL http://localhost/laravel/public/adresses :
Bon… ça fait partie des joies des générateurs . Apparemment quelque part notre classe Adresse a perdu son « e ». C'est apparemment un souci avec les pluriels entre la langue anglaise et la nôtre . Si vous prenez le mot pluriel anglais adresses en le mettant au singulier ça devient adress, alors que pour nous Français ça devient adresse. On trouve le bogue dans le contrôleur :
2.
3.
4.
public function __construct(Adress $adress
)
{
$this
->
adress =
$adress
;
}
Il suffit de corriger les trois références du nom de la classe… Et maintenant c'est mieux :
On peut maintenant créer une adresse :
Vous avez ainsi une trame de base fonctionnelle avec validations et tests .
XXII-A-8. Générer un formulaire▲
Voyons maintenant la dernière commande disponible. Elle permet de créer des formulaires. Mais ça fonctionne uniquement si vous avez un modèle. Prenons le cas du modèle vu précédemment pour les livres :
La commande est bien allée chercher les deux champs pour créer le formulaire avec le bon type. On peut juste se demander si l'utilisation d'une liste est judicieuse ? Pas toujours. On a la possibilité de demander une autre mise en forme avec l'option HTML :