欢迎来到利用自定义命令在特定目录中有效生成文件的简明指南。这个自定义命令是为了针对特定的领域进行工作,使得生成基于领域的文件变得更加容易。让我们直接进入领域感知 Artisan 命令的世界!
介绍
采取这种方法的理由是,我需要类似领域驱动设计的东西,但又不希望陷入维护这种架构的麻烦。我想保护升级 Laravel 版本的能力、维护的便利性和快速让团队成员上手。
然后我想起了我曾经使用过一个 Spatie 包(我们大家都这么做),具体是多租户包,它可以运行针对每个租户的 Artisan 命令,所以我想到可以很好地使用他们的一部分方法来调整我想要的 artisan 文件生成的位置。
命令类
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use function Laravel\Prompts\select;
use Illuminate\Support\Facades\Artisan;
class DomainArtisanCommand extends Command
{
public const DOMAINS = [
self::APP_DOMAIN => [],
self::TENANT_DOMAIN => [
'api',
'dashboard',
],
self::STAFF_DOMAIN => [],
self::CLIENT_DOMAIN => [],
];
public const APP_DOMAIN = 'app';
public const CLIENT_DOMAIN = 'client';
public const STAFF_DOMAIN = 'staff';
public const TENANT_DOMAIN = 'tenant';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:artisan
{artisanCommand : The Artisan command to run within "quotes"}
{domain? : The domain to run the command for (optional)}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run Artisan commands within a specific domain context.';
/**
* Execute the console command.
*
* @return int
*/
public function handle() : int
{
$domain = $this->retrieveDomain();
$artisanCommand = $this->argument('artisanCommand') ?: $this->askArtisanCommand($domain);
if ($this->isDomainSpecificCommand($artisanCommand) &&
$domain !== self::APP_DOMAIN
) {
$artisanCommand = $this->prefixCommandWithPath($artisanCommand, $domain);
}
info("Running artisan command: $artisanCommand");
Artisan::call($artisanCommand, [], $this->getOutput());
info('Artisan command completed successfully!');
return 0;
}
protected function isDomainSpecificCommand($artisanCommand) : bool
{
return in_array(
explode(' ', $artisanCommand)[0],
$this->getCommandsThatGenerateFilesSpecificToDomain()
);
}
protected function prefixCommandWithPath($artisanCommand, $domain) : string
{
$module = $this->retrieveModule($domain);
$modulePathSegment = $module ? Str::studly($module) . '/' : '';
$commandName = explode(' ', $artisanCommand)[0];
$fileFromCommand = explode(' ', $artisanCommand)[1] ?? '';
$path = $this->getDomainPath($domain) . '/' . $modulePathSegment . $this->getPathByArtisanCommand($commandName);
return "{$commandName} {$path}/{$fileFromCommand}";
}
protected function getDomainPath($domain) : string
{
return strtolower($domain) === self::APP_DOMAIN ?
"App" :
"App/Domains/{$domain}";
}
private function retrieveModule(string $domain): string
{
$modules = $this->getModulesPerDomain($domain);
if (empty($modules)) {
return '';
}
return select(
"Select a module to run this command for within the {$domain} domain:",
$modules,
0
);
}
private function getModulesPerDomain(string $domain): array
{
return self::DOMAINS[strtolower($domain)];
}
protected function askArtisanCommand($domain)
{
return $this->ask("Which artisan command do you want to run for the {$domain} Domain?");
}
public function getCommandsThatGenerateFilesSpecificToDomain() : array
{
return [
'make:controller',
'make:command',
'make:event',
'make:exception',
'make:job',
'make:listener',
'make:mail',
'make:middleware',
'make:model',
'make:notification',
'make:observer',
'make:policy',
'make:provider',
'make:request',
'make:resource',
'make:rule',
];
}
private function retrieveDomain(): string
{
if (!$this->argument('domain') || !in_array(
$this->argument('domain'),
array_keys($this->getDomainsWithPath())
)) {
$domain = select(
'Select a domain to run this command for:',
array_keys($this->getDomainsWithPath()),
0,
);
} else {
$domain = $this->argument('domain');
}
return Str::studly(Str::camel($domain));
}
public function getDomainsWithPath(): array
{
return [
'client' => app_path('Domains/Client'),
'staff' => app_path('Domains/Staff'),
'tenant' => app_path('Domains/Tenant'),
];
}
public function getPathByArtisanCommand($command): string
{
return match ($command) {
'make:controller' => 'Http/Controllers',
'make:command' => 'Console/Commands',
'make:event' => 'Events',
'make:exception' => 'Exceptions',
'make:job' => 'Jobs',
'make:listener' => 'Listeners',
'make:mail' => 'Mail',
'make:middleware' => 'Http/Middleware',
'make:model' => 'Models',
'make:notification' => 'Notifications',
'make:observer' => 'Observers',
'make:policy' => 'Policies',
'make:provider' => 'Providers',
'make:request' => 'Http/Requests',
'make:resource' => 'Http/Resources',
'make:rule' => 'Rules',
default => app_path(),
};
}
}
理解命令
领域感知 Artisan 命令无缝集成到 Laravel 的默认 Artisan 命令中,同时满足我们独特的项目结构。其主要功能是在指定的领域(App、Client、Staff、Tenant 等)内生成文件,确保纷争的清楚分离,同时又尽可能不偏离 Laravel 的常规。
基本语法
php artisan app:domain {artisanCommand} {domain?}
参数
- {artisanCommand}:用引号括起来所需的 Laravel Artisan 命令。
- 示例
- "make:controller SomeController"
- "make:observer Somewhere/SomeObserver"
- 示例
- {域?}(可选):指定您要针对的域:app、client、staff或tenant。如果您跳过此操作,命令将提示您进行交互式选择。
示例
在Staff域内创建控制器
php artisan app:domain "make:controller ClientController" staff
在Tenant域内生成请求
php artisan app:domain "make:request CompanyRequest" tenant
幕后
当运行app:domain
命令时,它执行以下任务:
域确定
如果您没有提供域,它将提示您选择一个。目前,我们有3个已确认的域:App(是标准App命名空间,所以我们不计入)、Client、Staff和Tenant。
路径解析
它根据提供的域和Artisan命令的性质确定正确的路径。例如,如果您在Staff域内生成控制器,它将解析为app/Domains/Staff/Http/Controllers
。
Artisan委派
然后它将命令委派给Laravel的Artisan,确保文件在正确的位置生成,保持我们的域结构完整。
公平起见,“App”域不应包含在此命令中,因为您可以使用Artisan来完成此操作,但我添加它只是为了预防可能的需要。
每个域的模块
app:domain
命令旨在与所有域一起使用。然而,一些域有模块,而另一些则没有。
例如
tenant域有模块,因此您可以在特定模块内生成文件
- API
- 仪表板
所以如果您想在API模块中生成控制器,您可以通过选择要生成文件的模块来完成。
Select a module to run this command for within the {$domain} domain:
[0] api
[1] dashboard
我们正在使用Jess Archer的出色软件包Laravel Prompts来为我们的命令提供良好的格式。
这将在app/Domains/Tenant/Api/Http/Controllers
目录中生成控制器。
您可以通过在DomainArtisanCommand类中的DOMAINS常量数组中添加小写模块名称将模块添加到域中(这可能需要更好的处理,只是需要一个快速解决模块问题的方法)。
const DOMAINS = [
self::APP_DOMAIN => [],
self::TENANT_DOMAIN => [
'api',
'dashboard',
],
self::STAFF_DOMAIN => [],
self::CLIENT_DOMAIN => [],
];
提示
- 始终引用您的命令:为了避免潜在的问题,始终用引号包住实际的Laravel Artisan命令。
- 了解您的域:熟悉您包含的域。这有助于快速确定生成的文件应该位于哪里。
总结
这就是全部!artisan:domain命令是为了使我们的模块化方法更加流畅。所以下次您需要在一个特定的域中快速创建新的控制器、请求或其他文件时,试试artisan:domain。
让我们保持代码库整洁高效。
driesvints, mlambertz喜欢这篇文章