We continue to post the articles dedicated to the Magento certification exam. Hope you will find them useful.
How Magento loads and manipulates configuration information.
Magento 1 and Magento 2 use XML files and database table to save the configuration. XML files can be read with simplexml of the PHP module. Magento 1 uses the class Mage_Core_Model_Config to save the configuration.
Magento 2 stores data in Magento\Framework\App\ObjectManager\Environment\Developer or Magento\Framework\App\ObjectManager\Environment\Compiled (it depends on the deployment mode). In both Magento versions configuration is loaded after running Application (M1 – run of the class Mage_Core_Mode_App, M2 – createApplication of the class Magento\Framework\App\Bootstrap).
In Magento 1 after running an application, the function “baselnitis” launched and then loadBase, loadModules, loadDb in Mage_Core_Model_Config is called in turns. Then the configuration from app/etc, module and database files are run.
1 2 3 4 5 6 7 8 9 10 11 12 |
public function init($options=array()) { $this->loadBase(); $cacheLoad = $this->loadModulesCache(); if ($cacheLoad) { return $this; } $this->loadModules(); $this->loadDb(); $this->saveCache(); return $this; } |
In Magento 2 Magento\Framework\App\ObjectManager\ConfigLoader is responsible for configuration load. It starts with objectManager initialization.
Magento\Framework\App\Bootstrap
1 2 3 4 5 6 7 8 9 10 |
public function createApplication($type, $arguments = []) { try { $this->initObjectManager(); ... private function initObjectManager() { if (!$this->objectManager) { $this->objectManager = $this->factory->create($this->server); ... |
\Magento\Framework\App\DeploymentConfig instance is created while app/etc/env.php and app/etc/config.php are loaded. app/etc/env.php and app/etc/config.php are the main config files in Magento 2 (app/etc/config.xml and app/etc/modules/* are analogs in Magento 1).
Magento\Framework\App\Magento\Framework\App\ObjectManagerFactory
1 2 3 4 |
... $deploymentConfig = $this->createDeploymentConfig($this->directoryList, $this->configFilePool, $arguments); $arguments = array_merge($deploymentConfig->get(), $arguments); ... |
Then app/etc/di.xml is loaded, containing dependencies for ObjectManager.
Magento\Framework\App\ObjectManagerFactory
1 2 3 4 5 6 7 8 9 10 11 12 |
… if ($env->getMode() != Environment\Compiled::MODE) { $configData = $this->_loadPrimaryConfig($this->directoryList, $this->driverPool, $argumentMapper, $appMode); ... $reader = new \Magento\Framework\ObjectManager\Config\Reader\Dom( $fileResolver, $argumentMapper, $schemaLocator, $validationState ); $configData = $reader->read('primary'); … |
Magento\Framework\Config\Reader
1 2 3 4 5 6 7 8 9 10 |
public function read($scope = null) { $scope = $scope ?: $this->_defaultScope; $fileList = $this->_fileResolver->get($this->_fileName, $scope); if (!count($fileList)) { return []; } $output = $this->_readFiles($fileList); return $output; } |
The configuration of modules and database is load when getValue from Magento\Backend\App\Config is called first:
1 2 3 4 5 |
public function getValue($path) { ... return $this->appConfig->get(System::CONFIG_TYPE, $configPath); } |
Magento\Framework\App\Config
1 2 3 4 5 6 7 8 |
public function get($configType, $path = '', $default = null) { $result = null; if (isset($this->types[$configType])) { $result = $this->types[$configType]->get($path); } return $result !== null ? $result : $default; } |
Magento\Config\App\Config\Type\System
1 2 3 4 5 6 7 8 9 10 11 12 |
public function get($path = '') { ... $config = $this->loadConfig(); $this->cacheConfig($config); $this->data = new DataObject($config); return $this->data->getData($path); } private function loadConfig() { $data = $this->preProcessor->process($this->source->get()); … |
On this stage, the configuration is read in its turn. First, module config.xml is read, then database.
Config.xml is loaded with the use of the class Magento\Framework\App\Config\ConfigSourceAggregated:
1 2 3 4 5 6 7 8 9 10 11 |
public function get($path = '') { $this->sortSources(); $data = []; foreach ($this->sources as $sourceConfig) { /** @var ConfigSourceInterface $source */ $source = $sourceConfig['source']; $data = array_replace_recursive($data, $source->get($path)); } return $data; } |
Magento\Config\App\Config\Source\ModularConfigSource
1 2 3 4 5 6 7 8 |
public function get($path = '') { $data = new DataObject($this->reader->read()); if ($path !== '') { $path = '/' . $path; } return $data->getData('data' . $path) ?: []; } |
Then config files from the list are read and converted from Dom objects into Array:
Magento\Framework\App\Config\Initial\Reader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public function read() { $fileList = []; foreach ($this->_scopePriorityScheme as $scope) { $directories = $this->_fileResolver->get($this->_fileName, $scope); foreach ($directories as $key => $directory) { $fileList[$key] = $directory; } } if (!count($fileList)) { return []; } /** @var \Magento\Framework\Config\Dom $domDocument */ $domDocument = null; foreach ($fileList as $file) { try { if (!$domDocument) { $domDocument = $this->domFactory->createDom(['xml' => $file, 'schemaFile' => $this->_schemaFile]); } else { $domDocument->merge($file); } } catch (\Magento\Framework\Config\Dom\ValidationException $e) { throw new \Magento\Framework\Exception\LocalizedException( new \Magento\Framework\Phrase("Invalid XML in file %1:\n%2", [$file, $e->getMessage()]) ); } } $output = []; if ($domDocument) { $output = $this->_converter->convert($domDocument->getDom()); } return $output; } |
After config.xml is loaded, the database config is loaded with the help of \Magento\Config\App\Config\Source\RuntimeConfigSource:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
public function get($path = '') { $this->sortSources(); $data = []; foreach ($this->sources as $sourceConfig) { /** @var ConfigSourceInterface $source */ $source = $sourceConfig['source']; $data = array_replace_recursive($data, $source->get($path)); } return $data; } public function get($path = '') { $data = new DataObject($this->loadConfig()); return $data->getData($path) ?: []; } Then config collection is loaded: private function loadConfig() { try { $collection = $this->collectionFactory->create(); } catch (\DomainException $e) { $collection = []; } $config = []; foreach ($collection as $item) { if ($item->getScope() === ScopeConfigInterface::SCOPE_TYPE_DEFAULT) { $config[$item->getScope()][$item->getPath()] = $item->getValue(); } else { $code = $this->scopeCodeResolver->resolve($item->getScope(), $item->getScopeId()); $config[$item->getScope()][$code][$item->getPath()] = $item->getValue(); } } foreach ($config as $scope => &$item) { if ($scope === ScopeConfigInterface::SCOPE_TYPE_DEFAULT) { $item = $this->converter->convert($item); } else { foreach ($item as &$scopeItems) { $scopeItems = $this->converter->convert($scopeItems); } } } return $config; } |
Class group configuration and its use in factory methods.
In Magento 1 factory methods were used (such as getModel, helper, getBlock) to create class instances. When creating instances, the class identificator described in the config.xml file was used (for example, catalog/product).
Magento 1:
1 |
Mage::getModel(‘catalog/product’); |
In Magento 2 composer autoload and class generator are used now (more details you can find here http://alanstorm.com/magento_2_autoloader_and_class_generation/), that allows describing necessary models in the class constructor and using it. Besides, in Magento 2 objectManager is used, that allows loading classes dynamically. ObjectManager is the analog of Mage::getModel() andMage::helper.
Magento 2:
1 |
$this->_objectManager->create('Magento\Catalog\Model\Product'); |
The configuration of class overrides in Magento.
Unlike Magento 1, Magento 2 does not use codepool priority (core, community, local), so all overrides are performed through config files and theme files (app/design/{adminhtml,frontend}/{vendor}/{theme}/).
In Magento 1 codepool priority can be used. Moreover, the override file can be created in higher priority codepool (dirty method) or just the config file can be used.
Class override in Magento 1 is used config files:
1 2 3 4 5 6 7 8 9 10 11 |
<global> … <models> … <catalog> <rewrite> <product>Belvg_Catalog_Model_Product </product> </rewrite> </catalog> </models> </global> |
In Magento 2 the class can be overridden completely or the functionality can be extended with plugin (before, after, around)
Magento 2 override:
1 2 3 |
<config> <preference for="Magento\Catalog\Api\Data\ProductInterface" type="Belvg\Catalog\Model\Product" /> </config> |
With that Belvg\Catalog\Model\Product should extend the initial class:
1 2 |
namespace Belvg\Catalog\Model; class Product extends \Magento\Catalog\Model\Product { … } |
Magento 2 plugin:
1 2 3 4 5 |
<config> <type name="Magento\Catalog\Api\Data\ProductInterface"> <plugin name="belvg_catalog_product" type="Belvg\Catalog\Plugin\Model\Product" /> </type> </config> |
The function should be called after{functionName}, before{functionName}, around{functionName}.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
namespace Belvg\Catalog\Plugin\Model; class Product { public function beforeAddProduct($subject, $productInfo, $requestInfo = null) { // do something } public function afterAddProduct($subject, $result){ // do something } public function aroundAddProduct($subject, $proceed) { // before save $result = $proceed(); // after save return $result; } } |