The Contao Open Source CMS can be integrated into a regular Symfony application.
It needs a few installation steps in order to be properly set up. The following
documentation leads you through them.
Install and set up your Symfony application
If you already have a full stack Symfony application set up, you can skip
this step and go directly to the installation procedure for the Contao bundles.
First of all we need a full stack Symfony application installed. You can
find further information about this subject in the Symfony documentation.
This command creates the directory contao-example, containing a bare bone
Symfony application in it.
The Contao core bundle has a recipe in the symfony/recipes-contrib repository.
Be sure to either enable contrib recipes for your project by running the following command or follow the
instructions to use the contrib recipe during the installation process.
Add the contao-component-dir to the extra section of your `composer.json file.
composer config extra.contao-component-dir assets
In order to complete our installation copy the .env file to .env.local and
change the environment values accordingly. In this case, we only need the
DATABASE_URL changed, so our .env.local will also only contain this configuration
value.
At this point contao-example should contain a working Symfony application and
you can proceed to the second step, the installation of Contao itself.
Prepare the application for the next step
Contao uses features of third party bundles. Most of them are configured automatically if you use
Symfony flex, except the 2FA bundle. If you skip this step, your
composer require task will fail. Therefore, you need to create a basic config yourself.
Create (or edit if the file already exists) the file config/packages/scheb_2fa.yaml and add the following entries:
Contao relies heavily on the security component of Symfony, which needs to be
configured accordingly. Replace the contents of the file config/packages/security.yaml
with the following lines.
Last part of this mandatory configuration is for the logging component. There is a development and a production configuration.
The development configuration is located in config/packages/monolog.yaml.
Tip
This is an example configuration and you can adjust this to your custom needs if required.
As long as the Symfony flex plugin is installed you will be asked to execute
contrib recipes for several packages. Answering a on those question sets you
up faster.
Tip
Check your composer.json file look for this following lines if they exist.
Add or enhance the section auto-scripts if they’re missing.
You can configure the Contao Core Bundle with the config file in config/packages/contao_core.yaml. Create (or edit
if the file already exists) the file and adjust it to your needs.
Make sure all the Contao routes are loaded by your application. Add the following
lines to config/routes.yaml. The Contao core bundle will provide a catch-all route.
Add the binary_string type to the list of Doctrine types.
Edit the file config/packages/doctrine.yaml. Be sure to merge the following configuration
into the existing one.
Depending on the language of your choice, change the default and fallback language to e.g. de.
In order to do so, change en to de in config/packages/translation.yaml.
framework:default_locale:detranslator:fallbacks:- de
The next step is to install the database schema to the configured database.
Run the contao:migrate command and follow the instructions there.
$ php bin/console contao:migrate
Since everything is configured correctly, add a admin user through the CLI interface by
running the contao:user:create command and follow the instructions given to you.
$ php bin/console contao:user:create
You can now start a local server and open up the installation tool in your browser.
For example, if you’re using the Symfony CLI, start the server like this:
$ symfony serve
Enable Cache and Front End Preview
At this point you have set up a working Symfony application and added the Contao
Core Bundle. A few last steps are required to properly set up the caching and
the front end preview.
First, we need to install and configure the FOSHttpCacheBundle. To do so, install the package with composer, create (or edit
if the file already exists) the file config/packages/fos_http_cache.yaml and add the following entries:
Replace your public/index.php with the following version to enable the caching
capabilities from Contao.
<?phpuseApp\HttpKernel\AppKernel;require_oncedirname(__DIR__).'/vendor/autoload_runtime.php';// Suppress error messages (see #1422)
@ini_set('display_errors','0');// Disable the phar stream wrapper for security reasons (see #105)
if(in_array('phar',stream_get_wrappers(),true)){stream_wrapper_unregister('phar');}// System maintenance mode comes first as it has to work even if the vendor directory does not exist
if(file_exists(__DIR__.'/../var/maintenance.html')){$contents=file_get_contents(__DIR__.'/../var/maintenance.html');http_response_code(503);header('Content-Type: text/html; charset=UTF-8');header('Content-Length: '.strlen($contents));header('Cache-Control: no-store');die($contents);}returnfunction(array$context){returnnewAppKernel($context['APP_ENV'],(bool)$context['APP_DEBUG']);};
The front end preview is an entry script on its own and needs to be placed in
public/preview.php containing the following lines:
<?phpuseApp\HttpKernel\AppKernel;useFOS\HttpCache\TagHeaderFormatter\TagHeaderFormatter;useSymfony\Component\HttpFoundation\Request;require_oncedirname(__DIR__).'/vendor/autoload_runtime.php';// Suppress error messages (see #1422)
@ini_set('display_errors','0');// Disable the phar stream wrapper for security reasons (see #105)
if(in_array('phar',stream_get_wrappers(),true)){stream_wrapper_unregister('phar');}// System maintenance mode comes first as it has to work even if the vendor directory does not exist
if(file_exists(__DIR__.'/../var/maintenance.html')){$contents=file_get_contents(__DIR__.'/../var/maintenance.html');http_response_code(503);header('Content-Type: text/html; charset=UTF-8');header('Content-Length: '.strlen($contents));header('Cache-Control: no-store');die($contents);}returnfunction(array$context,Request$request){$request->attributes->set('_preview',true);$kernel=newAppKernel($context['APP_ENV'],(bool)$context['APP_DEBUG']);$response=$kernel->handle($request);// Prevent preview URLs from being indexed
$response->headers->set('X-Robots-Tag','noindex');// Force no-cache on all responses in the preview front controller
$response->headers->set('Cache-Control','no-store');// Strip all tag headers from the response
$response->headers->remove(TagHeaderFormatter::DEFAULT_HEADER_NAME);return$response;};
And finally, we need to replace the applications Kernel.
Remove src/Kernel.php and a new file src/HttpKernel/AppKernel.php.
In the same directory create the file src/HttpKernel/AppCache.php.and
// src/HttpKernel/AppCache.php
<?phpdeclare(strict_types=1);namespaceApp\HttpKernel;useContao\CoreBundle\EventListener\HttpCache\StripCookiesSubscriber;useFOS\HttpCache\SymfonyCache\CacheInvalidation;useFOS\HttpCache\SymfonyCache\CleanupCacheTagsListener;useFOS\HttpCache\SymfonyCache\EventDispatchingHttpCache;useFOS\HttpCache\SymfonyCache\PurgeListener;useFOS\HttpCache\SymfonyCache\PurgeTagsListener;useFOS\HttpCache\TagHeaderFormatter\TagHeaderFormatter;useSymfony\Bundle\FrameworkBundle\HttpCache\HttpCache;useSymfony\Component\Cache\Adapter\FilesystemAdapter;useSymfony\Component\Cache\Adapter\TagAwareAdapter;useSymfony\Component\HttpFoundation\Request;useSymfony\Component\HttpFoundation\Response;useToflar\Psr6HttpCacheStore\Psr6Store;classAppCacheextendsHttpCacheimplementsCacheInvalidation{useEventDispatchingHttpCache;publicfunction__construct(AppKernel$kernel,string$cacheDir=null){$this->addSubscriber(newStripCookiesSubscriber(array_filter(explode(',',$_SERVER['COOKIE_ALLOW_LIST']??''))));$this->addSubscriber(newPurgeListener());$this->addSubscriber(newPurgeTagsListener());$this->addSubscriber(newCleanupCacheTagsListener());parent::__construct($kernel,$cacheDir);}/**
* {@inheritdoc}
*/publicfunctionfetch(Request$request,$catch=false):Response{returnparent::fetch($request,$catch);}/**
* {@inheritdoc}
*/protectedfunctiongetOptions():array{$options=parent::getOptions();// Only works as of Symfony 4.3+
$options['trace_level']=$_SERVER['TRACE_LEVEL']??'short';$options['trace_header']='Contao-Cache';return$options;}protectedfunctioncreateStore():Psr6Store{$cacheDir=$this->cacheDir?:$this->kernel->getCacheDir().'/http_cache';returnnewPsr6Store(['cache_directory'=>$cacheDir,'cache'=>newTagAwareAdapter(newFilesystemAdapter('',0,$cacheDir)),'cache_tags_header'=>TagHeaderFormatter::DEFAULT_HEADER_NAME,'prune_threshold'=>5000,]);}}
The last thing now is to adjust the config for the routing via annotations.
If the file config/routes/annotations.yaml and a config for kernel exists, change it to this: