├── .gitignore ├── admin_screenshot.png ├── src ├── registration.php ├── Model │ └── Config │ │ ├── AdditionalInterface.php │ │ ├── YamlFile.php │ │ └── AggregateYamlFiles.php ├── etc │ ├── module.xml │ └── di.xml ├── Test │ └── Unit │ │ ├── SourceTest.php │ │ ├── AdditionalConfigStub.php │ │ └── Model │ │ └── Config │ │ ├── YamlFileTest.php │ │ └── AggregateYamlFilesTest.php ├── Source.php └── Block │ └── System │ └── Config │ └── Form │ └── Field.php ├── phpcs.xml ├── composer.json ├── .travis.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | /vendor/ 3 | -------------------------------------------------------------------------------- /admin_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webgriffe/module-config-override/HEAD/admin_screenshot.png -------------------------------------------------------------------------------- /src/registration.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Magento 2 Module Coding Standard 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Model/Config/AdditionalInterface.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Test/Unit/SourceTest.php: -------------------------------------------------------------------------------- 1 | get(); 13 | $this->assertTrue($config['default']['additional']['config']['stub']); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Test/Unit/AdditionalConfigStub.php: -------------------------------------------------------------------------------- 1 | ['config' => ['stub' => true]]]; 15 | } 16 | 17 | /** 18 | * @return array 19 | */ 20 | public function asFlattenArray() 21 | { 22 | return ['additional/config/stub' => true]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Source.php: -------------------------------------------------------------------------------- 1 | additionalConfig = $additionalConfig; 19 | } 20 | 21 | /** 22 | * Retrieve configuration raw data array. 23 | * 24 | * @param string $path 25 | * @return array 26 | */ 27 | public function get($path = '') 28 | { 29 | return ['default' => $this->additionalConfig->asArray()]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Webgriffe\ConfigOverride\Source 10 | 150 11 | 12 | 13 | 14 | 15 | 16 | 17 | Magento\Backend\Block\Template\Context 18 | Webgriffe\ConfigOverride\Model\Config\AdditionalInterface 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webgriffe/module-config-override", 3 | "description": "A Magento 2 module that reads from file additional configuration", 4 | "type": "magento2-module", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Webgriffe SRL", 9 | "email": "support@webgriffe.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "~5.6.0|~7.0.0|~7.1.0", 14 | "magento/framework": "^100.0.0|^101.0.0", 15 | "magento/module-backend": "^100.0.0", 16 | "magento/module-config": "^100.1.1|^101.0.0", 17 | "symfony/yaml": "^2.0|^3.0" 18 | }, 19 | "require-dev": { 20 | "squizlabs/php_codesniffer": "^2.5", 21 | "phpunit/phpunit": "^5.7", 22 | "mikey179/vfsStream": "^1.6" 23 | }, 24 | "repositories": [ 25 | { 26 | "type": "composer", 27 | "url": "https://repo.magento.com/" 28 | } 29 | ], 30 | "autoload": { 31 | "files": [ "src/registration.php" ], 32 | "psr-4": { 33 | "Webgriffe\\ConfigOverride\\": "src/" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Magento\\Framework\\": "lib/internal/Magento/Framework/", 39 | "Magento\\Setup\\": "setup/src/Magento/Setup/" 40 | } 41 | }, 42 | "extra": { 43 | "magento-force": "override" 44 | }, 45 | "minimum-stability": "alpha", 46 | "prefer-stable": true 47 | } 48 | -------------------------------------------------------------------------------- /src/Model/Config/YamlFile.php: -------------------------------------------------------------------------------- 1 | data = Yaml::parse(file_get_contents($filePath)) ?: []; 24 | } 25 | } 26 | 27 | /** 28 | * @return array 29 | */ 30 | public function asArray() 31 | { 32 | return $this->data; 33 | } 34 | 35 | /** 36 | * @return array 37 | */ 38 | public function asFlattenArray() 39 | { 40 | if (is_null($this->flattenData)) { 41 | $this->flattenData = $this->doFlatten($this->data); 42 | } 43 | return $this->flattenData; 44 | } 45 | 46 | private function doFlatten($array, $prefix = '') 47 | { 48 | $result = array(); 49 | foreach ($array as $key => $value) { 50 | if (is_array($value)) { 51 | $result = $result + $this->doFlatten($value, $prefix . $key . '/'); 52 | } else { 53 | $result[$prefix . $key] = $value; 54 | } 55 | } 56 | return $result; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - '5.6' 4 | - '7.0' 5 | - '7.1' 6 | 7 | env: 8 | global: 9 | - DB_HOST="127.0.0.1" 10 | - DB_NAME="module-config-override" 11 | - DB_USER="root" 12 | - DB_PASS="" 13 | - DB_TEST="module-config-override-test" 14 | - MYSQL_CLI_CREDENTIALS=false 15 | - BASE_URL="http://module-config-override.test/" 16 | matrix: 17 | - MAGENTO_VERSION="2.1.*" 18 | - MAGENTO_VERSION="2.2.*" 19 | 20 | matrix: 21 | exclude: 22 | - php: "5.6" 23 | env: MAGENTO_VERSION="2.2.*" 24 | - php: "7.1" 25 | env: MAGENTO_VERSION="2.1.*" 26 | 27 | cache: 28 | directories: 29 | - $HOME/.composer 30 | 31 | install: 32 | - composer install -n 33 | before_script: 34 | - phpenv config-rm xdebug.ini 35 | script: 36 | - vendor/bin/phpcs --ignore=src/Test/ src/ 37 | - vendor/bin/phpunit src/Test/Unit 38 | - composer require -n --dev --no-update magento/product-community-edition "$MAGENTO_VERSION" 39 | - rm composer.lock 40 | - composer install -n 41 | - curl -Ls https://raw.githubusercontent.com/mmenozzi/m2-bash-ci/master/m2-bash-ci.sh | bash 42 | - curl -O https://files.magerun.net/n98-magerun2.phar 43 | - curl -LJO https://github.com/mikefarah/yq/releases/download/1.14.0/yq_linux_amd64 44 | - chmod +x yq_linux_amd64 45 | - touch app/etc/default.yml.dist 46 | - ./yq_linux_amd64 w -i app/etc/default.yml.dist web.unsecure.base_url http://overridden-from-file.test/ 47 | - cat app/etc/default.yml.dist 48 | - php n98-magerun2.phar cache:clean 49 | - php n98-magerun2.phar dev:console "\$di->get('\Magento\Framework\App\Config')->getValue('web/unsecure/base_url');exit;" | grep http://overridden-from-file.test/ 50 | -------------------------------------------------------------------------------- /src/Test/Unit/Model/Config/YamlFileTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(YamlFile::class, $defaultYamlFile); 16 | $this->assertEmpty($defaultYamlFile->asArray()); 17 | } 18 | 19 | public function testIsInitializableWithSomeData() 20 | { 21 | $filename = 'default.yml'; 22 | $content = << ['etc' => [$filename => $content]]]); 28 | $defaultYamlFile = new YamlFile(vfsStream::url('root/app/etc/default.yml')); 29 | $this->assertInstanceOf(YamlFile::class, $defaultYamlFile); 30 | $this->assertEquals(['design' => ['head' => ['default_title' => 'My Title']]], $defaultYamlFile->asArray()); 31 | } 32 | 33 | public function testAsFlattenArray() 34 | { 35 | $filename = 'default.yml'; 36 | $content = << ['etc' => [$filename => $content]]]); 45 | $defaultYamlFile = new YamlFile(vfsStream::url('root/app/etc/default.yml')); 46 | $this->assertInstanceOf(YamlFile::class, $defaultYamlFile); 47 | $this->assertEquals( 48 | ['design/head/default_title' => 'My Title', 'other/config/setting' => 'value'], 49 | $defaultYamlFile->asFlattenArray() 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Model/Config/AggregateYamlFiles.php: -------------------------------------------------------------------------------- 1 | directoryList = $directoryList; 25 | $this->request = $request; 26 | } 27 | 28 | /** 29 | * @return array 30 | */ 31 | public function asArray() 32 | { 33 | $data = []; 34 | foreach ($this->getYmlFiles() as $ymlFile) { 35 | $data = array_replace_recursive($data, $ymlFile->asArray()); 36 | } 37 | return $data; 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | public function asFlattenArray() 44 | { 45 | $data = []; 46 | foreach ($this->getYmlFiles() as $ymlFile) { 47 | $data = array_replace_recursive($data, $ymlFile->asFlattenArray()); 48 | } 49 | return $data; 50 | } 51 | 52 | /** 53 | * @return array 54 | */ 55 | private function getYmlFiles() 56 | { 57 | $configPath = $this->directoryList->getPath(DirectoryList::CONFIG); 58 | $ymlFiles = []; 59 | $ymlFiles[] = new YamlFile($configPath . DIRECTORY_SEPARATOR . self::BASE_FILE_NAME . '.yml.dist'); 60 | $ymlFiles[] = new YamlFile($configPath . DIRECTORY_SEPARATOR . self::BASE_FILE_NAME . '.yml'); 61 | $env = $this->request->getServer('MAGE_ENVIRONMENT'); 62 | if ($env) { 63 | $envBaseFileName = sprintf('%s-%s', self::BASE_FILE_NAME, $env); 64 | $ymlFiles[] = new YamlFile($configPath . DIRECTORY_SEPARATOR . $envBaseFileName . '.yml.dist'); 65 | $ymlFiles[] = new YamlFile($configPath . DIRECTORY_SEPARATOR . $envBaseFileName . '.yml'); 66 | } 67 | return $ymlFiles; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Block/System/Config/Form/Field.php: -------------------------------------------------------------------------------- 1 | flatOverriddenConfig = $additionalConfig->asFlattenArray(); 24 | } 25 | 26 | protected function _getElementHtml(AbstractElement $element) 27 | { 28 | if ($element->getData('scope') === self::ACTION_SCOPE_TYPE) { 29 | if ($this->isElementValueOverridden($element)) { 30 | $element->setValue($this->getElementOverriddenValue($element)); 31 | $element->setData('disabled', true); 32 | $element->setComment( 33 | sprintf( 34 | 'The value for this configuration setting, for scope "%s", comes from file ' . 35 | 'and cannot be modified.
', 36 | self::ACTION_SCOPE_TYPE 37 | ) . 38 | $element->getComment() 39 | ); 40 | } 41 | } 42 | return parent::_getElementHtml($element); 43 | } 44 | 45 | /** 46 | * @param AbstractElement $element 47 | * @return string 48 | */ 49 | protected function isElementValueOverridden(AbstractElement $element) 50 | { 51 | $fieldConfig = $element->getData('field_config'); 52 | $path = $fieldConfig['path'] . '/' . $fieldConfig['id']; 53 | return array_key_exists($path, $this->flatOverriddenConfig); 54 | } 55 | 56 | /** 57 | * @param AbstractElement $element 58 | * @return string 59 | */ 60 | protected function getElementOverriddenValue(AbstractElement $element) 61 | { 62 | $fieldConfig = $element->getData('field_config'); 63 | $path = $fieldConfig['path'] . '/' . $fieldConfig['id']; 64 | return $this->flatOverriddenConfig[$path]; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Test/Unit/Model/Config/AggregateYamlFilesTest.php: -------------------------------------------------------------------------------- 1 | directoryList = $this->prophesize(DirectoryList::class); 24 | $this->directoryList->getPath(DirectoryList::CONFIG)->willReturn(vfsStream::url('root')); 25 | $this->request = $this->prophesize(Http::class); 26 | $this->aggregateYamlFiles = new AggregateYamlFiles($this->directoryList->reveal(), $this->request->reveal()); 27 | } 28 | 29 | public function testAsArrayAggregatesYamlFilesInDirectory() 30 | { 31 | $defaultYmlDist = << $defaultYmlDist, 'default.yml' => $defaultYml]); 46 | $this->assertEquals( 47 | [ 48 | 'design' => ['head' => ['default_title' => 'Another title', 'default_other' => 'Other']], 49 | 'other' => ['config' => ['setting' => 'value']] 50 | ], 51 | $this->aggregateYamlFiles->asArray() 52 | ); 53 | } 54 | 55 | public function testAsFlattenArrayAggregatesYamlFilesInDirectory() 56 | { 57 | $defaultYmlDist = << $defaultYmlDist, 'default.yml' => $defaultYml]); 72 | $this->assertEquals( 73 | [ 74 | 'design/head/default_title' => 'Another title', 75 | 'design/head/default_other' => 'Other', 76 | 'other/config/setting' => 'value', 77 | ], 78 | $this->aggregateYamlFiles->asFlattenArray() 79 | ); 80 | } 81 | 82 | public function testLoadsEvenConfigForSpecifiedEnvironment() 83 | { 84 | $this->request->getServer('MAGE_ENVIRONMENT')->willReturn('dev'); 85 | $defaultYml = << $defaultYml, 112 | 'default-dev.yml' => $devConfigYml, 113 | 'default-dev.yml.dist' => $devConfigDistYml, 114 | 'default-prod.yml.dist' => $prodConfigDistYml 115 | ] 116 | ); 117 | $this->assertEquals( 118 | [ 119 | 'design' => ['head' => ['default_title' => 'My Title']], 120 | 'dev' => ['config' => ['setting' => 'value', 'other' => 'setting']], 121 | ], 122 | $this->aggregateYamlFiles->asArray() 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Config Override Magento 2 Module 2 | ================================ 3 | 4 | ## :warning:DEPRECATION:warning: 5 | 6 | This module it is now useless, you can: 7 | 1. Do `composer require symfony/dotenv`, take a look to [these exaplanations](https://github.com/symfony/demo/blob/v2.2.1/.env#L1-L12) to see the hierarchy; 8 | 2. Create the file `app/etc/EnvironmentVariablesLoader.php` 9 | ```php 10 | loadEnv(__DIR__ . "/../../.env"); 17 | ``` 18 | 3. Load it throught composer modifing the `composer.json` file: 19 | ```json 20 | "files": [ 21 | + "app/etc/EnvironmentVariablesLoader.php", 22 | "app/etc/NonComposerComponentRegistration.php" 23 | ], 24 | ``` 25 | 4. Create the env files configuring variables as written on the [magento docu](https://experienceleague.adobe.com/docs/commerce-operations/configuration-guide/paths/override-config-settings.html?lang=en#environment-variables). 26 | ## :warning:DEPRECATION:warning: 27 | 28 | [![Build Status](https://travis-ci.org/webgriffe/module-config-override.svg?branch=master)](https://travis-ci.org/webgriffe/module-config-override) 29 | 30 | A Magento 2 module that overrides default configuration from file which can be added to version control, inspired by this Magento 1.x extension: [https://github.com/webgriffe/config-extension](https://github.com/webgriffe/config-extension). 31 | 32 | Installation 33 | ------------ 34 | 35 | Add this extension as dependency using [Composer](https://getcomposer.org): 36 | 37 | composer require webgriffe/module-config-override 38 | php bin/magento setup:upgrade 39 | 40 | Config override 41 | --------------- 42 | 43 | Magento configuration is driven by database. This, sometimes, is overkill and forces us to maintain upgrade script to keep Magento envorinment aligned with features development. 44 | So, this extension enables additional config source that loads several YAML files and overrides database configuration. 45 | 46 | Loaded YAML files are (in this order): 47 | 48 | * `app/etc/default.yml.dist`: this intended to be under version control to distribute configuration for all environments. 49 | * `app/etc/default.yml`: this is intendet to be ignored by version control system to provide configuration related to local needs. 50 | 51 | Also, if the `MAGE_ENVIRONMENT` environment variable is defined, then two additiontal files are loaded. For example, if the `MAGE_ENVIRONMENT` variable value is `dev`, the following two files are loaded: 52 | 53 | * `app/etc/default-dev.yml.dist`: as `default.yml.dist`, this is intended to be under version control to distribute configuration but only for the `dev` environment. 54 | * `app/etc/default-dev.yml`: as `default.yml`, this is intended to be ignored by version control to provide configuration related to local needs but only for the `dev` environment. 55 | 56 | Configuration in YAML files must be specified with the same structure of Magento system configuration, for example: 57 | 58 | ```yml 59 | web: 60 | secure: 61 | base_url: "http://my-project-url.dev/" 62 | unsecure: 63 | base_url: "http://my-project-url.dev/" 64 | ``` 65 | Only `default` configuration scope is overridden. 66 | 67 | CLI notes 68 | --------- 69 | 70 | Please note that if you use the `MAGE_ENVIRONMENT` variable then it should be **always** set when Magento is running. So is not enough to set it through your webserver (for example with a `SetEnv "dev"` in Apache) but it should also be set into your command line shell. Otherwise, if you have the Magento's configuration cache enabled and you clear the cache from the command line, the `MAGE_ENVIRONMENT` will not be applied and you'll get unexpected behaviours. 71 | 72 | Remember to "export" you env variable when you run a command via shell: 73 | 74 | ``` 75 | export MAGE_ENVIRONMENT=dev && 76 | ``` 77 | 78 | alternative you can add the `MAGE_ENVIRONMENT=dev` in your shell configuration (ex. for bash): 79 | 80 | file `/home//.bashrc`: 81 | 82 | ``` 83 | .... 84 | .... 85 | 86 | MAGE_ENVIRONMENT=dev 87 | ``` 88 | 89 | Overridden config values are shown in backend 90 | --------------------------------------------- 91 | 92 | Overridden config values are shown in Magento's backend. Every config setting it's shown on its section. For example, if you have the following `default.yml` file: 93 | 94 | ```yml 95 | design: 96 | head: 97 | default_title: Webgriffe Store 98 | title_suffix: - Webgriffe Store 99 | ``` 100 | 101 | When you'll go to `Stores -> Configuration -> General -> Design` you'll find the overridden config value shown and not editable. 102 | 103 | ![Admin Screenshop](admin_screenshot.png) 104 | 105 | This feature improves a lot the usability of this extension. 106 | 107 | To Do 108 | ----- 109 | 110 | * Improve system config admin interface to support complex fields 111 | 112 | Credits 113 | ------- 114 | 115 | * Developed by [Webgriffe®](http://webgriffe.com) 116 | --------------------------------------------------------------------------------