InfyOm
J’ai souvent évoqué le site Laravel Schema Designer dans ce blog, notamment dans mon cours sur Laravel 5.3. Mais ce n’est pas le seul générateur même si sa principale qualité est d’offrir une interface graphique, ce qui le rend pour le moment unique. Dans cet article je vous propose de découvrir un autre générateur intéressant : celui d’infyOm.
Installation
La procédure d’installation se trouve sur cette page de la documentation.
La version rapide
La façon la plus simple d’installer le générateur est de partir de l’installation de base de Laravel qui comporte déjà les packages en question. D’autre part il est prévu le template back-office AdminLTE :
C’est certainement le template le plus utilisé actuellement et il faut avouer qu’il est vraiment bien équipé ! Mais ce n’est pas le sujet de cet article…
Il vous faudra quand même faire une installation avec composer (par sécurité faites aussi un update dans la foulée !), créer un fichier .env et générer une clé.
La version laborieuse
Si on veut ajouter le générateur sur une installation existante (Laravel 5.3) alors on doit passer par la voie classique dans composer.json :
"require": { "infyomlabs/laravel-generator": "5.3.x-dev", "laravelcollective/html": "^5.3.0", "infyomlabs/adminlte-templates": "5.3.x-dev" }
Si on veut créer une API et profiter des annotations Swagger alors il faut aussi installer ces packages :
"require": { "infyomlabs/swagger-generator": "dev-master", "jlapp/swaggervel": "dev-master" }
Ce qui permet de générer automatiquement la documentation :
Une autre possibilité consiste à pouvoir utiliser le générateur avec une table existante, ce qui peut être bien pratique. Pour en profiter il faut installer ce package :
"require": { "doctrine/dbal": "~2.3" }
Il faut ensuite mettre les dépendances à jour avec composer :
composer update
Il faut ensuite renseigner la configuration (config/app.php) :
Collective\Html\HtmlServiceProvider::class, Laracasts\Flash\FlashServiceProvider::class, Prettus\Repository\Providers\RepositoryServiceProvider::class, \InfyOm\Generator\InfyOmGeneratorServiceProvider::class, \InfyOm\AdminLTETemplates\AdminLTETemplatesServiceProvider::class, ... 'Form' => Collective\Html\FormFacade::class, 'Html' => Collective\Html\HtmlFacade::class, 'Flash' => Laracasts\Flash\Flash::class,
Il y a ensuite des publications à effectuer
php artisan vendor:publish
L’inconvénient avec cette commande indifférenciée c’est qu’elle publie aussi les notifications et la pagination de Laravel, ce que l’on ne désire pas forcément :
On peut supprimer les publications correspondantes de façon manuelle :
On doit ensuite mettre à jour les routes pour les API (app\Providers\RouteServiceProvider.php méthode mapApiRoutes), si on veut générer des API :
Route::group([ 'middleware' => 'api', 'namespace' => $this->namespace."\\API", 'prefix' => 'api', 'as' => 'api.', ], function ($router) { require base_path('routes/api.php'); });
De la même manière si on veut générer des API il faut faire cette publication supplémentaire :
php artisan infyom:publish
Ce qui a pour effet de créer ces deux fichiers :
Et voilà tout est en place, on peut commencer à s’amuser !
Interface graphique
A la base le générateur fonctionne à partir de la console en lignes de commandes mais il existe un GUI actuellement en version alpha. Dans la documentation on trouve une mise en garde :
Generator GUI Builder is currently in Beta and its not in a sync with current laravel-generator package. We highly recommend to use command line interface.
Donc à utiliser avec prudence mais ça serait franchement dommage de s’en priver !
Pour l’installer c’est tout simple :
"require": { "infyomlabs/generator-builder": "dev-master" }
Avec un petit :
composer update
Un ajout dans config/app.php :
\InfyOm\GeneratorBuilder\GeneratorBuilderServiceProvider::class,
Et deux publications :
php artisan vendor:publish php artisan infyom.publish:generator-builder
On dispose alors d’une belle interface graphique :
C’est franchement plus accueillant que la console !
Les commandes
On dispose des commandes suivantes avec artisan :
php artisan infyom:api $MODEL_NAME php artisan infyom:scaffold $MODEL_NAME php artisan infyom:api_scaffold $MODEL_NAME
La première est pour une API, la seconde pour du scaffold, la dernière pour les deux. On va voir des exemples bientôt !
Classiquement les entrées se font à partir de la console mais on a deux autres possibilités :
- à partir d’un fichier JSON, on a un exemple ici :
{ "name": "title", "dbType": "string", "htmlType": "text", "validations": "required", "searchable": true },
- à partir d’une table existante :
php artisan infyom:scaffold $MODEL_NAME --fromTable --tableName=$TABLE_NAME
Cette option est intéressante quand on a déjà une base constituée !
On dispose également de commandes individuelles pour générer :
- la migration (php artisan infyom:migration $MODEL_NAME)
- le modèle (php artisan infyom:model $MODEL_NAME )
- le repository (php artisan infyom:repository $MODEL_NAME)
- le contrôleur d’API (php artisan infyom.api:controller $MODEL_NAME)
- les requêtes API (php artisan infyom.api:requests $MODEL_NAME)
- les tests (php artisan infyom.api:tests $MODEL_NAME)
- le scaffold (php artisan infyom.scaffold:controller $MODEL_NAME)
- les requêtes de formulaire (php artisan infyom.scaffold:requests $MODEL_NAME)
- les vues (php artisan infyom.scaffold:views $MODEL_NAME)
On peut ainsi générer ou régénérer de manière très fine ce dont on a besoin.
Scaffold
Commençons avec un scaffold avec la console. Par exemple on veut une table de villes avec : nom et nombre d’habitants. Un truc simple pour nos essais :
php artisan infyom:scaffold Ville
On a alors ces générations :
Donc on a :
- la migration
- le modèle
- le repository
- la requête de formulaire
- le contrôleur
- les vues
- les routes
On a la possibilité dans la foulée d’avoir la migration :
Voyons un peu tout ça…
La migration
Le code généré :
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; class CreateVillesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('villes', function (Blueprint $table) { $table->increments('id'); $table->string('nom'); $table->integer('habitants'); $table->timestamps(); $table->softDeletes(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('villes'); } }
On a la génération automatique de l’id et des timestamps. On voit également que par défaut on a le soft delete. Dans la documentation il est précisé le contraire… d’ailleurs si je regarde dans la configuration (config/infyom/laravel_generator.php) :
'options' => [ 'softDelete' => true, ... ]
La documentation n’est pas tout à fait à jour…
Le modèle
Pour le modèle on trouve un dossier app/Models :
Avec ce code :
<?php namespace App\Models; use Eloquent as Model; use Illuminate\Database\Eloquent\SoftDeletes; /** * Class Ville * @package App\Models * @version January 9, 2017, 5:02 pm UTC */ class Ville extends Model { use SoftDeletes; public $table = 'villes'; protected $dates = ['deleted_at']; public $fillable = [ 'nom', 'habitants' ]; /** * The attributes that should be casted to native types. * * @var array */ protected $casts = [ 'nom' => 'string', 'habitants' => 'integer' ]; /** * Validation rules * * @var array */ public static $rules = [ 'nom' => 'required|max:255', 'habitants' => 'required|numeric' ]; }
On retrouve logiquement les informations pour le soft delete.
On peut remarquer que les règles de validation figurent dans le modèle.
Enfin on trouve la propriété $casts qui permet de fixer le type d’une donnée lorsqu’on y accède.
On trouve aussi les annotations pour Swagger.
Le repository
Il y a création d’un dossier app/Repositories :
<?php namespace App\Repositories; use App\Models\Ville; use InfyOm\Generator\Common\BaseRepository; class VilleRepository extends BaseRepository { /** * @var array */ protected $fieldSearchable = [ 'nom', 'habitants' ]; /** * Configure the Model **/ public function model() { return Ville::class; } }
On n’y trouve pas grand chose mais les champs sur lesquels pourront porter une recherche et une méthode qui permet d’avoir le modèle.
Ca semble bien vide mais… On voit que notre repository étend InfyOm\Generator\Common\BaseRepository qu’on peut trouver ici :
On y trouve quelques méthodes :
- findWithoutFail
- create
- update
- updateRelations
Et ce repository de base étend lui-même Prettus\Repository\Eloquent\BaseRepository. que vous pouvez trouver sur Github :
C’est un package très fourni et intéressant qui mériterait à lui seul un article complet. On se retrouve ainsi avec de très nombreuses méthodes disponibles !
Les requêtes de formulaire
On en trouve deux (création et mise à jour) :
Le code est le même pour les deux :
<?php namespace App\Http\Requests; use Illuminate\Foundation\Http\FormRequest; use App\Models\Ville; class CreateVilleRequest extends FormRequest { /** * Determine if the user is authorized to make this request. * * @return bool */ public function authorize() { return true; } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { return Ville::$rules; } }
Comme les règles de validation sont dans le modèle on va les chercher là.
Le contrôleur
Là le code est assez fourni :
<?php namespace App\Http\Controllers; use App\Http\Requests\CreateVilleRequest; use App\Http\Requests\UpdateVilleRequest; use App\Repositories\VilleRepository; use App\Http\Controllers\AppBaseController; use Illuminate\Http\Request; use Flash; use Prettus\Repository\Criteria\RequestCriteria; use Response; class VilleController extends AppBaseController { /** @var VilleRepository */ private $villeRepository; public function __construct(VilleRepository $villeRepo) { $this->villeRepository = $villeRepo; } /** * Display a listing of the Ville. * * @param Request $request * @return Response */ public function index(Request $request) { $this->villeRepository->pushCriteria(new RequestCriteria($request)); $villes = $this->villeRepository->all(); return view('villes.index') ->with('villes', $villes); } /** * Show the form for creating a new Ville. * * @return Response */ public function create() { return view('villes.create'); } /** * Store a newly created Ville in storage. * * @param CreateVilleRequest $request * * @return Response */ public function store(CreateVilleRequest $request) { $input = $request->all(); $ville = $this->villeRepository->create($input); Flash::success('Ville saved successfully.'); return redirect(route('villes.index')); } /** * Display the specified Ville. * * @param int $id * * @return Response */ public function show($id) { $ville = $this->villeRepository->findWithoutFail($id); if (empty($ville)) { Flash::error('Ville not found'); return redirect(route('villes.index')); } return view('villes.show')->with('ville', $ville); } /** * Show the form for editing the specified Ville. * * @param int $id * * @return Response */ public function edit($id) { $ville = $this->villeRepository->findWithoutFail($id); if (empty($ville)) { Flash::error('Ville not found'); return redirect(route('villes.index')); } return view('villes.edit')->with('ville', $ville); } /** * Update the specified Ville in storage. * * @param int $id * @param UpdateVilleRequest $request * * @return Response */ public function update($id, UpdateVilleRequest $request) { $ville = $this->villeRepository->findWithoutFail($id); if (empty($ville)) { Flash::error('Ville not found'); return redirect(route('villes.index')); } $ville = $this->villeRepository->update($request->all(), $id); Flash::success('Ville updated successfully.'); return redirect(route('villes.index')); } /** * Remove the specified Ville from storage. * * @param int $id * * @return Response */ public function destroy($id) { $ville = $this->villeRepository->findWithoutFail($id); if (empty($ville)) { Flash::error('Ville not found'); return redirect(route('villes.index')); } $this->villeRepository->delete($id); Flash::success('Ville deleted successfully.'); return redirect(route('villes.index')); } }
Le contrôleur utilise intensément le repository. Pour les sessions c’est la package Laracasts\Flash\Flash qui a été choisi.
Les routes
Les routes sont toutes générées avec :
Route::resource('villes', 'VilleController');
Les vues
On se retrouve avec 7 vues pour nos villes :
Action !
Il ne nous reste plus qu’à voir tout ça en oeuvre !
Création
Pour la création d’une ville on a donc l’url …/villes/create :
C’est plutôt élégant et je me retrouve bien avec un champ numérique pour les habitants :
La validation fonctionne :
La liste
Pour avoir la liste des villes l’url est …/villes :
On dispose des boutons classiques pour voir, modifier et supprimer. D’autre part il y a un bouton pour la création d’une nouvelle ville.
Lorsqu’on crée le scaffold il y a la possibilité d’ajouter la pagination, pour ça il suffit de prévoir l’option :
php artisan infyom:scaffold Ville --paginate=10
Je n’ai pas essayé…
La suppression
Pour la suppression c’est une fenêtre Javascript classique pour la confirmation :
Une API
La seconde possibilité est donc de créer une API. La commande est très proche :
php artisan infyom:api Ville
On a les mêmes saisies :
Et presque les mêmes générations :
Au niveau des différences on a les requêtes de formulaires rangées dans un dossier API :
Le contrôleur lui aussi dans un dossier API :
Et évidemment maintenant on ne renvoie plus des vues mais des réponses JSON :
return $this->sendResponse($villes->toArray(), 'Villes retrieved successfully');
Les routes sont créées dans routes/api.php.
Si on veut le scaffold en plus de l’API la commande est celle-ci :
php artisan infyom:api_scaffold Ville
Génération à partir d’une table existante
Une possibilité intéressante est celle qui consiste à générer le code à partir d’une table existante :
php artisan infyom:scaffold Ville --fromTable --tableName=villes
Et ça fonctionne :
Par contre la migration n’est pas crée, ça pourrait pourtant être intéressant pour des modifications. D’autre part il faut aussi renseigner les règles de validation dans les modèles.
Les relations
Il est très fréquent et pour ne pas dire incontournable d’avoir des relations entre les tables. Qu’a à nous proposer Infyom dans ce domaine ?
On peut définir des relations lors de la création (documentation ici). Alors essayons :
php artisan infyom:scaffold Post --relations
Là je crée un modèle Post en relation (1 à plusieurs) avec un modèle Comment. Au niveau du modèle généré j’ai bien la relation :
/** * @return \Illuminate\Database\Eloquent\Relations\HasMany **/ public function comments() { return $this->hasMany(\App\Models\Comment::class, 'post_id'); }
Il me faut maintenant le second modèle (Comment) :
php artisan infyom:scaffold Comment --relations
La migration est correcte :
public function up() { Schema::create('comments', function (Blueprint $table) { $table->increments('id'); $table->text('content'); $table->integer('post_id')->unsigned(); $table->timestamps(); $table->softDeletes(); $table->foreign('post_id')->references('id')->on('posts'); }); }
La relation aussi :
/** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo **/ public function post() { return $this->belongsTo(\App\Models\Post::class, 'post_id'); }
J’ai fait aussi un essai avec des tables existantes et les relations sont très bien générées dans les modèles !
Evidemment au niveau du scaffold on a les deux tables gérés séparément sans tenir compte de la relation.
Le repository de base
Un aspect important d’Infyom est l’utilisation du package andersao/l5-repository dont j’ai commencé à parler pour le repository. Le débat sur la pertinence des repositoy n’est pas clos et ne le sera sans doute jamais, mais là la question n’est pas d’actualité parce qu’on en dispose d’office, alors autant aller voir tout ce qui nous est offert. Il est à noter que la documentation d’Infyom est plutôt laconique sur le sujet, alors creusons un peu.
Les méthodes de base
Déja à la base on a à disposition de nombreuses méthodes :
- all($columns = array(‘*’))
- first($columns = array(‘*’))
- paginate($limit = null, $columns = [‘*’])
- find($id, $columns = [‘*’])
- findByField($field, $value, $columns = [‘*’])
- findWhere(array $where, $columns = [‘*’])
- findWhereIn($field, array $where, $columns = [*])
- findWhereNotIn($field, array $where, $columns = [*])
- create(array $attributes)
- update(array $attributes, $id)
- updateOrCreate(array $attributes, array $values = [])
- delete($id)
- orderBy($column, $direction = ‘asc’);
- with(array $relations);
- has(string $relation);
- whereHas(string $relation, closure $closure);
- hidden(array $fields);
- visible(array $fields);
- scopeQuery(Closure $scope);
- getFieldsSearchable();
- setPresenter($presenter);
- skipPresenter($status = true);
C’est ce qui permet d’utiliser dans un contrôleur généré :
$posts = $this->postRepository->all();
On a ainsi tous les posts. On voit qu’on pourrait spécifier les colonnes qu’on désire. Par exemple avec :
$posts = $this->postRepository->all('title');
On se retrouve avec juste les titres :
Pour l’affichage des posts (show) c’est la méthode findWithoutFail qui est utilisée :
$post = $this->postRepository->findWithoutFail($id);
Celle-là on la trouve dans le repository de base d’Infyom :
public function findWithoutFail($id, $columns = ['*']) { try { return $this->find($id, $columns); } catch (Exception $e) { return; } }
Elle utilise la méthode find du repository du package. On voit qu’on peut aussi préciser les colonnes désirées.
De la même manière dans les contrôleurs on trouve les méthodes create, update et delete pour les différentes opérations.
Il y a même une méthode prévue (with) pour l' »eager loading ».
Les critères
Dans les cas réels les méthodes de base sont rapidement débordées et on a besoin de plus de précision dans les requêtes. C’est là qu’interviennent les critères. Prenons un exemple encore avec les posts :
use Prettus\Repository\Contracts\RepositoryInterface; use Prettus\Repository\Contracts\CriteriaInterface; class PostTest implements CriteriaInterface { public function apply($model, RepositoryInterface $repository) { return $model->where('content', 'test'); } }
Je construit le critère : je ne veux que les enregistrements où la colonne content est test. Je peux ensuite utiliser le critère dans le contrôleur :
$this->postRepository->pushCriteria(PostTest::class);
Et ça marche :
Un autre type de critère intéressant est celui qui prend l’url comme référence, le critère de requête. Il est d’ailleurs mis en place dans le contrôleur généré si on veut la recherche :
$this->postRepository->pushCriteria(new RequestCriteria($request));
Pour obtenir le même résultat que le critère vu ci-dessus il suffit d’utiliser l’url :
../posts?search=content:test
Les possibilités sont assez riches, il y a de nombreux exemples dans la documentation.
Le package Fractal est également installé et peut donc être utilisé. Le but est de modifier les données de la base pour avoir un affichage correct pour l’utilisateur, ce qui est judicieux pour les API.
Conclusion
Infyom est intéressant parce qu’il fait gagner beaucoup de temps. Il est dommage qu’il ne propose pas une interface graphique comme Laravel Schema Designer ! Par contre il va bien plus loin dans la génération du code. L’idéal serait une combinaison de ces deux outils…
D’autre part l’installation automatisée du template de backend est intéressante.
4 commentaires
Nanourgo
J’ai suivit toutes les étapes, et lorsque j’exécute la commande « composer update », j’ai l’erreur ci-dessous
Problem 1
– illuminate/support[v6.0.0, …, v6.19.1] require php ^7.2 -> your php version (8.1.2) does not satisfy that requirement.
– illuminate/support[v7.0.0, …, v7.28.4] require php ^7.2.5 -> your php version (8.1.2) does not satisfy that requirement.
– illuminate/support[v8.0.0, …, v8.11.2] require php ^7.3 -> your php version (8.1.2) does not satisfy that requirement.
– Root composer.json requires infyomlabs/adminlte-templates ^3.0 -> satisfiable by infyomlabs/adminlte-templates[v3.0.0, v3.0.1, v3.0.2, v3.0.3].
– Conclusion: don’t install laravel/framework v9.0.0-beta.2 (conflict analysis result)
– Conclusion: don’t install laravel/framework v9.0.0-beta.3 (conflict analysis result)
– Conclusion: don’t install laravel/framework v9.0.0-beta.4 (conflict analysis result)
– Conclusion: don’t install laravel/framework v9.0.0-beta.5 (conflict analysis result)
– Conclusion: don’t install laravel/framework v9.0.0 (conflict analysis result)
– Conclusion: don’t install laravel/framework v9.0.1 (conflict analysis result)
– Conclusion: don’t install laravel/framework v9.0.2 (conflict analysis result)
– Conclusion: don’t install laravel/framework v9.1.0 (conflict analysis result)
– Conclusion: don’t install laravel/framework v9.2.0 (conflict analysis result)
– Conclusion: don’t install laravel/framework v9.0.0-beta.1 (conflict analysis result)
– infyomlabs/adminlte-templates[v3.0.0, …, v3.0.3] require illuminate/support ^6.0|^7.0|^8.0 -> satisfiable by illuminate/support[v6.0.0, …, 6.x-dev, v7.0.0, …, 7.x-dev, v8.0.0, …, 8.x-dev].
– Only one of these can be installed: illuminate/support[v5.0.0, …, 5.8.x-dev, v6.0.0, …, 6.x-dev, v7.0.0, …, 7.x-dev, v8.0.0, …, 8.x-dev, v9.0.0-beta.1, …, 9.x-dev], laravel/framework[v9.0.0-beta.1, …, 9.x-dev]. laravel/framework replaces illuminate/support and thus cannot coexist with it.
– Root composer.json requires laravel/framework ^9.0 -> satisfiable by laravel/framework[v9.0.0-beta.1, …, 9.x-dev].
Que dois-je faire ?
bestmomo
Bonjour,
Cet article commence à dater et il est devenu obsolète. Il vaut mieux faire l’installation à partir de la documentation officielle.
Nanourgo
Cela a peut-être un lien avec la version de Laravel 9. Sinon j’ai suivit la documentation officiel pour l’installation.
J’ai crée le projet et apparemment c’est un projet laravel 9. Je ne comprend pas du tout.
Merci !
bestmomo
Salut,
Oui forcément la dernière version doit être adaptée à Laravel 9, c’est le souci dans ce domaine, ça évolue rapidement…