17

I am a developer and maintainer for the CiviCRM project. We've been trying to make a Drupal 8 version CiviCRM, and have a come a long way. We're beating our heads against our collective keyboards trying to figure a major blocker for the project.

CiviCRM has used Symfony for a while, and the version that is included is different than what ships with Drupal.

We can get CiviCRM installed with Drupal 8, but after you install it, we cannot install any other Drupal module.

I believe it boils down to a situation where somehow the CiviCRM version of Symfony loads before the Drupal version, and this causes problems.

Does anyone know of a Drupal 8 module that includes a different version of Symfony than the one that ships with Drupal?

Recently I ran across the Ludwig project. This module allows the registering of namespaces in a class that extends ServiceProviderBase.

Would it be possible for the Drupal 8 version of the CiviCRM module to include a CivicrmServiceProvider.php file, which defines a CivicrmServiceProvider class, and a register() method that adds a container namespace to allow this to work?

Many CiviCRM files have use statements like Drupal starting with Symfony, like here.

We actually put CiviCRM Core into Drupal doc_root/libraries folder, and use the libraries module.

This is the repo for the CiviCRM Drupal module 8.x version, if somebody wants to look at what we got so far. If somebody has the magic elixir for this, I can tell you there would be many happy folks in our community. So if you know how to help us, please do.

CiviCRM does install, and the CiviCRM pages do work. What doesn't work is that after CiviCRM is installed, we can't install other modules via the admin/modules page. As far as I know that's the only thing that is broken. Also installing modules with Drush, after installing CiviCRM, does work.

Trying to install another module after CiviCRM is installed causes the following error:

PHP Fatal error: Call to undefined method Symfony\Component\DependencyInjection\Definition::setFactory() in /var/www/html/civi-for-d8/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php on line 206

That's in Drupal 8.3.5. Trying to install CiviCRM for Drupal 8 into a clean Drupal 8.4-dev instance causes the following error:

Drupal\Component\Serialization\Exception\InvalidDataTypeException: The reserved indicator "@" cannot start a plain scalar; you need to quote the scalar at line 8 (near "arguments: [@string_translation, @civicrm.page_state]"). in Drupal\Component\Serialization\YamlSymfony::decode() (line 40 of /var/www/html/drupal84/core/lib/Drupal/Component/Serialization/YamlSymfony.php).

  • On mobile, but what version of Symfony? 8.4 will use 3.x, a jump from v2. – Matt Glaman Jul 15 '17 at 3:02
  • We're at version 2.5.0 in CiviCRM – jackrabbithanna Jul 15 '17 at 3:04
  • Some documentation of the issue: issues.civicrm.org/jira/browse/CRM-17652 .... One person reports he didn't see the problem, but I'm not sure about that, everyone else that tries get an error like reported there – jackrabbithanna Jul 15 '17 at 3:09
  • 4
    I don't think this is possible. Drupal 8.4 actually already switched to Symfony3 although there are still similar discussions related to drush, which has the same problem. It's not possible to load two different symfony versions, either you break your integration or you break Drupal. Maybe symfony3 will not be in 8.4 yet, but the security support for symfony2 will end before Drupal8 security support, so at some point, we will have to switch – Berdir Jul 15 '17 at 9:36
  • 1
    @Berdir that might make a good answer? – Clive Jul 15 '17 at 10:39
8
+200

So, I think if CiviCRM were installed into Drupal 8 via composer (ie. composer require civicrm/civicrm-core in the Drupal root) and CiviCRM's use of Symfony was compatible with Symfony 2.8 or 3.x (ie. not using deprecated functionality), this could work.

This would get everything installed in Drupal's vendor directory, rather than having two, and it'd mean that CiviCRM would use the Symfony version in Drupal 8. But if CiviCRM was compatible with later Symfony versions (even if it bundled an older version for Drupal 6 & 7 and other CMS's) it should be fine.

I think?

UPDATED: Yes, it works - I tried it. :-) I originally posted the below in the CiviCRM issue queue (CRM-17652), but re-posting here for completeness.

The big idea:

Since composer is pretty new to a lot of people, I'm going to attempt to go step by step, from some high-level composer stuff all the way to one way it could be done in CiviCRM:

  • Composer allows applications to require the libraries it needs (and libraries, of course, can require other libraries).
  • Libraries have a composer.json file which says what other libraries it needs and what versions it's compatible with (but not necessarily a specific single version - usually a range of versions, like ^2.4.3 which says a minimum of 2.4.3 and up to (but not including) 3.0.0)
  • Applications have a composer.json which similarly describes needed libraries and compatibility with a range of versions, but the range is really to help with updating. An application will also have a composer.lock which is a specific set of individual versions
  • Libraries can also have a composer.lock for their own testing or distribution (like building the release tarball with the dependencies bundled), but this is ignored when an application requires the given library (see https://getcomposer.org/doc/02-libraries.md#lock-file)
  • When an application wants to require a new library, composer finds an intersection of version compatibility between all the things the application requires (including all the libraries already installed and their dependencies) and the new library, possibly doing some updates to make everything line up (or erroring out if it can't find a compatible mix of versions)
  • In this case, CiviCRM is a library, and a particular Drupal 8 site is the application (Drupal core itself is a library)
  • CiviCRM could say it "requires" Symfony ^2.5 in its composer.json which means it is compatible with versions 2.5.0 up to (but not including) 3.0.0
  • When a Drupal 8 site wants to use CiviCRM, the site admin uses composer require civicrm/civicrm-core to require the CiviCRM library and all it's dependencies. If CiviCRM is compatible with Symfony 2.8 (like used in Drupal 8.3.x) everything will install and work fine, using the single Symfony 2.8 from Drupal. All the dependencies end up in Drupal's vendor directory.
  • However, CiviCRM could keep Symfony 2.5 in its composer.lock, which means the tests would use that, and the tarballs for Drupal 6 & 7 and other CMS's would bundle Symfony 2.5

The proposal:

  1. Update the composer.json of CiviCRM so it can be used as a library by composer-based CMS's like Drupal 8 (but probably others could move that way in the future - composer is getting quite popular)
  2. Make sure CiviCRM core is compatible with Symfony 2.8 and 3.0 (used by Drupal 8.3.x and 8.4.x respectively) but keep the "officially supported" version (currently Symfony 2.5) in the composer.lock for testing and the tarball for distribution. Being compatible with multiple Symfony versions may not be as hard as it sounds - there's a number of libraries out there compatible with both Symfony 2.8 and 3.0. It may just be a matter of avoiding deprecated methods/classes/features! The composer.json will need to be updated to reflect this
  3. Use composer to install the CiviCRM library on Drupal 8 rather than copying into the libraries directory. This is becoming the normal way of installing 3rd party PHP libraries in Drupal 8 (this is used extensively by Drupal Commerce, for example)

For composer-based CMS's, I really think this is The Right Way. While this issue is currently affecting Symfony and Drupal, as the PHP community starts using more and more 3rd party libraries via composer, this could very well affect other CMS's with other version conflicts.

Some working code to test:

So, as promised, I actually got this to work to a limited degree :-) I'm totally coming at this from a Drupal/Composer/Symfony perspective -- I don't have a ton of CiviCRM experience, so there's probably some better ways to do my process below. I welcome any advice!

  1. Download and install Drupal 8.3.5 (or the latest dev of Drupal 8.4.x!)
  2. Go into the root directory in the shell and run these commands to install CiviCRM via composer: https://gist.github.com/dsnopek/56311dbea347874e75180883efabb620
  3. If you use Apache, remove the vendor/.htacess file. This is a security measure from Drupal, which prevents resources like CSS/JS being loaded. This will need some collaboration with the Drupal project to figure out a proper solution because removing this file altogether is a bad idea on production. See: vendor/.htaccess blocking CSS/JS assets from composer libraries.
  4. Go into the /modules directory and do git clone https://github.com/dsnopek/civicrm-drupal.git --branch composer-library
  5. Go to the "Extend" page (/admin/modules) and install the CiviCRM module
  6. Clear drupal cache via Drush (drush cr)
  7. Log out and log back in again per CRM-19878
  8. CiviCRM works! :-)

After all of this, CiviCRM is using Symfony 2.8 from Drupal and the dependencies in Drupal's vendor directory, and isn't loading anything from it's own vendor directory. Huzzah!

I tested enabling the "Telephone" module which failed before these changes (see my steps to reproduce), but works fine with them. :-)

  • So here's a question related to all of this, using composer....is it possible to have one package use Symfony 2.8, and another package use Symfony 3.2 .... – jackrabbithanna Jul 17 '17 at 17:42
  • There's pretty difficult institutional resistance to CiviCRM being required to always use what version of Symfony that Drupal 8/9 does. – jackrabbithanna Jul 17 '17 at 17:43
  • 1
    "using composer....is it possible to have one package use Symfony 2.8, and another package use Symfony 3.2" -> No, PHP can't have two classes with the same name. This isn't really a composer thing. – David Snopek Jul 17 '17 at 17:46
  • "There's pretty difficult institutional resistance to CiviCRM being required to always use what version of Symfony that Drupal 8/9 does" -> All that would be required of upstream CiviCRM core is that the code is compatible with the later Symfony used in Drupal. It wouldn't have to bundle it or use it by default, just be compatible, ie. avoid deprecated methods/classes/features. – David Snopek Jul 17 '17 at 17:48
  • I get why one would be interested in running the two major versions of Symfony side-by-side -- SemVer sorta implies the necessity. But I think it's important that large swaths of Symfony components are similar in v2/v3, and that Civi's integration of v2 has been pretty modest. So I'm optimistic about having or achieving PHP code which is compatible with both. IMHO, the real work is updating the distribution-channel and directory structure. – Tim Otten Jul 17 '17 at 19:29
5

I don't think this is possible.

Drupal 8.4 actually already switched to Symfony 3 although there are still similar discussions related to drush, which has the same problem. see Drush 8.x doesn't install Drupal 8.4.x and Drush master doesn't install Drupal 8.3.x and Symfony components are updated to 3.2.6

It's not possible to load two different symfony versions, either you break your integration or you break Drupal. Maybe symfony3 will not be in 8.4 yet, but the security support for symfony2 will end before Drupal8 security support, so at some point, we will have to switch.

  • Well.....Everything works except installing modules from admin/modules....installing modules with Drush does work...All the CiviCRM pages do work. So I'm not convinced its impossible. Why would it be impossible? – jackrabbithanna Jul 15 '17 at 15:32
  • 1
    You can not load two different versions of the same class at the same time, that is not possible. The error sounds exactly like what I'd expect to happen. You managed to load the 2.5 version of the Definition class first and then Drupal breaks because it expects a method to exist that actually doesn't. And the differences will get bigger as Drupal switches to Symfony 3. I don't quite understand why you are stuck with 2.5, 2.8 is a minor update and should be backwards compatible (but not the other way round as you found out). So you should be able to update CiviCRM to require 2.8? – Berdir Jul 16 '17 at 10:07
  • 1
    As I mentioned in my comment, I assumed that you wouldn't like me answer, but that doesn't change it. None of those projects you mention use symfony (joomal seems to use a handful of components that might not conflict but possibly will eventually), so you can't compare that. Is is technically impossible to load two conflicting versions of the same class, nothing can change that. That's why dependencies is a complex business and why composer exists. Instead of using libraries, you should probably look into using composer and making CiviCRM compatible with multiple symfony versions – Berdir Jul 16 '17 at 22:16
  • 2
    Also, security support for Symfony 2.5 ended in 2015 according to symfony.com/roadmap?version=2.5#checker, which means CiviCRM is built on an insecure and outdated symfony version. That alone should be enough to convince them that an update is necessary, at least to version 2.8, it's not just about Drupal8. – Berdir Jul 16 '17 at 22:27
  • 1
    @DavidSnopek right, what you wrote in your answer is basically what I mentioned in my comments as well, but as long as composer.json of CiviCRM specifices "~2.5.0" for its symfony components, that's not going to work. See github.com/civicrm/civicrm-core/blob/master/composer.json. So my answer "you can not use two different versions" is still correct IMHO, you can only improve/update the version contraints in civicrm and then install it through composer and use the same version. – Berdir Jul 17 '17 at 20:33
1

Theoretically the only issues here are the file location and class namespace. Unfortunately the only tools I know of in composer for doing that don't let you specify per VERSION, just per package name.

Have you tried setting it up as a completely separate autoloader?

  • Can you explain in more detail what you mean? – jackrabbithanna Jul 16 '17 at 7:59
  • getcomposer.org/doc/faqs/… is how to do the custom location. I've seen people fork a project just to make this possible... Also getcomposer.org/doc/06-config.md#prepend-autoloader for the option to keep composer's autoloader separate. In the end the autoloader is just a php file, so you could write your own autoloader that decides which one to include depending on whatever factors you like. – Ohthehugemanatee Jul 16 '17 at 8:29
  • Also relevant: stackoverflow.com/questions/30000063/… – Ohthehugemanatee Jul 16 '17 at 8:48
  • Do be clear, ATM there is no composer based installation method to install Civi with D8. I had though maybe that was one way to accomplish this. That was what the ServiceProvider object mentioned in the question was about, like adding a PSR-4 namespace to Drupal to point to the CiviCRM library...If I did this, would Civi need to change all of its files from 'use Symfony\....' to 'use Civicrm\Symfony\..' ? Pardon my ignorance of composer pls. – jackrabbithanna Jul 16 '17 at 16:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.