Dynamically Configuring a DI Container

Ok, I’ll start with a brief outline of the system. I think the easiest way to represent it is to show you the file structure.

It’s broken in to 3 parts, the core system, the application and public files.

  • Application
    [LIST]
  • Components
    [LIST]
  • [component_name]
    [LIST]
  • Configuration
  • Controllers
  • Helpers
  • Views
    [/LIST]

    [/LIST]
  • Configuration
  • Layouts
  • Temporary
    [/LIST]
  • System
    [LIST]
  • Configuration
  • Controllers
  • Helpers
  • Libraries
  • Tests
  • Vendor
  • Views
  • Bootstrap.php
    [/LIST]
  • public_html

You’ll notice that there are 3 configuration directories, in System, Application and Components.

In System there is a configuration class could Wiring. It defines the default dic wiring, and looks like this.


<?php
namespace System\\Configuration;

class Wiring
{
    public function wireDependencies(\\System\\DiContainer $dic)
    {
        //dic is based on Fabien Potencier pimple
        
        //example shared object
        $dic->example = $dic->shared(function($c) {
            return new \\System\\Example();
        });
    }
}


Another Wiring class exists in Application and extends the class in System. It looks like this.


<?php
namespace Application\\Configuration;

class Wiring extends \\System\\Configuration\\Wiring
{
    public function wireDependencies(\\System\\DiContainer $dic)
    {
        parent::wireDependencies($dic);
        
        $dic->another_example = $dic->shared(function($c) {
            return new \\System\\AnotherExample();
        });
    }
}


Finally each component has a Wiring class which extends an abstract base. It looks like this.


<?php
namespace Application\\Components\\Example\\Configuration;

class Wiring extends \\System\\Configuration\\Components\\AWiring
{
    public function wireDependencies(\\System\\DiContainer $dic)
    {
        $dic->another_example = $dic->shared(function($c) {
            return new \\System\\AnotherExample();
        });
    }
}


The bootstrap needs to be able to load the Wiring object in Application/Configuration if it exists, otherwise revert to the Wiring in System/Configuration. Like so.


<?php
namespace System\\Bootstrap;

$application_wiring = 'Application\\\\Configuration\\\\Wiring';
$system_wiring      = 'System\\\\Configuration\\\\Wiring';

if (class_exists($application_wiring)) {
    $wiring_config = new $application_wiring();
} else {
    $wiring_config = new $system_wiring();
}

$dic = \\System\\DiContainer();
$wiring_config->wireDependencies($dic);


It then needs to load the Wiring configuration object for each component and run the dic through each of them in order to update any of the wiring.


foreach ($array_of_wiring_objects as $wiring) {
    $wiring->wireDependencies($dic);
}


Now here’s where it gets really difficult. Wiring isn’t the only configuration, there are also things like core and router. Each with different methods and properties.

They need to be compiled similarly to the Wiring, but resulting in a single configuration object, and then stored in the wiring, like so.


<?php

namespace System\\Configuration;

class Wiring
{
    public function wireDependencies(\\System\\DiContainer $dic)
    {
        //configuration container
        $dic->configuration = $dic->shared(function($c) {
            $cdic = new \\System\\DiContainer();

            $cdic->router = $cdic->shared(function($cc) {
                return new Router(); //how to get the correct and updated object here
            });

            return $cdic;
        });
    }
}


This has to all be processed and compiled before the anything else.

I hope that all makes sense, it’s surprisingly difficult to describe.

I’m completely lost with it now and could really do with some pointers.

Best Regards, George

Initial feeling is that there is too much reliance on a single DI instance.

I can a see the need for a default system wiring, and then having an application specific wiring which tweaks the default system wiring as needed.

But I don’t think the components should be relying on a container. They should just have their dependencies satisfied when created, and completely ignorant of any container.

Thanks Ren,

Initial feeling is that there is too much reliance on a single DI instance.
You also say that the components should be “ignorant of any container”. So how do you suggest one container access another? Currently when I call an action controller from the default container, I have to pass it a name ($dic->action_controller(‘example’); ), I then loads the particular components dic and returns the wired up action controller. Is this a good way?

Originally the wiring was a single file included in the bootstrap. The file created an instance on DiContainer, wired up the dependencies and then the bootstrap called the front controller from the DiContainer.

Each component has there own separate wiring for libraries, models, helpers.

I want to be able to install components from a zip. Some components will bundle in libraries that will extend and replace system files. I think the only option to keep components self contained is to allow them to internally change system wide configurations.

The difficulty is compiling this configuration. Using objects to hold the config make a lot of sense until you get to the components, in which case you have to almost merge objects.

When I originality decided to change things around the plan was to use the visitor pattern to “visit” all of the component configurations. This would have required a chunk of code for booting, which would wire up the system (very messy with lots of loops). That would then result in a compiled configuration from which I could call my front controller as before. Once I started writing it I realised it was a really bad idea, but I need this sort of configuration layout, so how else could I do it?

Best Regards, George

Meant the container, rather than any.

If a top level component requires the ability to instantiate other objects to get its’ work done, I’d create a factory interface & implementation, and di container inject that into the component.

If the factory only needs to instantiate one class, can just ask my DI to provide a closure which gets injected.

Sorry Ren, I’m not really sure I understand what you’re saying.

I really need to simplify the whole thing. I’m tempted to put the wiring in files (not classes) then in the boot strap just include all the wiring files (in System, Application and one for each Component) in the right order. This will give me a completed container to use in the system.

The front controller can call the action controller with something like this.


    include \\APPLICATION_LOCATION.$this->request->getComponentName()
            .'/Configuration/ComponentWiring.php';

    $action_controller_class = '\\\\Application\\\\Components\\\\'
            .$this->request->getComponentName()
            .'\\\\Controllers\\\\'
            .$this->request->getControllerName();

    $action_controller = new $action_controller_class($dic);

Other than passing the dic to the component action controller I’m not sure how it could be wired up.

To be honest I almost need it spelled out for me. I feel like I’m coding with blinkers on and I’m struggling to see the whole picture.

Best Regards, George

I think I’ve finally understood the problem. :slight_smile:
I put wiring in a file, rather than class.

Wiring.php…


<?php
	$container = new IoC\\Container();

	$container->registerShared('Config\\Gearman',
		function()
		{
			$config = new Config\\Gearman();
			$config->servers[] = '127.0.0.1';
			return $config;
		});

	$container->registerShared('GearmanClient',
		function(IoC\\Container $container)
		{
			$config = $container->create('Config\\Gearman');
			$client = new \\GearmanClient();
			$config->applyToClient($client);
			return $client;
		});

	$container->register('GearmanWorker',
		function (IoC\\Container $container)
		{
			$config = $container->create('Config\\Gearman');
			$worker = new \\GearmanWorker();
			$config->applyToWorker($worker);
			return $worker;
		});
	return $container;

And then just include/assign to import it into local namespace.


$container = include('Wiring.php');

$gearman = $container->create('GearmanClient');

That’s what I’ll do, I was just way over complicating things. This is my first attempt at a system using dependency injection, It’s kind of a learning exercise.

The only bit I’m still not sure about is instantiating the action controller for a component. The only way I can think of doing it is like this.


    include APPLICATION_LOCATION.$this->request->getComponentName()
            .'/Configuration/ComponentWiring.php';

    $action_controller_class = '\\\\Application\\\\Components\\\\'
            .$this->request->getComponentName()
            .'\\\\Controllers\\\\'
            .$this->request->getControllerName();

    $action_controller = new $action_controller_class($dic);  

Doesn’t really seem right though. Shouldn’t it be the containers job to instantiate the controller?

I also pass the components dic into the action controller. I access it from my actions using

$object_name = $this->models->object_name;

are there better ways I should be doing this?

Best Regards, George