自定义模块生成器

未匹配的标注

构建你自己的模块生成器

从默认模块开始就可以了,但通常你会有自己的模块结构,可以在模块之间使用。能够使用你的结构作为模板,以便将来创建所有模块,这是很好的。

这里有两个选择。编辑存根文件或创建自己的基本模块和自定义artisan命令。

让我们探索这两种选项

Stub 文件

默认情况下,config/modules.php 文件有一个指向 laravel-modules 包供应商的存根路径:

'stubs' => [
    'enabled' => false,
    'path' => base_path() . '/vendor/nwidart/laravel-modules/src/Commands/stubs',

如果您可以编辑文件,则可以将此路径更改为一个。你永远不应该编辑vendor文件夹下的文件,所以我们应该拷贝一个 vendor/nwidart/laravel-modules/src/Commands/stub 文件夹到 stub /module 目录下。

如果你没有stubs文件夹,那么首先发布 Laravel 的 stub:

php artisan stub:publish

这将创建一个存根文件夹,其中包含Laravel在创建类时使用的所有 stub 文件。在 stub 中创建一个名为 module的文件夹。

确保你已经将 vendor/nwidart/laravel-modules/src/Commands/stub 的内容复制到 stub /module 中,现在打开config/modules.php并编辑 stub 的路径:

'stubs' => [
    'enabled' => false,
    'path' => base_path() . '/stubs/module',

现在您可以编辑任何 stub,例如,我喜欢从控制器中删除所有文档块。stub 文件包含:

<?php

namespace $CLASS_NAMESPACE$;

use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class $CLASS$ extends Controller
{
    /**
     * 显示资源列表。
     * @return Renderable
     */
    public function index()
    {
        return view('$LOWER_NAME$::index');
    }

    /**
     * 显示用于创建新资源的表单。
     * @return Renderable
     */
    public function create()
    {
        return view('$LOWER_NAME$::create');
    }

    /**
     * 将新创建的资源存储在storage中。
     * @param Request $request
     * @return Renderable
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * 显示指定的资源。
     * @param int $id
     * @return Renderable
     */
    public function show($id)
    {
        return view('$LOWER_NAME$::show');
    }

    /**
     * 显示用于编辑指定资源的表单。
     * @param int $id
     * @return Renderable
     */
    public function edit($id)
    {
        return view('$LOWER_NAME$::edit');
    }

    /**
     * 更新存储中的指定资源。
     * @param Request $request
     * @param int $id
     * @return Renderable
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * 从存储中移除指定的资源。
     * @param int $id
     * @return Renderable
     */
    public function destroy($id)
    {
        //
    }
}

我总是删除文档块,所以让我们编辑这个文件的 stub,打开 stubs/module/controllers.stub

<?php

namespace $CLASS_NAMESPACE$;

use Illuminate\Contracts\Support\Renderable;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class $CLASS$ extends Controller
{
    public function index()
    {
        return view('$LOWER_NAME$::index');
    }

    public function create()
    {
        return view('$LOWER_NAME$::create');
    }

    public function store(Request $request)
    {
        //
    }

    public function show($id)
    {
        return view('$LOWER_NAME$::show');
    }

    public function edit($id)
    {
        return view('$LOWER_NAME$::edit');
    }

    public function update(Request $request, $id)
    {
        //
    }

    public function destroy($id)
    {
        //
    }
}

现在,当你创建模块或控制器时:

php artisan module:make-controller CustomerController Customers

控制器的 stub 将被使用。

如你所见,编辑创建的默认文件很简单。现在,您可以修改许多文件以获得默认所需的结构,但这些存根用于创建模块和文件。这意味着如果你将文件修改为默认值,然后生成额外的类,它们也将具有set结构。你可能并不总是想要这样。

在生成类时,我通常想要整个模块的初始结构和基本的样板文件。

基本模块和自定义Artisan命令

我们要做的是创建一个 artisan 命令,它将获取一个模块的副本,并将模块内的文件和占位符重命名为一个新模块。

要生成新模块,通常需要先创建一个模块,确保它具有你想要的一切作为基础。保持简单,例如在CRUD(创建、读取、更新和删除)模块中,你希望路由列出、添加、编辑和删除它们的控制器方法视图和测试。

基础模块

创建好模块后,你就可以把它转换为基础模块了。将模块复制到一个位置。我将使用 stub /base-module 作为位置。

stubs/
    base-module
        Config
        Console
        Database
        Http
        Models
        Providers
        Resources
        Routes
        Tests
        composer.json
        module.json
        package.json
        webpack.mix.js

创建新模块时,对模块的每个引用都需要更改,例如有一个名为contacts的模块,在使用该模块的模块中到处都有一个名为contact的模型,该模型将使用contact::,但创建新模块时,需要将其更改为新模块的名称。

你可以使用占位符,而不是手动查找和复制所有对控制器、模型、命名空间、视图等的引用。

占位符

这些是我使用的占位符:

{module_}      

模块名所有小写空格与下划线分隔。

{module-}  

模块名全部小写,用连字符分隔空格。

{Module}  

驼峰式大小写格式的模块名。

{module} 

模块名全部小写。

{Model}

驼峰式的模型名称。

{model} 

模型名称全部小写。

这些占位符可以在整个模块中使用,例如控制器:

namespace Modules\{Module}\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\{Module}\Models\{Model};

class {Module}Controller extends Controller
{
    public function index()
    {
        ${module} = {Model}::get();

        return view('{module}::index', compact('{module}'));
    }
}

当占位符被换出时看起来像这样

namespace Modules\Contacts\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\Contacts\Models\Contact;

class {Module}Controller extends Controller
{
    public function index()
    {
        $contacts = Contact::get();

        return view('contacts::index', compact('contacts'));
    }
}

这允许很棒的定制。您可以创建任何您需要的结构,稍后能够将占位符替换为实际值。

如前所述,首先需要使用这些占位符创建一个基础模块。此外,您将编辑文件名,如 Contact.php,将模型更改为 model.php,这些文件名将自动重命名为新的模块名称。

基本模块源代码

你可以在这里找到一个完整的模块模板 github.com/modularlaravel/base-mod...

为了完整性,让我们在这里构建一个基础模块。

该模块将具有以下结构:

base-module
  Config
    config.php
  Console
  Database
    Factories
        ModelFactory.php
    Migrations
        create_module_table.php
    Seeders
        ModelDatabaseSeeder.php
  Http
    Controllers
        ModuleController.php
    Middleware
    Requests
  Models
    Model.php
  Providers
    ModuleServiceProvider.php
    RouteServiceProvider.php
  Resources
    assets
        js
            app.js
        sass
    lang
    views
        create.blade.php
        edit.blade.php
        index.blade.php
  Routes
    api.php
    web.php
  Tests
    Feature
        ModuleTest.php
    Unit
  composer.json
  module.json
  package.json
  webpack.mix.js

Config.php

将保存模块的名称,如Contacts

<?php

return [
    'name' => '{Module}'
];

ModelFactory.php

<?php
namespace Modules\{Module}\Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Modules\{Module}\Models\{Model};

class {Model}Factory extends Factory
{
    protected $model = {Model}::class;

    public function definition(): array
    {
        return [
            'name' => $this->faker->name()
        ];
    }
}

create_model_table.php

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class Create{Module}Table extends Migration
{
    public function up()
    {
        Schema::create('{module}', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('{module}');
    }
}

ModuleDatabaseSeeder.php

<?php

namespace Modules\{Module}\Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;

class {Module}DatabaseSeeder extends Seeder
{
    public function run()
    {
        Model::unguard();

        // $this->call("OthersTableSeeder");
    }
}

ModuleController.php

<?php

namespace Modules\{Module}\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Modules\{Module}\Models\{Model};

class {Module}Controller extends Controller
{
    public function index()
    {
        ${module} = {Model}::get();

        return view('{module}::index', compact('{module}'));
    }

    public function create()
    {
        return view('{module}::create');
    }

    public function store(Request $request)
    {
        $request->validate([
            'name' => 'required|string'
        ]);

        {Model}::create([
            'name' => $request->input('name')
        ]);

        return redirect(route('app.{module}.index'));
    }

    public function edit($id)
    {
        ${model} = {Model}::findOrFail($id);

        return view('{module}::edit', compact('{model}'));
    }

    public function update(Request $request, $id)
    {
        $request->validate([
            'name' => 'required|string'
        ]);

        {Model}::findOrFail($id)->update([
            'name' => $request->input('name')
        ]);

        return redirect(route('app.{module}.index'));
    }

    public function destroy($id)
    {
        {Model}::findOrFail($id)->delete();

        return redirect(route('app.{module}.index'));
    }
}

Model.php

<?php

namespace Modules\{Module}\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Modules\{Module}\Database\Factories\{Model}Factory;

class {Model} extends Model
{
    use HasFactory;

    protected $fillable = ['name'];

    protected static function newFactory()
    {
        return {Model}Factory::new();
    }
}

ModuleServiceProvider.php

<?php

namespace Modules\{Module}\Providers;

use Illuminate\Support\ServiceProvider;

class {Module}ServiceProvider extends ServiceProvider
{
    protected $moduleName = '{Module}';
    protected $moduleNameLower = '{module}';

    public function boot()
    {
        $this->registerTranslations();
        $this->registerConfig();
        $this->registerViews();
        $this->loadMigrationsFrom(module_path($this->moduleName, 'Database/Migrations'));
    }

    public function register()
    {
        $this->app->register(RouteServiceProvider::class);
    }

    protected function registerConfig()
    {
        $this->publishes([
            module_path($this->moduleName, 'Config/config.php') => config_path($this->moduleNameLower . '.php'),
        ], 'config');
        $this->mergeConfigFrom(
            module_path($this->moduleName, 'Config/config.php'), $this->moduleNameLower
        );
    }

    public function registerViews()
    {
        $viewPath = resource_path('views/modules/' . $this->moduleNameLower);

        $sourcePath = module_path($this->moduleName, 'Resources/views');

        $this->publishes([
            $sourcePath => $viewPath
        ], ['views', $this->moduleNameLower . '-module-views']);

        $this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), $this->moduleNameLower);
    }

    public function registerTranslations()
    {
        $langPath = resource_path('lang/modules/' . $this->moduleNameLower);

        if (is_dir($langPath)) {
            $this->loadJsonTranslationsFrom($langPath, $this->moduleNameLower);
        } else {
            $this->loadJsonTranslationsFrom(module_path($this->moduleName, 'Resources/lang'), $this->moduleNameLower);
        }
    }

    public function provides()
    {
        return [];
    }

    private function getPublishableViewPaths(): array
    {
        $paths = [];
        foreach (\Config::get('view.paths') as $path) {
            if (is_dir($path . '/modules/' . $this->moduleNameLower)) {
                $paths[] = $path . '/modules/' . $this->moduleNameLower;
            }
        }
        return $paths;
    }
}

RouteServiceProvider.php

<?php

namespace Modules\{Module}\Providers;

use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;

class RouteServiceProvider extends ServiceProvider
{
    protected $moduleNamespace = 'Modules\{Module}\Http\Controllers';

    public function boot()
    {
        parent::boot();
    }

    public function map()
    {
        $this->mapApiRoutes();
        $this->mapWebRoutes();
    }

    protected function mapWebRoutes()
    {
        Route::middleware('web')
            ->namespace($this->moduleNamespace)
            ->group(module_path('{Module}', '/Routes/web.php'));
    }

    protected function mapApiRoutes()
    {
        Route::prefix('api')
            ->middleware('api')
            ->namespace($this->moduleNamespace)
            ->group(module_path('{Module}', '/Routes/api.php'));
    }
}

create.blade.php

@extends('layouts.app')

@section('content')
    <div class="card">
        <h1>Add {Model}</h1>

        <x-form action="{{ route('app.{module}.create') }}">
            <x-form.input name="name" />
            <x-form.button>Submit</x-form.button>
        </x-form>
    </div>
@endsection

edit.blade.php

@extends('layouts.app')

@section('content')
    <div class="card">
        <h1>Edit {Model}</h1>

        <x-form action="{{ route('app.{module}.update', ${model}->id) }}" method="patch">
            <x-form.input name="name">{{ ${model}->name }}</x-form.input>
            <x-form.button>Update</x-form.button>
        </x-form>
    </div>
@endsection

index.blade.php

@extends('layouts.app')

@section('content')
    <div class="card">
    <h1>{Module}</h1>

    <p><a href="{{ route('app.{module}.create') }}">Add {Model}</a> </p>

    <table>
        <tr>
            <td>Name</td>
            <td>Action</td>
        </tr>
        @foreach(${module} as ${model})
            <tr>
                <td>{{ ${model}->name }}</td>
                <td>
                    <a href="{{ route('app.{module}.edit', ${model}->id) }}">Edit</a>

                    <a href="#" onclick="event.preventDefault(); document.getElementById('delete-form').submit();">Delete</a>
                    <x-form id="delete-form" method="delete" action="{{ route('app.{module}.delete', ${model}->id) }}" />
                </td>
            </tr>
        @endforeach
    </table>
    </div>
@endsection

api.php

<?php

use Illuminate\Http\Request;

Route::middleware('auth:api')->get('/{module}', function (Request $request) {
    return $request->user();
});

web.php

<?php

use Modules\{Module}\Http\Controllers\{Module}Controller;

Route::middleware('auth')->prefix('app/{module}')->group(function() {
    Route::get('/', [{Module}Controller::class, 'index'])->name('app.{module}.index');
    Route::get('create', [{Module}Controller::class, 'create'])->name('app.{module}.create');
    Route::post('create', [{Module}Controller::class, 'store'])->name('app.{module}.store');
    Route::get('edit/{id}', [{Module}Controller::class, 'edit'])->name('app.{module}.edit');
    Route::patch('edit/{id}', [{Module}Controller::class, 'update'])->name('app.{module}.update');
    Route::delete('delete/{id}', [{Module}Controller::class, 'destroy'])->name('app.{module}.delete');
});

ModuleTest.php

<?php

use Modules\{Module}\Models\{Model};

uses(Tests\TestCase::class);

test('can see {model} list', function() {
    $this->authenticate();
   $this->get(route('app.{module}.index'))->assertOk();
});

test('can see {model} create page', function() {
    $this->authenticate();
   $this->get(route('app.{module}.create'))->assertOk();
});

test('can create {model}', function() {
    $this->authenticate();
   $this->post(route('app.{module}.store', [
       'name' => 'Joe'
   ]))->assertRedirect(route('app.{module}.index'));

   $this->assertDatabaseCount('{module}', 1);
});

test('can see {model} edit page', function() {
    $this->authenticate();
    ${model} = {Model}::factory()->create();
    $this->get(route('app.{module}.edit', ${model}->id))->assertOk();
});

test('can update {model}', function() {
    $this->authenticate();
    ${model} = {Model}::factory()->create();
    $this->patch(route('app.{module}.update', ${model}->id), [
        'name' => 'Joe Smith'
    ])->assertRedirect(route('app.{module}.index'));

    $this->assertDatabaseHas('{module}', ['name' => 'Joe Smith']);
});

test('can delete {model}', function() {
    $this->authenticate();
    ${model} = {Model}::factory()->create();
    $this->delete(route('app.{module}.delete', ${model}->id))->assertRedirect(route('app.{module}.index'));

    $this->assertDatabaseCount('{module}', 0);
});

composer.json

确保你在这里编辑作者详细信息,所有未来的模块都将使用这些详细信息。

{
    "name": "dcblogdev/{module}",
    "description": "",
    "authors": [
        {
            "name": "David Carr",
            "email": "dave@dcblog.dev"
        }
    ],
    "extra": {
        "laravel": {
            "providers": [],
            "aliases": {

            }
        }
    },
    "autoload": {
        "psr-4": {
            "Modules\\{Module}\\": ""
        }
    }
}

module.json

{
    "name": "{Module}",
    "label": "{Module}",
    "alias": "{module}",
    "description": "manage all {module}",
    "keywords": ["{module}"],
    "priority": 0,
    "providers": [
        "Modules\\{Module}\\Providers\\{Module}ServiceProvider"
    ],
    "aliases": {},
    "files": [],
    "requires": []
}

package.json

{
    "private": true,
    "scripts": {
        "dev": "npm run development",
        "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch-poll": "npm run watch -- --watch-poll",
        "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
        "prod": "npm run production",
        "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
    },
    "devDependencies": {
        "cross-env": "^7.0",
        "laravel-mix": "^5.0.1",
        "laravel-mix-merge-manifest": "^0.1.2"
    }
}

webpack.mix.js

const dotenvExpand = require('dotenv-expand');
dotenvExpand(require('dotenv').config({ path: '../../.env'/*, debug: true*/}));

const mix = require('laravel-mix');
require('laravel-mix-merge-manifest');

mix.setPublicPath('../../public').mergeManifest();

mix.js(__dirname + '/Resources/assets/js/app.js', 'js/{module}.js')
    .sass( __dirname + '/Resources/assets/sass/app.scss', 'css/{module}.css');

if (mix.inProduction()) {
    mix.version();
}

Artisan make:module 命令

现在我们有了一个基础模块和它的占位符,是时候编写一个artisan命令了,该命令将其作为蓝图来创建一个新模块。

使用下面的命令创建一个新命令:

php artisan make:command MakeModuleCommand

这将在内部生成一个新的命令类 app/Console/Commands/MakeModuleCommand.php

<?php

declare(strict_types=1);

namespace App\Console\Commands;

use Illuminate\Console\Command;

class MakeModule2Command extends Command
{
    protected $signature = 'command:name';
    protected $description = 'Command description';

    public function __construct()
    {
        parent::__construct();
    }

    public function handle()
    {
        return 0;
    }
}

我们要使用 make:module 调用该命令,替换签名和描述:

protected $signature = 'make:module';
protected $description = 'Create starter CRUD module';

导入Str和Symfony文件系统类:

use Illuminate\Support\Str;
use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;

对于文件系统,你需要通过 composer 安装这个类:

composer require symfony/filesystem

在handle方法中,我们希望命令询问模块的名称,我们可以使用$this-&gt;ask

通过验证确保模块名不是空的。

当提供模块名称时,我们将尝试猜测模型的名称,然后询问模型名称是否正确。

如果名称正确,将打印模块名称和模型,并在进行下一步之前进行最终确认。

如果确认失败,进程将重新启动。

一旦最终确认发生,一个名为 generate 的方法将被调用。

public function handle()
{
  $this->container['name'] = ucwords($this->ask('Please enter the name of the Module'));

  if (strlen($this->container['name']) == 0) {
      $this->error("\nModule name cannot be empty.");
  } else {
      $this->container['model'] = ucwords(Str::singular($this->container['name']));

      if ($this->confirm("Is '{$this->container['model']}' the correct name for the Model?", 'yes')) {
          $this->comment('You have provided the following information:');
          $this->comment('Name:  ' . $this->container['name']);
          $this->comment('Model: ' . $this->container['model']);

          if ($this->confirm('Do you wish to continue?', 'yes')) {
              $this->comment('Success!');
              $this->generate();
          } else {
              return false;
          }

          return true;
      } else {
          $this->handle();
      }
  }

  $this->info('Starter '.$this->container['name'].' module installed successfully.');
}

现在我们需要添加一个generate方法,这将需要添加一些其他方法,让我们先添加这些方法。

添加一个名为 rename 的方法,该方法接受一个路径、目标和类型。这用于重命名文件并将重命名的文件保存到新的 $target 位置。

如果将 $type设置为 migration,则创建一个时间戳,该时间戳将添加到文件名中并作为迁移文件的前缀。否则直接重命名。

protected function rename($path, $target, $type = null)
{
    $filesystem = new SymfonyFilesystem;
    if ($filesystem->exists($path)) {
        if ($type == 'migration') {
            $timestamp = date('Y_m_d_his_');
            $target = str_replace("create", $timestamp."create", $target);
            $filesystem->rename($path, $target, true);
            $this->replaceInFile($target);
        } else {
            $filesystem->rename($path, $target, true);
        }
    }
}

接下来,我们希望能够将整个文件夹复制到一个新位置。

protected function copy($path, $target)
{
    $filesystem = new SymfonyFilesystem;
    if ($filesystem->exists($path)) {
        $filesystem->mirror($path, $target);
    }
}

最后一个辅助方法是替换文件中的所有占位符。

在这个方法中,我们定义占位符和它们的值。

类型定义了占位符的所有类型这是区分大小写和字符的,接下来每个类型都被循环遍历。如果类型的键是 module_,那么所有带有空格的名称将切换为下划线分隔,modules-也一样,将用连字符分隔空格。

最后,文件的内容将被替换为占位符的值模型内容的名称。

protected function replaceInFile($path)
{
    $name = $this->container['name'];
    $model = $this->container['model'];
    $types = [
        '{module_}' => null,
        '{module-}' => null,
        '{Module}' => $name,
        '{module}' => strtolower($name),
        '{Model}' => $model,
        '{model}' => strtolower($model)
    ];

    foreach($types as $key => $value) {
        if (file_exists($path)) {

            if ($key == "module_") {
                $parts = preg_split('/(?=[A-Z])/', $name, -1, PREG_SPLIT_NO_EMPTY);
                $parts = array_map('strtolower', $parts);
                $value = implode('_', $parts);
            }

            if ($key == 'module-') {
                $parts = preg_split('/(?=[A-Z])/', $name, -1, PREG_SPLIT_NO_EMPTY);
                $parts = array_map('strtolower', $parts);
                $value = implode('-', $parts);
            }

            file_put_contents($path, str_replace($key, $value, file_get_contents($path)));
        }
    }
}

现在我们有了这些辅助方法,我们可以创建 generate 方法。

首先,我们创建局部变量 name 和 model,以便于引用。$targetPath 变量存储了最终的模块路径。

接下来,我们需要将基础模块复制到 Modules 文件夹中。

这个新模块现在在 modules 文件夹中,包含所有占位符和临时文件名,现在我们需要列出所有需要检查内容的文件,以放置占位符。

最后一部分列出了所有需要重命名的文件名。

protected function generate()
{
    $module     = $this->container['name'];
    $model      = $this->container['model'];
    $Module     = $module;
    $module     = strtolower($module);
    $Model      = $model;
    $targetPath = base_path('Modules/'.$Module);

    //copy folders
    $this->copy(base_path('stubs/base-module'), $targetPath);

    //replace contents
    $this->replaceInFile($targetPath.'/Config/config.php');
    $this->replaceInFile($targetPath.'/Database/Factories/ModelFactory.php');
    $this->replaceInFile($targetPath.'/Database/Migrations/create_module_table.php');
    $this->replaceInFile($targetPath.'/Database/Seeders/ModelDatabaseSeeder.php');
    $this->replaceInFile($targetPath.'/Http/Controllers/ModuleController.php');
    $this->replaceInFile($targetPath.'/Models/Model.php');
    $this->replaceInFile($targetPath.'/Providers/ModuleServiceProvider.php');
    $this->replaceInFile($targetPath.'/Providers/RouteServiceProvider.php');
    $this->replaceInFile($targetPath.'/Resources/views/create.blade.php');
    $this->replaceInFile($targetPath.'/Resources/views/edit.blade.php');
    $this->replaceInFile($targetPath.'/Resources/views/index.blade.php');
    $this->replaceInFile($targetPath.'/Routes/api.php');
    $this->replaceInFile($targetPath.'/Routes/web.php');
    $this->replaceInFile($targetPath.'/Tests/Feature/ModuleTest.php');
    $this->replaceInFile($targetPath.'/composer.json');
    $this->replaceInFile($targetPath.'/module.json');
    $this->replaceInFile($targetPath.'/webpack.mix.js');

    //rename
    $this->rename($targetPath.'/Database/Factories/ModelFactory.php', $targetPath.'/Database/Factories/'.$Model.'Factory.php');
    $this->rename($targetPath.'/Database/migrations/create_module_table.php', $targetPath.'/Database/migrations/create_'.$module.'_table.php', 'migration');
    $this->rename($targetPath.'/Database/Seeders/ModelDatabaseSeeder.php', $targetPath.'/Database/Seeders/'.$Module.'DatabaseSeeder.php');
    $this->rename($targetPath.'/Http/Controllers/ModuleController.php', $targetPath.'/Http/Controllers/'.$Module.'Controller.php');
    $this->rename($targetPath.'/Models/Model.php', $targetPath.'/Models/'.$Model.'.php');
    $this->rename($targetPath.'/Providers/ModuleServiceProvider.php', $targetPath.'/Providers/'.$Module.'ServiceProvider.php');
    $this->rename($targetPath.'/Tests/Feature/ModuleTest.php', $targetPath.'/Tests/Feature/'.$Module.'Test.php');
}

综上所述:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;

class MakeModuleCommand extends Command
{
    protected $signature = 'make:module';
    protected $description = 'Create starter CRUD module';

    public function handle()
    {
        $this->container['name'] = ucwords($this->ask('Please enter the name of the Module'));

        if (strlen($this->container['name']) == 0) {
            $this->error("\nModule name cannot be empty.");
        } else {
            $this->container['model'] = ucwords(Str::singular($this->container['name']));

            if ($this->confirm("Is '{$this->container['model']}' the correct name for the Model?", 'yes')) {
                $this->comment('You have provided the following information:');
                $this->comment('Name:  ' . $this->container['name']);
                $this->comment('Model: ' . $this->container['model']);

                if ($this->confirm('Do you wish to continue?', 'yes')) {
                    $this->comment('Success!');
                    $this->generate();
                } else {
                    return false;
                }

                return true;
            } else {
                $this->handle();
            }
        }

        $this->info('Starter '.$this->container['name'].' module installed successfully.');
    }

    protected function generate()
    {
        $module     = $this->container['name'];
        $model      = $this->container['model'];
        $Module     = $module;
        $module     = strtolower($module);
        $Model      = $model;
        $targetPath = base_path('Modules/'.$Module);

        //copy folders
        $this->copy(base_path('stubs/base-module'), $targetPath);

        //replace contents
        $this->replaceInFile($targetPath.'/Config/config.php');
        $this->replaceInFile($targetPath.'/Database/Factories/ModelFactory.php');
        $this->replaceInFile($targetPath.'/Database/Migrations/create_module_table.php');
        $this->replaceInFile($targetPath.'/Database/Seeders/ModelDatabaseSeeder.php');
        $this->replaceInFile($targetPath.'/Http/Controllers/ModuleController.php');
        $this->replaceInFile($targetPath.'/Models/Model.php');
        $this->replaceInFile($targetPath.'/Providers/ModuleServiceProvider.php');
        $this->replaceInFile($targetPath.'/Providers/RouteServiceProvider.php');
        $this->replaceInFile($targetPath.'/Resources/views/create.blade.php');
        $this->replaceInFile($targetPath.'/Resources/views/edit.blade.php');
        $this->replaceInFile($targetPath.'/Resources/views/index.blade.php');
        $this->replaceInFile($targetPath.'/Routes/api.php');
        $this->replaceInFile($targetPath.'/Routes/web.php');
        $this->replaceInFile($targetPath.'/Tests/Feature/ModuleTest.php');
        $this->replaceInFile($targetPath.'/composer.json');
        $this->replaceInFile($targetPath.'/module.json');
        $this->replaceInFile($targetPath.'/webpack.mix.js');

        //rename
        $this->rename($targetPath.'/Database/Factories/ModelFactory.php', $targetPath.'/Database/Factories/'.$Model.'Factory.php');
        $this->rename($targetPath.'/Database/migrations/create_module_table.php', $targetPath.'/Database/migrations/create_'.$module.'_table.php', 'migration');
        $this->rename($targetPath.'/Database/Seeders/ModelDatabaseSeeder.php', $targetPath.'/Database/Seeders/'.$Module.'DatabaseSeeder.php');
        $this->rename($targetPath.'/Http/Controllers/ModuleController.php', $targetPath.'/Http/Controllers/'.$Module.'Controller.php');
        $this->rename($targetPath.'/Models/Model.php', $targetPath.'/Models/'.$Model.'.php');
        $this->rename($targetPath.'/Providers/ModuleServiceProvider.php', $targetPath.'/Providers/'.$Module.'ServiceProvider.php');
        $this->rename($targetPath.'/Tests/Feature/ModuleTest.php', $targetPath.'/Tests/Feature/'.$Module.'Test.php');
    }

    protected function rename($path, $target, $type = null)
    {
        $filesystem = new SymfonyFilesystem;
        if ($filesystem->exists($path)) {
            if ($type == 'migration') {
                $timestamp = date('Y_m_d_his_');
                $target = str_replace("create", $timestamp."create", $target);
                $filesystem->rename($path, $target, true);
                $this->replaceInFile($target);
            } else {
                $filesystem->rename($path, $target, true);
            }
        }
    }

    protected function copy($path, $target)
    {
        $filesystem = new SymfonyFilesystem;
        if ($filesystem->exists($path)) {
            $filesystem->mirror($path, $target);
        }
    }

    protected function replaceInFile($path)
    {
        $name = $this->container['name'];
        $model = $this->container['model'];
        $types = [
            '{module_}' => null,
            '{module-}' => null,
            '{Module}' => $name,
            '{module}' => strtolower($name),
            '{Model}' => $model,
            '{model}' => strtolower($model)
        ];

        foreach($types as $key => $value) {
            if (file_exists($path)) {

                if ($key == "module_") {
                    $parts = preg_split('/(?=[A-Z])/', $name, -1, PREG_SPLIT_NO_EMPTY);
                    $parts = array_map('strtolower', $parts);
                    $value = implode('_', $parts);
                }

                if ($key == 'module-') {
                    $parts = preg_split('/(?=[A-Z])/', $name, -1, PREG_SPLIT_NO_EMPTY);
                    $parts = array_map('strtolower', $parts);
                    $value = implode('-', $parts);
                }

                file_put_contents($path, str_replace($key, $value, file_get_contents($path)));
            }
        }
    }
}

这看起来工作量很大,但这让你可以完全控制生成模块时包含的内容。对于简单的CRUD模块,您可以使用这种方法以令人难以置信的速度构建应用程序。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
Slowlyo
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
发起讨论 只看当前版本


暂无话题~