Files
livewire-material/src/LivewireMaterialServiceProvider.php
T
Andreas Reinhold / reiniandClaude Opus 5 db179e6b4e
tests / lint (push) Successful in 1m6s
tests / feature (8.4) (push) Successful in 1m18s
tests / feature (8.5) (push) Successful in 1m17s
tests / browser (chrome, chromium) (push) Successful in 3m27s
tests / browser (firefox, firefox) (push) Successful in 4m57s
tests / browser (safari, webkit) (push) Successful in 5m29s
Compile the showcase's views with the showcase off
php artisan view:cache compiles every view in the package's namespace,
the showcase's pages included, but the showcase:: component prefix was
only registered while the showcase was enabled. In production, where it
is off, view:cache failed with "Unable to locate a class or view for
component [showcase::example]" and an application caching its views on
start would not boot. The prefix is now always registered; the routes
still are not.

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

196 lines
7.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\Facades\View;
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, or
* under `livewire-material` without one.
*
* A compiled `<x-button>` names the view namespace Blade derives from the prefix, or from the
* component folder's absolute path when there is none. That path differs between a laptop and
* a container sharing `storage/framework/views`, or between release directories, while the
* compiled file's name does not — so a view compiled on one showed `a1b2…::button` as text on
* the other. A prefix does not stop an unprefixed tag resolving. The path's namespace stays
* registered, so views compiled before this release still render until they are recompiled.
*
* 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') : 'livewire-material',
);
View::addNamespace(hash('xxh128', static::componentPath()), static::componentPath());
}
/**
* 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
{
// Registered even with the showcase off: `php artisan view:cache` compiles every view in the
// package's namespace, the showcase's pages included, and <x-showcase::…> must resolve there.
Blade::anonymousComponentPath(__DIR__.'/../resources/views/showcase/components', 'showcase');
if (! config('livewire-material.showcase.enabled')) {
return;
}
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');
}
}