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 相关文章推荐
在Zeus Web Server中安装PHP语言支持
Oct 09 PHP
php查看session内容的函数
Aug 27 PHP
php中强制下载文件的代码(解决了IE下中文文件名乱码问题)
May 09 PHP
PHP 正则表达式常用函数
Aug 17 PHP
php绘制一条直线的方法
Jan 24 PHP
PHP如何实现订单的延时处理详解
Dec 30 PHP
php empty 函数判断结果为空但实际值却为非空的原因解析
May 28 PHP
PHP实现小程序批量通知推送
Nov 27 PHP
Laravel5.7 Eloquent ORM快速入门详解
Apr 12 PHP
PHP实现基于状态的责任链审批模式详解
May 31 PHP
PHP使用CURL实现下载文件功能示例
Jun 03 PHP
PHP中用Trait封装单例模式的实现
Dec 18 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面向对象概念
2011/11/06 PHP
php获取bing每日壁纸示例分享
2014/02/25 PHP
PHP获取MySql新增记录ID值的3种方法
2014/06/24 PHP
php中curl使用指南
2015/02/05 PHP
PHP浮点比较大小的方法
2016/02/14 PHP
如何正确配置Nginx + PHP
2016/07/15 PHP
PHP实现时间日期友好显示实现代码
2019/09/08 PHP
jquery获取当前元素索引值用法实例
2015/06/10 Javascript
Bootstrap框架动态生成Web页面文章内目录的方法
2016/05/12 Javascript
概述一个页面从输入URL到页面加载完的过程
2016/12/16 Javascript
Angularjs的$http异步删除数据详解及实例
2017/07/27 Javascript
详解JS模块导入导出
2017/12/20 Javascript
彻底弄懂 JavaScript 执行机制
2018/10/23 Javascript
vue-cli3全面配置详解
2018/11/14 Javascript
JS数组求和的常用方法总结【5种方法】
2019/01/14 Javascript
Vue CLI3基础学习之pages构建多页应用
2019/06/02 Javascript
ES6 Generator函数的应用实例分析
2019/06/26 Javascript
微信小程序实现页面跳转传递参数(实体,对象)
2019/08/12 Javascript
Vue 的 v-model用法实例
2020/11/23 Vue.js
[00:17]天涯墨客一技能展示
2018/08/25 DOTA
解密Python中的描述符(descriptor)
2015/06/03 Python
Python字符串转换成浮点数函数分享
2015/07/24 Python
pyqt5 使用label控件实时显示时间的实例
2019/06/14 Python
python实现串口自动触发工作的示例
2019/07/02 Python
详解如何用TensorFlow训练和识别/分类自定义图片
2019/08/05 Python
Python实现自动签到脚本的示例代码
2020/08/19 Python
Sunglasses Shop丹麦:欧洲第一的太阳镜在线销售网站
2017/10/22 全球购物
彪马俄罗斯官网:PUMA俄罗斯
2019/07/13 全球购物
美国美食礼品篮网站:Gourmet Gift Baskets
2019/12/15 全球购物
教师专业自荐书范文
2014/02/10 职场文书
人代会标语
2014/06/30 职场文书
2014年小班保育员工作总结
2014/12/23 职场文书
学雷锋日活动总结
2015/02/06 职场文书
大学生个人简历自我评价
2015/03/11 职场文书
技术入股协议书
2016/03/22 职场文书
IDEA2021.2配置docker如何将springboot项目打成镜像一键发布部署
2021/09/25 Java/Android