Velocizzare il proprio workflow con Generators

Il package Generators di Jeffrey Way è una manna dal cielo che ti farà risparmiare un sacco di tempo. Non ci credi? Dai un'occhiata!
francesco
Francesco Malatesta
16/04/2014 in Package

Inutile negarlo: Laravel velocizza di molto il workflow a cui siamo abituati. A volte, però, ci sono delle azioni che dobbiamo ripetere, che ci piaccia o meno: la creazione di questo o quel model, o di un controller magari.

Jeffrey Way ha pensato di risolvere il problema creando Generators, un interessante insieme di comandi Artisan che, se ben usati, ci levano di torno un bel po’ di noie.

Un esempio pratico?

Creare un model: diventa semplicissimo sapendo che basta un’istruzione come questa.

php artisan generate:model MyModel

Comodo vero? Vediamo insieme come funziona.

Installazione

Installare il package Generators è un gioco da ragazzi. Aggiungiamo la dipendenza al file composer.json del nostro progetto.

Richiamiamo composer per effettuare l’update:

Il passo successivo è aggiungere il service provider del package all’elenco presente in app/config/app.php. La riga da aggiungere è

"require-dev": {
    "way/generators": "2.*"
}

Per verificare la corretta riuscita dell’installazione basta usare il comando

php artisan

e controllare se esistono i nuovi comandi nella lista presentata.

Cosa posso fare con Generators?

Le possibilità, con Generators, sono molte. Ecco un elenco di tutto ciò che è possibile creare in modo automatizzato:

  • Migration
  • Model
  • View
  • Seed
  • Pivot
  • Resource
  • Scaffolding

Vediamo insieme un po’ la sintassi ed un esempio per ognuno di questi comandi.

Migration

Generators offre una marea di possibilità e di opzioni per creare al volo la migration più adatta alle proprie esigenze.

php artisan generate:migration create_comments_table

La scelta del nome non è del tutto casuale. Tramite “create_” e “_table” nel nome stiamo chiedendo a Generators di compilare per noi il file.

Ecco il risultato:

<?php

	use Illuminate\Database\Migrations\Migration;
	use Illuminate\Database\Schema\Blueprint;
	
	class CreateCommentsTable extends Migration {
	
	    /**
	     * Run the migrations.
	     *
	     * @return void
	     */
	    public function up()
	    {
	        Schema::create('comments', function(Blueprint $table) {
	            $table->increments('id');
	            $table->timestamps();
	        });
	    }
	
	    /**
	     * Reverse the migrations.
	     *
	     * @return void
	     */
	    public function down()
	    {
	        Schema::drop('comments');
	    }
	
	}

Tramite l’uso del flag “--fields”, tra l’altro, è possibile specificare queli campi inserire nella tabella.

php artisan generate:migration create_comments_table --fields=”title:string, body:text”

Il risultato stavolta sarà il seguente:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateCommentsTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('comments', function(Blueprint $table) {
            $table->increments('id');
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('comments');
    }

}

Generators permette inoltre di definire un po’ tutti gli aspetti di un singolo campo. Ecco alcuni esempi presi direttamente dalla documentazione che danno un’idea più precisa delle possibilità che ci vengono offerte:

--fields=”first:string, last:string”;
--fields=”age:integer, yob:date”;
--fields=”username:string:unique, age:integer:nullable”;
--fields=”name:string:default(‘John’), username:string(30):unique”;

C’è da dire, inoltre, che non si sta sempre a creare tabelle: le action da effettuare possono essere diverse. Generators tiene conto della cosa e permette:

  • Creazione (esempio: create_users_table);
  • Aggiunta di un campo (esempio: add_user_id_to_posts_table);
  • Rimozione di un campo (esempio: remove_user_id_from_posts_table);
  • Cancellazione (esempio: delete_users_table);

Model

Altrettanto semplice è la creazione di un model Eloquent. Non bisogna fare altro che usare il comando apposito

php artisan generate:model Comment

per avere, come risultato, un file Comment.php pronto:

<?php

	class Post extends \Eloquent {
	
	}

View

La creazione di una view viene portata a termine dal comando generate:view. Come parametro viene specificato il percorso preciso da usare.

php artisan generate:view admin.reports.index

Il risultato, in questo caso, sarà una view vuota. Le eventuali cartelle non esistenti verranno create appositamente.

Seed

I seed servono a popolare le tabelle della nostra applicazione. Possono essere utili, ad esempio, durante una procedura di installazione. Crearne è semplice ed il comando da usare è

php artisan generate:seed users

Verrà creato il file corrispondente app/database/seeds/UserTableSeeder.php.

<?php

// Composer: "fzaninotto/faker": "v1.3.0"
use Faker\Factory as Faker;

class UsersTableSeeder extends Seeder {

    public function run()
    {
        $faker = Faker::create();

        foreach(range(1, 10) as $index)
        {
            User::create([
            
            ]);
        }
    }

}

Nota: la libreria usata è faker di fzaninotto. Puoi includerla aggiungendo al tuo file composer.json la stringa

"fzaninotto/faker": "v1.3.0"

Pivot

Se hai bisogno di una tabella Pivot il comando giusto è… Pivot, appunto. La sintassi è semplice e basta passare come parametri i nomi delle due tabelle legate.

php artisan generate:pivot orders users

Il risultato:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateOrderUserTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('order_user', function(Blueprint $table) {
            $table->increments('id');
            $table->integer('order_id')->unsigned()->index();
            $table->foreign('order_id')->references('id')->on('orders')->onDelete('cascade');
            $table->integer('user_id')->unsigned()->;index();
            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('order_user');
    }

}

Nota, inoltre, che la tabella viene creata correttamente con i nomi delle entità ordinati alfabeticamente.

Insomma, una comodità non indifferente. Adesso però facciamo un passo avanti, con la generazione di resource.

Resource

Il comando generate:resource, a differenza degli altri visti finora, farà una serie di azioni ben distinte:

  • Creazione di un model;
  • Generazione di varie view (index, show, create ed edit);
  • Creazione di un controller;
  • Creazione di una migration con relativo schema;
  • Generazione di un table seeder;

Immagina di avere la necessità di realizzare, al volo, tutto quello che riguarda l’elaborazione di una serie di post nella tua applicazione. Basta usare un comando come

php artisan generate:resource post --fields="title:string, body:text"

per risparmiare un sacco di tempo e ritrovarti, neanche un secondo dopo, questi file già pronti:

  • app/models/Post.php
  • app/controllers/PostController.php
  • app/database/migrations/timestamp-create_post_table.php
  • app/database/seeds/PostsTableSeeder.php

Scaffolding

Il generatore di scaffolding è leggermente più sofisticato di generate:resource, dato che oltre a fare quello che il comando appena visto già fa provvede ad aggiungere un po’ di materiale aggiuntivo.

Torniamo all’esempio precedente, provando il comando:

php artisan generate:scaffold post

Il controller risultante PostController sarà simile al seguente:

<?php

class PostsController extends \BaseController {

    /**
     * Display a listing of posts
     *
     * @return Response
     */
    public function index()
    {
        $posts = Post::all();

        return View::make('posts.index', compact('posts'));
    }

    /**
     * Show the form for creating a new post
     *
     * @return Response
     */
    public function create()
    {
        return View::make('posts.create');
    }

    /**
     * Store a newly created post in storage.
     *
     * @return Response
     */
    public function store()
    {
        $validator = Validator::make($data = Input::all(), Post::$rules);

        if ($validator->fails())
        {
            return Redirect::back()->withErrors($validator)->withInput();
        }

        Post::create($data);

        return Redirect::route('posts.index');
    }

    /**
     * Display the specified post.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        $post = Post::findOrFail($id);

        return View::make('posts.show', compact('post'));
    }

    /**
     * Show the form for editing the specified post.
     *
     * @param  int  $id
     * @return Response
     */
    public function edit($id)
    {
        $post = Post::find($id);

        return View::make('posts.edit', compact('post'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  int  $id
     * @return Response
     */
    public function update($id)
    {
        $post = Post::findOrFail($id);

        $validator = Validator::make($data = Input::all(), Post::$rules);

        if ($validator->fails())
        {
            return Redirect::back()->withErrors($validator)->withInput();
        }

        $post->update($data);

        return Redirect::route('posts.index');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return Response
     */
    public function destroy($id)
    {
        Post::destroy($id);

        return Redirect::route('posts.index');
    }

}

Nota: ovviamente dovrai modificare il controller in base alle tue necessità… ma come inizio non è assolutamente indifferente.

Configurazione

Generators non si ferma qui. Potresti avere, infatti, la necessità di personalizzare i template che vengono usati in fase di generazione automatica. Tramite il comando

php artisan generate:publish-templates

i vari template usati da Generators vengono così copiati all’interno della cartella app/templates. Puoi anche specificare una directory di destinazione diversa, così:

php artisan generate:publish-templates --path=app/foo/bar/templates

Verrà copiato, inoltre, anche il file di configurazione che indica i vari path.

Ecco un esempio:

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Where the templates for the generators are stored...
    |--------------------------------------------------------------------------
    |
    */
    'model_template_path' => '/Users/jeffreyway/Desktop/generators-testing/app/templates/model.txt',

    'scaffold_model_template_path' => '/Users/jeffreyway/Desktop/generators-testing/app/templates/scaffolding/model.txt',

    'controller_template_path' => '/Users/jeffreyway/Desktop/generators-testing/app/templates/controller.txt',

    'scaffold_controller_template_path' => '/Users/jeffreyway/Desktop/generators-testing/app/templates/scaffolding/controller.txt',

    'migration_template_path' => '/Users/jeffreyway/Desktop/generators-testing/app/templates/migration.txt',

    'seed_template_path' => '/Users/jeffreyway/Desktop/generators-testing/app/templates/seed.txt',

    'view_template_path' => '/Users/jeffreyway/Desktop/generators-testing/app/templates/view.txt',

    /*
    |--------------------------------------------------------------------------
    | Where the generated files will be saved...
    |--------------------------------------------------------------------------
    |
    */
    'model_target_path'   => app_path('models'),

    'controller_target_path'   => app_path('controllers'),

    'migration_target_path'   => app_path('database/migrations'),

    'seed_target_path'   => app_path('database/seeds'),

    'view_target_path'   => app_path('views')

];

Anche qui ovviamente massima libertà: puoi adattare i percorsi alla struttura del tuo progetto come meglio credi.

Conclusioni

Generators di Jeffrey Way è davvero una bella svolta e può farti risparmiare un sacco di tempo. Anche io, personalmente, lo sto usando ed è diventato praticamente uno standard nei miei ultimi progetti. Sia che si parli di operazioni piccole e isolate (creazione di un model o di un controller), sia che si debba creare una resource da zero.