I have a class ParentIdResolver which returns the parent id of the product based on the type.
The class looks like:
<?php
namespace App\Model;
use App\Model\Product\Bundle;
use App\Model\Product\Configurable;
use App\Model\Product\Downloadable;
class ParentIdResolver
{
/**
* #var Bundle
*/
private $bundle;
/**
* #var Configurable
*/
private $configurable;
/**
* #var Downloadable
*/
private $downloadable;
public function __construct(
Bundle $bundle,
Configurable $configurable,
Downloadable $downloadable
) {
$this->bundle = $bundle;
$this->configurable = $configurable;
$this->downloadable = $downloadable;
}
public function getParentId($productId, $productType)
{
$parentIds = [];
if ($productType == 'bundle') {
$parentIds = $this->bundle->getParentIdsByChild($productId);
} elseif ($productType == 'configurable') {
$parentIds = $this->configurable->getParentIdsByChild($productId);
} elseif ($productType == 'downloadable') {
$parentIds = $this->downloadable->getParentIdsByChild($productId);
}
return $parentIds[0] ?? null;
}
}
And I am trying to test the getParentId() as:
<?php
namespace App\Test\Unit;
use PHPUnit\Framework\TestCase;
use App\Model\ParentIdResolver;
use App\Model\Product\Bundle;
use App\Model\Product\Configurable;
use App\Model\Product\Downloadable;
class ParentIdResolverTest extends TestCase
{
protected $model;
protected $bundleMock;
protected $configurableMock;
protected $downloadableMock;
public function setUp(): void
{
$this->bundleMock = $this->createPartialMock(
Bundle::class,
['getParentIdsByChild']
);
$this->configurableMock = $this->createPartialMock(
Configurable::class,
['getParentIdsByChild']
);
$this->downloadableMock = $this->createPartialMock(
Downloadable::class,
['getParentIdsByChild']
);
$this->model = new ParentIdResolver(
$this->bundleMock,
$this->configurableMock,
$this->downloadableMock
);
}
/**
* #dataProvider getParentIdDataProvider
*/
public function testGetParentId($productId, $productType, $parentId)
{
if ($productType == 'bundle') {
$this->bundleMock->expects($this->any())
->method('getParentIdsByChild')
->willReturn([$parentId]);
}
if ($productType == 'configurable') {
$this->configurableMock->expects($this->any())
->method('getParentIdsByChild')
->willReturn([$parentId]);
}
if ($productType == 'downloadable') {
$this->downloadableMock->expects($this->any())
->method('getParentIdsByChild')
->willReturn([$parentId]);
}
$this->assertEquals($parentId, $this->model->getParentId($productId, $productType));
}
public function getParentIdDataProvider()
{
return [
[1, 'bundle', 11],
[2, 'configurable', 22],
[3, 'downloadable', 33],
];
}
}
And I don't feel that I am doing it correctly, maybe I need to refactor the main class?
Please suggest how would you refactor or write unit test in this case.
Personally I would consider moving the responsibility for resolving the right class towards each class itself. Some would call this "ask, don't tell". It'll look something like this
<?php
namespace App\Model;
use App\Model\Product\ResolvesParentId;
class ParentIdResolver
{
/** #var ResolvesParentId[] */
private $parentIdResolvers;
public function __construct(array $parentIdResolvers)
{
$this->parentIdResolvers = $parentIdResolvers;
}
public function getParentId(int $productId, string $productType): int
{
foreach ($this->parentIdResolvers as $parentIdResolver) {
if ($parentIdResolver->supports($productType)) {
return $parentIdResolver->getParentId($productId)[0] ?? null;
}
}
return null;
}
}
<?php
namespace App\Model\Product;
interface ResolvesParentId
{
public function supports(string $productType): bool;
public function getParentIdsByChild(int $productId): array;
}
<?php
namespace App\Model\Product;
class Bundle implements ResolvesParentId
{
public function supports(string $productType): bool
{
return $productType === 'bundle';
}
public function getParentIdsByChild(int $productId): array
{
// Your implementation here.
}
}
<?php
namespace App\Model\Product;
class Configurable implements ResolvesParentId
{
public function supports(string $productType): bool
{
return $productType === 'configurable';
}
public function getParentIdsByChild(int $productId): array
{
// Your implementation here.
}
}
<?php
namespace App\Model\Product;
class Downloadable implements ResolvesParentId
{
public function supports(string $productType): bool
{
return $productType === 'downloadable';
}
public function getParentIdsByChild(int $productId): array
{
// Your implementation here.
}
}
Some consider this overkill, which depends purely on the situation you're in. Do you expect the if/else to grow in the future? Then this solution might be for you.
consider this example:
class MyClass
{
public function doSomething()
{
$this->injected->getIt();
}
}
so far so simple (apart from injected is not injected). So, in full version:
class MyClass
{
/**
* #var Injected
*/
private $injected;
public function __constructor(Injected $injected)
{
$this->injected = $injected;
}
public function doSomething()
{
$this->injected->getIt();
}
}
but I find it enoromous. Why to pollute my class with tons of code of DI? Lets split this into two entities:
trait MyClassTrait
{
/**
* #var Injected
*/
private $injected;
public function __constructor(Injected $injected)
{
$this->injected = $injected;
}
}
class MyClass
{
use MyClassTrait;
public function doSomething()
{
$this->injected->getIt();
}
}
its much nicer although I never seen anybody using it like this. Is it a good approach?
For example like this:
<?php
class Factory
{
private $services = [];
public function __construct() {
$this->services[self::class] = $this;
}
public function getByType($type){
if(isset($services[$type])){
return $services[$type];
}
if(class_exists($type)){
$reflection = new ReflectionClass($type);
$constructor = $reflection->getConstructor();
$parameters = [];
if($constructor)
foreach($constructor->getParameters() as $parameter){
if($parameter->getClass()) {
$parameters[] = $this->getByType($parameter->getClass()->name);
} else if($parameter->isDefaultValueAvailable()){
$parameters[] = $parameter->getDefaultValue();
}
}
return $services[$type] = $reflection->newInstanceArgs($parameters);
} // else throw Exception...
}
}
abstract class DI
{
public function __construct(Factory $factory) {
$reflection = new ReflectionClass(get_class($this));
foreach($reflection->getProperties() as $property){
preg_match('/#var ([^ ]+) #inject/', $property->getDocComment(), $annotation);
if($annotation){
$className = $annotation[1];
if(class_exists($className)){
$property->setAccessible(true);
$property->setValue($this, $factory->getByType($className));
} // else throw Exception...
}
}
}
}
class Injected
{
public function getIt($string){
echo $string.'<br />';
}
}
class DIByConstructor
{
/** #var Injected */
private $byConstructor;
public function __construct(Injected $injected) {
$this->byConstructor = $injected;
}
public function doSomething()
{
echo 'Class: '.self::class.'<br />';
$this->byConstructor->getIt('By Constructor');
echo '<br />';
}
}
class DIByAnnotation extends DI
{
/** #var Injected #inject */
private $byAnnotation;
public function doSomething()
{
echo 'Class: '.self::class.'<br />';
$this->byAnnotation->getIt('By Annotation');
echo '<br />';
}
}
class DIBothMethods extends DI
{
/** #var Injected */
private $byConstructor;
/** #var Injected #inject */
private $byAnnotation;
public function __construct(Factory $factory, Injected $injected) {
parent::__construct($factory);
$this->byConstructor = $injected;
}
public function doSomething()
{
echo 'Class: '.self::class.'<br />';
$this->byConstructor->getIt('By Constructor');
$this->byAnnotation->getIt('By Annotation');
echo '<br />';
}
}
$factory = new Factory();
$DIByConstructor = $factory->getByType('DIByConstructor');
$DIByConstructor->doSomething();
$DIByAnnotation = $factory->getByType('DIByAnnotation');
$DIByAnnotation->doSomething();
$DIBothMethods = $factory->getByType('DIBothMethods');
$DIBothMethods->doSomething();
Note that with #Kazz approaching (DI by Annotations) you cannot reference an Interface, instead you are referencing a Class. So this is good for fast instantiating with almost zero verbose but at the end, you are loosing all the DI potential.
I've been doing a lot of reading on constructors and initialising variables, and I've came across a problem which I'm trying to solve. I'm trying to solve the lack of generics support by introducing a variable that needs to be initialised by the subclass.
<?php
abstract class Collection_Base {
protected $typeOf; // Must be initialised by the subclass
protected $collection = array();
}
class Cookie_Collection extends Collection_Base {
protected $typeOf = 'System\Web\Http_Cookie';
public function set ($item) {
if (!$item instanceof $this->typeOf) {
throw new \InvalidArgumentException;
}
$this->collection[] = $item;
}
}
?>
So I was wondering, is it bad practice to include variable which must be initialised by subclass constructor in PHP? Is there anything I need to be aware of when doing so?
While not directly related, I've used the following sources to gather my information:
http://docs.hhvm.com/manual/en/hack.otherrulesandfeatures.classinitialization.php
http://ralphschindler.com/2012/03/09/php-constructor-best-practices-and-the-prototype-pattern
SOLUTION
<?php
abstract class Collection_Base {
protected $collection = array();
public abstract function getType();
private function getTypeInternal () {
$type = $this->getType();
if (is_class($type)) {
throw new \UnexpectedValueException;
}
return $type;
}
public function set ($item) {
$type = $this->getTypeInternal();
if (!$item instanceof $type) {
throw new \InvalidArgumentException;
}
$this->collection[] = $item;
}
}
class Cookie_Collection extends Collection_Base {
protected $type = 'System\Web\Http_Cookie';
public function getType () {
return $this->type;
}
}
?>
I thought I recognized this as an anti-pattern, so I looked for where I read about it, but then I remembered it was this: http://en.wikipedia.org/wiki/Call_super, which isn't quite the same thing.
On to what you are doing however. There are a lot of similar libraries which use a practice like this, however they differ by enforcing the practice in way of abstract methods:
abstract class Collection_Base {
protected $typeOf;
protected $collection = array();
/**
* #return string
*/
public function getType()
{
if (null === $this->typeOf) {
$this->typeOf = $this->doGetType();
}
return $this->typeOf;
}
/**
* #return string
*/
abstract protected function doGetType();
}
class Cookie_Collection extends Collection_Base {
/**
* #param $item
*/
public function set ($item) {
if (!$item instanceof $this->getType()) {
throw new \InvalidArgumentException;
}
$this->collection[] = $item;
}
/**
* #inheritdoc
*/
protected function doGetType()
{
return 'System\Configuration\Configuration_Element';
}
}
Use a Factory pattern to hide constructor details ... see http://www.phptherightway.com/pages/Design-Patterns.html
Make Collection_Base an abstract class and define a method that returns appropriate class name:
abstract class Collection_Base
{
protected $collection = [];
public function add($item)
{
if (!$item instanceof $this->getType()) {
throw new \InvalidArgumentException();
}
$this->collection[] = $item;
}
abstract protected function getType();
}
class Collection_Cookie extends Collection_Base
{
protected function getType()
{
return Configuration_Element::class;
}
}
Using this approach it's not possible for other developers to forget about type "property".
EDIT:
Using a factory, as suggested by Luca Rocchi is also a very good idea.
I am trying to implement a simple menu composite pattern.
These are the following classes i came up with.
MenuItem:
namespace MYNAME\MYBUNDLE\Entity;
use MYNAME\MYBUNDLE\Menu\MenuComponent;
class MenuItem implements MenuComponent
{
private $id;
private $name;
private $path;
private $parent;
private $visible;
private $createdOn;
private $templating;
private $attr;
private $children;
private $website;
private $position = 1;
public function __construct($name = null, $path = null, $attr = array(), $visible = true)
{
$this->name = $name;
$this->path = $path;
$this->visible = $visible;
$this->attr = $attr;
$this->createdOn = new \DateTime;
}
public function prePersist()
{
$this->createdOn = new \DateTime;
}
public function build()
{
$data['menu_item'] = $this;
$data['options'] = $this->attr;
if($this->hasChildren())
return $this->templating->render('MYBUNDLE:Menu:menu_dropdown.html.twig', $data);
if($this->isChild())
return $this->parent->getTemplating()->render('MYBUNDLE:Menu:menu_item.html.twig', $data);
return $this->templating->render('MYBUNDLE:Menu:menu_item.html.twig', $data);
}
public function __toString()
{
return $this->name;
}
public function setTemplating($templating)
{
$this->templating = $templating;
}
/**
* #return bool
*/
public function isChild()
{
return $this->hasParent();
}
/**
* #return bool
*/
public function hasParent()
{
return isset($this->parent);
}
/**
* #return bool
*/
public function hasChildren()
{
return count($this->children) > 0;
}
}
If left out the getters and setters to make it a bit shorter here.
As you can see this is the entity and it contains a build() function, however this function uses the render method which in my opinion shouldn't be in an entity.
MenuController
<?php
namespace MYNAME\MYBUNDLE\Controller;
use MYNAME\MYBUNDLE\Menu\Menu;
use MYNAME\MYBUNDLE\Entity\MenuItem;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
class MenuController extends Controller
{
public function generateAction()
{
$menu = new Menu($this->get('templating'));
// load menu items
$items = $this->getDoctrine()->getRepository('MYBUNDLE:MenuItem')->findOrdered();
foreach($items as $item)
{
if(!$item->hasParent())
$menu->add($item);
}
return new Response($menu->build());
}
}
The MenuController gets called to render the menu:
{{ render(controller('MYBUNDLE:Menu:generate')) }}
I would also like this to be different since it doesn't look right. Perhaps it's better to create a twig function to render the menu?
MenuComponent:
namespace MYNAME\MYBUNDLE\Menu;
interface MenuComponent {
public function build();
}
Menu:
namespace MYNAME\MYBUNDLE\Menu;
class Menu implements MenuComponent
{
private $children;
private $templating;
public function __construct($templating)
{
$this->templating = $templating;
}
public function add(MenuComponent $component)
{
$component->setTemplating($this->templating);
$this->children[] = $component;
}
public function build()
{
return $this->templating->render('MYBUNDLE:Menu:menu.html.twig', array("menu_items" => $this->children));
}
}
Menu Contains the MenuComponents and will render the menu first, in each MenuItem it's build() method is called.
I think it's better to remove the rendering logic from my MenuItem entity and place this somewhere else, however i can't figure out on how to do this properly within this design pattern.
Any help or suggestion is appreciated.
Do anyone know why this occurs?
as far I can get, the child class method is declared in the same way as parent's.
Thanks!
here is my kernel code:
<?php
require_once __DIR__.'/../src/autoload.php';
use Symfony\Framework\Kernel;
use Symfony\Components\DependencyInjection\Loader\YamlFileLoader as ContainerLoader;
use Symfony\Components\Routing\Loader\YamlFileLoader as RoutingLoader;
use Symfony\Framework\KernelBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\ZendBundle\ZendBundle;
use Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle;
use Symfony\Bundle\DoctrineBundle\DoctrineBundle;
use Symfony\Bundle\DoctrineMigrationsBundle\DoctrineMigrationsBundle;
use Symfony\Bundle\DoctrineMongoDBBundle\DoctrineMongoDBBundle;
use Symfony\Bundle\PropelBundle\PropelBundle;
use Symfony\Bundle\TwigBundle\TwigBundle;
use Application\UfaraBundle\UfaraBundle;
class UfaraKernel extends Kernel {
public function registerRootDir() {
return __DIR__;
}
public function registerBundles() {
$bundles = array(
new KernelBundle(),
new FrameworkBundle(),
new ZendBundle(),
new SwiftmailerBundle(),
new DoctrineBundle(),
//new DoctrineMigrationsBundle(),
//new DoctrineMongoDBBundle(),
//new PropelBundle(),
//new TwigBundle(),
new UfaraBundle(),
);
if ($this->isDebug()) {
}
return $bundles;
}
public function registerBundleDirs() {
$bundles = array(
'Application' => __DIR__.'/../src/Application',
'Bundle' => __DIR__.'/../src/Bundle',
'Symfony\\Framework' => __DIR__.'/../src/vendor/symfony/src/Symfony/Framework',
'Symfony\\Bundle' => __DIR__.'/../src/vendor/symfony/src/Symfony/Bundle',
);
return $bundles;
}
public function registerContainerConfiguration(LoaderInterface $loader) {
return $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
}
public function registerRoutes() {
$loader = new RoutingLoader($this->getBundleDirs());
return $loader->load(__DIR__.'/config/routing.yml');
}
}
here is the parent class code:
<?php
namespace Symfony\Framework;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
use Symfony\Component\DependencyInjection\Resource\FileResource;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Loader\DelegatingLoader;
use Symfony\Component\DependencyInjection\Loader\LoaderResolver;
use Symfony\Component\DependencyInjection\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\DependencyInjection\Loader\IniFileLoader;
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Framework\ClassCollectionLoader;
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien.potencier#symfony-project.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* The Kernel is the heart of the Symfony system. It manages an environment
* that can host bundles.
*
* #author Fabien Potencier <fabien.potencier#symfony-project.org>
*/
abstract class Kernel implements HttpKernelInterface, \Serializable
{
protected $bundles;
protected $bundleDirs;
protected $container;
protected $rootDir;
protected $environment;
protected $debug;
protected $booted;
protected $name;
protected $startTime;
protected $request;
const VERSION = '2.0.0-DEV';
/**
* Constructor.
*
* #param string $environment The environment
* #param Boolean $debug Whether to enable debugging or not
*/
public function __construct($environment, $debug)
{
$this->environment = $environment;
$this->debug = (Boolean) $debug;
$this->booted = false;
$this->rootDir = realpath($this->registerRootDir());
$this->name = basename($this->rootDir);
if ($this->debug) {
ini_set('display_errors', 1);
error_reporting(-1);
$this->startTime = microtime(true);
} else {
ini_set('display_errors', 0);
}
}
public function __clone()
{
if ($this->debug) {
$this->startTime = microtime(true);
}
$this->booted = false;
$this->container = null;
$this->request = null;
}
abstract public function registerRootDir();
abstract public function registerBundles();
abstract public function registerBundleDirs();
abstract public function registerContainerConfiguration(LoaderInterface $loader);
/**
* Checks whether the current kernel has been booted or not.
*
* #return boolean $booted
*/
public function isBooted()
{
return $this->booted;
}
/**
* Boots the current kernel.
*
* This method boots the bundles, which MUST set
* the DI container.
*
* #throws \LogicException When the Kernel is already booted
*/
public function boot()
{
if (true === $this->booted) {
throw new \LogicException('The kernel is already booted.');
}
if (!$this->isDebug()) {
require_once __DIR__.'/bootstrap.php';
}
$this->bundles = $this->registerBundles();
$this->bundleDirs = $this->registerBundleDirs();
$this->container = $this->initializeContainer();
// load core classes
ClassCollectionLoader::load(
$this->container->getParameter('kernel.compiled_classes'),
$this->container->getParameter('kernel.cache_dir'),
'classes',
$this->container->getParameter('kernel.debug'),
true
);
foreach ($this->bundles as $bundle) {
$bundle->setContainer($this->container);
$bundle->boot();
}
$this->booted = true;
}
/**
* Shutdowns the kernel.
*
* This method is mainly useful when doing functional testing.
*/
public function shutdown()
{
$this->booted = false;
foreach ($this->bundles as $bundle) {
$bundle->shutdown();
$bundle->setContainer(null);
}
$this->container = null;
}
/**
* Reboots the kernel.
*
* This method is mainly useful when doing functional testing.
*
* It is a shortcut for the call to shutdown() and boot().
*/
public function reboot()
{
$this->shutdown();
$this->boot();
}
/**
* Gets the Request instance associated with the master request.
*
* #return Request A Request instance
*/
public function getRequest()
{
return $this->request;
}
/**
* Handles a request to convert it to a response by calling the HttpKernel service.
*
* #param Request $request A Request instance
* #param integer $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST)
* #param Boolean $raw Whether to catch exceptions or not
*
* #return Response $response A Response instance
*/
public function handle(Request $request = null, $type = HttpKernelInterface::MASTER_REQUEST, $raw = false)
{
if (false === $this->booted) {
$this->boot();
}
if (null === $request) {
$request = $this->container->get('request');
} else {
$this->container->set('request', $request);
}
if (HttpKernelInterface::MASTER_REQUEST === $type) {
$this->request = $request;
}
$response = $this->container->getHttpKernelService()->handle($request, $type, $raw);
$this->container->set('request', $this->request);
return $response;
}
/**
* Gets the directories where bundles can be stored.
*
* #return array An array of directories where bundles can be stored
*/
public function getBundleDirs()
{
return $this->bundleDirs;
}
/**
* Gets the registered bundle names.
*
* #return array An array of registered bundle names
*/
public function getBundles()
{
return $this->bundles;
}
/**
* Checks if a given class name belongs to an active bundle.
*
* #param string $class A class name
*
* #return Boolean true if the class belongs to an active bundle, false otherwise
*/
public function isClassInActiveBundle($class)
{
foreach ($this->bundles as $bundle) {
$bundleClass = get_class($bundle);
if (0 === strpos($class, substr($bundleClass, 0, strrpos($bundleClass, '\\')))) {
return true;
}
}
return false;
}
/**
* Returns the Bundle name for a given class.
*
* #param string $class A class name
*
* #return string The Bundle name or null if the class does not belongs to a bundle
*/
public function getBundleForClass($class)
{
$namespace = substr($class, 0, strrpos($class, '\\'));
foreach (array_keys($this->getBundleDirs()) as $prefix) {
if (0 === $pos = strpos($namespace, $prefix)) {
return substr($namespace, strlen($prefix) + 1, strpos($class, 'Bundle\\') + 7);
}
}
}
public function getName()
{
return $this->name;
}
public function getSafeName()
{
return preg_replace('/[^a-zA-Z0-9_]+/', '', $this->name);
}
public function getEnvironment()
{
return $this->environment;
}
public function isDebug()
{
return $this->debug;
}
public function getRootDir()
{
return $this->rootDir;
}
public function getContainer()
{
return $this->container;
}
public function getStartTime()
{
return $this->debug ? $this->startTime : -INF;
}
public function getCacheDir()
{
return $this->rootDir.'/cache/'.$this->environment;
}
public function getLogDir()
{
return $this->rootDir.'/logs';
}
protected function initializeContainer()
{
$class = $this->getSafeName().ucfirst($this->environment).($this->debug ? 'Debug' : '').'ProjectContainer';
$location = $this->getCacheDir().'/'.$class;
$reload = $this->debug ? $this->needsReload($class, $location) : false;
if ($reload || !file_exists($location.'.php')) {
$this->buildContainer($class, $location.'.php');
}
require_once $location.'.php';
$container = new $class();
$container->set('kernel', $this);
return $container;
}
public function getKernelParameters()
{
$bundles = array();
foreach ($this->bundles as $bundle) {
$bundles[] = get_class($bundle);
}
return array_merge(
array(
'kernel.root_dir' => $this->rootDir,
'kernel.environment' => $this->environment,
'kernel.debug' => $this->debug,
'kernel.name' => $this->name,
'kernel.cache_dir' => $this->getCacheDir(),
'kernel.logs_dir' => $this->getLogDir(),
'kernel.bundle_dirs' => $this->bundleDirs,
'kernel.bundles' => $bundles,
'kernel.charset' => 'UTF-8',
'kernel.compiled_classes' => array(),
),
$this->getEnvParameters()
);
}
protected function getEnvParameters()
{
$parameters = array();
foreach ($_SERVER as $key => $value) {
if ('SYMFONY__' === substr($key, 0, 9)) {
$parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value;
}
}
return $parameters;
}
protected function needsReload($class, $location)
{
if (!file_exists($location.'.meta') || !file_exists($location.'.php')) {
return true;
}
$meta = unserialize(file_get_contents($location.'.meta'));
$time = filemtime($location.'.php');
foreach ($meta as $resource) {
if (!$resource->isUptodate($time)) {
return true;
}
}
return false;
}
protected function buildContainer($class, $file)
{
$parameterBag = new ParameterBag($this->getKernelParameters());
$container = new ContainerBuilder($parameterBag);
foreach ($this->bundles as $bundle) {
$bundle->registerExtensions($container);
if ($this->debug) {
$container->addObjectResource($bundle);
}
}
if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) {
$container->merge($cont);
}
$container->freeze();
foreach (array('cache', 'logs') as $name) {
$dir = $container->getParameter(sprintf('kernel.%s_dir', $name));
if (!is_dir($dir)) {
if (false === #mkdir($dir, 0777, true)) {
die(sprintf('Unable to create the %s directory (%s)', $name, dirname($dir)));
}
} elseif (!is_writable($dir)) {
die(sprintf('Unable to write in the %s directory (%s)', $name, $dir));
}
}
// cache the container
$dumper = new PhpDumper($container);
$content = $dumper->dump(array('class' => $class));
if (!$this->debug) {
$content = self::stripComments($content);
}
$this->writeCacheFile($file, $content);
if ($this->debug) {
$container->addObjectResource($this);
// save the resources
$this->writeCacheFile($this->getCacheDir().'/'.$class.'.meta', serialize($container->getResources()));
}
}
protected function getContainerLoader(ContainerInterface $container)
{
$resolver = new LoaderResolver(array(
new XmlFileLoader($container, $this->getBundleDirs()),
new YamlFileLoader($container, $this->getBundleDirs()),
new IniFileLoader($container, $this->getBundleDirs()),
new PhpFileLoader($container, $this->getBundleDirs()),
new ClosureLoader($container),
));
return new DelegatingLoader($resolver);
}
/**
* Removes comments from a PHP source string.
*
* We don't use the PHP php_strip_whitespace() function
* as we want the content to be readable and well-formatted.
*
* #param string $source A PHP string
*
* #return string The PHP string with the comments removed
*/
static public function stripComments($source)
{
if (!function_exists('token_get_all')) {
return $source;
}
$output = '';
foreach (token_get_all($source) as $token) {
if (is_string($token)) {
$output .= $token;
} elseif (!in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) {
$output .= $token[1];
}
}
// replace multiple new lines with a single newline
$output = preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $output);
// reformat {} "a la python"
$output = preg_replace(array('/\n\s*\{/', '/\n\s*\}/'), array(' {', ' }'), $output);
return $output;
}
protected function writeCacheFile($file, $content)
{
$tmpFile = tempnam(dirname($file), basename($file));
if (false !== #file_put_contents($tmpFile, $content) && #rename($tmpFile, $file)) {
chmod($file, 0644);
return;
}
throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file));
}
public function serialize()
{
return serialize(array($this->environment, $this->debug));
}
public function unserialize($data)
{
list($environment, $debug) = unserialize($data);
$this->__construct($environment, $debug);
}
}
Your answer lies in the imported namespaces. In the Kernel's file, there's this use clause:
use Symfony\Component\DependencyInjection\Loader\LoaderInterface;
So that ties LoaderInterface to the fully namespaced class Symfony\Component\DependencyInjection\Loader\LoaderInterface.
Basically making the signature:
public function registerContainerConfiguration(Symfony\Component\DependencyInjection\Loader\LoaderInterface $loader);
In your class, you don't import that namespace. So PHP by default assumes the class is in your namespace (since none of the imported namespaces have that interface name).
So your signature is (since you don't declare a namespace):
public function registerContainerConfiguration(\LoaderInterface $loader);
So to get them to match, simply add the use line to the top of your file:
use Symfony\Component\DependencyInjection\Loader\LoaderInterface;