自定义模块生成器
构建你自己的模块生成器
从默认模块开始就可以了,但通常你会有自己的模块结构,可以在模块之间使用。能够使用你的结构作为模板,以便将来创建所有模块,这是很好的。
这里有两个选择。编辑存根文件或创建自己的基本模块和自定义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->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模块,您可以使用这种方法以令人难以置信的速度构建应用程序。