uxf / page-builder
UXF Page Builder - CMS page/section versioning system
Requires
- php: ^8.4
- doctrine/dbal: ^4.5
- doctrine/orm: ^3.7
- ramsey/uuid-doctrine: ^2.0
- symfony/config: ^8.0
- symfony/console: ^8.0
- symfony/dependency-injection: ^8.0
- symfony/framework-bundle: ^8.0
- symfony/http-kernel: ^8.0
- symfony/lock: ^8.0
- symfony/routing: ^8.0
- symfony/security-bundle: ^8.0
- symfony/security-core: ^8.0
- symfony/validator: ^8.0
- uxf/core: 3.88.0
- uxf/datagrid: 3.88.0
- uxf/hydrator: 3.88.0
Requires (Dev)
None
Suggests
- uxf/cms: for automatic crontab generation via bin/console uxf:cron-gen (#[CronJob] on PublishScheduledCommand)
Provides
None
Conflicts
None
Replaces
None
README
Symfony bundle for pages built from sections: pages and their revisions, sections and shared sections, publishing and scheduled publishing, previews, the REST API for the backoffice editors and the public read API.
The bundle holds no section types. The project defines them (request class with #[SectionField] attributes, a
SectionContentMapper and a response) and registers its layouts, groups and languages in a registry JSON.
Frontend counterparts: @uxf/page-builder (contract), @uxf/page-builder-cms (backoffice editors) and
@uxf/page-builder-web (renderer, editor iframe, previews).
Install in a project
composer require uxf/page-builder
The bundle needs uxf/core, uxf/datagrid, symfony/security-bundle and a Symfony security
setup that resolves Symfony\Component\Security\Core\User\UserInterface to a concrete user
entity (e.g. via uxf/security or uxf/cms).
Setup
Register the bundle in config/bundles.php:
UXF\PageBuilder\UXFPageBuilderBundle::class => ['all' => true],
Import its routes in config/routes.php:
$routingConfigurator->import('@UXFPageBuilderBundle/config/routes.php');
The routes are /api/cms/page-builder/* (editors) and /api/app/page-builder/* (public read API). The editors' routes
(the bundle's Controller\Admin controllers, under whatever path the project mounts them) need the editor_role
(default ROLE_ADMIN) on top of the project's access_control; keep the project's ^/api/cms rule too.
Configuration
$containerConfigurator->extension('uxf_page_builder', [
// role allowed to publish, unpublish and archive; other admins edit drafts only
'publisher_role' => 'ROLE_ROOT',
// role every editors' route needs; null turns the bundle's check off
'editor_role' => 'ROLE_ADMIN',
// the project's registry JSON (layouts, groups, section types, languages), the one source of it;
// null = nothing is restricted
'registry' => '%kernel.project_dir%/config/page-builder/registry.json',
// `uxf:page-builder:schema-gen` writes section-schemas.json and registry.json into each directory
'gen' => [
'destinations' => [
'%kernel.project_dir%/../web/src/generated/page-builder-schema',
'%kernel.project_dir%/../backoffice/src/generated/page-builder-schema',
],
],
// where `uxf:page-builder:make-section` creates a new section type (these are the defaults)
'make_section' => [
'directory' => '%kernel.project_dir%/src/PageBuilderZone/SectionContent',
'namespace' => 'App\PageBuilderZone\SectionContent',
],
]);
Add the bundle sources to the hydrator map sources, so the section requests and the bundle DTOs share one map:
$containerConfigurator->extension('uxf_hydrator', [
'map_sources' => [
__DIR__ . '/../../src',
__DIR__ . '/../../vendor/uxf/page-builder/src',
],
]);
Database
The bundle ships no migrations. Generate them in the project after installing or upgrading the bundle:
bin/console doctrine:migrations:diff
bin/console doctrine:migrations:migrate
Generated clients
The backoffice client comes from the project's uxf_gen hook area. Include the page builder paths in its pattern:
'hook' => [
'areas' => [
'admin' => [
'path_pattern' => '/^\/api\/(app\/page-builder|admin|cms)/',
'output' => 'swr',
'destination' => '%kernel.project_dir%/../backoffice/src/generated/api.ts',
],
],
],
The web's types of the section outputs come from a second area with only the render endpoint (output: empty): its
union of the public outputs (PageBuilderSectionContentPublicOutput) types the web's section components.
bin/console uxf:page-builder:schema-gen (in the project's make gen, next to uxf:grid-gen and uxf:form-gen)
writes the section schemas (section-schemas.json, from the section requests) and the registry (registry.json) into
each gen.destinations directory. Like the grid and form schemas, the apps generate their TypeScript from them:
uxf-page-builder-generator of @uxf/page-builder writes registry.ts (registry, ProjectSectionType) and
section-schemas.ts (sectionSchemas), run from the apps' yarn gen. The registry JSON stays in api/ (the API reads
it, and its image is built from api/ alone); nothing has to copy it into the apps.
The registry is read and checked during cache:warmup: a missing or broken file fails the deploy, not the first
request.
Section types
bin/console uxf:page-builder:make-section quote --label=Citace creates the request, the response, the public response and
the mapper of a type with one translatable title, all keyed by the type, and prints its registry entry with an example
in every language. Then add the type to the registry and to the groups that take it, add its fields, run make gen
and write the web component.
The type string is the key everywhere: #[HydratorMapKey] on the three DTOs, their $type default,
#[AsTaggedItem(index:)] on the mapper, the registry key and its example.type. Stored content is never migrated:
mapToOutput gives a field added later a default.
Public read API
| Route | Answer |
|---|---|
GET /api/app/page-builder/public/page-by-url?url=/o-nas&lang=cs | the page published at the URL (a relative URL, UrlRelative), for the web's catch-all route |
GET /api/app/page-builder/public/page/{page}?lang=cs | the same by the page's id |
GET /api/app/page-builder/public/preview/{token}?lang=cs | a revision shared through a time-limited link |
The page answer carries the page's id, layout (the registry layout the web arranges the sections by), url, the meta
data in the language, noIndex and the sections with their public output. A URL without a published page, a language
outside the registry and a page hidden in the language answer 404.
Shared contract
test-cases/ holds the files the npm packages share: section-validation.json (the section validation cases both
sides run) and api-routes.json (the routes the packages call, checked against the bundle's router by
ApiRoutesContractTest). Change them together with @uxf/page-builder in fe-monorepo.
Scheduled publishing
bin/console uxf:page-builder:publish-scheduled publishes approved page and shared section revisions whose scheduled time has passed. It carries
#[CronJob('* * * * *')] (uxf/cms), an optional integration: when uxf/cms is installed, it lands in the project's
crontab once the bundle sources are among the cron sources:
$containerConfigurator->extension('uxf_cms', [
'cron' => [
'sources' => [__DIR__ . '/../../src', __DIR__ . '/../../vendor/uxf/page-builder/src'],
'destinations' => [__DIR__ . '/../../crontab'],
],
]);
Then run bin/console uxf:cron-gen and run the crontab with supercronic. The command takes a lock (symfony/lock),
so overlapping runs skip.
Without uxf/cms, add the command to the project's crontab by hand instead:
* * * * * bin/console uxf:page-builder:publish-scheduled
License
MIT