问题
视图组件的默认命名空间AppViewComponents
文件夹app/View/Components
。我正在设置DDD文件结构,并希望做两件事:
- 将"共享"视图组件分别移动到
AppViewComponents
和src/app/ViewComponents
的命名空间和文件夹
具有 - 特定于各个"应用程序"的视图组件,这些组件分别具有自己的命名空间以及
AppMyApplicationViewComponents
和/src/app/MyApplication/ViewComponets
文件夹。
新的App
命名空间/文件夹设置是通过作曲家 psr-4 自动加载键完成的,并且工作正常。但是Laravel在尝试加载组件时总是使用AppViewComponents
命名空间。
我的尝试
我已经解决了我问题的第一部分,但我希望有更好的方法。例如,当我想移动视图时,我可以在我的AppServiceProvider
中设置view.paths
config 指令,但我没有看到类似的方式,本质上,将命名空间添加到 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代码库下运行,因此例如,我们可能有AppWebsite
,AppAdmin
和AppBlog
,并且根据当前正在运行的应用程序,为正在运行的应用程序加载不同的命名空间,即博客将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 />