如何在 Laravel 中添加额外的视图组件类路径



问题

视图组件的默认命名空间AppViewComponents文件夹app/View/Components。我正在设置DDD文件结构,并希望做两件事:

  1. 将"共享"视图组件分别移动到AppViewComponentssrc/app/ViewComponents的命名空间和文件夹
  2. 具有
  3. 特定于各个"应用程序"的视图组件,这些组件分别具有自己的命名空间以及AppMyApplicationViewComponents/src/app/MyApplication/ViewComponets文件夹。

新的App命名空间/文件夹设置是通过作曲家 psr-4 自动加载键完成的,并且工作正常。但是Laravel在尝试加载组件时总是使用AppViewComponents命名空间。

我的尝试

我已经解决了我问题的第一部分,但我希望有更好的方法。例如,当我想移动视图时,我可以在我的AppServiceProvider中设置view.pathsconfig 指令,但我没有看到类似的方式,本质上,将命名空间添加到 Laravel 查找视图组件的位置。所以我最终做的是:

  • 创建一个ViewServiceProvider类,扩展IlluminateViewViewServiceProvider::class并在bootstrap/app.php中指向它
  • 在那里,覆盖registerBladeEngine方法,在那里指向我自己的BladeCompiler类而不是内置的类
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类中,它扩展了IlluminateViewCompilersBladeCompiler,覆盖了component()compileComponentTags()方法——基本上是引用View\Components的任何地方——几乎是抄本,而是使用ViewComponents并确保它们返回IlluminateViewCompilersComponentTagCompiler的地方我引用了我自己的ComponentTagCompiler
  • 按照我自己的TagCompiler,我覆盖了guessClassName()方法,同样基本上是抄送,只是将View\Components重命名为ViewComponents

如您所见,仅仅为了改变路径,就需要做很多工作。我还想添加另一条路径。多个"应用程序"在同一个Laravel代码库下运行,因此例如,我们可能有AppWebsiteAppAdminAppBlog,并且根据当前正在运行的应用程序,为正在运行的应用程序加载不同的命名空间,即博客将AppBlogViewComponents指向src/app/Blog/ViewComponents

有没有办法在不覆盖上述那么多的情况下实现这一目标?如果没有,您能建议一种方法来实现要求的第二部分吗?

注意:我还没有排除使用子文件夹并继续主AppViewComponents命名空间下的所有内容 - 我不想与 Laravel 战斗太多,如果没有更好的方法,我愿意承认,但如果我可以实现我想要的文件夹结构,它会感觉更整洁。

更新;通过使用配置和 php 8 注释获得有效的实现

按照以下步骤操作,以便根据你提供的问题和详细信息,为边栏选项卡视图组件功能添加更多查找文件夹。发布您已经拥有的代码会有所帮助。但是我添加了一个可能的解决方案来使其工作,使用注释并使用带有命名空间/路径映射的配置。

根据您在一个应用程序与另一个应用程序之间切换的方式(您的问题中未提供详细信息),您必须修改在MyComponentTagCompiler类中检索配置的方式。

刀片编译器

为了更改ComponentTagCompiler我们需要更改BladeCompiler类:

namespace App;
class YourBladeCompiler extends IlluminateViewCompilersBladeCompiler
{
protected function compileComponentTags($value)
{
if (! $this->compilesComponentTags) {
return $value;
}
return (new AppMyComponentTagCompiler( //it is about this line 
$this->classComponentAliases, $this->classComponentNamespaces, $this
))->compile($value);
}
}

服务提供商

现在在YourViewServiceProvider中注册YourBladeCompiler

class YourViewServiceProvider extends IlluminateViewViewServiceProvider
{
public function registerBladeEngine($resolver)
{
$this->app->singleton('blade.compiler', function () {
return new AppYourBladeCompiler( //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;
}
}

使用此属性,您可以在视图组件类上声明包名称和组件名称(请参阅底部的示例)。因此,在查找过程中,可以在这些参数上匹配组件。

但是,如果需要,您可以将其更改为自己的要求。

它的作用:

  • 首先,让我们 Laravel 在parent::componentClass方法中通过它自己的机制查找视图组件。
  • 如果未找到组件并抛出异常(InvalidArgumentException),则我的实现将遍历给定的路径和命名空间(来自getLookupPaths方法),并查看属性是否与组件名称和包名称匹配。如果是这样,它将返回此类并相应地加载视图组件。
namespace App;
use AppViewViewComponentName;
use IlluminateViewCompilersComponentTagCompiler;
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;
}
}
}

配置包含 (配置/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 AppTest;
use AppViewViewComponentName;
use IlluminateViewComponent;
#[ViewComponentName('test', 'namespace')]
class MyViewComponent extends Component
{
public function render()
{
return view('components.test');
}
}

在刀片中:

<x-namespace-test />

它现在应该可以工作了。我认为这些信息足以让您了解如何在自己的应用程序中实现这一点。除了扩展一些基类之外,似乎没有其他方法。但是看看这个答案,可以基于全局查找配置和 php 注释(或您希望的其他机制,例如将带有命名空间的类名转换为视图组件名称)创建高级实现。

旧答案

问题中定义的问题 2

具有

特定于单个"应用程序"的视图组件,它们分别具有自己的命名空间和文件夹App\MyApplication\ViewComponents和/src/app/MyApplication/ViewComponets。

可悲的是,似乎没有办法在 Laravel 中为视图组件定义多个类路径。但是,您可以更改应用程序路径和命名空间前缀。据我所知,您只需覆盖Application类中的以下属性。

引导程序/应用程序.php

替换以下行:

$app = new IlluminateFoundationApplication(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

跟:

class YourApplication extends IlluminateFoundationApplication
{
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 中是硬编码的,因此不容易更改。如果按照上述定义进行更改,在这种情况下,视图组件命名空间将变为:AppMyApplicationViewComponents,路径:app/MyApplication/View/Components

问题中定义的问题 1

将"共享"视图组件分别移动到 App\ViewComponents 和 src/app/ViewComponents 的命名空间和文件夹

如上所述更改应用程序路径时,不可能具有"共享"视图组件文件夹。Laravel似乎只有一个默认的View Components路径,该路径基于硬编码路径和动态命名空间前缀,如上所述。但是,您当然可以创建共享命名空间并手动注册视图组件:

视图组件

(应用程序/视图组件/文件夹)

namespace AppViewComponents;
use IlluminateViewComponent;
class Test extends Component
{
public function render()
{
return view('components.test');
}
}

不要忘记组件。测试边栏选项卡视图。

服务提供商

Blade::component("shared-test",AppViewComponentsTest::class);

叶片

<x-shared-test />

最新更新