Laravel学习笔记之Artisan命令生成自定义模板的方法
本文主要讲述Laravel的Artisan命令来实现自定义模板,就如经常输入的php artisan make:controller ShopController就会自动生成一个ShopController.php模板文件一样,通过命令生成模板也会提高开发效率。同时,作者会将开发过程中的一些截图和代码黏上去,提高阅读效率。
备注:个人平时在写Repository代码时会这样写,如先写上ShopRepositoryInterface并定义好接口方法如all()、create()、update()、delete()、findBy()等等,然后再写上接口对应的实现ShopRepository并注入对应的Model即Shop。别的PostRepository、TagRepository也会是这么写(当然,对于很多重用的Repository方法可以集体拿到AbstractRepository抽象类里供子类继承,实现代码复用)。那能不能直接命令行生成模板文件呢,就不用自己一个个的写了,就像输入php artisan make:controller PostController给我一个Controller模板来。
关于使用Repository模式来封装下Model逻辑,不让Controller里塞满了很多Model逻辑,这样做是有很多好处的,最主要的就是好测试和代码架构清晰,也符合SOLID原则。如果使用PHPUnit来做测试就知道了为啥说好测试了。SegmentFault上也有相关的文章描述。作者也打算最近新开一篇文章聊一聊这个,PHPUnit也打算过段时间聊一聊。
个人研究了下Artisan命令行,是可以的。经过开发后,结果是输入自定义指令php artisan make:repository PostRepository --model=Post(这个option可要可不要),就会帮我生成一个PostRepositoryInterface和对应的接口实现PostRepository。
模板文件Stub
由于个人需要生成一个RepositoryInterface和对应接口实现Repository,那就需要两个模板文件了。在resources/stubs新建两个模板文件,以下是个人经常需要的两个模板文件(你可以自定义):
- /**
- * @param array $columns
- * @return \Illuminate\Database\Eloquent\Collection|static[]
- */
- public- function- all(- $columns- =- array- (- '*'- ))
- {
- return- $this- ->- $model_var_name- ->all(- $columns- );
- }
- /**
- * @param int $perPage
- * @param array $columns
- * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
- */
- public- function- paginate(- $perPage- = 15,- $columns- =- array- (- '*'- ))
- {
- return- $this- ->- $model_var_name- ->paginate(- $perPage- ,- $columns- );
- }
- /**
- * Create a new $model_var_name
- * @param array $data
- * @return \$model_namespace
- */
- public- function- create(- array- $data- )
- {
- return- $this- ->- $model_var_name- ->create(- $data- );
- }
- /**
- * Update a $model_var_name
- * @param array $data
- * @param $id
- * @return \$model_namespace
- */
- public- function- update(- $data- = [],- $id- )
- {
- return- $this- ->- $model_var_name- ->whereId(- $id- )->update(- $data- );
- }
- /**
- * Store a $model_var_name
- * @param array $data
- * @return \$model_namespace
- */
- public- function- store(- $data- = [])
- {
- $this- ->- $model_var_name- ->id =- $data- [- 'id'- ];
- //...
- $this- ->- $model_var_name- ->save();
- }
- /**
- * Delete a $model_var_name
- * @param array $data
- * @param $id
- * @return \$model_namespace
- */
- public- function- delete- (- $data- = [],- $id- )
- {
- $this- ->- $model_var_name- ->whereId(- $id- )->- delete- ();
- }
- /**
- * @param $id
- * @param array $columns
- * @return array|\Illuminate\Database\Eloquent\Collection|static[]
- */
- public- function- find(- $id- ,- $columns- =- array- (- '*'- ))
- {
- $- $model_name- =- $this- ->- $model_var_name- ->whereId(- $id- )->get(- $columns- );
- return- $- $model_name- ;
- }
- /**
- * @param $field
- * @param $value
- * @param array $columns
- * @return \Illuminate\Database\Eloquent\Collection|static[]
- */
- public- function- findBy(- $field- ,- $value- ,- $columns- =- array- (- '*'- ))
- {
- $- $model_name- =- $this- ->- $model_var_name- ->where(- $field- ,- '='- ,- $value- )->get(- $columns- );
- return- $- $model_name- ;
- }
- }
模板文件里包括参数,这些参数将会根据命令行中输入的参数和选项被相应替换:
复制代码代码如下:
- ['$repository_namespace', '$model_namespace', '$repository_interface_namespace', '$repository_interface', '$class_name', '$model_name', '$model_var_name']
Artisan命令生成Repository模板文件
生成Artisan命令并注册
Laravel提供了Artisan命令自定义,输入指令:
- php artisan make:console MakeRepositoryCommand
然后改下签名和描述:
- // app/Console/Commands/MakeRepositoryCommand
- /**
- * The name and signature of the console command.
- *
- * @var string
- */
- protected- $signature- =- 'make:repository {repository} {--model=}'- ;
- /**
- * The console command description.
- *
- * @var string
- */
- protected- $description- =- 'Make a repository and interface'- ;
这里{repository}是必填参数并指明(选填参数加个?,就和路由参数一样),将会被$this->argument('repository')方法捕捉到,{--model=}是选项,可填可不填,将会被$this->option('model')方法捕捉到。填上这个命令的描述,最后在Console的Kernel里注册下命令:
- // app/Console/Kernel
- protected- $commands- = [
- // Commands\Inspire::class,
- // Commands\RedisSubscribe::class,
- // Commands\RedisPublish::class,
- // Commands\MakeTestRepositoryCommand::class,
- Commands\MakeRepositoryCommand::- class- ,
- ];
然后输入php artisan命令后就能看到这个make:repository命令了。

自动化生成RepositoryInterface和Repository文件
在MakeRepositoryCommand.php命令执行文件里写上模板自动生成逻辑,代码也不长,有些逻辑也有注释,可看:
- use- Config;
- use- Illuminate\Console\Command;
- use- Illuminate\Filesystem\Filesystem;
- use- Illuminate\Support\Composer;
- class- MakeRepositoryCommand- extends- Command
- {
- /**
- * The name and signature of the console command.
- *
- * @var string
- */
- protected- $signature- =- 'make:repository {repository} {--model=}'- ;
- /**
- * The console command description.
- *
- * @var string
- */
- protected- $description- =- 'Make a repository and interface'- ;
- /**
- * @var
- */
- protected- $repository- ;
- /**
- * @var
- */
- protected- $model- ;
- /**
- * Create a new command instance.
- *
- * @param Filesystem $filesystem
- * @param Composer $composer
- */
- public- function- __construct(Filesystem- $filesystem- , Composer- $composer- )
- {
- parent::__construct();
- $this- ->files =- $filesystem- ;
- $this- ->composer =- $composer- ;
- }
- /**
- * Execute the console command.
- *
- * @return mixed
- */
- public- function- handle()
- {
- //获取repository和model两个参数值
- $argument- =- $this- ->argument(- 'repository'- );
- $option- =- $this- ->option(- 'model'- );
- //自动生成RepositoryInterface和Repository文件
- $this- ->writeRepositoryAndInterface(- $argument- ,- $option- );
- //重新生成autoload.php文件
- $this- ->composer->dumpAutoloads();
- }
- private- function- writeRepositoryAndInterface(- $repository- ,- $model- )
- {
- if- (- $this- ->createRepository(- $repository- ,- $model- )){
- //若生成成功,则输出信息
- $this- ->info(- 'Success to make a '- .ucfirst(- $repository- ).- ' Repository and a '- .ucfirst(- $repository- ).- 'Interface Interface'- );
- }
- }
- private- function- createRepository(- $repository- ,- $model- )
- {
- // getter/setter 赋予成员变量值
- $this- ->setRepository(- $repository- );
- $this- ->setModel(- $model- );
- // 创建文件存放路径, RepositoryInterface放在app/Repositories,Repository个人一般放在app/Repositories/Eloquent里
- $this- ->createDirectory();
- // 生成两个文件
- return- $this- ->createClass();
- }
- private- function- createDirectory()
- {
- $directory- =- $this- ->getDirectory();
- //检查路径是否存在,不存在创建一个,并赋予775权限
- if- (!- $this- ->files->isDirectory(- $directory- )){
- return- $this- ->files->makeDirectory(- $directory- , 0755, true);
- }
- }
- private- function- getDirectory()
- {
- return- Config::get(- 'repository.directory_eloquent_path'- );
- }
- private- function- createClass()
- {
- //渲染模板文件,替换模板文件中变量值
- $templates- =- $this- ->templateStub();
- $class- = null;
- foreach- (- $templates- as- $key- =>- $template- ) {
- //根据不同路径,渲染对应的模板文件
- $class- =- $this- ->files->put(- $this- ->getPath(- $key- ),- $template- );
- }
- return- $class- ;
- }
- private- function- getPath(- $class- )
- {
- // 两个模板文件,对应的两个路径
- $path- = null;
- switch- (- $class- ){
- case- 'Eloquent'- :
- $path- =- $this- ->getDirectory().DIRECTORY_SEPARATOR.- $this- ->getRepositoryName().- '.php'- ;
- break- ;
- case- 'Interface'- :
- $path- =- $this- ->getInterfaceDirectory().DIRECTORY_SEPARATOR.- $this- ->getInterfaceName().- '.php'- ;
- break- ;
- }
- return- $path- ;
- }
- private- function- getInterfaceDirectory()
- {
- return- Config::get(- 'repository.directory_path'- );
- }
- private- function- getRepositoryName()
- {
- // 根据输入的repository变量参数,是否需要加上'Repository'
- $repositoryName- =- $this- ->getRepository();
- if- ((- strlen- (- $repositoryName- ) <- strlen- (- 'Repository'- )) ||- strrpos- (- $repositoryName- ,- 'Repository'- , -11)){
- $repositoryName- .=- 'Repository'- ;
- }
- return- $repositoryName- ;
- }
- private- function- getInterfaceName()
- {
- return- $this- ->getRepositoryName().- 'Interface'- ;
- }
- /**
- * @return mixed
- */
- public- function- getRepository()
- {
- return- $this- ->repository;
- }
- /**
- * @param mixed $repository
- */
- public- function- setRepository(- $repository- )
- {
- $this- ->repository =- $repository- ;
- }
- /**
- * @return mixed
- */
- public- function- getModel()
- {
- return- $this- ->model;
- }
- /**
- * @param mixed $model
- */
- public- function- setModel(- $model- )
- {
- $this- ->model =- $model- ;
- }
- private- function- templateStub()
- {
- // 获取两个模板文件
- $stubs- =- $this- ->getStub();
- // 获取需要替换的模板文件中变量
- $templateData- =- $this- ->getTemplateData();
- $renderStubs- = [];
- foreach- (- $stubs- as- $key- =>- $stub- ) {
- // 进行模板渲染
- $renderStubs- [- $key- ] =- $this- ->getRenderStub(- $templateData- ,- $stub- );
- }
- return- $renderStubs- ;
- }
- private- function- getStub()
- {
- $stubs- = [
- 'Eloquent'- =>- $this- ->files->get(resource_path(- 'stubs/Repository'- ).DIRECTORY_SEPARATOR.- 'Eloquent'- .DIRECTORY_SEPARATOR.- 'repository.stub'- ),
- 'Interface'- =>- $this- ->files->get(resource_path(- 'stubs/Repository'- ).DIRECTORY_SEPARATOR.- 'repository_interface.stub'- ),
- ];
- return- $stubs- ;
- }
- private- function- getTemplateData()
- {
- $repositoryNamespace- = Config::get(- 'repository.repository_namespace'- );
- $modelNamespace- =- 'App\\'- .- $this- ->getModelName();
- $repositoryInterfaceNamespace- = Config::get(- 'repository.repository_interface_namespace'- );
- $repositoryInterface- =- $this- ->getInterfaceName();
- $className- =- $this- ->getRepositoryName();
- $modelName- =- $this- ->getModelName();
- $templateVar- = [
- 'repository_namespace'- =>- $repositoryNamespace- ,
- 'model_namespace'- =>- $modelNamespace- ,
- 'repository_interface_namespace'- =>- $repositoryInterfaceNamespace- ,
- 'repository_interface'- =>- $repositoryInterface- ,
- 'class_name'- =>- $className- ,
- 'model_name'- =>- $modelName- ,
- 'model_var_name'- =>- strtolower- (- $modelName- ),
- ];
- return- $templateVar- ;
- }
- private- function- getRenderStub(- $templateData- ,- $stub- )
- {
- foreach- (- $templateData- as- $search- =>- $replace- ) {
- $stub- =- str_replace- (- '$'- .- $search- ,- $replace- ,- $stub- );
- }
- return- $stub- ;
- }
- private- function- getModelName()
- {
- $modelName- =- $this- ->getModel();
- if- (isset(- $modelName- ) && !- empty- (- $modelName- )){
- $modelName- = ucfirst(- $modelName- );
- }- else- {
- // 若option选项没写,则根据repository来生成Model Name
- $modelName- =- $this- ->getModelFromRepository();
- }
- return- $modelName- ;
- }
- private- function- getModelFromRepository()
- {
- $repository- =- strtolower- (- $this- ->getRepository());
- $repository- =- str_replace- (- 'repository'- ,- ''- ,- $repository- );
- return- ucfirst(- $repository- );
- }
- }
这里把一些常量值放在config/repository.php配置文件里了:
- <?php
- /**
- * Created by PhpStorm.
- * User: liuxiang
- * Date: 16/6/22
- * Time: 17:06
- */
- return- [
- 'directory_path'- =>- 'App'- .DIRECTORY_SEPARATOR.- 'Repositories'- ,
- 'directory_eloquent_path'- =>- 'App'- .DIRECTORY_SEPARATOR.- 'Repositories'- .DIRECTORY_SEPARATOR.- 'Eloquent'- ,
- 'repository_namespace'- =>- 'App\Repositories\Eloquent'- ,
- 'repository_interface_namespace'- =>- 'App\Repositories'- ,
- ];
运行一下看可不可以吧,这里截个图:


- It is working!!!
是可以生成RepositoryInterface和对应的接口实现文件,这里一个是加了--model选项一个没加的,没加的话这里第一个指令就默认Model的名称是Shop。
生成的文件内容不截图了,看下新生成的ShopRepository.php文件,的确是我想要的模板文件:
总结:本文主要用Laravel的Artisan命令来自动生成个人需要的模板,减少平时开发中重复劳动。就像Laravel自带了很多模板生成命令,用起来会节省很多时间。这是作者在平时开发中遇到的问题,通过利用Laravel Artisan命令解决了,所以Laravel还是挺好玩的。有兴趣的可以把代码扒下来玩一玩,并根据你自己想要的模板做修改。这两天想就Repository模式封装Model逻辑的方法和好处聊一聊,到时见。希望对大家的学习有所帮助
- <?php
- /**
- * Created by PhpStorm.
- * User: liuxiang
- */
- namespace- App\Repositories\Eloquent;
- use- App\Shop;
- use- App\Repositories\ShopRepositoryInterface;
- class- ShopRepository- implements- ShopRepositoryInterface
- {
- /**
- * @var \App\Shop
- */
- public- $shop- ;
- public- function- __construct(Shop- $shop- )
- {
- $this- ->shop =- $shop- ;
- }
- /**
- * @param array $columns
- * @return \Illuminate\Database\Eloquent\Collection|static[]
- */
- public- function- all(- $columns- =- array- (- '*'- ))
- {
- return- $this- ->shop->all(- $columns- );
- }
- /**
- * @param int $perPage
- * @param array $columns
- * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
- */
- public- function- paginate(- $perPage- = 15,- $columns- =- array- (- '*'- ))
- {
- return- $this- ->shop->paginate(- $perPage- ,- $columns- );
- }
- /**
- * Create a new shop
- * @param array $data
- * @return \App\Shop
- */
- public- function- create(- array- $data- )
- {
- return- $this- ->shop->create(- $data- );
- }
- /**
- * Update a shop
- * @param array $data
- * @param $id
- * @return \App\Shop
- */
- public- function- update(- $data- = [],- $id- )
- {
- return- $this- ->shop->whereId(- $id- )->update(- $data- );
- }
- /**
- * Store a shop
- * @param array $data
- * @return \App\Shop
- */
- public- function- store(- $data- = [])
- {
- $this- ->shop->id =- $data- [- 'id'- ];
- //...
- $this- ->shop->save();
- }
- /**
- * Delete a shop
- * @param array $data
- * @param $id
- * @return \App\Shop
- */
- public- function- delete- (- $data- = [],- $id- )
- {
- $this- ->shop->whereId(- $id- )->- delete- ();
- }
- /**
- * @param $id
- * @param array $columns
- * @return array|\Illuminate\Database\Eloquent\Collection|static[]
- */
- public- function- find(- $id- ,- $columns- =- array- (- '*'- ))
- {
- $Shop- =- $this- ->shop->whereId(- $id- )->get(- $columns- );
- return- $Shop- ;
- }
- /**
- * @param $field
- * @param $value
- * @param array $columns
- * @return \Illuminate\Database\Eloquent\Collection|static[]
- */
- public- function- findBy(- $field- ,- $value- ,- $columns- =- array- (- '*'- ))
- {
- $Shop- =- $this- ->shop->where(- $field- ,- '='- ,- $value- )->get(- $columns- );
- return- $Shop- ;
- }
- }