Using Symfony Finder and YAML components to transform Drupal 8 configurations to eZ Platform
Bolt CMS, Drupal 8 and eZ Platform all content management systems that use Symfony in one form or another. All of them use YAML to store configurations of content types and other system properties. This is great as it makes transferring between different formats quite straightforward.
This could be done by hand, but ideally configurations can be converted programatically. In this article we'll take a look at how to transfer content types from Drupal 8 to eZ Platform using a standard Symfony Command.
Configurations in Drupal 8
Drupal has traditionally held configurations in a mix of configuration files and databases. There were additional modules like Features that could be used to export and import configurations, but configuration management itself was not really a part of the tool.
Drupal 8 still contains configurations in a mix of formats: PHP, YAML and Database. The big feature is the introduced a configuration manager that allows exporting and importing configurations using archive files that contain YAML:
The Configuration Manager module in Drupal 8 provides a user interface for importing and exporting configuration changes between a Drupal installation in different environments, such as Development, Staging and Production, so you can make and verify your changes with a comfortable distance from your live environment.
- Managing configuration in Drupal 8
These configurations are still stored in the database and so the package contains a lot of files, ranging from content structures to internal entity types, layouts definitions (blocks) to even the content queries that have been built with the views module.
Configurations in eZ Platform
As a Symfony Framework application eZ Platform uses YAML files by default. Database configurations and other system configurations are in YAML files that should be set up following Symfony Best Practises and can be transferred from one environment to another with version control.
eZ Platform also stores dynamic configurations in the database for items such as the content structure and accompanying meta data. The eZ repository does not use the Doctrine ORM, so the usual route of using Doctrine Migrations is not available to developers.
There are at least two migration bundles for the eZ Publish Kernel that work in a similar way as Doctrine Migrations:
Both of these use the eZ Platform Public API internally and can essentially be thought of as generic importing frameworks. In our example we will use the Kaliop one because it favours YAML configurations over the code centric way of the Kreait alternative.
Transforming Drupal 8 node type configurations from to eZ Platform content types
In this example we'll take a look at how to transform Drupal 8 configurations files to the Kaliop Migrations format that can be executed in various environments like development, staging and production. There are a lot of additional transformations that could be done, but with them both being content management systems I chose that one as an example.
When you export configurations from Drupal 8 you get a file like config-example-com-2016-05-07-20-29.tar.tgz. Within this archive you've got a number of files, but the ones we will focus on are the ones related to content definitions:
- node.type.article.yml
- node.type.page.yml
The files above contain the definitions for the Node types in Drupal. The individual field definitions are reusable across node types and are thus are in individual files:
- field.field.node.article.body.yml
- field.field.node.article.comment.yml
- field.field.node.article.field_image.yml
- field.field.node.article.field_tags.yml
- field.field.node.page.body.yml
These need to be merged to form the YAML format that is compatible with the Kaliop Migrations Bundle:
-
mode: create
type: content_type
content_type_group: 1
name: Imported Content Type
identifier: imported_content_type
name_pattern: <title>
attributes:
-
type: ezstring
name: Title
identifier: title
description: Field is only used to name the object in the admin interface.
required: true
-
type: ezrichtext
name: Body
identifier: body
required: true
The above format can then be stored to version control and executed to do migrations such as creating, updating and deleting for content types, content and more.
Talk is cheap, show me the code
Below is an example of how working with the same technologies allows transitioning from one system to another. This is very similar to how you could potentially port a site from Drupal 8 to Bolt CMS while retaining quite a bit of your Twig template logic.
<?php
namespace Janit\DrupalConverterTestBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Dumper;
class ConvertCommand extends ContainerAwareCommand
{
// Mapping Drupal field types to eZ Platform comparables
private $fieldTypes = array(
'text_with_summary' => 'ezrichtext',
'comment' => 'eztext',
'image' => 'ezimage',
'entity_reference' => 'ezobjectrelation',
'string' => 'ezstring'
);
protected function configure()
{
$this
->setName('convert:drupal8')
->setDescription('Convert Drupal 8 Configuration for migration to eZ Platform / eZ Publish 5.x')
->addArgument(
'directory',
InputArgument::REQUIRED,
'Where is your exported configuration?'
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$yaml = new Parser();
$contentTypes = array();
// boilerplate for imports
$migrationBase = array(
'mode' => 'create',
'type' => 'content_type',
'content_type_group' => 4,
'name_pattern' => 'title'
);
// Get the directory and the node configurations there
$directory = $input->getArgument('directory');
// Symfony Finder component helps us find files :)
$nodeFinder = new Finder();
$nodeFinder->name('node.type.*.yml')->in($directory);
foreach($nodeFinder as $nodeFile){
$contentType = $migrationBase;
$contentType['name'] = $nodeDefinition['name'];
$contentType['identifier'] = $nodeDefinition['type'];
$nodeName = explode('.',$nodeFile)[2];
$fieldFinder = new Finder();
$fieldFinder->name('field.field.node.' . $nodeName . '.*.yml')->in($directory);
$nodeDefinition = $yaml->parse(file_get_contents($nodeFile));
$fields = array();
// We'll set this field always so we can generate content name meta
$fields[] = array(
'type' => 'ezstring',
'name' => 'Title',
'identifier' => 'title',
'description' => 'Title field, used for object name',
'required' => true
);
foreach($fieldFinder as $fieldFile){
// Symfony Yaml components helps us parse configurations :)
$fieldDefinition = $yaml->parse(file_get_contents($fieldFile));
// set fields, not that we'll need to translate the field type identifiers from Drupal ones to eZ
$fields[] = array(
'type' => $this->translateFieldType($fieldDefinition['field_type']),
'name' => $fieldDefinition['label'],
'identifier' => $fieldDefinition['field_name'],
'description' => strip_tags($fieldDefinition['description']),
'required' => $fieldDefinition['required']
);
}
// add attributes and push to content type
$contentType['attributes'] = $fields;
$contentTypes[] = $contentType;
}
// dump generated Migration config to a YAML file
$dumper = new Dumper();
$outputYaml = $dumper->dump($contentTypes);
file_put_contents('drupal_export.yml', $outputYaml);
}
private function translateFieldType($fieldType){
return $this->fieldTypes[$fieldType];
}
}