- The Magento Global Config, Revisited
- Magento Configuration: Loading Declared Modules
- The Magento Config: Loading System Variables
- Magento Config: A Critique and Caching
This article is part of a longer series exploring the Magento global configuration object. While this article contains useful stand-alone information, you’ll want to read part 1 of the series to fully understand what’s going on.
When our last article finished up, we had successfully instantiated the global config object (Mage_Core_Model_Config
), and loaded the base configuration files from app/etc/*.xml
. In this article we’ll be covering the loading of Magento’s module configuration.
A key piece of the Varien/Magento business strategy involved relying on third-party developers to fill holes in their startup-developed system. It’s no surprise the Magento engineers chose to implement a formal system of code modules so developers across the world could avoid stepping on each other’s toes. While it’s still possible to create conflicting modules in Magento’s system, it’s done a good enough job and enabled a world wide community of commercial and open-source extensions
Magento’s configuration loading is a vital step to bootstrapping the module system. The Mage_Core_Model_Config
object identifies which modules are active in a system, and loads the necessary information into the global config that will allow these modules to function properly.
Time to Load the Modules
We’re going to start in the Magento app object’s run
method
#File: app/code/core/Mage/Core/Model/App.php
public function run($params)
{
$options = isset($params['options']) ? $params['options'] : array();
$this->baseInit($options);
Mage::register('application_params', $params);
if ($this->_cache->processRequest()) {
$this->getResponse()->sendResponse();
} else {
$this->_initModules();
$this->loadAreaPart(Mage_Core_Model_App_Area::AREA_GLOBAL, Mage_Core_Model_App_Area::PART_EVENTS);
if ($this->_config->isLocalConfigLoaded()) {
$scopeCode = isset($params['scope_code']) ? $params['scope_code'] : '';
$scopeType = isset($params['scope_type']) ? $params['scope_type'] : 'store';
$this->_initCurrentStore($scopeCode, $scopeType);
$this->_initRequest();
Mage_Core_Model_Resource_Setup::applyAllDataUpdates();
}
$this->getFrontController()->dispatch();
}
return $this;
}
Our last article was all about the call to baseInit
. This time we’re interested in the following method invocation
#File: app/code/core/Mage/Core/Model/App.php
$this->_initModules();
This is the method that kicks off loading module information into the Magento global configuration tree. If we look at the _initModules
definition
#File: app/code/core/Mage/Core/Model/App.php
protected function _initModules()
{
if (!$this->_config->loadModulesCache()) {
$this->_config->loadModules();
if ($this->_config->isLocalConfigLoaded() && !$this->_shouldSkipProcessModulesUpdates()) {
Varien_Profiler::start('mage::app::init::apply_db_schema_updates');
Mage_Core_Model_Resource_Setup::applyAllUpdates();
Varien_Profiler::stop('mage::app::init::apply_db_schema_updates');
}
$this->_config->loadDb();
$this->_config->saveCache();
}
return $this;
}
the method call we’re interested in is
#File: app/code/core/Mage/Core/Model/App.php
$this->_config->loadModules();
This is the code that will tell the config object it’s time to load the XML information for each module. The surrounding if
block ensures the full configuration loading only happens if Magento can’t load the configuration information from cache. This bit of code is why the phrase “clear your cache” is on every Magento developer’s lips.
What Needs to be Loaded?
Taking a look at our config class, we can see the loadModules
method.
#File: app/code/core/Mage/Core/Model/Config.php
public function loadModules()
{
Varien_Profiler::start('config/load-modules');
$this->_loadDeclaredModules();
$resourceConfig = sprintf('config.%s.xml', $this->_getResourceConnectionModel('core'));
$this->loadModulesConfiguration(array('config.xml',$resourceConfig), $this);
/**
* Prevent local.xml directives overwriting
*/
$mergeConfig = clone $this->_prototype;
$this->_isLocalConfigLoaded = $mergeConfig->loadFile($this->getOptions()->getEtcDir().DS.'local.xml');
if ($this->_isLocalConfigLoaded) {
$this->extend($mergeConfig);
}
$this->applyExtends();
Varien_Profiler::stop('config/load-modules');
return $this;
}
Merging (or extending
, see the first article in our series) each module’s config.xml
tree into the main config object is actually a two step process.
First, we need to identify which modules are declared in the system, and which code pool they reside in. Once those modules have been identified we can start step two; loading their config.xml
files from disk.
It’s worth repeating the two types of configuration files in play here. Files in
app/etc/modules/*.xml
are for declaring Magento modules. A file here tells Magento to look for a module.
Files in
app/code/[code pool]/Packagename/Modulename/etc/config.xml
are the files that contain the actual configuration information for a module.
Declared Modules
Jumping to the _loadDeclaredModules
method, our first step is to grab a list of all the “declared” modules.
To identify which modules are declared, Magento will glob up the files in
app/etc/modules/*.xml
and merge/extend
them into the global config. This will let Magento know which modules are installed into the system, which are active, and which code pool the module files are loaded in. By loading this information into the global configuration tree, Magento ensures the information will be available to any developer who wants it without having to reload everything.
This loading and extend
ing is handled by the call to _loadDeclaredModules
.
#File: app/code/core/Mage/Core/Model/Config.php
protected function _loadDeclaredModules($mergeConfig = null)
{
$moduleFiles = $this->_getDeclaredModuleFiles();
if (!$moduleFiles) {
return ;
}
A declared modules is a module that has a configuration node in
app/etc/modules/*.xml
The _getDeclaredModuleFiles
method is responsible for fetching a list of these files. Unlike loading the app/etc/*.xml
files (per the last article), this is far from a simple file glob. Let’s take a look
#File: app/code/core/Mage/Core/Model/Config.php
protected function _getDeclaredModuleFiles()
{
$etcDir = $this->getOptions()->getEtcDir();
$moduleFiles = glob($etcDir . DS . 'modules' . DS . '*.xml');
if (!$moduleFiles) {
return false;
}
$collectModuleFiles = array(
'base' => array(),
'mage' => array(),
'custom' => array()
);
foreach ($moduleFiles as $v) {
$name = explode(DIRECTORY_SEPARATOR, $v);
$name = substr($name[count($name) - 1], 0, -4);
if ($name == 'Mage_All') {
$collectModuleFiles['base'][] = $v;
} else if (substr($name, 0, 5) == 'Mage_') {
$collectModuleFiles['mage'][] = $v;
} else {
$collectModuleFiles['custom'][] = $v;
}
}
return array_merge(
$collectModuleFiles['base'],
$collectModuleFiles['mage'],
$collectModuleFiles['custom']
);
}
First, Magento globs up a list of files,
#File: app/code/core/Mage/Core/Model/Config.php
$etcDir = $this->getOptions()->getEtcDir();
$moduleFiles = glob($etcDir . DS . 'modules' . DS . '*.xml');
after which it needs to reorder the files in the $moduleFiles
array before returning a final list. Here’s the plain english explanation of the sorting
Mage_All
.xml is always first- Any files that start with
Mage_
come next - Any other files (with a non-
Mage
namespace) are last
This is necessary to ensure the configuration for any core Magento modules load before any third party modules. While Magento offers the ability to configure the loading order via a <depends/>
tag, by jury-rigging the order ahead of time Magento ensures third-party modules without such a tag are loaded last.
Back up in _loadDeclaredModules
, our $moduleFiles
variable now contains an array of values, something like this
array
0 => string '/path/to/magento/app/etc/modules/Mage_All.xml' (length=80)
1 => string '/path/to/magento/app/etc/modules/Mage_Api.xml' (length=80)
2 => string '/path/to/magento/app/etc/modules/Mage_Authorizenet.xml' (length=89)
3 => string '/path/to/magento/app/etc/modules/Mage_Bundle.xml' (length=83)
4 => string '/path/to/magento/app/etc/modules/Mage_Centinel.xml' (length=85)
5 => string '/path/to/magento/app/etc/modules/Mage_Compiler.xml' (length=85)
6 => string '/path/to/magento/app/etc/modules/Mage_Connect.xml' (length=84)
7 => string '/path/to/magento/app/etc/modules/Mage_Downloadable.xml' (length=89)
8 => string '/path/to/magento/app/etc/modules/Mage_ImportExport.xml' (length=89)
9 => string '/path/to/magento/app/etc/modules/Mage_PageCache.xml' (length=86)
10 => string '/path/to/magento/app/etc/modules/Mage_Persistent.xml' (length=87)
11 => string '/path/to/magento/app/etc/modules/Mage_Weee.xml' (length=81)
12 => string '/path/to/magento/app/etc/modules/Mage_Widget.xml' (length=83)
13 => string '/path/to/magento/app/etc/modules/Mage_XmlConnect.xml' (length=87)
...
Next up is this chunk of code
#File: app/code/core/Mage/Core/Model/Config.php
$unsortedConfig = new Mage_Core_Model_Config_Base();
$unsortedConfig->loadString('<config/>');
$fileConfig = new Mage_Core_Model_Config_Base();
// load modules declarations
foreach ($moduleFiles as $file) {
$fileConfig->loadFile($file);
$unsortedConfig->extend($fileConfig);
}
This code creates a new Mage_Core_Model_Config_Base
object to hold a merged XML configuration tree. Then, each of the above XML files are loaded into another Mage_Core_Model_Config_Base
object, and this object is merged into the first with extend
. This is a similar pattern to the one we saw when loading the base config, except we’re merging files into a new first object instead of merging them into the Magento global config tree (in $this->_xml
). Before we can add this node to $this->_xml
, we need to ensure the modules are in the correct order, (per the previously mentioned <depends/>
tag)
Before we continue, it’s interesting to note another way this pattern is different from loading our base configuration. Rather than cloning the _prototype
object to hold a loaded configuration, the hard-coded Mage_Core_Model_Config_Base
object is used. It’s unclear if this is a deliberate deviation, two developers working at a similar system level who had a difference of opinion on things, or a half done refactoring job. We mention it mainly to put to rest any confusion you might have about the weird discrepancy.
It’s also worth noting the call to the extend
method
#File: app/code/core/Mage/Core/Model/Config.php
$unsortedConfig->extend($fileConfig);
doesn’t pass in extend
‘s second parameter. This means identical nodes in the second file will replace nodes in the first. The implication here is you could, theoretically, use your own module declaration file to replace the values of a core module’s declaration. While the system was design to allow this, it’s probably not what you want to do, so be careful with your own declaration files. For a full explanation of extends
, see part one of our series.
Depends Sorting
At this point, we have a merged tree in $unsortedConfig
that contains all the declared modules for our system, along with their code pools, active state, etc.
<config>
<modules>
<Mage_Core>
<active>true</active>
<codePool>core</codePool>
</Mage_Core>
<Mage_Eav>
<active>true</active>
<codePool>core</codePool>
<depends>
<Mage_Core/>
</depends>
</Mage_Eav>
<!-- etc ... -->
</modules>
</config>
However, we’ve done nothing to merge this into our main configuration tree in $this->_xml
. Before we do that, we need to reorder this tree such that modules which depend on other modules (configured with a <depends/>
tag) appear later in the list of modules. Later on, this will control the order in which modules are loaded into the system, allowing one module to “depend” on another.
For example, if you look at the declaration file for the Mage_Bundle
module, you can see it’s been made to depend on the Mage_Catalog
module.
<!-- File: app/etc/modules/Mage_Bundle.xml -->
<config>
<modules>
<Mage_Bundle>
<active>true</active>
<codePool>core</codePool>
<depends>
<Mage_Catalog />
</depends>
</Mage_Bundle>
</modules>
</config>
The first step is looping over our just merged configuration tree, and serializing an array of module information.
#File: app/code/core/Mage/Core/Model/Config.php
$moduleDepends = array();
foreach ($unsortedConfig->getNode('modules')->children() as $moduleName => $moduleNode) {
if (!$this->_isAllowedModule($moduleName)) {
continue;
}
$depends = array();
if ($moduleNode->depends) {
foreach ($moduleNode->depends->children() as $depend) {
$depends[$depend->getName()] = true;
}
}
$moduleDepends[$moduleName] = array(
'module' => $moduleName,
'depends' => $depends,
'active' => ('true' === (string)$moduleNode->active ? true : false),
);
}
Next, this information is passed off to a separate method to sort it based on <depends/>
rules.
#File: app/code/core/Mage/Core/Model/Config.php
$moduleDepends = $this->_sortModuleDepends($moduleDepends);
We’re going to skip looking into the specifics of _sortModuleDepends
, but it’s worth investigating if you’re interested in that sort of thing. Once sorting is complete, the $moduleDepends
array will look something like this
array
0 =>
array
'module' => string 'Mage_Core' (length=9)
'depends' =>
array
empty
'active' => boolean true
1 =>
array
'module' => string 'Mage_Eav' (length=8)
'depends' =>
array
'Mage_Core' => boolean true
'active' => boolean true
2 =>
array
'module' => string 'Mage_Page' (length=9)
'depends' =>
array
'Mage_Core' => boolean true
'active' => boolean true
3 =>
array
...
with each array of module information being in the correct order.
Next, we create a new config object to hold our sorted module information
#File: app/code/core/Mage/Core/Model/Config.php
$sortedConfig = new Mage_Core_Model_Config_Base();
$sortedConfig->loadString('<config><modules/></config>');
and copy (not merge) any top level nodes from the unsorted configuration information that aren’t the <modules/>
node into the new $sortedConfig
#File: app/code/core/Mage/Core/Model/Config.php
foreach ($unsortedConfig->getNode()->children() as $nodeName => $node) {
if ($nodeName != 'modules') {
$sortedConfig->getNode()->appendChild($node);
}
}
This is necessary to preserve any additional information added to the declaration files, such as the <any_additional_information>
node below
<config>
<any_additional_information>
<added_to>the declaration file</added_to>
</any_additional_information>
<modules>
<Foo_Bar>
<active>true</active>
<codePool>core</codePool>
</Foo_Bar>
</modules>
</config>
With a new XML “shell” tree built up, we loop over the sorted $moduleDepends
data array, and use the information therein to append (again, not merge) information from the unsorted config into a new <modules>
node in the new sorted config
#File: app/code/core/Mage/Core/Model/Config.php
foreach ($moduleDepends as $moduleProp) {
$node = $unsortedConfig->getNode('modules/'.$moduleProp['module']);
$sortedConfig->getNode('modules')->appendChild($node);
}
Finally, the <modules>
configuration tree, now properly loaded and sorted in the $sortedConfig
variable, is merged in with our main global config
#File: app/code/core/Mage/Core/Model/Config.php
$this->extend($sortedConfig);
The end results of which will be an updated global XML tree in $this->_xml
property.
Loading the Modules
It wasn’t always pretty, but we now have a new <modules/>
node in our global configuration tree. This is a list of modules, but we haven’t yet loaded each of our module’s config.xml
files. That’s handled by the following two lines in the loadModules
method
#File: app/code/core/Mage/Core/Model/Config.php
$resourceConfig = sprintf('config.%s.xml', $this->_getResourceConnectionModel('core'));
$this->loadModulesConfiguration(array('config.xml',$resourceConfig), $this);
If you’re familiar with Magento, you may know the loadModulesConfiguration
method can be used to merge together any XML configuration from all the installed modules. When called with a single parameter, Magento will return a merged configuration object. For example, my Simple Page module uses this method to fetch a list of routes from all simplepage.xml
in the system.
However, if a configuration object is passed in as the second parameter, then Magento will use that object as the “caller” object for extends
. As you can see above, the Mage_Core_Model_Config
object passes itself in (as $this
), which means the merged files will be added directly to the global config.
The first line of the above pair is another interesting bit of code to note. In versions prior to 1.6, this call looked a little different.
#File: app/code/core/Mage/Core/Model/Config.php
$this->loadModulesConfiguration('config.xml', $this);
Rather than passing in an array of configuration file names, a single file named config.xml
was passed in. Magento 1.6, however, passes in two file names. We’re getting a little ahead of ourselves, but these are the files Magento will attempt to load from each and every installed module’s etc
folder. If we expand $resourceConfig
‘s parameters, the call looks more like this
$this->loadModulesConfiguration(array('config.xml','config.mysql4.xml'), $this);
Magento 1.6 CE, in addition to merging in config.xml
files, will also look for a config.mysql4.xml
file. It’s unclear if this was an early attempt at breaking out the new database resource configuration into their own files, or if it’s a portent of things to come.
Regardless of all that, let’s take a look the loadModulesConfiguration
method itself. To start with, Magento checks if “local” modules have been disabled.
#File: app/code/core/Mage/Core/Model/Config.php
public function loadModulesConfiguration($fileName, $mergeToObject = null, $mergeModel=null)
{
$disableLocalModules = !$this->_canUseLocalModules();
...
If true, this will be used later to skip loading modules in the local code pool. (In my 3+ years of using Magento, I’ve never seen this feature used, but it’s worth being aware of).
Next, Magento ensures we have an object that will be used to hold our merged config.
#File: app/code/core/Mage/Core/Model/Config.php
if ($mergeToObject === null) {
$mergeToObject = clone $this->_prototype;
$mergeToObject->loadString('<config/>');
}
As previously mentioned, this is the object that Magento will be merging each config.xml
into. In our case this will be the Mage_Core_Model_Config
object itself. Similarly, Magento sets up a “merge model” object
#File: app/code/core/Mage/Core/Model/Config.php
if ($mergeModel === null) {
$mergeModel = clone $this->_prototype;
}
This is the object that will be used to initially load the individual config.xml
files prior to merging. If that didn’t make sense don’t worry, all will become clear in a moment.
Before moving on, it’s (once again!) worth noting that we’re back to using the prototype cloning to create our merge objects. When loading the declared modules, Magento simply instantiated a Mage_Core_Model_Config_Base
object. The core team moves in mysterious ways.
Merging into the Global Config
With our objects instantiated, we’re ready to go. Using the config object’s getNode
method, we grab the (previously inserted) top level
node and all its children<modules/>
#File: app/code/core/Mage/Core/Model/Config.php
$modules = $this->getNode('modules')->children();
and then loop over each child node, in order
#File: app/code/core/Mage/Core/Model/Config.php
foreach ($modules as $modName=>$module) {
//...
}
This is why it was so vital our module information nodes be in a particular order. PHP’s children
method will return a list of nodes in document order. In other words the order of the nodes in the document dictates the order each module’s config.xml
will be loaded
Inside the foreach
loop we have the following code
#File: app/code/core/Mage/Core/Model/Config.php
if ($module->is('active')) {
if ($disableLocalModules && ('local' === (string)$module->codePool)) {
continue;
}
if (!is_array($fileName)) {
$fileName = array($fileName);
}
foreach ($fileName as $configFile) {
$configFile = $this->getModuleDir('etc', $modName).DS.$configFile;
if ($mergeModel->loadFile($configFile)) {
$mergeToObject->extend($mergeModel, true);
}
}
}
The if
conditional that surrounds the body of the foreach
loop has a pretty obvious meaning. If the module isn’t active, then we skip this particular go through the loop. It’s worth looking at the implementation of the is
method
#File: app/code/core/Mage/Core/Model/Config/Element.php
public function is($var, $value = true)
{
$flag = $this->$var;
if ($value === true) {
$flag = strtolower((string)$flag);
if (!empty($flag) && 'false' !== $flag && 'off' !== $flag) {
return true;
} else {
return false;
}
}
return !empty($flag) && (0 === strcasecmp($value, (string)$flag));
}
Here we can see the call to $module->is('active')
will return false if
- There’s no
<active/>
; node - The
<active/>
node has a string value of ‘false
‘ - The
<active/>
node has a string value of ‘off
‘
While there’s nothing particularly earth shattering in this code, it does serve as an example of why a developer might use the custom class feature of simple XML objects. Magento has given all their SimpleXML config objects this additional is
method.
Back in the loop, if Magento determined local modules are disabled, it will continue
on to the next iteration of the loop
#File: app/code/core/Mage/Core/Model/Config.php
if ($disableLocalModules && ('local' === (string)$module->codePool)) {
continue;
}
Pausing again as an aside, this is a curious mixing of coding styles. On one hand, the loop is skipped for inactive
modules by placing its contents in an if
block. On the other hand, the way the loop is skipped for disabled local modules uses a continue
. It’s likely that the local module disabling came later, and the second developer was unwilling to mess with the outer if clause.
Regardless, if we’re still here Magento will ensure our $fileName
parameter is an array
, even if we only requested the merging of a single file.
#File: app/code/core/Mage/Core/Model/Config.php
if (!is_array()) {
$fileName = array($fileName);
}
Finally, we’re at the point where the configuration files are merged in. Magento will now loop over each request file
#File: app/code/core/Mage/Core/Model/Config.php
foreach ($fileName as $configFile) {
$configFile = $this->getModuleDir('etc', $modName).DS.$configFile;
if ($mergeModel->loadFile($configFile)) {
$mergeToObject->extend($mergeModel, true);
}
}
and for each (in our case, config.xml
and config.mysql4.xml
) Magento will use the module name to construct a full path to the configuration file.
$configFile = '/path/to/magento/app/code/community/Foo/Bar/etc/config.xml'
Then, Magento will attempt to load in and merge the XML file at the constructed path
#File: app/code/core/Mage/Core/Model/Config.php
if ($mergeModel->loadFile($configFile)) {
$mergeToObject->extend($mergeModel, true);
}
If $configFile
doesn’t exist or is unreadable, loadFile
will return false and the extend
will be skipped.
Magento does this for each and every declared module. Notice that the extend
method’s $override
parameter is made explicitly true
here. That means if there’s conflicts with specific nodes between modules, the value in the config.xml
loaded later will win out. This is one of the many ways a module with a poorly configured config.xml
can wreck a system, so be vigilant in making sure your nodes don’t conflict.
Cleaning Up
At this point, we have all our module’s config.xml
nodes loaded into the system. We can start using getModel
to instantiate objects, query the config for template files, or any of the other countless things that Magento needs its module configuration for. If you’ve ever wondered why the magento app model and configuration model are instantiated by regular PHP code
#File: app/code/core/Mage/Core/Model/Config.php
self::$_app = new Mage_Core_Model_App();
self::$_config = new Mage_Core_Model_Config($options);
instead of using the getModel
factory, this is why. Magento needs its module configuration in the global config before it can use its getModel
factory pattern, and these are the classes that get that information into the configuration. This sort of chicken/egg problem is common in systems programming, and while it’s always easy to see a better way after the system’s been in use for a while, it’s not always easy to see that better way at the get go.
We’re not quite done with the loadModules
method yet. After we get our configuration loaded, there’s two more things that happen. First, there’s this bit of code
#File: app/code/core/Mage/Core/Model/Config.php
/**
* Prevent local.xml directives overwriting
*/
$mergeConfig = clone $this->_prototype;
$this->_isLocalConfigLoaded = $mergeConfig->loadFile($this->getOptions()->getEtcDir().DS.'local.xml');
if ($this->_isLocalConfigLoaded) {
$this->extend($mergeConfig);
}
Here Magento is remerging the app/etc/local.xml
file we loaded back in loadBase
(see part one of the series). Magento reloads this file in case any of the loaded modules inadvertently or maliciously replace some local.xml nodes via extend
. Without this in place, a module author could replace/ruin important information in local.xml
(database connection information, etc.).
The last bit of loadModules
we’ll consider is this call
#File: app/code/core/Mage/Core/Model/Config.php
$this->applyExtends();
If we take a look at the applyExtends
method, we see a curious bit of code
#File: lib/Varien/Simplexml/Config.php
public function applyExtends()
{
$targets = $this->getXpath($this->_xpathExtends);
if (!$targets) {
return $this;
}
foreach ($targets as $target) {
$sources = $this->getXpath((string)$target['extends']);
if ($sources) {
foreach ($sources as $source) {
$target->extend($source);
}
} else {
#echo "Not found extend: ".(string)$target['extends'];
}
#unset($target['extends']);
}
return $this;
}
From what I can gather, this method will search the loaded configuration for nodes with an attribue named extends
<foo extends="/some/other/xpath" />
For each “target node” found, it will use the value in the extends
attribute to make another xpath query. Nodes found from this query are known as source nodes.
Then, Magento will go through each source node, and use it to extend
/merge the target node
#File: app/code/core/Mage/Core/Model/Config.php
$target->extend($source);
From what I can see, this feature hasn’t been used by core code since at least Magento 1.3.4 CE, and it’s unclear if it ever was. My instincts say this was an early system that would provide an explicit path for third party developers to overwrite existing configuration nodes with their own values. For whatever reasons, this was abandoned/never-adopted, but it remains a curious artifact. If anyone knows the history and/or current usage of this feature, use the comments below or get in touch.
Wrap Up
So, that’s our module configuration loaded into the Magento global config. As you can see, the code is less than elegant in places, but it gets the job done. You can also start to see why Magento relies so heavily on caching. Without configuration caching in place a Magento store would start bottlenecking at disk access in a heartbeat. as each configuration file was loaded for each individual http request.
If you think we’re done with the loading of the global config — you’re wrong!
Magento’s system configuration variables were an early topic here, but I never covered how these values are persisted in Magento, or how they’re read back out of the database. It turns out Magento’s global configuration tree plays a huge role in this vital Magento sub-system, which will be the topic of our next article.