SDK: Symfony Example
This project demonstrates how to build a DotCMS webapp with Symfony using the DotCMS PHP SDK. It provides a complete example of rendering DotCMS pages within a Symfony application, including layouts, containers, and content types.
Overview#
This integration allows you to:
- Fetch and render DotCMS pages in a Symfony application
- Integrate Symfony routing with DotCMS, enabeling content authors to create new pages without developer intervention.
- Render DotCMS Pages using Twig templates, this includes layouts, containers and contentlets.
- Integrating DotCMS SDK feedback into Symfony's exception framework for a smooth and reliable developer experience.
Prerequisites#
- PHP 8.1 or higher
- Composer
- Symfony CLI
- DotCMS instance with API access
Project Structure#
dotcms-symfony/ ├── assets/ # Frontend assets (managed by Webpack Encore) │ ├── app.js # Main JavaScript entry point │ ├── styles/ # CSS files │ └── controllers/ # Stimulus controllers │ └── dotcms_edit_controller.js # DotCMS UVE edit controller ├── config/ # Symfony configuration │ ├── packages/ │ │ └── webpack_encore.yaml # Webpack Encore configuration │ ├── routes.yaml # Route definitions including catch-all route │ └── services.yaml # Service definitions including DotCMS client ├── public/ │ └── build/ # Compiled assets (generated by Webpack Encore) ├── src/ │ ├── Controller/ │ │ └── CatchAllController.php # Handles all DotCMS page requests │ ├── Service/ │ │ └── DotCMSService.php # Wrapper for DotCMS PHP SDK │ └── Twig/ │ └── DotCMSExtension.php # Twig extensions for DotCMS rendering ├── templates/ │ ├── base.html.twig # Base template │ ├── page.html.twig # Main page template │ └── dotcms/ # DotCMS-specific templates │ ├── container.twig # Container template │ ├── header.twig # Header template │ └── content-types/ # Content type templates │ ├── banner.twig │ ├── product.twig │ └── activity.twig ├── package.json # Node.js dependencies and build scripts └── webpack.config.js # Webpack Encore configuration
Using SDK Utilities#
This example leverages the utility helpers provided by the DotCMS PHP SDK. The SDK includes a DotCmsHelper
class with common functions for rendering and working with DotCMS content:
getContainerData()
: Retrieves container data by identifierhtmlAttributes()
: Generates HTML attributes from an associative arraysimpleContentHtml()
: Provides fallback HTML rendering for content types
These utilities are used in the Twig extension (DotCMSExtension.php
) to simplify templating:
<?php namespace App\Twig; use Dotcms\PhpSdk\Utils\DotCmsHelper; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; class DotCMSExtension extends AbstractExtension { // ... public function htmlAttr(array $attrs): string { return DotCmsHelper::htmlAttributes($attrs); } // ... public function generateHtmlBasedOnProperty(array $content): string { // Try to use Twig templates first // ... // Fall back to the SDK's simple content HTML renderer if no template is found return DotCmsHelper::simpleContentHtml($content); } }
Installation#
- Clone this repository or create a new Symfony project:
symfony new my-dotcms-project cd my-dotcms-project
- Install the DotCMS PHP SDK and required dependencies:
composer require dotcms/php-sdk composer require symfony/webpack-encore-bundle
- Install Node.js dependencies:
npm install
- Configure your DotCMS connection in
.env
:
DOTCMS_HOST=https://demo.dotcms.com DOTCMS_API_KEY=your-api-key-here
- Build frontend assets:
# For development npm run dev # For production npm run build # For development with file watching npm run watch
Using Local SDK Development Setup#
If you want to test the Symfony example with a local version of the PHP SDK (for development or testing new changes), you can use the composer.dev.json
configuration:
- First, ensure you're in the Symfony example directory:
cd examples/dotcms-symfony
- If you have previously run
composer install
, remove the vendor directory:
rm -rf vendor
- Install dependencies using the development configuration:
COMPOSER=composer.dev.json composer install
This will use the local SDK from the parent directory instead of the published package version.
Configuration#
All the configuration described below is already implemented in this example project. The following sections explain the key components and how they work together to integrate DotCMS with Symfony.
1. Configure DotCMS Client#
Symfony have Service Container allows you to centralize useful objects waiting to be used within the app.
In config/services.yaml
, add the DotCMS client configuration:
services: # Configure the DotCMS client service dotcms.client: class: Dotcms\PhpSdk\DotCMSClient public: true arguments: - '@dotcms.config' # Create an alias for the DotCMSClient to resolve autowiring Dotcms\PhpSdk\DotCMSClient: '@dotcms.client' # Configure the DotCMS config dotcms.config: class: Dotcms\PhpSdk\Config\Config arguments: $host: '%env(DOTCMS_HOST)%' $apiKey: '%env(DOTCMS_API_KEY)%' $clientOptions: timeout: 30 verify: true $logConfig: level: !php/const Dotcms\PhpSdk\Config\LogLevel::DEBUG console: true # Register the Twig extension App\Twig\DotCMSExtension: tags: ['twig.extension']
In this case we register two objects from the SDK the dotCMS Config
and the DotCMSClient
.
In Symfony's service container, we're initializing DotCMSClient
by injecting a Config
object as a dependency. The Config
object itself is configured with environment variables and predefined options for the HTTP requests to the dotCMS APIs.
2. Create a DotCMS Service#
Create a service to wrap the PHP DotCMSClient
in src/Service/DotCMSService.php
:
<?php namespace App\Service; use Dotcms\PhpSdk\DotCMSClient; use Dotcms\PhpSdk\Model\PageAsset; class DotCMSService { private DotCMSClient $client; public function __construct(DotCMSClient $client) { $this->client = $client; } public function getClient(): DotCMSClient { return $this->client; } public function getPage(string $path): PageAsset { $pageRequest = $this->client->createPageRequest($path, 'json'); return $this->client->getPage($pageRequest); } }
The DotCMSService
class serves two important purposes:
-
Dependency Injection: Through Symfony's service container, the fully configured
DotCMSClient
is automatically injected into our service. -
Facade Pattern: The service acts as a facade, exposing only specific DotCMS client methods needed by the application (like
getPage()
).
3. Configure Routes#
DotCMS allows content authors to create pages without developer intervention. To support this, we use a catch-all route that handles all page requests through a single controller.
In config/routes.yaml
, add a catch-all route to handle DotCMS pages:
catch_all: path: /{path} controller: App\Controller\CatchAllController::show requirements: path: '^(?!(favicon\.ico|.*\.(css|js|jpg|jpeg|png|gif|svg|woff|woff2|ttf|eot|map|json)$)).*' controllers: resource: path: ../src/Controller/ namespace: App\Controller type: attribute
This route will catch all requests except for static assets.
4. Create a Controller#
Create a controller to handle DotCMS page requests in src/Controller/CatchAllController.php
:
<?php namespace App\Controller; use App\Service\DotCMSService; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Symfony\Component\HttpKernel\Exception\HttpException as SymfonyHttpException; use Dotcms\PhpSdk\Exception\HttpException; use Dotcms\PhpSdk\Exception\ResponseException; class CatchAllController extends AbstractController { private DotCMSService $dotCMSService; public function __construct(DotCMSService $dotCMSService) { $this->dotCMSService = $dotCMSService; } public function show(string $path = ''): Response { try { $request = $this->container->get('request_stack')->getCurrentRequest(); $actualPath = $request->getPathInfo(); $pageAsset = $this->dotCMSService->getPage($actualPath); if (!$pageAsset || !isset($pageAsset->page)) { throw new NotFoundHttpException('Page not found'); } return $this->render('page.html.twig', [ 'pageAsset' => $pageAsset, 'layout' => $pageAsset->layout ?? null, 'page' => $pageAsset->page ?? null, 'containers' => $pageAsset->containers ?? [] ]); } catch (HttpException $e) { // Map HTTP errors to appropriate Symfony exceptions throw match($e->getCode()) { 400 => new BadRequestHttpException($e->getMessage(), $e), 401 => new UnauthorizedHttpException('Bearer', $e->getMessage(), $e), 404 => new NotFoundHttpException($e->getMessage(), $e), 500 => new SymfonyHttpException(500, $e->getMessage(), $e), 503 => new ServiceUnavailableHttpException(null, $e->getMessage(), $e), default => new ServiceUnavailableHttpException(null, $e->getMessage(), $e) }; } catch (ResponseException $e) { // ResponseException indicates invalid/missing data in the response // This is a server error since the response format is controlled by DotCMS throw new ServiceUnavailableHttpException(null, $e->getMessage(), $e); } } }
This controller serves as the central entry point for all DotCMS page requests. It retrieves the current request path, fetches the corresponding page from DotCMS via the service, and renders it using the page template.
The controller also handles error cases by mapping DotCMS exceptions to appropriate Symfony HTTP exceptions, ensuring proper error responses.
5. Create Twig Extension#
Create a Twig extension to help with rendering DotCMS content in src/Twig/DotCMSExtension.php
:
<?php namespace App\Twig; use Twig\Environment; use Twig\Extension\AbstractExtension; use Twig\TwigFunction; use InvalidArgumentException; use Dotcms\PhpSdk\Utils\DotCmsHelper; use Dotcms\PhpSdk\Model\Content\Contentlet; class DotCMSExtension extends AbstractExtension { public function __construct( private Environment $twig ) { } public function getFunctions(): array { return [ new TwigFunction('getGridClass', [$this, 'getGridClass']), new TwigFunction('generateHtmlBasedOnProperty', [$this, 'generateHtmlBasedOnProperty'], ['is_safe' => ['html']]), new TwigFunction('htmlAttr', [$this, 'htmlAttr'], ['is_safe' => ['html']]) ]; } public function htmlAttr(array $attrs): string { return DotCmsHelper::htmlAttributes($attrs); } public function getGridClass(int $position, string $type = 'start'): string { return match($type) { 'start' => "col-start-{$position}", 'end' => "col-end-{$position}", default => throw new InvalidArgumentException('Invalid grid class type') }; } public function generateHtmlBasedOnProperty(Contentlet $content): string { if (empty($content)) { return ''; } $contentType = $content->contentType; if ($contentType) { $template = 'dotcms/content-types/' . strtolower($contentType) . '.twig'; if ($this->twig->getLoader()->exists($template)) { return $this->twig->render($template, ['content' => $content]); } } // Fall back to the SDK simple HTML renderer return DotCmsHelper::simpleContentHtml($content->jsonSerialize()); } }
This Twig extension provides utility functions for rendering DotCMS content in templates - handling content-type HTML generation and proper attribute formatting for DotCMS elements.
6. Create Templates#
Create the necessary Twig templates to render DotCMS content:
Main Page Template (templates/page.html.twig
):#
{% extends 'base.html.twig' %} {% block title %}{{ page.friendlyName|default('Welcome') }}{% endblock %} {% block body %} {% include 'dotcms/header.twig' %} {# Page Content #} {% if layout is not null and layout.body is defined %} {% for row in layout.body.rows %} <div class="container"> <div data-dot-object="row" class="row{% if row.styleClass %} {{ row.styleClass }}{% endif %}"> {% if row.columns is defined and row.columns is not empty %} {% for column in row.columns %} {% set startClass = 'col-start-' ~ column.leftOffset|default(0) %} {% set endClass = 'col-end-' ~ (column.width|default(12) + column.leftOffset|default(0)) %} <div data-dot-object="column" class="{{ startClass }} {{ endClass }}{% if column.styleClass %} {{ column.styleClass }}{% endif %}"> {% if column.containers is defined and column.containers is not empty %} {% for container in column.containers %} {% include 'dotcms/container.twig' with { 'container': container } %} {% endfor %} {% endif %} </div> {% endfor %} {% endif %} </div> </div> {% endfor %} {% else %} <div class="container"> <div class="row"> <div class="col-start-1 col-end-13"> <div class="alert alert-warning"> No layout found </div> </div> </div> </div> {% endif %} {% endblock %}
Container Template (templates/dotcms/container.twig
):#
{% set containerAttrs = { 'data-dot-object': 'container', 'data-dot-identifier': container.identifier, 'data-dot-accept-types': container.acceptTypes, 'data-max-contentlets': container.maxContentlets, 'data-dot-uuid': container.uuid } %} <div {{ htmlAttr(containerAttrs) }}> {% for content in container.contentlets %} {% set contentAttrs = { 'data-dot-object': 'contentlet', 'data-dot-identifier': content.identifier, 'data-dot-basetype': content.baseType, 'data-dot-title': content.widgetTitle|default(content.title), 'data-dot-inode': content.inode, 'data-dot-type': content.contentType, 'data-dot-container': { 'acceptTypes': container.acceptTypes, 'identifier': container.identifier, 'maxContentlets': container.maxContentlets, 'variantId': container.variantId, 'uuid': container.uuid }|json_encode } %} <div {{ htmlAttr(contentAttrs) }}> {{ generateHtmlBasedOnProperty(content)|raw }} </div> {% endfor %} </div>
Running the Application#
- Start the Symfony development server:
symfony server:start
- Visit
http://localhost:8000
in your browser. The application will fetch and render pages from your DotCMS instance.
How It Works#
- When a user visits a URL, the catch-all route directs the request to
CatchAllController::show()
. - The controller uses
DotCMSService
to fetch the page from DotCMS. - The page data is passed to the Twig templates for rendering.
- The Twig extension provides helper functions for rendering DotCMS containers and content.
- Content-type specific templates render each content type appropriately.
sequenceDiagram participant User participant Symfony as Symfony Router participant Controller as CatchAllController participant Service as DotCMSService participant DotCMS as DotCMS API participant Twig as Twig Templates User->>Symfony: Request URL Symfony->>Controller: Route to show() method Controller->>Service: getPage(path) Service->>DotCMS: API Request DotCMS-->>Service: Return PageAsset Service-->>Controller: Return PageAsset Controller->>Twig: Render with page data Twig->>Twig: Process with Twig Extensions Twig-->>User: Return rendered HTML
Error Handling#
The application includes comprehensive error handling:
- HTTP errors from DotCMS are mapped to appropriate Symfony exceptions
- Response parsing errors are handled gracefully
- Server errors are presented with appropriate HTTP status codes
Customization#
Adding New Content Types#
To add support for a new content type:
- Create a new template in
templates/dotcms/content-types/your-content-type.twig
- Add the content type to the
generateHtmlBasedOnProperty
method inDotCMSExtension.php
Styling#
The project uses Pico.css for base styling and includes a basic grid system. Assets are managed by Webpack Encore, allowing you to:
- Add CSS/SCSS files to the
assets/styles/
directory - Import styles in your JavaScript files
- Use CSS frameworks like Bootstrap, Tailwind, etc.
- Leverage Webpack's asset optimization features
To add new styles:
- Create CSS/SCSS files in
assets/styles/
- Import them in
assets/app.js
- Run
npm run dev
to compile
Frontend Architecture#
The project uses Stimulus for JavaScript functionality:
- Stimulus Controllers: Located in
assets/controllers/
- DotCMS UVE Integration: The
dotcms_edit_controller.js
provides content editing capabilities - Build Process: Webpack Encore handles asset compilation and optimization
UVE (Universal Visual Editor) Integration#
The project includes DotCMS UVE integration for content editing:
- Edit Mode: Content editors can edit contentlets directly in the frontend
- Stimulus Controller:
dotcms_edit_controller.js
handles the edit functionality - Dependencies: Uses
@dotcms/uve
and@dotcms/types
packages - Automatic Detection: Edit buttons only appear when in UVE edit mode
The UVE integration allows content editors to:
- Edit contentlets directly on the page
- See changes in real-time
- Use DotCMS's visual editing interface
Resources#
License#
This project is open-sourced under the MIT license.
main
Found an issue with this documentation? Report it on GitHub