Issue #2302617 by dawehner, chx | Crell: Define a standard mechaism for backend-aware service overrides.

8.0.x
Alex Pott 2014-07-24 16:42:57 +01:00
parent 64387e5c5e
commit 36faf4e8f1
3 changed files with 159 additions and 0 deletions

View File

@ -9,6 +9,7 @@ namespace Drupal\Core;
use Drupal\Core\Cache\CacheContextsPass;
use Drupal\Core\Cache\ListCacheBinsPass;
use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass;
@ -49,6 +50,8 @@ class CoreServiceProvider implements ServiceProviderInterface {
// list-building passes are operating on the post-alter services list.
$container->addCompilerPass(new ModifyServiceDefinitionsPass());
$container->addCompilerPass(new BackendCompilerPass());
// Collect tagged handler services as method calls on consumer services.
$container->addCompilerPass(new TaggedHandlersPass());

View File

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass.
*/
namespace Drupal\Core\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* Defines a compiler pass to allow automatic override per backend.
*
* A module developer has to tag his backend service with "backend_overridable":
* @code
* custom_service:
* class: ...
* tags:
* - { name: backend_overridable }
* @endcode
*
* As a site admin you set the 'default_backend' in your services.yml file:
* @code
* parameters:
* default_backend: sqlite
* @endcode
*
* As a developer for alternative storage engines you register a service with
* $yourbackend.$original_service:
*
* @code
* sqlite.custom_service:
* class: ...
* @endcode
*/
class BackendCompilerPass implements CompilerPassInterface {
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container) {
$default_backend = $container->hasParameter('default_backend') ? $container->getParameter('default_backend') : NULL;
// No default backend was configured, so continue as normal.
if (!isset($default_backend)) {
return;
}
foreach ($container->findTaggedServiceIds('backend_overridable') as $id => $attributes) {
// If the service is already an alias it is not the original backend, so
// we don't want to fallback to other storages any longer.
if ($container->hasAlias($id)) {
continue;
}
if ($container->hasDefinition("$default_backend.$id") || $container->hasAlias("$default_backend.$id")) {
$container->setAlias($id, new Alias("$default_backend.$id"));
}
}
}
}

View File

@ -0,0 +1,93 @@
<?php
/**
* @file
* Contains \Drupal\Tests\Core\DependencyInjection\Compiler\BackendCompilerPassTest.
*/
namespace Drupal\Tests\Core\DependencyInjection\Compiler;
use Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
/**
* @coversDefaultClass \Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass
* @group DependencyInjection
*/
class BackendCompilerPassTest extends UnitTestCase {
/**
* The tested backend compiler pass.
*
* @var \Drupal\Core\DependencyInjection\Compiler\BackendCompilerPass
*/
protected $backendPass;
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->backendPass = new BackendCompilerPass();
}
/**
* Tests the process method.
*
* @param string $expected_class
* The expected used class.
* @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
* The container.
*
* @dataProvider providerTestProcess
*
* @covers ::process
*/
public function testProcess($expected_class, ContainerBuilder $container) {
$this->backendPass->process($container);
$this->assertInstanceOf($expected_class, $container->get('service'));
}
/**
* Provides test data for testProcess().
*
* @return array
*/
public function providerTestProcess() {
$data = array();
// Add a container with no set default_backend.
$container = new ContainerBuilder();
$prefix = '\\' . __NAMESPACE__ . '\\';
$container->setDefinition('service', (new Definition($prefix . 'ServiceClassDefault'))->addTag('backend_overridable'));
$container->setDefinition('mysql.service', new Definition($prefix . 'ServiceClassMysql'));
$data[] = array($prefix . 'ServiceClassDefault', $container);
// Set the default_backend so the mysql service should be used.
$container = clone $container;
$container->setParameter('default_backend', 'mysql');
$data[] = array($prefix . 'ServiceClassMysql', $container);
// Configure a manual alias for the service, so ensure that it is not
// overridden by the default backend.
$container = clone $container;
$container->setDefinition('mariadb.service', new Definition($prefix . 'ServiceClassMariaDb'));
$container->setAlias('service', new Alias('mariadb.service'));
$data[] = array($prefix . 'ServiceClassMariaDb', $container);
return $data;
}
}
class ServiceClassDefault {
}
class ServiceClassMysql extends ServiceClassDefault {
}
class ServiceClassMariaDb extends ServiceClassMysql {
}