Laravel学习笔记之Artisan命令生成自定义模板的方法


Posted in PHP onNovember 22, 2018

说明:本文主要讲述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命令了。

Laravel学习笔记之Artisan命令生成自定义模板的方法

自动化生成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',

];

运行一下看可不可以吧,这里截个图:

Laravel学习笔记之Artisan命令生成自定义模板的方法

Laravel学习笔记之Artisan命令生成自定义模板的方法

It is working!!!

是可以生成RepositoryInterface和对应的接口实现文件,这里一个是加了--model选项一个没加的,没加的话这里第一个指令就默认Model的名称是Shop。

生成的文件内容不截图了,看下新生成的ShopRepository.php文件,的确是我想要的模板文件:

<?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;
  }

}

总结:本文主要用Laravel的Artisan命令来自动生成个人需要的模板,减少平时开发中重复劳动。就像Laravel自带了很多模板生成命令,用起来会节省很多时间。这是作者在平时开发中遇到的问题,通过利用Laravel Artisan命令解决了,所以Laravel还是挺好玩的。有兴趣的可以把代码扒下来玩一玩,并根据你自己想要的模板做修改。这两天想就Repository模式封装Model逻辑的方法和好处聊一聊,到时见。希望对大家的学习有所帮助,也希望大家多多支持三水点靠木

PHP 相关文章推荐
php5中类的学习
Mar 28 PHP
理解和运用PHP中的多态性[译]
Aug 02 PHP
PHP版国家代码、缩写查询函数代码
Aug 14 PHP
ThinkPHP3.1的Widget新用法
Jun 19 PHP
Linux下php5.4启动脚本
Aug 03 PHP
php生成QRcode实例
Sep 22 PHP
全新Mac配置PHP开发环境教程
Feb 03 PHP
利用PHP访问带有密码的Redis方法示例
Feb 09 PHP
YII框架学习笔记之命名空间、操作响应与视图操作示例
Apr 30 PHP
PHP封装请求类实例分析【基于Yii框架】
Oct 17 PHP
PHP pthreads v3下worker和pool的使用方法示例
Feb 21 PHP
PHP扩展安装方法步骤解析
Nov 24 PHP
关于PHP虚拟主机概念及如何选择稳定的PHP虚拟主机
Nov 20 #PHP
phpMyAdmin通过密码漏洞留后门文件
Nov 20 #PHP
ThinkPHP5 的简单搭建和使用详解
Nov 15 #PHP
关于php unset对json_encode的影响详解
Nov 14 #PHP
PHP集成环境XAMPP的安装与配置
Nov 13 #PHP
python进程与线程小结实例分析
Nov 11 #PHP
PHP 获取客户端 IP 地址的方法实例代码
Nov 11 #PHP
You might like
php foreach 使用&amp;(与运算符)引用赋值要注意的问题
2010/02/16 PHP
PHP判断搜索引擎蜘蛛并自动记忆到文件的代码
2012/02/04 PHP
学习php中的正则表达式
2014/08/17 PHP
编写PHP脚本使WordPress的主题支持Widget侧边栏
2015/12/14 PHP
PHP中SESSION过期设置
2021/03/09 PHP
JavaScript 全面解析各种浏览器网页中的JS 执行顺序
2009/02/17 Javascript
Javascript学习笔记9 prototype封装继承
2010/01/11 Javascript
javascript break指定标签打破多层循环示例
2014/01/20 Javascript
javascript解析json数据的3种方式
2014/05/08 Javascript
javaScript实现滚动新闻的方法
2015/07/30 Javascript
AngularJS页面访问时出现页面闪烁问题的解决
2016/03/06 Javascript
基于JS模仿windows文件按名称排序效果
2016/06/29 Javascript
详解AngularJs中$resource和restfu服务端数据交互
2016/09/21 Javascript
JS常见疑难点分析之match,charAt,charCodeAt,map,search用法分析
2016/12/25 Javascript
JavaScript两个变量交换值的实现方法
2017/03/01 Javascript
使用JSON格式提交数据到服务端的实例代码
2018/04/01 Javascript
Vue+mui实现图片的本地缓存示例代码
2018/05/24 Javascript
微信小程序五子棋游戏的悔棋实现方法【附demo源码下载】
2019/02/20 Javascript
npm qs模块使用详解
2020/02/07 Javascript
Vue+element-ui添加自定义右键菜单的方法示例
2020/12/08 Vue.js
[05:29]2014DOTA2国际邀请赛 赛后专访:LGDNewbee顺利过关
2014/07/13 DOTA
python多线程用法实例详解
2015/01/15 Python
python文件操作相关知识点总结整理
2016/02/22 Python
详解Python中 sys.argv[]的用法简明解释
2017/12/20 Python
Python类反射机制使用实例解析
2019/12/30 Python
sklearn和keras的数据切分与交叉验证的实例详解
2020/06/19 Python
HashMap和Hashtable的区别
2013/05/18 面试题
如何写自我评价?自我评价写什么好?
2014/03/14 职场文书
《第一朵杏花》教学反思
2014/04/16 职场文书
大学学雷锋活动总结
2014/06/26 职场文书
会计学专业求职信
2014/07/17 职场文书
单位在职证明书
2014/09/11 职场文书
中学团支部工作总结
2015/08/13 职场文书
中秋节作文(五年级)之关于月亮
2019/09/11 职场文书
Java Dubbo框架知识点梳理
2021/06/26 Java/Android
MySQL中JOIN连接的基本用法实例
2022/06/05 MySQL