↓ 快轉到主要內容
Larvel透過GeneratorCommand使用artisan產出php執行檔
技術文章Laravel

Larvel透過GeneratorCommand使用artisan產出php執行檔

Posted by Alvin Chen on

Larvel透過GeneratorCommand使用artisan產出php執行檔

·1135 字·3 分鐘
作者
Alvin Chen
相信寫 Code 可以改變世界

基本概念
#

將原始碼寫在.stub檔中,內容中如果有需要替換的則使用{{ }} 標記,例如 namespace {{ namespace }};

GeneratoreCommand會讀出.stub檔案將需要替換的文字替換完成後依照namespace寫入.php檔案

幾個需要理解的method與參數

首先參數的部分,固定第一個為name,也就是要產出的檔案名稱

php artisan GeneratorCommand {name}

method: getStub()
#

protected function getStub(): string

需要return .stub檔案的路徑,如果.stub檔案在vendor資料夾中則可以透過上篇說明來取得package中的檔案

https://www.alvinchen.club/2025/04/08/%e9%80%8f%e9%81%8enamespace%e6%89%be%e5%88%b0composer-package%e4%b8%ad%e7%9a%84%e6%aa%94%e6%a1%88%e8%b7%af%e5%be%91/

範例可以像這樣子,class中依照上一篇說明定義好get_file_path後,就可以找到指定的Vendor\package\路徑下的檔案了

如果是在同一個專案目錄中,則直接return專案中路徑即可

protected function getStub(): string
{
    return $this->get_file_path("/http/Controllers");
}

method: getDefaultNamespace()
#

protected function getDefaultNamespace($rootNamespace): string

需要return 產出php檔正確擺放位置的namespace,例如

protected function getDefaultNamespace($rootNamespace): string
{
    return $rootNamespace . "\Http\\Controllers";
}

這樣子就會把產出的php檔案放在app\Http\Controllers中

method: getNameInput()
#

protected function getNameInput(): string

return值作為檔案的名稱,也就是return command第一個參數name就行了,通常會把class name做大駝峰處理,範例如下

protected function getNameInput(): string
{
    return Str::studly(trim($this->argument('name')));
}

method: buildClass()
#

protected function buildClass($name): array|string

如果產出的php檔沒有其他需要替換的標記,或者沒有其他邏輯需要處理是可以不必處理buildClass()這個method,這邊提供一個範例

把stub內容中多組tag紀錄在$this->replace_tags中,然後再buildClass method中替換掉他

protected function buildClass($name): array|string
{
    $stub = $this->files->get($this->getStub());

    $namespace = $this->replaceNamespace($stub, $name)->replaceClass($stub, $name);
    $replacedStub = $namespace;
    foreach ($this->replace_tags as $tag => $text){
        $replacedStub = str_replace("{{ {$tag} }}", $text, $replacedStub);
    }

    return $replacedStub;
}

method: handle()
#

public function handle(): ?bool

如果在產出檔案前,有需要對class的其他參數做調整,可以複寫handle() method,記得最後要return parent::handle()就好

例如像這樣

public function handle(): ?bool
{
    #你想要的內容寫在這裡

    return parent::handle(); // TODO: Change the autogenerated stub
}

method: interact()
#

protected function interact(InputInterface $input, OutputInterface $output)

假設需要對輸入的參數做一些調整,可以透過複寫interact來達成,下面的範例處理了name參數,讓他變成首字大寫

/**
 * Override
 * 重新處理name參數
 */
protected function interact(InputInterface $input, OutputInterface $output)
{
    parent::interact($input, $output);
    if ($input->hasArgument('name')) {
        $name = implode('/',array_map('ucfirst',explode('/',$input->getArgument('name'))));
        $input->setArgument('name',$name);
    }
}

最後提供一個完整範例
#

將virtualorz\mysdk中的 Stuns/http/controllers.stub 生成 /app/Http/Controllers/CustomerController.php

#command.php

<?php
namespace App\Console\Commands;

use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Str;
class MakeCustomerController extends GeneratorCommand
{
    protected $name = 'make:my-controller';
    protected $description = 'Create my controller class';
    protected string $stub_path = 'http/ClassController';
    protected string $target_path = '\Http\\Controllers';
    protected array $replace_tags = ['use_string'=>'use App\Services\Config\ConfigService'];

    protected function getStub(): string
    {
        return $this->get_file_path($this->stub_path);
    }

    protected function getDefaultNamespace($rootNamespace): string
    {
        return $rootNamespace . $this->target_path;
    }

    protected function getNameInput(): string
    {
        return Str::studly(trim($this->argument('name')));
    }

    protected function buildClass($name): array|string
    {
        $stub = $this->files->get($this->getStub());

        $namespace = $this->replaceNamespace($stub, $name)->replaceClass($stub, $name);
        $replacedStub = $namespace;
        foreach ($this->replace_tags as $tag => $text){
            $replacedStub = str_replace("{{ {$tag} }}", $text, $replacedStub);
        }

        return $replacedStub;
    }

    protected function get_file_path($path): string
    {
        $loader = $this->_getClassLoader();
        $psr4 = $loader->getPrefixesPsr4();

        $namespace = 'virtualorz\\mysdk\\';
        $stubFilename = "Stubs/{$path}.stub"; // Stub 檔案名稱
        $stub_path_location = '';

        foreach ($psr4 as $prefix => $paths) {
            if (str_starts_with($namespace, $prefix)) {
                foreach ($paths as $path) { // 處理多個路徑
                    $relativePath = str_replace($prefix, '', $namespace);
                    $relativePath = str_replace('\\', '/', $relativePath);
                    $stubPath = $path . '/' . $relativePath . $stubFilename;

                    if (file_exists($stubPath)) {
                        $stub_path_location =  $stubPath;
                    }
                }
            }
        }
        return $stub_path_location;
    }

    protected function _getClassLoader()
    {
        return require base_path('vendor/autoload.php');
    }
}
Controllers.stub

<?php

namespace {{ namespace }};

{{ use_string }}

class {{ class }} extends Controller
{

}值

執行命令
#

php artisan make:my-controller CustomerController