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 相关文章推荐
杏林同学录(一)
Oct 09 PHP
PHP Session_Regenerate_ID函数双释放内存破坏漏洞
Jan 27 PHP
php array_filter除去数组中的空字符元素
Jun 21 PHP
解析wamp5下虚拟机配置文档
Jun 27 PHP
Yii2使用小技巧之通过 Composer 添加 FontAwesome 字体资源
Jun 22 PHP
php上传文件常见问题总结
Feb 03 PHP
大家在抢红包,程序员在研究红包算法
Aug 31 PHP
PHP中addcslashes与stripcslashes函数用法分析
Jan 07 PHP
PHP清除缓存的几种方法总结
Sep 12 PHP
PHP获取ttf格式文件字体名的方法示例
Mar 06 PHP
Laravel5.1 框架表单验证操作实例详解
Jan 07 PHP
PHP文件操作简单介绍及函数汇总
Dec 11 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
大师制作的中短波矿石收音机
2020/04/02 无线电
一个ubbcode的函数,速度很快.
2006/10/09 PHP
PHP 遍历XP文件夹下所有文件
2008/11/27 PHP
php自动加载的两种实现方法
2010/06/21 PHP
Parse正式发布开源PHP SDK
2014/08/11 PHP
php正则表达式获取内容所有链接
2015/07/24 PHP
laravel框架 laravel-admin上传图片到oss的方法
2019/10/13 PHP
JavaScript 字符串与数组转换函数[不用split与join]
2009/12/13 Javascript
json数据与字符串的相互转化示例
2013/09/18 Javascript
JavaScript实现的经典文件树菜单效果
2015/09/08 Javascript
纯JS实现弹性导航条效果
2017/03/06 Javascript
websocket+node.js实现实时聊天系统问题咨询
2017/05/17 Javascript
MvcPager分页控件 适用于Bootstrap
2017/06/03 Javascript
Vue项目webpack打包部署到服务器的实例详解
2017/07/17 Javascript
JavaScript中in和hasOwnProperty区别详解
2017/08/04 Javascript
微信小程序之判断页面滚动方向的示例代码
2018/08/30 Javascript
Vue v-bind动态绑定class实例方法
2020/01/15 Javascript
逐行分析鸿蒙系统的 JavaScript 框架(推荐)
2020/09/17 Javascript
[14:51]DOTA2 HEROS教学视频教你分分钟做大人-卓尔游侠
2014/06/13 DOTA
从零学python系列之浅谈pickle模块封装和拆封数据对象的方法
2014/05/23 Python
简单的Python抓taobao图片爬虫
2014/10/26 Python
python实现快速排序的示例(二分法思想)
2018/03/12 Python
Python八大常见排序算法定义、实现及时间消耗效率分析
2018/04/27 Python
python 从文件夹抽取图片另存的方法
2018/12/04 Python
python列表生成器迭代器实例解析
2019/12/19 Python
Flask模板引擎Jinja2使用实例
2020/04/23 Python
Keras中的多分类损失函数用法categorical_crossentropy
2020/06/11 Python
解决Keras 自定义层时遇到版本的问题
2020/06/16 Python
俄罗斯电动工具和设备购物网站:Vseinstrumenti.ru
2020/11/12 全球购物
Linux文件操作命令都有哪些
2015/02/27 面试题
11月升旗仪式讲话稿
2014/02/15 职场文书
菜篮子工程实施方案
2014/03/08 职场文书
家居装修公司创业计划书范文
2014/03/20 职场文书
2015小学教师德育工作总结
2015/05/12 职场文书
大学毕业晚会开场白
2015/05/29 职场文书
教师节主题班会教案
2015/08/17 职场文书