AI agent guidance for working on the firegento/magento2-content-provisioning Magento 2 module
(Firegento_ContentProvisioning).
Name: Firegento_ContentProvisioning
Composer package: firegento/magento2-content-provisioning
Type: Full Magento 2 module (recurring setup installer, admin UI, CLI commands, cron job)
Purpose: Lets other modules declare CMS pages/blocks via a content_provisioning.xml file. Entries are
(re-)applied to the database on every setup:upgrade run. Entries can be maintained (always overwritten by
code) or one-shot (only created once, then left alone for editors).
Originally created during a FireGento e.V. hackathon, now actively maintained by TechDivision via a mirror
repository (see README.md for details). Do not assume this is a passive read-only mirror — the develop
branch here receives real feature/fix work.
| Requirement | Version |
|---|---|
| Magento | >= 2.4.9 |
| PHP | ^8.4 |
magento/framework |
~103.0.9 |
magento/module-cms / -widget / -backend / -ui / -store / -config |
loose >= bounds (kept intentionally wide for backward compatibility with older Magento installs — do not tighten without a good reason, see CHANGELOG "Soften dependencies" history) |
There is no Hyvä-compatibility module maintained for this repository at this time.
m2-content-provisioning/
├── Api/ # Public interfaces (@api)
│ ├── Data/ # EntryInterface, PageEntryInterface, BlockEntryInterface
│ ├── ConfigParserInterface.php
│ ├── ConfigurationInterface.php
│ ├── ContentResolverInterface.php
│ ├── MediaFilesParserInterface.php
│ ├── StoreCodeResolverInterface.php
│ └── TargetMediaDirectoryPathProviderInterface.php
├── Block/ # Admin block "content is maintained by code" warning + Save button blocks
├── Controller/Adminhtml/ # Admin Save controllers for pages/blocks (edit form save)
├── Cron/
│ └── InconsistenciesInCmsBlock.php # Detects DB content that has drifted from XML config, emails a report
├── Exception/
│ └── CommandInputException.php
├── Model/
│ ├── BlockEntry.php / PageEntry.php # Data models (extend Magento\Cms\Model\Block/Page)
│ ├── BlockInstaller.php / PageInstaller.php # Applies configured entries during setup:upgrade
│ ├── Command/ # ApplyBlockEntry, ApplyPageEntry, ApplyMediaFiles, NormalizeData
│ ├── Config/ # XML config reading: Converter, Data, NodeConverter, SchemaLocator, Parser/*
│ ├── Console/ # CLI commands (see below)
│ ├── DTO/CmsBlockDto.php
│ ├── Query/ # Small single-purpose query/lookup classes (Get*, Is*, Has*)
│ ├── Resolver/ # Content/store-code resolvers
│ ├── ResourceModel/ # Raw DB lookups (GetCmsBlockByIdentifier, GetDbCmsBlockContent)
│ └── Validator/ # CanApplyBlockEntry, CanApplyPageEntry
├── Service/
│ ├── BlockQueryService.php # Orchestrates the inconsistency-detection query flow used by the cron job
│ └── EmailService.php # Sends the inconsistency report email
├── Setup/
│ └── RecurringData.php # InstallDataInterface — runs BlockInstaller/PageInstaller on every setup:upgrade
├── ViewModel/
│ ├── BlockDataProvider.php # Builds a Symfony Table from all configured block entries
│ └── ChangedBlockDataProvider.php # Builds a Symfony Table from changed/inconsistent block entries
├── etc/
│ ├── module.xml, di.xml, config.xml, crontab.xml
│ └── content_provisioning.xsd # XSD schema for the `content_provisioning.xml` files consumed from other modules
├── Test/Integration/ # PHPUnit integration tests (no Unit tests exist)
├── phpstan.neon / phpstan-baseline-5.neon
├── registration.php, composer.json, README.md, CHANGELOG.md
All extend Symfony\Component\Console\Command\Command.
| Command | Class | Notes |
|---|---|---|
content-provisioning:block:apply |
AddBlockCommand |
Applies a single block entry by key |
content-provisioning:page:apply |
AddPageCommand |
Applies a single page entry by key |
content-provisioning:block:list |
BlockListCommand |
Lists all/changed block entries as a table |
content-provisioning:page:list |
PageListCommand |
Lists all page entries as a table |
content-provisioning:block:reset |
BlockResetCommand |
Force-reapplies block(s) by --key/--identifier, bypassing maintained semantics |
Critical constraint for this module: Magento 2.4.9 bundles symfony/console 7.x, whose Command::execute()
is declared as abstract protected function execute(InputInterface $input, OutputInterface $output): int.
Every execute() override in this module (and any new command added) must declare : int and actually
return an int (Command::SUCCESS / Command::FAILURE / Magento\Framework\Console\Cli::RETURN_SUCCESS /
RETURN_FAILURE). Omitting or mistyping this return type is a fatal Declaration ... must be compatible
error at class-load time, not a mere deprecation — this exact bug existed in AddBlockCommand,
AddPageCommand, and PageListCommand before the 2.4.9 compatibility update and was fixed there.
Similarly, Symfony\Component\Console\Helper\Table::render() takes zero arguments in symfony/console 7.x
(the OutputInterface is only accepted in the constructor new Table($output)). Calling ->render($output)
does not crash (PHP silently ignores extra positional arguments to userland methods) but is stale/incorrect —
keep it argument-less if you touch this code.
Invoked from Setup/RecurringData::install() on every setup:upgrade. Iterate all configured entries
(merged from every module's content_provisioning.xml via Model/Config/*), and for each entry:
- If
maintained="true": always (re-)write content to the DB, matched per configured store - If
maintained="false": only create if no matching entity already exists for that store
Converter (registered via di.xml) transforms the merged content_provisioning.xml DOM into arrays keyed by
key attribute, using two virtual-typed ParserChains (one for page, one for block) built from the
Parser/* classes (MetaDataParser, StoresParser, ContentParser, SeoParser, DesignParser,
CustomDesignParser, MediaDirectoryParser, etc.). SchemaLocator points at
etc/content_provisioning.xsd for merge-time validation.
Scheduled via etc/crontab.xml (config path trans_email/ident_content_provisioning/cron_schedule, default
0 7 * * *). Compares configured block content against what's actually in the DB
(Service/BlockQueryService), and if drift is found, emails an HTML report
(Service/EmailService) — throttled by trans_email/ident_content_provisioning/email_frequency
(Model/Config/Source/EmailFrequency) using a cache-stored last-sent timestamp.
declare(strict_types=1)in every PHP file- Native param/return types are used throughout newer code (
Model/Console/BlockListCommand.php,Model/Console/BlockResetCommand.php,Cron/*,Service/*) — older classes (Model/BlockEntry.php,Model/PageEntry.php) intentionally keep doc-block-only typed getters/setters to mirror theMagento\Cms\Model\Block/Pageparent convention (magicgetData()/setData()); don't add native types there without checking PHPStan/parent-class compatibility first - No constructor property promotion is used in most existing classes — follow the surrounding file's style when editing; prefer explicit typed property declarations for new code
- Keep
@var/@param/@returndocblocks accurate — PHPStan trusts docblock types over the actual assigned class in some cases (see theAddBlockCommand/AddPageCommandfactory-vs-non-factory docblock bug fixed in the 2.4.9 compatibility update as a cautionary example)
Run PHPStan from the module root (deployed under vendor/firegento/magento2-content-provisioning/ in a real
Magento install, so Magento/Symfony classes are resolvable):
../../bin/phpstan analyse . -c phpstan.neon- Level: 5
- phpVersion: 80400
Test/is excluded from analysisphpstan-baseline-5.neoncaptures 16 pre-existing (not 2.4.9/PHP 8.4-related) type-strictness findings — mostlyMagento\Cms\Model\BlockvsBlockInterface/PageInterfacemismatches and a few docblock/return type inaccuracies inController/Adminhtml/*/Save.php,Model/Command/Apply*.php,Model/Query/*.php,Model/ResourceModel/*.php. These are intentionally not fixed as part of the compatibility update to avoid untested behavioral changes; feel free to fix the underlying issues and shrink the baseline in a follow-up, but re-verify test coverage first- Standard Magento-specific ignore rules are pre-configured in
phpstan.neon(magic getters/setters, Factory classes, etc.)
- No Unit tests exist. Only
Test/Integration/exists, coveringBlockInstaller,PageInstaller(including media file installation), the configConverter, andFetchMediaFilesChain. - Two PHPUnit config variants are kept for CI:
Test/Integration/phpunit.gitlab.xml(PHPUnit 9.1 schema,<extensions>) andphpunit9.gitlab.xml(PHPUnit 9.5 schema,<listeners>) — the CI test-executor tooling selects the appropriate one for the Magento/PHPUnit version under test. mikey179/vfsstream(require-dev) is used byTest/Integration/Model/PageInstaller/InstallMediaFilesTest.phpto mock the media filesystem — keep this dependency.- Run locally against a real Magento instance:
php vendor/bin/phpunit -c $(pwd)/vendor/firegento/magento2-content-provisioning/Test/Integration/phpunit.xml
Adding a new CLI command: Create it under Model/Console/, extend Symfony\Component\Console\Command\Command,
register it in etc/di.xml's console command list. Always declare execute(InputInterface $input, OutputInterface $output): int
and configure(): void, and return a proper Command::SUCCESS/FAILURE (or Magento\Framework\Console\Cli::RETURN_*) constant.
Adding a new content_provisioning.xml node/attribute: Extend etc/content_provisioning.xsd, add a
matching Parser/* class implementing Api\ConfigParserInterface, and wire it into the relevant ParserChain
virtual type (PageParserChain/BlockParserChain) in etc/di.xml.
Changing inconsistency-detection/email logic: Edit Cron/InconsistenciesInCmsBlock.php and
Service/BlockQueryService.php / Service/EmailService.php. Config paths live under
trans_email/ident_content_provisioning/* (see etc/config.xml).
Updating dependencies: Edit composer.json and update CHANGELOG.md/README.md accordingly. Keep
magento/module-* constraints loose (>=) unless there's a concrete compatibility reason to tighten them —
this package is also consumed outside of TechDivision-internal Magento installs.