johnnyxlemonade / cms-core
Reusable public CMS route runtime for Lemonade.
Requires
- php: >=8.3 <8.6
- johnnyxlemonade/framework: dev-main
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- nyholm/psr7: ^1.8
- phpstan/phpstan: 2.3.0
- phpstan/phpstan-strict-rules: 2.1.0
- phpunit/phpunit: ^10.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-09 13:07:33 UTC
README
johnnyxlemonade/cms-core provides reusable public CMS routing for Lemonade
Framework applications. It owns the canonical cms_route URL index, route
reservation and resolution, locale-aware public routing, and contracts for
module-provided public detail and collection handlers.
The package is runtime-only. It does not own Admin UI, permissions, DataGrid, AdminEditor, public HTML, content publication policy, or file storage. The host supplies locale and module-state runtime ports. A host or content module registers the handler that authoritatively renders a public CMS response.
Installation
The package requires PHP >=8.3 <8.6 and johnnyxlemonade/framework.
composer require johnnyxlemonade/cms-core:dev-main
Register Lemonade\Cms\CmsCoreServiceProvider in the host application and bind
implementations of PublicLocaleRegistryInterface and
PublicModuleStateResolverInterface.
Public CMS routing
CMS Core resolves a public request through the current
PublicLocaleResolution, the canonical cms_route record, module lifecycle
state, and the host-provided public handler. A route record establishes a
canonical address; it is not proof that the target content remains public. The
module handler remains authoritative for publication state, soft deletion,
visibility windows, and locale-specific content availability.
Detail handlers implement PublicCmsRouteHandlerInterface. Collection pages are
ordinary host application routes and remain outside CMS Core.
Package boundaries
johnnyxlemonade/frameworkowns the base application runtime and generic infrastructure.- CMS Core owns public CMS route resolution and handler contracts.
- The host owns public presentation, module publication rules, locale ports and concrete handlers.
- Admin packages must not become a dependency of CMS Core.
Development and QA
From the package root, run:
composer cs:check
composer stan
composer test
composer qa
Non-hierarchical content reads
CmsContentReadModel(Database, CmsContentDefinition) supplies exists(),
ownerId(), metadata(), translation(), publication(), findForEditor(),
tagNamesForLocale(), editorLocales(), dataGridLocales() and enabledLocales().
CmsDomainMapping::columns() reads the same CmsRoot and CmsLocalized
attributes used by CmsContentLifecycle; modules do not maintain another list
of domain database columns or implement a read adapter. Explicitly attributed
private properties are supported as well; unannotated private state is excluded.
Public domain properties retain the requirement for exactly one mapping attribute.
The editor projection includes all stored translations, ordered localized tags, canonical paths, domain values, ownership and audit names. Active reads exclude soft-deleted roots; owner lookup includes them. Translation and publication lookups retain the stored mutations of deleted roots. Editor languages include enabled languages plus disabled languages with a stored mutation; the grid language list considers mutations across the whole module, including deleted records. Tag suggestions retain their existing locale-wide alphabetical order.
CmsContentQueries contains the shared QueryBuilder projections and canonical
and audit joins. It has no Admin dependency. Pagination, search, filters and
DataGrid transport are provided by admin-platform.
Shared-table content definitions
CmsContentDefinition::rootScope optionally constrains a shared root table to an
explicit partition (for example a content type ID). CMS root reads, locks, writes,
translation reads and locale projections preserve that scope; domain mapping
cannot overwrite it. Existing definitions have an empty scope and retain their
behavior.
An optional CmsContentValueWriter participates in CmsContentLifecycle's existing
transaction. It checks creation before root insertion, initializes draft defaults,
validates/prepares values after the effective publication state is resolved, and
writes them once the translation exists. Preparation runs even on otherwise
unchanged published content; the change flag participates in CMS no-op detection.
System data, related values, canonical reservations and audit remain atomic. The
writer must not start a parallel lifecycle or dispatch an independent audit.