Modules and generators
Anatomy
Section titled “Anatomy”hilms:make-module produces the skeleton; every module grows into the same shape:
app-modules/<name>/ composer.json hilms/<name>, PSR-4 Hilms\<Name>\ -> src/ README.md what the module owns, per directory routes/<name>-routes.php wrapped in the web middleware group, and public routes in Route::localize() for their language prefix src/ Providers/<Name>ServiceProvider.php attaches the Filament plugin Filament/<Name>Plugin.php discovers src/Filament/{Resources,Pages,Widgets} Models/ Enums/ Actions/ Support/ Exceptions/ Rules/ Jobs/ Contracts/ what a module below asks for and a module above binds Http/{Controllers,Middleware,Requests} Livewire/ View/Components/ Policies/ Listeners/ Settings/ Blocks/ Patterns/ Console/Commands/ the directory modular scans Theme/ContractViews.php the views this module asks a theme for PersonalData/ what it contributes to a data export resources/views/ panel views only: <name>::filament.* resources/lang/{pl,en}/ <name>::file.key database/{migrations,factories,seeders}/ tests/Feature/Student-facing Blade never lives here: it belongs to a theme, and the module writes down what it renders in src/Theme/ContractViews.php.
src/Contracts is how a module asks a question whose answer lives above it without importing that module: it declares the interface and binds a floor with bindIf() in its provider, and the module above binds the real answer. Destinations, SpokenLanguages, ReadingPosition, CourseHistory and TeachingScope are all built that way (Architecture).
What registers automatically:
| Thing | Convention |
|---|---|
| Service provider | listed in the module’s composer.json, loaded by package discovery |
| Routes | every file in routes/; a visitor-facing group goes inside Route::localize(), which names it with_locale.* and without_locale.* (Languages) |
| Views | view('<name>::filament.something'), resolved against the theme chain first |
| Translations | __('<name>::courses.fields.title') |
| Migrations, factories, seeders | database/*; factories resolve by model namespace |
| Policies | src/Policies/<Model>Policy for src/Models/<Model> |
| Listeners | a class in src/Listeners with a typed handle(); registering it again in a provider doubles every entry |
| Blade components | anonymous in the theme; class-based in src/View/Components: <x-catalog::latest-courses> |
| Livewire components | classes in src/Livewire as <name>::component-name |
| Console commands | classes in src/Console/Commands |
| Filament resources | src/Filament/Resources/... through the module plugin |
Creating a module
Section titled “Creating a module”bin/artisan hilms:make-module billingThe command scaffolds from stubs/modular/, adds the path repository and the hilms/billing requirement to the root composer.json, and runs composer update hilms/billing. The module test passes immediately. If the stubs lack something every module needs, change stubs/modular/ and config/app-modules.php first, then scaffold.
Decide where the module sits in the direction rule before writing any code (see Architecture); a module with no obvious place in that order is a design smell.
Everyday generators
Section titled “Everyday generators”Anything created repeatedly from a scaffold has a generator, and the generator is how it is created. If the generator is missing something, fix the generator first.
| Command | Writes |
|---|---|
hilms:make-module <name> |
A module, wired into Composer |
hilms:make-block <Name> [--module=] [--theme=] [--container] [--parent=] [--interactive] [--looks=] [--variants=] |
A block class, its Pest test and its view in the theme; with --interactive a Livewire component and its view too (Blocks) |
hilms:make-pattern <Name> [--module=] |
A pattern class: the blocks an editor inserts in one go |
hilms:make-theme <name> [--parent=] [--standalone] [--brand=] [--neutral=] [--fonts=] [--shape=] [--density=] |
A theme directory with its manifest, views, languages, stylesheets, a tokens.json with the sheet generated from it (Design tokens) and styles/<name>.json, the theme’s own built-in style (Styles) |
hilms:make-settings <Name> --module= [--group=site|people|integrations] |
A section of Settings: the settings class, its defaults in a settings migration, the section page, its words in both languages and its test, and the class registered in config/settings.php (Settings) |
All Laravel generators accept --module:
bin/artisan make:model Invoice --module=billing -mfbin/artisan make:migration add_vat_to_invoices_table --module=billingbin/artisan make:controller InvoiceController --module=billingbin/artisan make:livewire invoice-list --module=billingbin/artisan make:seeder BillingPermissionSeeder --module=billingbin/artisan make:test InvoiceTest --module=billing --pestFilament resources are generated into the module by passing both namespaces:
bin/artisan make:filament-resource Invoice \ --model-namespace="Hilms\Billing\Models" \ --resource-namespace="Hilms\Billing\Filament\Resources" \ --generate--generate reads the table, so migrate first. Nested resources cannot be generated non-interactively; copy the Section and Lesson resources of the catalog module instead (see Conventions).
After adding a resource, regenerate policies with bin/artisan shield:generate --all --panel=admin, trim the regenerated policy back to the actions its entity really has, and add the entity to the module’s permission seeder.
Seeding
Section titled “Seeding”Hilms\Ops\Install\BaseSeeders::all() is the single list of what every installation needs: RoleSeeder first, then one <Module>PermissionSeeder per module. Both DatabaseSeeder and the installer’s SeedBase step read that list, so a new module’s permissions reach a fresh database and an upgraded one the same way.
DatabaseSeeder then runs LanguagesSeeder, AdminUserSeeder, PagesSeeder (the starter pages, and only onto an installation with none of its own) and MenusSeeder (a header and a footer per language, pointing at them), and in the local environment three demo seeders:
| Seeder | Makes |
|---|---|
CatalogDemoSeeder |
Six categories, five instructors, 24 published and six draft courses with sections, lessons, tags, and sample blocks on the first two lessons of each course |
LearningDemoSeeder |
The demo student with access to three courses, some completions, one expired API entitlement, a quiz and a passing attempt |
ApiDemoSeeder |
An API client “WooCommerce (demo)” with every scope and the secret from HILMS_API_DEMO_SECRET |
The learning seeder is skipped when HILMS_ADMIN_PASSWORD is empty and the API one when the demo secret is. Run a single seeder with bin/artisan db:seed --class="Hilms\Catalog\Database\Seeders\CatalogDemoSeeder".
For a database large enough to measure, use hilms:seed:load --courses=500 --students=5000: it writes with chunked insertOrIgnore, so no model event fires and nothing reaches the audit trail, and a rerun with the same numbers adds nothing. It is the one writer of entitlements and enrollments outside the learning actions.
HiLMS is MIT-licensed. No replicants were harmed in the writing of these books.