Laravel學習筆記之Artisan命令生成自定義模板

說明:本文主要講述Laravel的Artisan命令來實現自定義模板,就如常常輸入的php artisan make:controller ShopController就會自動生成一個ShopController.php模板文件同樣,經過命令生成模板也會提升開發效率。同時,做者會將開發過程當中的一些截圖和代碼黏上去,提升閱讀效率。php

備註:我的平時在寫Repository代碼時會這樣寫,如先寫上ShopRepositoryInterface並定義好接口方法如all()create()update()delete()findBy()等等,而後再寫上接口對應的實現ShopRepository並注入對應的Model即Shop。別的PostRepository、TagRepository也會是這麼寫(固然,對於不少重用的Repository方法能夠集體拿到AbstractRepository抽象類裏供子類繼承,實現代碼複用)。那能不能直接命令行生成模板文件呢,就不用本身一個個的寫了,就像輸入php artisan make:controller PostController給我一個Controller模板來。laravel

關於使用Repository模式來封裝下Model邏輯,不讓Controller裏塞滿了不少Model邏輯,這樣作是有不少好處的,最主要的就是好測試和代碼架構清晰,也符合SOLID原則。若是使用PHPUnit來作測試就知道了爲啥說好測試了。SegmentFault上也有相關的文章描述。做者也打算最近新開一篇文章聊一聊這個,PHPUnit也打算過段時間聊一聊。api

我的研究了下Artisan命令行,是能夠的。通過開發後,結果是輸入自定義指令php artisan make:repository PostRepository --model=Post(這個option可要可不要),就會幫我生成一個PostRepositoryInterface和對應的接口實現PostRepository。架構

模板文件Stub

因爲我的須要生成一個RepositoryInterface和對應接口實現Repository,那就須要兩個模板文件了。在resources/stubs新建兩個模板文件,如下是我的常常須要的兩個模板文件(你能夠自定義):app

// resources/stubs/Repository/repository_interface.stub
<?php
/**
 * Created by PhpStorm.
 * User: liuxiang
 */
namespace $repository_interface_namespace;

interface  $repository_interface
{
    /**
     * @param array $columns
     * @return \Illuminate\Database\Eloquent\Collection|static[]
     */
    public function all($columns = array('*'));

    /**
     * @param int $perPage
     * @param array $columns
     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
     */
    public function paginate($perPage = 15, $columns = array('*'));

    /**
     * Create a new $model_name
     * @param array $data
     * @return \$model_namespace
     */
    public function create(array $data);

    /**
     * Update a $model_name
     * @param array $data
     * @return \$model_namespace
     */
    public function update($data = [], $id);

    /**
     * Store a $model_name
     * @param array $data
     * @return \$model_namespace
     */
    public function store($data = []);

    /**
     * Delete a $model_name
     * @param array $data
     * @return \$model_namespace
     */
    public function delete($data = [], $id);

    /**
     * @param $id
     * @param array $columns
     * @return array|\Illuminate\Database\Eloquent\Collection|static[]
     */
    public function find($id, $columns = array('*'));

    /**
     * @param $field
     * @param $value
     * @param array $columns
     * @return \Illuminate\Database\Eloquent\Collection|static[]
     */
    public function findBy($field, $value, $columns = array('*'));
}

// resources/stubs/Repository/Eloquent/repository.stub
<?php
/**
 * Created by PhpStorm.
 * User: liuxiang
 */
namespace $repository_namespace;

use $model_namespace;
use $repository_interface_namespace\$repository_interface;

class $class_name implements $repository_interface
{
    /**
     * @var \$model_namespace
     */
    public $$model_var_name;

    public function __construct($model_name $$model_var_name)
    {
        $this->$model_var_name = $$model_var_name;
    }

    /**
     * @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;
    }

}

模板文件裏包括參數,這些參數將會根據命令行中輸入的參數和選項被相應替換:composer

['$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

而後改下簽名和描述:this

// 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裏註冊下命令:spa

// 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命令執行文件裏寫上模板自動生成邏輯,代碼也不長,有些邏輯也有註釋,可看:

// app/Console/Commands/MakeRepositoryCommand
<?php

namespace App\Console\Commands;

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文件,的確是我想要的模板文件:

<?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邏輯的方法和好處聊一聊,到時見。

歡迎關注Laravel-China

RightCapital招聘Laravel DevOps

相關文章
相關標籤/搜索