Skip to content

Commit cc118cb

Browse files
committed
[WIP][HttpKernel] Added support for registering Bundle dependencies
For further info on the new function and why it returns class names see BundleDependenciesInterface.
1 parent 17ad6fd commit cc118cb

File tree

3 files changed

+157
-9
lines changed

3 files changed

+157
-9
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\Bundle;
13+
14+
/**
15+
* Class BundleDependenciesInterface
16+
*
17+
* Adds capability to let Bundles specify other bundles they need to load (before this one).
18+
*
19+
* This allows you to define only the bundle you want to use in registerBundles(), but don't need to take care about
20+
* registering the dependencies it uses, and you won't need to make any changes in your kernel if those dependencies
21+
* changes.
22+
*
23+
* In this interface bundle dependencies are returned as FQN strings because several bundles (and root) might register
24+
* the same bundle. This means dependencies can not have dependencies in its constructor, reflecting best practice in
25+
* Symfony of not having any logic in Bundle constructors given it is executed on every Kernel boot.
26+
*
27+
* @author André Roemcke <andre.romcke@ez.no>
28+
* @since Symfony 2.7
29+
*
30+
* @api
31+
*/
32+
interface BundleDependenciesInterface
33+
{
34+
/**
35+
* Returns an array of bundle dependencies Kernel should register on boot
36+
*
37+
* Dependencies will be registered before current bundle, implying current bundle *MUST* be loaded after as it
38+
* for instance extends it.
39+
*
40+
* Example of use:
41+
* ```php
42+
* class AcmeBundle extends Bundle implements BundleDependenciesInterface
43+
* {
44+
* public function getBundleDependencies()
45+
* {
46+
* return array(
47+
*
48+
* // All values must be FQN strings to avoid bundles being loaded several times
49+
* 'FOS\HttpCacheBundle\FOSHttpCacheBundle',
50+
*
51+
* // If you require PHP 5.5 or higher it is better to use `::CLASS` constant:
52+
* Oneup\FlysystemBundle\OneupFlysystemBundle::CLASS
53+
* );
54+
* }
55+
* }
56+
* ```
57+
*
58+
*
59+
* @return string[] An array of bundle class (FQN) names as strings.
60+
*
61+
* @api
62+
*/
63+
public function getBundleDependencies();
64+
}

src/Symfony/Component/HttpKernel/Kernel.php

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Symfony\Component\HttpFoundation\Request;
2626
use Symfony\Component\HttpFoundation\Response;
2727
use Symfony\Component\HttpKernel\Bundle\BundleInterface;
28+
use Symfony\Component\HttpKernel\Bundle\BundleDependenciesInterface;
2829
use Symfony\Component\HttpKernel\Config\EnvParametersResource;
2930
use Symfony\Component\HttpKernel\Config\FileLocator;
3031
use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass;
@@ -459,7 +460,7 @@ protected function initializeBundles()
459460
$topMostBundles = array();
460461
$directChildren = array();
461462

462-
foreach ($this->registerBundles() as $bundle) {
463+
foreach ($this->orderedRegisteredBundlesAndDependencies() as $bundle) {
463464
$name = $bundle->getName();
464465
if (isset($this->bundles[$name])) {
465466
throw new \LogicException(sprintf('Trying to register two bundles with the same name "%s"', $name));
@@ -505,6 +506,89 @@ protected function initializeBundles()
505506
}
506507
}
507508

509+
/**
510+
* Returns an array of bundles to register, with dependencies, ordered
511+
*
512+
* @return \Symfony\Component\HttpKernel\Bundle\BundleInterface[] An array of bundle instances.
513+
*/
514+
protected function orderedRegisteredBundlesAndDependencies()
515+
{
516+
$bundles = $this->registerBundles();
517+
$topMostBundles = $topMostBundlesMap = array();
518+
$hasDependencies = false;
519+
520+
// Build up bundles as a hash with FQN for basis to rebuild again in correct order given dependencies
521+
foreach ($bundles as $bundle) {
522+
$topMostBundles[] = $bundleFQN = get_class($bundle);
523+
$topMostBundlesMap[$bundleFQN] = $bundle;
524+
525+
if ($bundle instanceof BundleDependenciesInterface) {
526+
$hasDependencies = true;
527+
}
528+
}
529+
530+
if (!$hasDependencies) {
531+
return $bundles;
532+
}
533+
534+
// Rebuild bundles order with dependencies recursively
535+
$bundles = array();
536+
$this->appendDependenciesRecursively(
537+
$topMostBundles,
538+
$bundles,
539+
$topMostBundlesMap,
540+
get_class($this)
541+
);
542+
543+
return $bundles;
544+
}
545+
546+
/**
547+
* Append dependencies of bundles recursively
548+
*
549+
* Accepted arguments are as documented below where `string` implies a string with class FQN(Fully Qualified Name),
550+
* this string must be in exactly same format as returned by get_class() and PHP 5.5's CLASS constant.
551+
* Example of FQN string: "Symfony\Component\HttpKernel\Bundle\BundleInterface"
552+
*
553+
* @param string[] $directChildren Dependencies to apply
554+
* @param \Symfony\Component\HttpKernel\Bundle\BundleInterface[<string>] $bundles Ordered bundles to append dependencies to
555+
* @param \Symfony\Component\HttpKernel\Bundle\BundleInterface[<string>] $topMostBundlesMap Loaded Bundles from registerRootBundles()
556+
* @param string $parent For use in exception message if a dependency can not be loaded.
557+
*
558+
* @throws \Exception
559+
*/
560+
protected function appendDependenciesRecursively(array $directChildren, array &$bundles, array $topMostBundlesMap, $parent)
561+
{
562+
while (!empty($directChildren)) {
563+
$dependencyFQN = array_shift($directChildren);
564+
if (isset($bundles[$dependencyFQN])) {
565+
continue;
566+
}
567+
568+
// If already loaded root bundle, use that to not re instantiate
569+
if (isset($topMostBundlesMap[$dependencyFQN])) {
570+
$dependency = $topMostBundlesMap[$dependencyFQN];
571+
} else {
572+
if (!class_exists($dependencyFQN)) {
573+
throw new \RuntimeException(sprintf("Could not find Bundle '%s', set as dependency by '%s'", $dependencyFQN, $parent));
574+
}
575+
$dependency = new $dependencyFQN();
576+
}
577+
578+
// Append dependencies of the dependency before we append dependency itself
579+
if ($dependency instanceof BundleDependenciesInterface) {
580+
$this->appendDependenciesRecursively(
581+
$dependency->getBundleDependencies(),
582+
$bundles,
583+
$topMostBundlesMap,
584+
$dependencyFQN
585+
);
586+
}
587+
588+
$bundles[$dependencyFQN] = $dependency;
589+
}
590+
}
591+
508592
/**
509593
* Gets the container class.
510594
*

src/Symfony/Component/HttpKernel/Tests/KernelTest.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -615,10 +615,10 @@ public function testInitializeBundles()
615615
$child = $this->getBundle(null, 'ParentABundle', 'ChildABundle');
616616

617617
// use test kernel so we can access getBundleMap()
618-
$kernel = $this->getKernelForTest(array('registerBundles'));
618+
$kernel = $this->getKernelForTest(array('orderedRegisteredBundlesAndDependencies'));
619619
$kernel
620620
->expects($this->once())
621-
->method('registerBundles')
621+
->method('orderedRegisteredBundlesAndDependencies')
622622
->will($this->returnValue(array($parent, $child)))
623623
;
624624
$kernel->boot();
@@ -634,10 +634,10 @@ public function testInitializeBundlesSupportInheritanceCascade()
634634
$child = $this->getBundle(null, 'ParentBBundle', 'ChildBBundle');
635635

636636
// use test kernel so we can access getBundleMap()
637-
$kernel = $this->getKernelForTest(array('registerBundles'));
637+
$kernel = $this->getKernelForTest(array('orderedRegisteredBundlesAndDependencies'));
638638
$kernel
639639
->expects($this->once())
640-
->method('registerBundles')
640+
->method('orderedRegisteredBundlesAndDependencies')
641641
->will($this->returnValue(array($grandparent, $parent, $child)))
642642
;
643643
$kernel->boot();
@@ -666,10 +666,10 @@ public function testInitializeBundlesSupportsArbitraryBundleRegistrationOrder()
666666
$child = $this->getBundle(null, 'ParentCBundle', 'ChildCBundle');
667667

668668
// use test kernel so we can access getBundleMap()
669-
$kernel = $this->getKernelForTest(array('registerBundles'));
669+
$kernel = $this->getKernelForTest(array('orderedRegisteredBundlesAndDependencies'));
670670
$kernel
671671
->expects($this->once())
672-
->method('registerBundles')
672+
->method('orderedRegisteredBundlesAndDependencies')
673673
->will($this->returnValue(array($parent, $grandparent, $child)))
674674
;
675675
$kernel->boot();
@@ -816,7 +816,7 @@ protected function getBundle($dir = null, $parent = null, $className = null, $bu
816816
*/
817817
protected function getKernel(array $methods = array(), array $bundles = array())
818818
{
819-
$methods[] = 'registerBundles';
819+
$methods[] = 'orderedRegisteredBundlesAndDependencies';
820820

821821
$kernel = $this
822822
->getMockBuilder('Symfony\Component\HttpKernel\Kernel')
@@ -825,7 +825,7 @@ protected function getKernel(array $methods = array(), array $bundles = array())
825825
->getMockForAbstractClass()
826826
;
827827
$kernel->expects($this->any())
828-
->method('registerBundles')
828+
->method('orderedRegisteredBundlesAndDependencies')
829829
->will($this->returnValue($bundles))
830830
;
831831
$p = new \ReflectionProperty($kernel, 'rootDir');

0 commit comments

Comments
 (0)