What You Are Building

The capstone module in one page: one entity, an admin grid, a storefront page, REST and GraphQL, and tests.

Syntaxapp/code/Acme/Locator/{registration.php, etc/module.xml, Api, Model, Controller, view, Test}

The capstone is one module, Acme_Locator, that stores pickup locations and shows them in three places: the admin, the storefront and the API. A module is a directory under app/code/Vendor/Name with a registration.php and an etc/module.xml. Nothing in Magento loads any other way.

The five pieces

  • Data: a table in db_schema.xml, the model trio, a data interface and a repository.
  • Admin: a UI component grid and a form, an ACL rule, a menu entry.
  • Storefront: a route, a controller, layout XML, a view model, a template.
  • API: a REST route in webapi.xml and a GraphQL query over the same repository.
  • Tests: one unit test, one integration test, one API functional test.

Decide the names first

Four names are expensive to change later, because each ends up in a table, a URL or an API path: the module name Acme_Locator, the table acme_locator_location, the storefront front name locator, and the ACL resource id Acme_Locator::location. Choose them now and keep them identical everywhere.

Order of work

Data first. The grid, the storefront page and both APIs read the same repository, so building any of them before the repository exists means writing it twice.

Edition and commands

Every file in this stage is Magento Open Source 2.4.9 on PHP 8.4. Nothing here needs Adobe Commerce. Once the two files exist, run bin/magento module:enable Acme_Locator and then bin/magento setup:upgrade. The CLI accepts unambiguous abbreviations: bin/magento s:up is the same command as bin/magento setup:upgrade.

Example

Example · xml
<?xml version="1.0"?>
<!-- app/code/Acme/Locator/etc/module.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Acme_Locator">
        <sequence>
            <module name="Magento_Ui"/>
            <module name="Magento_Backend"/>
            <module name="Magento_Webapi"/>
            <module name="Magento_GraphQl"/>
        </sequence>
    </module>
</config>

<!--
What this declares: the module exists, and its configuration and data
patches are processed after Magento_Ui, Magento_Backend, Magento_Webapi
and Magento_GraphQl. Load order is the only thing <sequence> controls;
it is not a list of Composer requirements.

module.xsd still allows a setup_version attribute, but 2.4 modules leave
it out: declarative schema and data patches replaced the install and
upgrade scripts it used to version. It is not ignored if you add one.
Magento compares it with the module's row in setup_module and reports
the database as out of date until the two match.

Apply it with:
  bin/magento module:enable Acme_Locator
  bin/magento setup:upgrade
-->

When to use it

  • Scope a custom entity for a client so the admin grid and the API are designed in, not bolted on later.
  • Fix the module name, table name, front name and ACL id once, before they appear in a URL and a migration.
  • Use the file tree as a checklist when reviewing somebody else's module for missing pieces.

More examples

registration.php, the file that makes the module exist

Magento finds modules by running every registration.php it can see, not by scanning directories for classes. The name here, Acme_Locator, is the one used by module:enable, by the <sequence> entries of other modules, and by the Acme_Locator:: prefix on ACL resources and template paths. If this file is missing, the module is invisible and no error is printed.

Example · php
<?php
// app/code/Acme/Locator/registration.php
declare(strict_types=1);

use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Acme_Locator', __DIR__);

Enabling it and reading the output

Two commands, two different jobs. module:enable records the module in app/etc/config.php. setup:upgrade is the one that creates the table from db_schema.xml and runs the data patches, and the per-module lines it prints are how you confirm your module was seen. Reach for this pair after every change to module.xml, db_schema.xml or a patch class.

Example · bash
# run from the Magento root
bin/magento module:enable Acme_Locator

# The following modules have been enabled:
# - Acme_Locator
#
# Cache cleared successfully.
# Generated classes cleared successfully. Please run the 'setup:di:compile' command to generate classes.

bin/magento setup:upgrade

# Cache cleared successfully
# File system cleanup:
# ...
# Module 'Acme_Locator':
# Installing schema..
# Installing data..
# Nothing to import.

Where each capstone file lives

In Magento the path is part of the contract. etc/di.xml applies everywhere, etc/frontend/routes.xml only on the storefront, etc/adminhtml/menu.xml only in the admin, and a UI component is found only under view/<area>/ui_component. Keep this tree open while working through the next five lessons; each one fills in part of it.

Example · text
app/code/Acme/Locator/
  registration.php
  composer.json
  etc/module.xml
  etc/db_schema.xml
  etc/db_schema_whitelist.json
  etc/di.xml
  etc/acl.xml
  etc/webapi.xml
  etc/schema.graphqls
  etc/frontend/routes.xml
  etc/adminhtml/menu.xml
  Api/Data/LocationInterface.php
  Api/LocationRepositoryInterface.php
  Model/Location.php
  Model/LocationRepository.php
  Model/ResourceModel/Location.php
  Model/ResourceModel/Location/Collection.php
  Model/Resolver/Locations.php
  Controller/Index/Index.php
  Controller/Adminhtml/Location/Index.php
  ViewModel/LocationList.php
  Setup/Patch/Data/AddDefaultLocations.php
  view/frontend/layout/acme_locator_index_index.xml
  view/frontend/templates/list.phtml
  view/adminhtml/layout/acme_locator_location_index.xml
  view/adminhtml/ui_component/acme_locator_location_listing.xml
  Test/Unit/ViewModel/LocationListTest.php

Discussion

  • Be the first to comment on this lesson.