The problem

When you work on building a website in a team, each member needs to be up to date with the progress of the other members. Whenever one person adds a new element, such as a content type, to the website, the rest of the team needs to be able to download and apply the modifications to their local development environment. Git is a great tool to keep the code synchronized between developers, but traditionally, Drupal stores the configuration in the database, mixed with the content, so the first step has to be to export the configuration from the database to code files. In Drupal 7 we use the Features module to export configuration into code. Having this ability built in to Drupal 8 is arguably one of it’s biggest improvements. It wasn’t always smooth sailing, but we were able to utilize this new feature in the end.

Our solution

Each member of our team has installed the latest Drupal 8 version (8.0.0-beta6 at the time) on his computer. We created a custom install profile by copying the .yml files from core/profiles/standard/config/install to our custom profile folder profile/<your_profile>/config/install. Later as we’ll build up the install profile we will copy the modified and newly created config files as well to this folder. I was tasked with creating new content types and when I finished, I wanted to share it with the other members of my team, so I did a full config export (admin/config/development/configuration/full/export) and my colleagues tried to import (admin/config/development/configuration/full/import) the tarball but the import failed, showing this message: “The staged configuration cannot be imported, because it originates from a different site than this site. You can only synchronize configuration between cloned instances of this site.” When we checked the exported .yml files in the system.site.yml we could see that the UUID (universally unique identifier) of each site is different. After some googling, we found this blog post, and based on what we learned from it, we wrote a deploy module so that all of our dev sites would have the same UUID. We placed the deploy module in profiles/<your_profile>/modules/custom/<your_module> folder. This folder consists of the following files: <your_module>.install

  1. function <your_module>_install() {
  2.  // This module is designed to be enabled on a brand new instance of
  3.  // Drupal. Settings its uuid here will tell this instance that it is
  4.  // in fact the same site as any other instance. Therefore, all local
  5.  // instances, continuous integration, testing, dev, and production
  6.  // instances of a codebase will have the same uuid, enabling us to
  7.  // sync these instances via the config management system.
  8.  // See also https://www.drupal.org/node/2133325
  9.  <your_module>_set_uuid('<a uuid like: af5248ff-4844-4461-b200-ce87afba7af9>');  
  10. }

<your_module>.module

  1. <?php
  2. /**
  3. * @file
  4. * site deployment functions
  5. */
  6. use Drupal\Core\Extension\InfoParser;
  7.  
  8. /**
  9. * Set the UUID of this website.
  10. *
  11. * By default, reinstalling a site will assign it a new random UUID, making
  12. * it impossible to sync configuration with other instances. This function
  13. * is called by site deployment module's .install hook.
  14. *
  15. * @param $uuid
  16. *   A uuid string, for example 'e732b460-add4-47a7-8c00-e4dedbb42900'.
  17. */
  18. function <your_module>_set_uuid($uuid) {
  19. // After Jan. 16th, 2015, see [Nathanael's comment](http://dcycleproject.org/comment/2603#comment-2603)
  20.  \Drupal::configFactory() ->getEditable('system.site')
  21. 	->set('uuid', $uuid)
  22. 	->save();
  23. }

<your_module>.info.yml

  1. name: Your module name
  2. type: module
  3. description: 'Your module’s description'
  4. package: <a package name>
  5. version: VERSION
  6. core: 8.x

Once this was done and all of the dev sites were reinstalled, they had identical UUIDs. To check this, do a full export on both sites and compare UUID in system.site.yml files. Then, we tried a full export/import again. After my teammates uploaded the config.tar.gz file consisting of the settings of my dev site, we saw a long list of configuration files and received this message: Your configuration files were successfully uploaded, ready for import. Everything looked good up to that point. We pressed the [Import all] button. Earlier, we had selected the All messages, with backtrace information option on admin/config/development/logging page and we received this error message (I'll paste the whole message so others with the same problem might find this post easier).

  1. Drupal\Core\Entity\Query\QueryException: Null implementation can not be queried. in Drupal\Core\Entity\ContentEntityNullStorage->getQueryServiceName() (line 82 of core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php).
  2. Drupal\Core\Entity\EntityStorageBase->getQuery()
  3. Drupal\Core\Entity\Event\BundleConfigImportValidate->onConfigImporterValidate(Object, 'config.importer.validate', Object)
  4. Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher->dispatch('config.importer.validate', Object)
  5. Drupal\Core\Config\ConfigImporter->validate()
  6. Drupal\Core\Config\ConfigImporter->initialize()
  7. Drupal\config\Form\ConfigSync->submitForm(Array, Object)
  8. call_user_func_array(Array, Array)
  9. Drupal\Core\Form\FormSubmitter->executeSubmitHandlers(Array, Object)
  10. Drupal\Core\Form\FormSubmitter->doSubmitForm(Array, Object)
  11. Drupal\Core\Form\FormBuilder->processForm('config_admin_import_form', Array, Object)
  12. Drupal\Core\Form\FormBuilder->buildForm(Object, Object)
  13. Drupal\Core\Controller\FormController->getContentResult(Object, Object)
  14. call_user_func_array(Array, Array)
  15. Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1)
  16. Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1)
  17. Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1)
  18. Drupal\Core\StackMiddleware\PageCache->pass(Object, 1, 1)
  19. Drupal\Core\StackMiddleware\PageCache->handle(Object, 1, 1)
  20. Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1)
  21. Stack\StackedHttpKernel->handle(Object, 1, 1)
  22. Drupal\Core\DrupalKernel->handle(Object) 

This error occured because we hadn’t cloned the database and the export/import mechanism only works smoothly on sites in which the databases are exactly the same. Besides that, we realized that one file was missing from the tarball made by full config export. I couldn’t find an issue about this on drupal.org, so I submitted one. Trying to find a solution, we wrote a module we called config_partial_export, which we made into a contrib module. You can find it here, any feedback is welcome. With the help of this module, we can now easily export only the modified config files. The team members copy the exported files (through git, of course) to their profiles/<your_profile> config/install folder, and after reinstalling their sites, we see all the changes made on dev site. To make the site install easy, we use a phing script. The details about how we do this might be a subject of an upcoming blog post. Solving this problem will make the rest of this project much simpler, so this has been a very important part of our progress in learning to work with Drupal 8. We think we came up with quite a good solution, but if you think you can do better, please let us know.