Files
livewire-material/src/LivewireMaterialServiceProvider.php
T
Andreas Reinhold / reiniandClaude Opus 5 1e580c573e Resolve the package's own components through its namespace
Package views now write <x-livewire-material::button>, so a configured
prefix (which Blade spells <x-m::button>) and an application component
of the same name can no longer break or shadow them. The showcase
rewrites its examples to the configured prefix. Adds the README, and
waits for the adaptive rail to hear a resize before the navigation
tests press its menu button.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
2026-09-13 09:05:24 +02:00

183 lines
6.7 KiB
PHP

<?php
namespace NoNameWeb\LivewireMaterial;
use Illuminate\Contracts\View\Factory;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
class LivewireMaterialServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->mergeConfigFrom(__DIR__.'/../config/livewire-material.php', 'livewire-material');
$this->disableBladeIconsComponent();
$this->registerErrorViews();
$this->registerMailComponents();
}
public function boot(): void
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'livewire-material');
$this->loadTranslationsFrom(__DIR__.'/../lang', 'livewire-material');
$this->registerComponents();
$this->registerPagination();
$this->registerShowcase();
if ($this->app->runningInConsole()) {
$this->commands([
Console\SchemeCommand::class,
]);
$this->publishes([
__DIR__.'/../config/livewire-material.php' => config_path('livewire-material.php'),
], 'livewire-material-config');
$this->publishes([
static::errorViewPath().'/errors' => resource_path('views/errors'),
], 'livewire-material-errors');
$this->publishes([
static::mailComponentPath().'/html' => resource_path('views/vendor/mail/html'),
static::mailComponentPath().'/text' => resource_path('views/vendor/mail/text'),
], 'livewire-material-mail');
}
}
/**
* The anonymous components. Blade names their view namespace after a hash of this string, and
* compiled views keep that name, so it stays written the way it always was.
*/
public static function componentPath(): string
{
return __DIR__.'/../resources/views/components';
}
/**
* The error-view root: a folder holding only `errors/`.
*/
public static function errorViewPath(): string
{
return dirname(__DIR__).'/resources/views/error-pages';
}
/**
* The mail component root, laid out like the framework's: `html/` and `text/`.
*/
public static function mailComponentPath(): string
{
return dirname(__DIR__).'/resources/views/mail';
}
/**
* Append the error-view root to `view.paths`, after the application's own.
*
* The exception handler replaces the `errors` namespace each time it renders an HTTP
* exception, with every entry of `view.paths` plus `/errors` and the framework's views last
* (RegisterErrorViewPaths), so a namespace added with addNamespace() is gone by then; only a
* view path survives. That config is read at render time, but also once when the view
* finder is built: appended here, in register(), before any boot() resolves the view
* factory, the finder and `view:cache` see the same paths the handler does. Appending keeps
* `view.paths[0]` — Laravel's viewPath() — the application's, and puts the application's
* own `resources/views/errors` first. Idempotent, since a cached config already holds it.
*/
protected function registerErrorViews(): void
{
$paths = (array) config('view.paths', []);
if (! in_array(static::errorViewPath(), $paths, true)) {
config(['view.paths' => [...$paths, static::errorViewPath()]]);
}
}
/**
* Put the package's mail header and message after the application's own mail components,
* when `livewire-material.mail.components` asks for it. Off by default: it changes every
* Markdown mail the application sends, theme or not, and the framework reads
* `mail.markdown.paths` once, when the Markdown renderer is first made — after register().
*/
protected function registerMailComponents(): void
{
if (! config('livewire-material.mail.components')) {
return;
}
$paths = (array) config('mail.markdown.paths', []);
if (! in_array(static::mailComponentPath(), $paths, true)) {
config(['mail.markdown.paths' => [...$paths, static::mailComponentPath()]]);
}
}
/**
* Register the components as anonymous Blade components under the configured prefix.
*
* The package's own views never go through this registration: they write
* `<x-livewire-material::button>`, which resolves through the view namespace whatever the
* prefix, and which an application's own `<x-button>` cannot shadow.
*/
protected function registerComponents(): void
{
Blade::anonymousComponentPath(
static::componentPath(),
filled(config('livewire-material.prefix')) ? config('livewire-material.prefix') : null,
);
}
/**
* Put the M3 paginators in front of Laravel's and Livewire's own. Prepended to their
* namespaces rather than set as the default view, because Livewire sets its own default on
* every render; an application's published `vendor/pagination` or `vendor/livewire` views are
* looked up before any namespace path, so they still win.
*/
protected function registerPagination(): void
{
if (! config('livewire-material.pagination')) {
return;
}
$this->callAfterResolving('view', function (Factory $view): void {
$view->prependNamespace('pagination', __DIR__.'/../resources/views/pagination/laravel');
$view->prependNamespace('livewire', __DIR__.'/../resources/views/pagination/livewire');
});
}
/**
* blade-icons registers a class-based <x-icon> of its own, and Blade resolves a
* registered class alias before any anonymous component path, so ours would never
* render. It reads the key in its boot(); a booting callback runs after every
* register() — so after blade-icons merges its defaults over the key — and before
* any provider boots.
*/
protected function disableBladeIconsComponent(): void
{
$this->app->booting(function (): void {
config(['blade-icons.components.default' => null]);
});
}
/**
* Mount the showcase when it is enabled.
*/
protected function registerShowcase(): void
{
if (! config('livewire-material.showcase.enabled')) {
return;
}
Blade::anonymousComponentPath(__DIR__.'/../resources/views/showcase/components', 'showcase');
if ($this->app->routesAreCached()) {
return;
}
Route::middleware(config('livewire-material.showcase.middleware'))
->prefix(config('livewire-material.showcase.path'))
->name('livewire-material.')
->group(__DIR__.'/../routes/showcase.php');
}
}