Panels
Moduly
Modul je manifest jedné business oblasti — resources, dashboardy a nadpis v menu, ze kterých se skládá — registrovaný jako plugin a rozvezený do registrů, které je vlastní.
Na této stránce
Balíčky jsou technická osa tohohle frameworku: core, forms, table, sortable.
Modul je ta druhá — billing vedle operations vedle crm — a existuje
proto, aby business oblast byla deklarovaná na jednom místě místo rozsypaná
po provideru aplikace jako tři nesouvisející seznamy.
Modul nevlastní žádné primitivy a žádný neforkuje. Pojmenuje, z čeho se oblast skládá; vrstvy, které ty věci už vlastní, je vlastní dál.
Je to manifest, ne doménová vrstva. Tahle třída se dřív jmenovala
DomainModulea to slibovalo víc, než dělá: drží tři seznamy názvů tříd a nadpis do menu. Není to bounded context, není to hranice agregátu a není to místo, kde se cokoli modeluje — modul nemá žádné vlastní chování a nic tady neizoluje kód jedné oblasti od druhé. Když tohle aplikace chce, chce to ve vlastních namespacech a vlastních testech.
Jak to funguje
Modul je plugin, ne paralelní registrační systém. To je celé to designové rozhodnutí a je to ono, co drží životní cyklus poctivý:
- Registruje se jako každý jiný plugin — z
config('wire-core.plugins'), když ho deklaruje aplikace, nebo z vlastního service provideru balíčku, když ho dodává balíček — takže se modul instaluje stejnou cestou jako všechno ostatní. PluginManagermu dá záruky, které modul potřebuje a už je měl: jedno id na modul, všechny moduly zaregistrované dřív, než se kterýkoli bootne, a závislost, která musí být zaregistrovaná první, jinak se registrace odmítne.WireCoreServiceProviderpak přečte, co který modul deklaruje, a naplní registr resources, registr dashboardů a navigační skupiny.
Oba registry jsou zdroje jednoho Catalogu, takže se
resources a dashboardy modulu z téhle jediné deklarace dostanou do menu, do
routeru i do palety globálního hledání — včetně zón, které
si ze stejného katalogu vybírají podle klíče.
Krok 3 dělá provider, ne modul, a to schválně. Dashboard bydlí ve widgetové
vrstvě a kontrakt modulu, který by sáhl na DashboardRegistry, by byl import,
který architektonický test odmítá; pojmenovat třídu žádný import nestojí, takže
modul zůstává deklarací a zapojení dělá provider, který všechny registry stejně
už drží.
Registr modulů záměrně neexistuje: PluginManager ten seznam už drží
a druhý registr nad jedním seznamem je přesně ta duplicita, kterou tenhle
codebase pořád odstraňuje.
Jak se deklaruje
use NyonCode\WireCore\Core\Modules\Module; use NyonCode\WireCore\Core\Resources\Navigation\NavigationGroup; final class BillingModule extends Module{ public function getId(): string { return 'billing'; } public function resources(): array { return [InvoiceResource::class, CreditNoteResource::class]; } public function navigation(): ?NavigationGroup { return NavigationGroup::make('billing') ->label(__('nav.billing')) ->icon('outline:banknotes') ->sort(20); }}
// config/wire-core.php'plugins' => [ App\Modules\BillingModule::class, App\Modules\OperationsModule::class,],
Povinné je jen id, všechno ostatní je volitelné. Modul, který deklaruje jen resources, je běžný; stejně tak ten, který deklaruje jen dashboard.
Závislost na jiném modulu
dependencies() je z plugin systému beze změny — vyjmenuj id, která musí být
zaregistrovaná první:
use NyonCode\WireCore\Core\Plugin\Contracts\HasDependencies; final class OperationsModule extends Module implements HasDependencies{ public function getId(): string { return 'operations'; } public function dependencies(): array { return ['billing']; } public function dashboards(): array { return [OverviewDashboard::class]; }}
Registrace operations před billing vyhodí výjimku místo bootu do napůl
postavené aplikace — pořadí se kontroluje, ne doufá.
Modul jako balíček
Modul je plugin, takže balíček ho dodává stejně jako kterýkoli jiný plugin: jeho
vlastní service provider ho zaregistruje a aplikace balíček nainstaluje. Do
config/wire-core.php se nepřidává nic — balíček ten soubor upravit nemůže,
a nepotřebuje to.
use Illuminate\Support\ServiceProvider;use NyonCode\WireCore\Core\Plugin\PluginManager; final class BillingModuleServiceProvider extends ServiceProvider{ public function register(): void { // `resolving`, v register() — callback běží ve chvíli, kdy container // // staví manager, takže modul je v seznamu dřív než boot() a dřív, než // ho core provider rozprostře do registrů. $this->app->resolving(PluginManager::class, function (PluginManager $manager) { if (! $manager->has('billing')) { // idempotentní, i když ho aplikace uvádí taky $manager->register(new BillingModule); } }); } public function boot(): void { $this->loadViewsFrom(__DIR__.'/../resources/views', 'billing'); $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); }}
Registrace v boot() místo toho hodí výjimku — pravidlo o fázi a důvod, proč
pozdní příchod nejde zachránit, je v
Registrace pluginů z balíčku.
Dvě cesty a co je čí:
| Cesta | Kdo ji používá |
|---|---|
config('wire-core.plugins') |
Aplikace, která deklaruje své vlastní moduly |
$this->app->resolving(PluginManager::class, …) |
Balíček, který modul dodává aplikacím, do kterých nevidí |
Obě končí ve stejném seznamu, takže modul z balíčku se rozprostře do registru
resources, registru dashboardů a navigačních skupin přesně jako lokální a do
menu, routeru i vyhledávací palety se dostane přes stejný
Catalog.
Všechno ostatní, co balíček s modulem nese — config, views, překlady, migrace a assety — je běžná práce balíčku a patří jeho vlastnímu service provideru.
Balíček přidává, nepřepisuje
Modul registruje klíče, na které si nikdo jiný nedělá nárok. Dvě různé třídy na jednom klíči se odmítnou, místo aby se rozsoudily, takže nainstalovaný balíček nikdy nemůže převzít resource, routu ani položku menu, kterou už vlastní aplikace.
Platí to i opačným směrem: aplikace, která chce upravit, co modul dodává, mění komponentu, ne třídu:
$manager->hook(Hook::TableComposing, function (TableComposingPayload $payload) { $payload->columns = [...$payload->columns, TextColumn::make('internal_note')]; return $payload;}, for: 'invoices'); // klíč, pod kterým se modul zaregistroval
Hook dosáhne na list toho modulu a na nic jiného, a přežije jeho další vydání — což fork ne. Podědit resource z modulu nefunguje: potomek si nese klíč rodiče a koliduje s ním.
Ten samý klíč dosáhne i na ostatní plochy modulu — a právě to dělá z věty výše tvrzení o celém modulu, ne jen o jeho listu:
| Změna | Hook |
|---|---|
| sloupec v jeho listu | Hook::TableComposing |
| pole v jeho formuláři | Hook::FormConfiguring |
| řádek v jeho detailu | Hook::InfolistConfiguring |
| co obsahuje jeho export | Hook::ExportConfiguring |
| co mapuje jeho import | Hook::ImportConfiguring |
| s čím přijde pole ve formuláři | Hook::FormFilling |
| co zapíše inline editace buňky | Hook::CellUpdating |
| veřejný stav na její stránce při mountu | Hook::PageMounting |
| jestli se vůbec objeví v menu | Hook::NavigationBuilding (zúžený zónou) |
Co modul nedělá
| Tohle ne | Protože |
|---|---|
| Registrovat workflow | Workflow má jednu skupinu konzumentů a nese ho resource, který vlastní entitu. Viz Workflow a přechody |
| Registrovat policies | Ty vlastní Laravelí Gate |
| Vyjmenovávat workspaces | Workspace je služba nad registry, ne třída k vyjmenování |
| Forkovat primitiv | Modul skládá Table, Form, Widget a Resource beze změny; je to doménová osa, ne druhá implementace |
Introspekce
describe-module reportuje, co moduly aplikace deklarují — jediná věc, kterou
describe-resource ukázat nemůže, protože resource neví, do které business
oblasti patří:
describe-module # každý registrovaný moduldescribe-module billing # jeden, podle id
Module API
| Metoda | Vrací | K čemu |
|---|---|---|
getId(): string |
string |
Id modulu, unikátní mezi všemi pluginy. Povinné |
resources(): array |
array<int, class-string> |
Třídy resources, ze kterých se oblast skládá |
dashboards(): array |
array<int, class-string> |
Třídy dashboardů, které přináší |
navigation(): ?NavigationGroup |
NavigationGroup|null |
Skupina menu, pod kterou její položky patří |
dependencies(): array |
array<int, string> |
Id modulů, které se musí zaregistrovat dřív (přes HasDependencies) |
register() / boot() |
void |
Životní cyklus pluginu; výchozí prázdný, přepište pro hooky nebo bindingy |