Skip to content

Liber Tertius · Chapter 5 · Working on modules

Create a module

The scaffold takes a minute. The rest of the half hour goes into the two things it cannot do for you, a migration and a test, and into the commit, which is the only step your colleagues ever see.

≈30 min 7 steps A project that installs and runs For a unit only: the right to create repositories in the organisation
Source

Before you start

The stack is up and the working tree of the project is clean, so the commit at the end holds the module and the files grim changed for it, nothing else.

$ grim up
$ git status --short

Decide what the module is. Behaviour only this project needs is a local module: it lives in the project's repository and nowhere else, and it is the usual case. A module other projects will install is a unit, with a repository of its own, grimoiry/grim-<name>, created with the token grim stored. For a unit your GitHub account has to be allowed to create repositories in that organisation. Ask before you start, and check the login with grim auth:whoami. A refusal arrives after the module is already on disk.

Decide the names first. The module name is PascalCase and plural, such as Admissions. The model inside it is singular, such as Entry. Renaming later means touching every generated file.

The ritual, in order

Seven steps. The first six make a local module, and until the sixth is done it exists on your machine only. The seventh is for a unit alone.

  1. Scaffold the module

    grim make:module writes src/Admissions/, lists it under local in grim.json, writes config/modules-available.php again and enables the module. For a unit, leave out --no-repo: the command then leaves local alone, creates the GitHub repository instead and pushes the scaffold to it from a copy outside the project, and the summary ends with a Remote repo row.

    $ grim make:module Admissions --with-crud=Entry -d "Course admissions" --no-repo
    
      Module Admissions created
      Path           src/Admissions/
      Namespace      Modules\Admissions
      Provider       AdmissionsProvider
      Kebab name     admissions
      CRUD           yes
      Model          Entry
      Plural         Entries
      CRUD slug      admissions/entries
      Table          admissions_entries
      GitHub repo    skipped (--no-repo) — local module, listed in grim.json "local"

    Read Plural, CRUD slug and Table before you build on them. The pluralizer knows regular English endings and little else. If a row reads wrong, delete src/Admissions/ and its entry under local in grim.json, or for a unit the new repository, and run it again with --plural. The slug carries a slash, not the dash the option's help text shows. The slash is correct. It is what lets the admin find the pages under Pages/Entries/.

    What landed: module.json, AdmissionsProvider.php, routes.php, a model, a repository class, four Vue pages, language files, and an empty Database/Migrations/. A unit also gets a workflow under .github/ that validates the manifest on every v* tag; a local module has no tags and gets none. There is no migration and no test. Those are the next steps.

  2. Give it a table

    The module is enabled and its table does not exist, so the admin page fails until you migrate. Create the migration inside the module, not in the project's database/ directory. Only what lives under src/Admissions/ travels with the module.

    $ grim artisan -- make:migration create_admissions_entries_table --path=src/Admissions/Database/Migrations
    $ grim artisan migrate

    The double dash matters. Without it grim artisan drops --path without a word and the file lands in the project. Write the columns into the new file before you run the second line, then fill in fields() and grid() on src/Admissions/Models/Entry.php to match.

  3. See it in the admin

    The provider registers the pages, the permissions and a menu entry. Check that the module is enabled, then log in to the admin at https://shop.test and look for the new entry in the menu.

    $ grep Admissions storage/config/modules-enabled.php
        Modules\Admissions\AdmissionsProvider::class,

    The menu entry sits in a group with a fixed label that the scaffold wrote into registerMenu() in AdmissionsProvider.php. Change the group value there by hand. The --menu-group option of make:module is accepted and has no effect, so do not rely on it.

  4. Write the first test

    The scaffold ships no tests. Put them where the project's PHPUnit configuration looks for module tests, src/Admissions/Tests/Unit and src/Admissions/Tests/Feature, and run that directory alone with grim test.

    $ mkdir -p src/Admissions/Tests/Feature
    $ grim test src/Admissions/Tests
    $ grim test

    The test run migrates shop_test first, so your new migration is applied there as well. Run the whole suite once at the end. A new provider can break something far away.

  5. Validate the manifest

    grim manifest:validate checks module.json against the rules the tag workflow enforces. Run it now, and again whenever you add a requires entry.

    $ grim manifest:validate src/Admissions

    The version in module.json was set to the first version of the train the project is on, 2.5.0. Leave it. For a unit, the tag you push in the last step has to say the same.

  6. Commit it in the project

    The project's repository tracks its whole tree, so the module is committed like any other code of the project.

    $ git add src/Admissions grim.json config/modules-available.php
    $ git commit -m "feat(admissions): entries table, model fields and first test"

    For a local module this is the end: a colleague gets it with git pull. Later changes are ordinary commits in the project.

    grim.json lists the module under local, and config/modules-available.php carries its provider, so grim install on a fresh machine enables it. A colleague whose storage/config/modules-enabled.php already exists adds the provider line from step three once; storage/ is not tracked. grim prune never deletes a local module.

  7. Publish a first version (a unit only)

    A project resolves units by tag, and make:module pushed a branch and no tag. src/Admissions/ has no .git of its own, so the release is cut from a clone outside the project: copy your work into it, commit and tag it with the version from module.json.

    $ git clone https://github.com/grimoiry/grim-admissions /tmp/grim-admissions
    $ cp -R src/Admissions/. /tmp/grim-admissions/
    $ git -C /tmp/grim-admissions add -A
    $ git -C /tmp/grim-admissions commit -m "feat: entries table, model fields and first test"
    $ git -C /tmp/grim-admissions tag v2.5.0
    $ git -C /tmp/grim-admissions push origin main v2.5.0

    The tag starts the Validate workflow in the new repository. It fails when the tag and the manifest disagree.

    Then grim require finds the tag, sees that src/Admissions/ is already in the project, and writes the module into grim.json and grim.lock.

    $ grim require admissions
    $ git add -A
    $ git commit -m "feat: add admissions module"

    grim.json now holds "admissions": "~2.5", and the module is a unit like any other. A change to it goes back to its repository with grim push Admissions and returns with the next tag and grim update.

What you have now

src/Admissions/                   a plain directory, committed in the project
  module.json                     name, namespace, version, provider
  AdmissionsProvider.php          CRUD, permissions, menu entry
  Database/Migrations/            your migration
  Tests/                          your tests
grimoiry/grim-admissions          a unit only: branch main and tag v2.5.0
grim.json                         local: lists admissions under "local"; a unit: under "requires"
grim.lock                         a unit only: pins v2.5.0

When it does not work

“Module directory already exists: src/Admissions/”

An earlier run failed after the scaffold, usually at the GitHub step. The module on disk is complete and enabled. Fix the cause, delete src/Admissions/ and run the command again, or create the repository yourself and push the module to it from a copy outside the project, as in step seven.

“Repository grimoiry/grim-admissions already exists.”

The name is taken. The message goes on to offer --use-existing, which attaches that repository as origin. Use it only for an empty repository. The push that follows fails when main there already has history.

“Heads up: 'Users' is a core module name.”

A warning, not a refusal. A local module with the name of a core one may shadow it. Pick another name unless that is what you want.

require says the package is not available

admissions is not available — grimoiry/grim-admissions does not exist, or your token cannot see it. means the repository was created somewhere else, typically with --org. grim resolves every module under grimoiry, so a module created under another organisation cannot be required. Create it without --org.

The admin page answers with an SQL error

The table is missing. Step two was skipped, or the migration landed in the project's database/migrations because the double dash was left out. Move the file into src/Admissions/Database/Migrations/ and run grim artisan migrate.