Forest header image

Symfony Finland
Random things on PHP, Symfony and web development

Using Symfony Finder and YAML components to transform Drupal 8 configurations to eZ Platform

Many different tools now use YAML configurations for many things. They've become a staple for many developers and are both human readable and easy to parse and generate using the Symfony Yaml Component. To help with locating the files themselves, the Finder component is a great help.

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];
    } 
    
}


Written by Jani Tarvainen on Saturday May 7, 2016
Permalink - Tags: symfony, yaml, drupal, ezplatform

« Symfony components and the full stack framework in CMS ecosystems - Universal Rendering in PHP/Twig could be done with the Angular 2 Template Compiler »