如何在 Laravel 中添加额外的视图组件 class 路径?
How can I add extra view component class paths in Laravel?
问题
视图组件的默认命名空间是 App\View\Components
,文件夹是 app/View/Components
。我正在设置 DDD 文件结构并希望做两件事:
- 将“共享”视图组件分别移动到
App\ViewComponents
和 src/app/ViewComponents
的命名空间和文件夹中
- 具有特定于各个“应用程序”的视图组件,它们分别具有自己的
App\MyApplication\ViewComponents
和 /src/app/MyApplication/ViewComponets
命名空间和文件夹。
新的 App
namespace/folder 设置是通过 composer psr-4 自动加载密钥完成的,并且工作正常。但是 Laravel 在尝试加载组件时总是使用 App\View\Components
命名空间。
我的尝试
我已经解决了问题的第一部分,但我希望有更好的方法。例如,当我想移动视图时,我可以只在我的 AppServiceProvider
中设置 view.paths
配置指令,但我没有看到类似的方式,本质上,将名称空间添加到 Laravel 的位置寻找视图组件。所以我最后做的是:
- 创建一个
ViewServiceProvider
class,扩展 Illuminate\View\ViewServiceProvider::class
并在 bootstrap/app.php
中指向它
- 在那里,覆盖
registerBladeEngine
方法,在那里指向我自己的 BladeCompiler
class 而不是内置的
public function registerBladeEngine($resolver)
{
// The Compiler engine requires an instance of the CompilerInterface, which in
// this case will be the Blade compiler, so we'll first create the compiler
// instance to pass into the engine so it can compile the views properly.
$this->app->singleton('blade.compiler', function () {
return new BladeCompiler(
$this->app['files'],
$this->app['config']['view.compiled'],
);
});
$resolver->register('blade', function () {
return new CompilerEngine(
$this->app['blade.compiler']
);
});
}
- 在我自己的
BladeCompiler
class 中,它扩展了 Illuminate\View\Compilers\BladeCompiler
,覆盖了 component()
和 compileComponentTags()
方法 - 基本上任何引用 [=32= 的地方] - 几乎是抄本,而是使用 ViewComponents
并且还确保它们 return 和 Illuminate\View\Compilers\ComponentTagCompiler
的地方我引用了我自己的 ComponentTagCompiler
- 在我自己的
TagCompiler
中,我覆盖了 guessClassName()
方法,本质上也是一个副本,只是将 View\Components
重命名为 ViewComponents
如您所见,仅更改路径就需要大量工作。而且我还想添加 另一个 路径。多个“应用程序”运行 在同一个 Laravel 代码库下,因此例如我们可能有 App\Website\
、App\Admin
和 App\Blog
,这取决于当前是哪个应用程序运行ning,为 运行ning 应用加载不同的命名空间,即博客将 App\Blog\ViewComponents
指向 src/app/Blog/ViewComponents
。
有没有一种方法可以实现这一点,而无需像上面那样重写?如果没有,你能建议一种方法来实现要求的第二部分吗?
注意:我还没有排除使用子文件夹并继续主 App\View\Components
命名空间下的所有内容的可能性 - 我不想打架 Laravel 如果没有更好的方法,我也愿意承认,但如果我能实现我想要的文件夹结构,它会感觉更整洁。
更新;通过使用配置和 php 8 个注释
获得了一个有效的实现
根据您的问题和提供的详细信息,按照以下步骤可以为 blade 查看组件功能添加更多查找文件夹。发布您已有的代码会有所帮助。但是我已经添加了一个可能的解决方案来让它工作,使用注释并使用带有 namespace/path 映射的配置。
根据您在一个应用程序和另一个应用程序之间切换的方式,您的问题中未提供详细信息,您必须修改 MyComponentTagCompiler
class 中检索配置的方式.
Blade编译器
为了改变 ComponentTagCompiler
我们需要改变 BladeCompiler
class:
namespace App;
class YourBladeCompiler extends \Illuminate\View\Compilers\BladeCompiler
{
protected function compileComponentTags($value)
{
if (! $this->compilesComponentTags) {
return $value;
}
return (new \App\MyComponentTagCompiler( //it is about this line
$this->classComponentAliases, $this->classComponentNamespaces, $this
))->compile($value);
}
}
服务提供商
现在在YourViewServiceProvider
中注册YourBladeCompiler
:
class YourViewServiceProvider extends \Illuminate\View\ViewServiceProvider
{
public function registerBladeEngine($resolver)
{
$this->app->singleton('blade.compiler', function () {
return new \App\YourBladeCompiler( //it is about this line
$this->app['files'],
$this->app['config']['view.compiled'],
);
});
$resolver->register('blade', function () {
return new CompilerEngine(
$this->app['blade.compiler']
);
});
}
}
MyComponentTagCompiler
这是我创建的与 PHP 8 个属性一起使用的实现,如下所示:
namespace App;
#[\Attribute]
class ViewComponentName
{
public string $name;
public string $package;
public function __construct(string $name, string $package)
{
$this->name = $name;
$this->package = $package;
}
}
通过该属性,您可以在视图组件上声明包名和组件名class(见底部示例)。因此,在查找过程中,可以根据这些参数匹配组件。
但如果需要,您可以将其更改为您自己的要求。
它的作用:
- 首先让我们Laravel在
parent::componentClass
方法中通过它自己的机制查找视图组件。
- 如果未找到组件并抛出异常 (
InvalidArgumentException
),之后我的实现将遍历给定的路径和名称空间(来自 getLookupPaths
方法)并查看是否有属性匹配组件名称和包名称。如果是这样,它 returns 这个 class 和视图组件相应地加载。
namespace App;
use App\View\ViewComponentName;
use Illuminate\View\Compilers\ComponentTagCompiler;
class MyComponentTagCompiler extends ComponentTagCompiler
{
protected function getLookupPaths() : array
{
/*
* add some logic here to get an application specific configuration
* since you have multiple application in one, I cannot know it works in your
* application, since the details are not provided in the question
*/
return config('view_component_paths');
}
private function getFiles(string $dir) : array
{
return scandir($dir);
}
private function isPhpFile(string $file) : bool
{
return strpos($file, ".php");
}
private function getClassNamespace(string $file, string $folderNamespace) : string
{
$class = str_replace(".php", "", $file);
$classNamespace = $folderNamespace . "\" . $class;
return $classNamespace;
}
private function getComponentName(string $file, string $namespace) : ?ViewComponentName
{
$classNamespace = $this->getClassNamespace($file, $namespace);
$reflection = new \ReflectionClass($classNamespace);
if(method_exists($reflection, 'getAttributes')) {
$attribute = $reflection->getAttributes()[0];
if ($attribute->getName() == ViewComponentName::class) {
return $attribute->newInstance();
}
}
return null;
}
public function componentClass(string $component)
{
try {
parent::componentClass($component);
} catch(\InvalidArgumentException $e) {
list($lookupComponentPackage, $lookupComponentName) = explode("-", $component);
foreach($this->getLookupPaths() as $namespace=>$dir) {
foreach ($this->getFiles($dir) as $file) {
if ($this->isPhpFile($file)) {
if($componentName = $this->getComponentName($file, $namespace)) {
if($componentName->name == $lookupComponentName && $componentName->package == $lookupComponentPackage) {
return $this->getClassNamespace($file, $namespace);
}
}
}
}
}
throw $e;
}
}
}
其中配置包含 (config/view_component_paths.php):
return [
"App\Test"=>__DIR__ . "/Test/"
];
如果您希望完全替换默认的 laravel 行为或不喜欢我基于注释的实现,请考虑实现您自己的方法版本:
public function componentClass(string $component)
{
//return the class name here based the component name
//without calling parent
dd($component);
}
示例视图组件
namespace App\Test;
use App\View\ViewComponentName;
use Illuminate\View\Component;
#[ViewComponentName('test', 'namespace')]
class MyViewComponent extends Component
{
public function render()
{
return view('components.test');
}
}
在blade中:
<x-namespace-test />
它现在应该可以工作了。我认为这些信息足以让您了解如何在您自己的应用程序中实现它。似乎除了扩展一些基础 classes 之外别无他法。但是看看这个答案,可以创建一个基于全局查找配置和 php 注释的高级实现(或者你希望的一些其他机制,例如将带有命名空间的 class 名称转换为查看组件名称)。
旧答案
您问题中定义的问题 2
Have view components specific to individual "apps" with their own namespace and folder of App\MyApplication\ViewComponents and /src/app/MyApplication/ViewComponets respectively.
遗憾的是,似乎无法为 Laravel 中的视图组件定义多个 class 路径。但是您可以更改应用程序路径和命名空间前缀。据我所知,您只需覆盖 Application
class.
中的以下属性
bootstrap/app.php
替换以下行:
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
与:
class YourApplication extends \Illuminate\Foundation\Application
{
protected $namespace = "App\MyApplication";
protected $appPath = __DIR__ . "/../app/MyApplication";
}
$app = new YourApplication(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
这足以将应用程序文件夹更改为另一个文件夹,并让您了解如何动态更改它以在不同命名空间中拥有多个应用程序。如果您现在 运行 laravel 命令如 php artisan make:component Test1234
它会在您的新应用程序文件夹中创建:app/MyApplication/View/Components/Test1234.php
.
硬编码路径
像View/Components
这样的一些路径在Laravel中是硬编码的,因此不容易改变。如果您按照上面的定义进行更改,在这种情况下,视图组件命名空间变为:App\MyApplication\View\Components
和路径:app/MyApplication/View/Components
.
您问题中定义的问题 1
Move "shared" view components to a namespace and folder of App\ViewComponents and src/app/ViewComponents respectively
当您如上所述更改应用程序路径时,不可能有一个“共享的”View Component 文件夹。 Laravel,看起来只有一个默认的 View Components 路径,它基于硬编码路径和动态命名空间前缀,如上所述。但是您当然可以创建共享命名空间并手动注册视图组件:
查看组件(app/ViewComponents/文件夹)
namespace App\ViewComponents;
use Illuminate\View\Component;
class Test extends Component
{
public function render()
{
return view('components.test');
}
}
不要忘记 components.test blade 视图。
服务提供商
\Blade::component("shared-test",\App\ViewComponents\Test::class);
Blade
<x-shared-test />
问题
视图组件的默认命名空间是 App\View\Components
,文件夹是 app/View/Components
。我正在设置 DDD 文件结构并希望做两件事:
- 将“共享”视图组件分别移动到
App\ViewComponents
和src/app/ViewComponents
的命名空间和文件夹中 - 具有特定于各个“应用程序”的视图组件,它们分别具有自己的
App\MyApplication\ViewComponents
和/src/app/MyApplication/ViewComponets
命名空间和文件夹。
新的 App
namespace/folder 设置是通过 composer psr-4 自动加载密钥完成的,并且工作正常。但是 Laravel 在尝试加载组件时总是使用 App\View\Components
命名空间。
我的尝试
我已经解决了问题的第一部分,但我希望有更好的方法。例如,当我想移动视图时,我可以只在我的 AppServiceProvider
中设置 view.paths
配置指令,但我没有看到类似的方式,本质上,将名称空间添加到 Laravel 的位置寻找视图组件。所以我最后做的是:
- 创建一个
ViewServiceProvider
class,扩展Illuminate\View\ViewServiceProvider::class
并在bootstrap/app.php
中指向它 - 在那里,覆盖
registerBladeEngine
方法,在那里指向我自己的BladeCompiler
class 而不是内置的
public function registerBladeEngine($resolver)
{
// The Compiler engine requires an instance of the CompilerInterface, which in
// this case will be the Blade compiler, so we'll first create the compiler
// instance to pass into the engine so it can compile the views properly.
$this->app->singleton('blade.compiler', function () {
return new BladeCompiler(
$this->app['files'],
$this->app['config']['view.compiled'],
);
});
$resolver->register('blade', function () {
return new CompilerEngine(
$this->app['blade.compiler']
);
});
}
- 在我自己的
BladeCompiler
class 中,它扩展了Illuminate\View\Compilers\BladeCompiler
,覆盖了component()
和compileComponentTags()
方法 - 基本上任何引用 [=32= 的地方] - 几乎是抄本,而是使用ViewComponents
并且还确保它们 return 和Illuminate\View\Compilers\ComponentTagCompiler
的地方我引用了我自己的ComponentTagCompiler
- 在我自己的
TagCompiler
中,我覆盖了guessClassName()
方法,本质上也是一个副本,只是将View\Components
重命名为ViewComponents
如您所见,仅更改路径就需要大量工作。而且我还想添加 另一个 路径。多个“应用程序”运行 在同一个 Laravel 代码库下,因此例如我们可能有 App\Website\
、App\Admin
和 App\Blog
,这取决于当前是哪个应用程序运行ning,为 运行ning 应用加载不同的命名空间,即博客将 App\Blog\ViewComponents
指向 src/app/Blog/ViewComponents
。
有没有一种方法可以实现这一点,而无需像上面那样重写?如果没有,你能建议一种方法来实现要求的第二部分吗?
注意:我还没有排除使用子文件夹并继续主 App\View\Components
命名空间下的所有内容的可能性 - 我不想打架 Laravel 如果没有更好的方法,我也愿意承认,但如果我能实现我想要的文件夹结构,它会感觉更整洁。
更新;通过使用配置和 php 8 个注释
获得了一个有效的实现根据您的问题和提供的详细信息,按照以下步骤可以为 blade 查看组件功能添加更多查找文件夹。发布您已有的代码会有所帮助。但是我已经添加了一个可能的解决方案来让它工作,使用注释并使用带有 namespace/path 映射的配置。
根据您在一个应用程序和另一个应用程序之间切换的方式,您的问题中未提供详细信息,您必须修改 MyComponentTagCompiler
class 中检索配置的方式.
Blade编译器
为了改变 ComponentTagCompiler
我们需要改变 BladeCompiler
class:
namespace App;
class YourBladeCompiler extends \Illuminate\View\Compilers\BladeCompiler
{
protected function compileComponentTags($value)
{
if (! $this->compilesComponentTags) {
return $value;
}
return (new \App\MyComponentTagCompiler( //it is about this line
$this->classComponentAliases, $this->classComponentNamespaces, $this
))->compile($value);
}
}
服务提供商
现在在YourViewServiceProvider
中注册YourBladeCompiler
:
class YourViewServiceProvider extends \Illuminate\View\ViewServiceProvider
{
public function registerBladeEngine($resolver)
{
$this->app->singleton('blade.compiler', function () {
return new \App\YourBladeCompiler( //it is about this line
$this->app['files'],
$this->app['config']['view.compiled'],
);
});
$resolver->register('blade', function () {
return new CompilerEngine(
$this->app['blade.compiler']
);
});
}
}
MyComponentTagCompiler
这是我创建的与 PHP 8 个属性一起使用的实现,如下所示:
namespace App;
#[\Attribute]
class ViewComponentName
{
public string $name;
public string $package;
public function __construct(string $name, string $package)
{
$this->name = $name;
$this->package = $package;
}
}
通过该属性,您可以在视图组件上声明包名和组件名class(见底部示例)。因此,在查找过程中,可以根据这些参数匹配组件。
但如果需要,您可以将其更改为您自己的要求。
它的作用:
- 首先让我们Laravel在
parent::componentClass
方法中通过它自己的机制查找视图组件。 - 如果未找到组件并抛出异常 (
InvalidArgumentException
),之后我的实现将遍历给定的路径和名称空间(来自getLookupPaths
方法)并查看是否有属性匹配组件名称和包名称。如果是这样,它 returns 这个 class 和视图组件相应地加载。
namespace App;
use App\View\ViewComponentName;
use Illuminate\View\Compilers\ComponentTagCompiler;
class MyComponentTagCompiler extends ComponentTagCompiler
{
protected function getLookupPaths() : array
{
/*
* add some logic here to get an application specific configuration
* since you have multiple application in one, I cannot know it works in your
* application, since the details are not provided in the question
*/
return config('view_component_paths');
}
private function getFiles(string $dir) : array
{
return scandir($dir);
}
private function isPhpFile(string $file) : bool
{
return strpos($file, ".php");
}
private function getClassNamespace(string $file, string $folderNamespace) : string
{
$class = str_replace(".php", "", $file);
$classNamespace = $folderNamespace . "\" . $class;
return $classNamespace;
}
private function getComponentName(string $file, string $namespace) : ?ViewComponentName
{
$classNamespace = $this->getClassNamespace($file, $namespace);
$reflection = new \ReflectionClass($classNamespace);
if(method_exists($reflection, 'getAttributes')) {
$attribute = $reflection->getAttributes()[0];
if ($attribute->getName() == ViewComponentName::class) {
return $attribute->newInstance();
}
}
return null;
}
public function componentClass(string $component)
{
try {
parent::componentClass($component);
} catch(\InvalidArgumentException $e) {
list($lookupComponentPackage, $lookupComponentName) = explode("-", $component);
foreach($this->getLookupPaths() as $namespace=>$dir) {
foreach ($this->getFiles($dir) as $file) {
if ($this->isPhpFile($file)) {
if($componentName = $this->getComponentName($file, $namespace)) {
if($componentName->name == $lookupComponentName && $componentName->package == $lookupComponentPackage) {
return $this->getClassNamespace($file, $namespace);
}
}
}
}
}
throw $e;
}
}
}
其中配置包含 (config/view_component_paths.php):
return [
"App\Test"=>__DIR__ . "/Test/"
];
如果您希望完全替换默认的 laravel 行为或不喜欢我基于注释的实现,请考虑实现您自己的方法版本:
public function componentClass(string $component)
{
//return the class name here based the component name
//without calling parent
dd($component);
}
示例视图组件
namespace App\Test;
use App\View\ViewComponentName;
use Illuminate\View\Component;
#[ViewComponentName('test', 'namespace')]
class MyViewComponent extends Component
{
public function render()
{
return view('components.test');
}
}
在blade中:
<x-namespace-test />
它现在应该可以工作了。我认为这些信息足以让您了解如何在您自己的应用程序中实现它。似乎除了扩展一些基础 classes 之外别无他法。但是看看这个答案,可以创建一个基于全局查找配置和 php 注释的高级实现(或者你希望的一些其他机制,例如将带有命名空间的 class 名称转换为查看组件名称)。
旧答案
您问题中定义的问题 2
Have view components specific to individual "apps" with their own namespace and folder of App\MyApplication\ViewComponents and /src/app/MyApplication/ViewComponets respectively.
遗憾的是,似乎无法为 Laravel 中的视图组件定义多个 class 路径。但是您可以更改应用程序路径和命名空间前缀。据我所知,您只需覆盖 Application
class.
bootstrap/app.php
替换以下行:
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
与:
class YourApplication extends \Illuminate\Foundation\Application
{
protected $namespace = "App\MyApplication";
protected $appPath = __DIR__ . "/../app/MyApplication";
}
$app = new YourApplication(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
这足以将应用程序文件夹更改为另一个文件夹,并让您了解如何动态更改它以在不同命名空间中拥有多个应用程序。如果您现在 运行 laravel 命令如 php artisan make:component Test1234
它会在您的新应用程序文件夹中创建:app/MyApplication/View/Components/Test1234.php
.
硬编码路径
像View/Components
这样的一些路径在Laravel中是硬编码的,因此不容易改变。如果您按照上面的定义进行更改,在这种情况下,视图组件命名空间变为:App\MyApplication\View\Components
和路径:app/MyApplication/View/Components
.
您问题中定义的问题 1
Move "shared" view components to a namespace and folder of App\ViewComponents and src/app/ViewComponents respectively
当您如上所述更改应用程序路径时,不可能有一个“共享的”View Component 文件夹。 Laravel,看起来只有一个默认的 View Components 路径,它基于硬编码路径和动态命名空间前缀,如上所述。但是您当然可以创建共享命名空间并手动注册视图组件:
查看组件(app/ViewComponents/文件夹)
namespace App\ViewComponents;
use Illuminate\View\Component;
class Test extends Component
{
public function render()
{
return view('components.test');
}
}
不要忘记 components.test blade 视图。
服务提供商
\Blade::component("shared-test",\App\ViewComponents\Test::class);
Blade
<x-shared-test />