Laravel

Un framework qui rend heureux

Voir cette catégorie
Vers le bas
Laravel 4 : chapitre 22 : Installer des générateurs
Jeudi 28 mars 2013 01:41

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éé par Jeffrey Way, c'est un ensemble de générateurs qui accélèrent le codage.

Installation du package

Vous pouvez trouver le package ici :

img08

Vous trouvez la procédure d'installation sur githug. La première chose à faire est d'ajouter la référence au fichier composer.json :

{
    "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 packages situés dans cette rubriques sont chargés par défaut (mettez bien à jour votre composer s'il vous le demande !). Lorsque vous voulez avoir ensuite seulement les packages utiles à la production vous ajoutez --no-dev à la commande update.

Faites ensuite une mise à jour avec composer :

img09

Vous attendez que tout soit en place. Il ne reste plus alors qu'à ajouter le service provider dans le fichier app/config/app.php :

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

img98

Big Boss Si vous utilisez Sublime Text il existe un plugin dédié pour ces générateurs.

Créer une migration

Créer une table

Voyons ce que donne la génération d'une migration avec cet outil :

img103

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 Tongue Out) :

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 ! Supprimez ce fichier et allons plus loin en déclarant aussi des champs :

img104

J'obtiens le même code avec en complément les deux déclarations de champ :

	public function up()
	{
		Schema::create('photos', function($table) {
			$table->increments('id');
			$table->string('nom');
			$table->integer('categorie');
			$table->timestamps();
		});
	}

Ajouter un champ

Allons encore plus loin, imaginons que je veux ajouter un champ style_id à ma table :

img105

Voyons le contenu de ce nouveau fichier de migration :

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 Smile

Supprimer un champ

Voyons maintenant comment supprimer un champ :

img106

Résultat :
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.

Créer un modèle

On peut facilement créer  un modèle :

img107

On retrouve bien le fichier app/models/Photo.php :
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 packages de Jeffrey Way.

Créer des seeds

Il est aussi facile de créer des seeds, autrement dit du code pour remplir les tables :

img108

Résultat avec le fichier app/database/seeds/PhotosTableSeeder.php :
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 :
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 :
class PhotosTableSeeder extends Seeder {

	public function run()
	{
		$photos = array(
			array(
				'nom' => 'montagne',
				'categorie' => '1'
			),
			array(
				'nom' => 'mer',
				'categorie' => '2'
			)
		);

		DB::table('photos')->insert($photos);
	}

}
En utilisant artisan (ça va marcher si vous avez effectivement créé la table avec une migration auparavant) : img20

img109

Créer une vue

On peut créer une vue :

img110

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 Tongue Out.

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 :

img99

On voit que le générateur crée pas mal de choses !

D'abord le modèle app/models/Livre.php :

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 :

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 4 vues dans un dossier app/views/livres :

img24

Et pour terminer un seed pour remplir la table app/database/seeds/LivresTableSeeder.php :

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 :

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)
	{
		//
	}

}

Utilisation de la ressource

On ne va pas rester en si bon chemin et maintenant utiliser la ressource que nous venons de créer. D'abord la migration :

img111

img26

Et insérer quelques livres :

class LivresTableSeeder extends Seeder {

	public function run()
	{
		$livres = array(
			array(
				'titre' => 'La grande barrique',
				'auteur' => 'Le Gnouf'
			),
			array(
				'titre' => 'Ma mer',
				'auteur' => 'Jean Veut'
			)
		);

		DB::table('livres')->insert($livres);
	}

}

img27

img28

Index

Voyons à présent si tout ça fonctionne. On va renseigner la méthode index du contrôleur :

	public function index()
	{
		return View::make('livres.index')->with('livres', Livre::all());
	}

Et évidemment renseigner aussi la vue app/view/livres/index.blade.php :

@foreach ($livres as $livre)
    <p>Titre du livre : {{ $livre->titre }}</p>
    <p>Auteur : {{ $livre->auteur }}</p>
@endforeach

Et voilà le résultat avec l'URL http://localhost/laravel/public/livres :

Titre du livre : La grande barrique

Auteur : Le Gnouf

Titre du livre : Ma mer

Auteur : Jean Veut

Show

Puisqu'on est bien partis renseignons aussi la fonction show :

	public function show($id)
	{
		return View::make('livres.show')->with('livre', Livre::find($id));
	}

Et la vue app/view/livres/show.blade.php :

<p>Titre du livre : {{ $livre->titre }}</p>
<p>Auteur : {{ $livre->auteur }}</p>

Et voilà le résultat avec l'URL http://localhost/laravel/public/livres/1 :

Titre du livre : La grande barrique

Auteur : Le Gnouf

Create

Voyons à présent la fonction create, renseignons le contrôleur :

	public function create()
	{
		return View::make('livres.create');
	}

Et la vue app/view/livres/create.blade.php :

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

img29

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 est juste de voir le mécanisme de la ressource REST :

	public function store()
	{
        $livre = new Livre;
        $livre->create(Input::all());
        return 'Livre enregistré';
	}

Faisons un essai  :

img30

img31

Edit

Le résultat est satisfaisant. Voyons maintenant l'édition d'un livre avec la fonction edit :

	public function edit($id)
	{
		return View::make('livres.edit')->with('livre', Livre::find($id));
	}

Et la vue app/view/livres/edit.blade.php :

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

img32

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 :

	public function update($id)
	{
		$livre = Livre::find($id);
		$livre->update(Input::all());
		return 'Livre modifié';
	}

On va juste changer l'orthographe du nom :

img33

img34

Destroy

Tout a encore l'air de bien se passer Smile. Il ne nous reste plus qu'à voir la suppression d'un livre avec la fonction destroy :

	public function destroy($id)
	{
		$livre = Livre::find($id);
		$livre->delete();
		return 'Livre supprimé';
	}

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 simple pour tester le contrôleur en modifiant un peu la vue app/view/livres/index.blade.php :

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

img35

On peut évidemment utiliser Ajax pour créer des solutions plus efficaces Wink.

Synthèse

Faisons une petite synthèse en modifiant encore une fois la vue app/view/livres/index.blade.php :
<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: 5px;
	}
	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 :

img36

Cette fois j'ai fait un petit effort de présentation Smile. 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 :

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é.');

Créer un "scaffold"

Vous pouvez aller encore plus loin avec ces générateurs et utiliser la commande scaffold. C'est quoi cette bête ? Tout simplement le générateur 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 :

img112

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 Undecided). Lançons la migration pour créer la table :

img113

Que se passe-t-il maintenant avec l'URL http://localhost/laravel/public/adresses :

img114

Bon... ça fait partie des joies de générateurs Smile. Apparemment quelque part notre classe Adresse a perdu son "e". C'est apparemment un souci avec les pluriels entre la langue anglaise et la notre Undecided. 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 bug dans le contrôleur :

    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 :

img115

On peut maintenant créer une adresse :

img116

img117

Vous avez ainsi une trame de base fonctionnelle avec validations et tests Laughing.

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 :

img118

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 :

img119



Par bestmomo

Aucun commentaire