Configuration/DI container

After thinking about configuration for a while, I came up with some new ideas, and I’ve been tinkering with a configuration container class, which is now fairly complete:

There’s an example at the end of the file, and inline notes describing what it is and what it does.

Essentially, I wanted a configuration container that will play well with an IDE, protects against errors and missing configuration, simplifies components with co-dependencies, and still supports late construction.

Figured I’d share this as inspiration for Yii 2.0.

Let me know what you think?

I really like the look of that. I’ll be interested to see the core team’s opinions.

Interesting approach. IDE support is a big pro of it.

Thanks :slight_smile:

I just got fed up with the array-array-array-ness of it all, and started thinking about how to fix that.

I don’t know yet how this compares in terms of performance - it’s going to be a lot more function-calls, as it is right now. Though I could reduce the number of function-calls quite a bit by allowing you to pass multiple configuration-functions with one call, e.g. one per configuration-file.

There’s also more work to do still on actually loading configuration-files - it’s not finished, but it shows off the important bits :slight_smile:

One weakness I see: IDE support doesn’t come from the classes you’re trying to configure. Instead, you have to replicate all configurable properties into the doc block of your ConfigContainer specialization. If you modify a class, you always have to take care to also modify the related config class. Not sure if or how this could be solved though…

Another thing which I think could come in handy is a cache. The container should be able to serve a validated configuration without the need to re-build and re-validate on every request.

No - take another look. The properties are only defined using doc-blocks, you don’t need to modify the class itself. There is no duplication - IDE support and run-time support both come from the @property-annotations.

As I said, I haven’t benchmarked it yet - it may need optimization. I’m not sure a cache would solve anything though - depending on the caching medium, it could even slow things down.

Perhaps better would be a $debug-flag, so you could turn off type-checking (and perhaps other checks) on production-systems?

I just posted an update with a load() method, more inline comments and additional documentation.

Any example on why and how to use it in a real application?

You would use this the same way you use configuration-files now - it’s not really different from what you’re doing with “config/main.php” right now, except of course the syntax is different, and plus all the benefits listed above. I think I’ve listed plenty of reasons “why”? :slight_smile:

As for “how”, there are still some unfinished parts here - but let’s assume that this had been integrated with the application-object in Yii, and here’s a partial example to give you a general idea.

First, the configuration-class for your application:




<?php # "component/MyAppConf.php"


/**

 * @property string $rootPath the root-path (e.g. "protected" folder)

 * @property CDbConnection $connection primary database-connection

 * @property CCache $cache common cache for db-connection and others

 */

class MyAppConf extends Configuration

{}



Next, the “index.php” main dispatch script - I’m skipping some parts here, and probably getting some property-names wrong etc., but just to give you a general idea:




<?php # "index.php"


// (bootstrap Yii here...)


Yii::app()->config->load('main.php');


if ($_SERVER['HTTP_HOST'] == 'localhost') {

  // load additional configuration for test/development environment:

  Yii::app()->config->load('test.php');

}


Yii::app()->run();




Now your application-wide configuration file - this one is always loaded first, so this should contain a complete configuration with defaults for everything:




<?php # "config/main.php"


$this->rootPath = dirname(dirname(__FILE__));


$this->connection = function(CCache $cache) {

  // note that $cache will automatically initialize when CDbConnection is initialized.


  $db = new CDbConnection();


  $db->host = 'db.foo.com';

  $db->username = 'foo';

  $db->password = 'bar';


  $db->cache = $cache;


  return $db;

};


$this->cache = function($rootPath) {

  $c = new CFileCache();


  $c->path = $rootPath.'/runtime/cache';


  return $c;

};




And now my local configuration-overrides for testing - as you saw in "index.php", this only loads when the hostname is "localhost", so I can put my local configuration-overrides here:




<?php # "config/test.php"


$this->configure(function(CDbConnection $connection) {

  $connection->host = 'localhost';

  $connection->username = 'root';

  $connection->password = '';

});




That’s it, a very basic example, probably with some errors here and there, I just typed this up quick to give you some idea of how you would use this…

mindplay, question but isnt this a repost of your other thread that’s very similar to this? (objects vs arrays)

http://www.yiiframework.com/forum/index.php/topic/32951-use-arrays-more-sparingly-in-20/

Hardly a repost, since this is an actual solution and code - although this definitely does relate to that general discussion, this implementation addresses the issue of configuration specifically. Each of the components that currently rely on arrays could receive similar treatments - and it is possible that there’s a subset of functionality in this class that could be separated for reuse in other components…

Brilliant.

Moreover we finally have some good reasons to not have Db config

There are different ways you could approach that - for really hard dependencies like a db username and password, you could add those to the configuration-container itself, alongside the connection-component, e.g.:




<?php


### "components/AppConfig.php"


/**

 * ...

 * @property CDbConnection $connection

 * @property string $dbUsername

 * @property string $dbPassword

 */

class AppConfig extends Configuration

{}


### "config/main.php"


$this->connection = function($dbUsername, $dbPassword) {

  $c = new CDbConnection;


  $c->username = $dbUsername;

  $c->password = $dbPassword;


  return $c;

};


### "config/local.php"


$this->dbUsername = 'root';

$this->dbPassword = '*****';



In this example, dbUsername and dbPassword properties will be injected when the connection-property is fisrt accessed, and checked when you seal() the configuration-container - so having a username and password for the DB becomes a defined requirement, and you’ll get an exception if you forget to configure those.

I didn’t refer to that :D

I can’t say I entirely understand the code but I think I get some of it.

I guess it’s possible to write type checkers for some other common type notations: arrays of scalars such as string[], and where a list of types is separated by |.

What about the type spec ‘mixed’ (the lazy programmer’s friend)?

And could it work recursively for example, @param CDataColumn[] $columns?

"mixed" is supported - basically just turns off the type-checking for the property.

Collection-type syntax (CDataColumn[] etc.) is not supported, and neither is multiple types with "|" - supporting these would take more than the 10 lines of code for the very simple @property-parser I built into this. I think if I were going to support full annotation-parsing, I would lean towards a real meta-data library, rather than hard-coding support into this class.

Configuration groupings can be implemented at the moment by declaring several configuration-types, and defining a property as another Configuration-type - this would provide even more "laziness", as nested Configuration-types would themselves load and initialize late, e.g.:




<?php


# "MainConfig.php"


/** 

 * ...

 * @property SubConfig $sub

 */


# "SubConfig.php"


/**

 * ...

 * @property mixed $foo

 * @property mixed $bar

 */


# "index.php"


$config = new MainConfig;


$config->sub = function() {

  $c = new SubConfig;

  $c->foo = 123;

  return $c;

};


// using late configuration and DI for sub-configuration:


$config->configure(function(SubConfig $sub)) {

  $sub->configure(function($bar, $foo) {

    return $foo+456;

  });

});



And so on…

I know Yii 2.0 is targeting PHP 5.3, but with PHP 5.4 you could simplify some things by adjusting the calling-context on the closures - so, for instance, $this in the anonymous functions would refer to the configuration-container, which would give you better control over initialization of co-dependent components.

For example, you could conditionally (if/else) select a dependent component - by going back to $this for the component you need, only the one you select would be initialized, where currently they would both must be initialized before the function is called.

That’s probably an exotic requirement though - only very few cases could make use of that optimization.

And you would not get IDE support for $this…