RFC: Native XML Configuration System
Tickets:
The current situation
Right now, XML configuration files are transformed to an object tree by AgaviConfigParser. The config object is then handed to a config handler, which first orders the <configuration> blocks after having checked for the existence of a parent attribute. If a parent attribute exists on the root element, the handler calls the config parser again to retrieve the contents of this parent file.
All Agavi configuration files share this basic structure:
<configurations xmlns="http://agavi.org/agavi/1.0/config">
<configuration>
<!-- contents here -->
</configuration>
</configurations>
All config files share this same namespace http://agavi.org/agavi/1.0/config.
The problems
- It's absolutely wrong for the config handler to go back to square one in case there is a parent file and call the parser again. XIncludes, for instance, are also resolved at parse time, after all. Same goes for the filtering/ordering of <configuration> blocks.
- Config handlers can only properly process Agavi configuration files, and have no chance of applying transformations et al themselves to the original XML structure.
- Config handlers cannot deal with namespaced XML documents. However, you might want those. Look at the SOAP functionality where a WSDL file is generated by the config handler, or consider the potential use of RDF in module.xml to describe module metadata. Having just one namespace won't allow this.
- Not being able to handle namespaces won't allow us to change the config format in the future while retaining BC.
- All config files share one namespace, no matter if it's factories.xml, databases.xml or a user-defined config file.
- The current namespace concept is just not forward compatible.
The solutions
- The parent resolving, <configuration> merging and ordering etc. should be done in one step in the parser. This is already implemented. It will not break BC, since old-style handlers can be detected and will still work, so your userspace handlers are fine. The idea is to deprecate this in 1.0 and to remove it in 1.1. Obviously, if you have subclassed Agavi's config handlers, those will break, but that can be fixed easily by copying and including the old 0.11 handler somewhere in the meantime.
- The config info tree given to handlers must be based on DOM. There is no reason not to do this, since we now only support XML, and we can extend DOM classes (already did that) so they offer an identical API to the old AgaviConfigValueHolders. So moving an old-style handler to the new system is mostly a matter of copy-and-paste, minus the orderConfigurations ugliness at the top.
- Once everything is DOM-based, we can easily handle multiple namespaces in a document.
- see above
- Now this is a bigger problem. Let me explain this one and
- this one in a separate section:
The namespaces issue
First of all, yes, this likely (but not definitely, will have to see) means that existing config files would not work anymore. We will provide an upgrade script to "rewrite" existing configs.
The idea behind config namespace versioning
The general idea is that if we introduce features in the future that need config format changes, we can avoid breaking BC entirely by simply treating "old" namespaces just like in previous versions based on their namespace.
Consider the Storage subsystem planned for Agavi 1.1 (#661), for example, where we will have a Storage Manager which has many storages. That means the <storage> block in factories.xml has to go, and <storage_manager> is introduced instead.
In the course of this, we would introduce a new configuration namespace like http://agavi.org/agavi/1.1/config.
If you now use your old factories.xml config file with a 1.0 namespace and a line like
<storage class="AgaviSessionStorage" />
this will not be compiled to this (like in 1.0):
$this->storage = new AgaviSessionStorage(); $this->storage->initialize($parameters);
but instead to something like this (all examples simplified, but you get the idea):
$this->storageManager = new AgaviStorageManager(); $this->storageManager->initialize($this, $parameters); $defaultStorage = new AgaviSessionStorage(); $defaultStorage->initialize($this, $parameters); $this->storageManager->addStorage($defaultStorage);
even though the new 1.1 config section would look like this:
<storage_manager class="AgaviStorageManager" />
with the default storage then defined in a separate storages.xml file.
This way, nobody has to touch their config files when upgrading Agavi. Old files just work. What's more important, you can use old config files from examples around the web or whatever, and they will work.
Obviously, the config handler needs to know what to do and how to compile the config file - that's exactly why we need (and now have) the native XML config system where handlers have this information.
A new version namespace means chaos
Now, as you can see, we introduced a new namespace for 1.1. And all config files use this same namespace. That means we would have to update the validation definitions of <configurations> and the other basic elements, too, even though only something in factories.xml changed, because that stuff is shared by all config files for validation. And then we need to touch all config handlers to support the new namespace.
Also, the <configurations> definition did not change at all. It was something in factories.xml that changed. From a logical point of view, those are two different things
The cure is here
This is the solution as we have it in mind right now:
<a:configurations xmlns="http://agavi.org/agavi/1.0/config/factories" xmlns:a="http://agavi.org/agavi/1.0/config">
<a:configuration>
<controller class="AgaviController">
<a:parameter name="max_executions">50</a:parameter>
</controller>
<!-- ... -->
<storage class="AgaviSessionStorage" />
</a:configuration>
</a:configurations>
And then, if we introduce changes to factories.xml so you can define a Storage Manager in 1.1, you can keep this old config. Or you update your config to the new version:
<a:configurations xmlns="http://agavi.org/agavi/1.1/config/factories" xmlns:a="http://agavi.org/agavi/1.0/config">
<a:configuration>
<controller class="AgaviController">
<a:parameter name="max_executions">50</a:parameter>
</controller>
<!-- ... -->
<storage_manager class="AgaviStorageManager" />
</a:configuration>
</a:configurations>
After that, version 1.2 might bring the ability to control the literalizing of parameters, for instance. So you have:
<a:configurations xmlns="http://agavi.org/agavi/1.1/config/factories" xmlns:a="http://agavi.org/agavi/1.2/config">
<a:configuration>
<controller class="AgaviController">
<a:parameter name="some_string" literalize="false">true</a:parameter>
</controller>
<!-- ... -->
<storage_manager class="AgaviStorageManager" />
</a:configuration>
</a:configurations>
All this is needed to we can be ready for future requirements and adapt quickly and in an agile fahsion, without breaking everyone's code or having to change a million files.
Also, the general idea for your custom config files is that they look like this:
<a:configurations xmlns="urn:com.acme.theproduct.some.part" xmlns:a="http://agavi.org/agavi/1.2/config">
<a:configuration>
<my_custom_stuff>
<whatever />
</my_custom_stuff>
<!-- ... -->
<storage_manager class="AgaviStorageManager" />
</a:configuration>
</a:configurations>
Of course, you only need to do this if you want the Agavi XML parser to resolve parents for you, filter and order <configuration> blocks etc. With the new xml-only config system, you can easily have:
<my_custom_stuff> <whatever /> </my_custom_stuff>
as well.

