├── tests ├── .gitkeep ├── LaravelTest.php └── LumenTest.php ├── .gitignore ├── renovate.json ├── src ├── config.local │ ├── nested │ │ └── app.php │ └── app.php └── CascadingConfigServiceProvider.php ├── .travis.yml ├── .editorconfig ├── phpunit.xml.dist ├── composer.json └── README.md /tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /composer.lock 2 | /composer.phar 3 | /phpunit.xml 4 | /vendor 5 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/config.local/nested/app.php: -------------------------------------------------------------------------------- 1 | true, 5 | ]; 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.6 5 | 6 | branches: 7 | only: 8 | - master 9 | 10 | before_script: 11 | - curl -s http://getcomposer.org/installer | php 12 | - php composer.phar install 13 | 14 | script: phpunit 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | 7 | [*.php] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [composer.json] 13 | indent_style = space 14 | indent_size = 2 15 | -------------------------------------------------------------------------------- /src/config.local/app.php: -------------------------------------------------------------------------------- 1 | true, 7 | 8 | // Or you can create a new one, just like old days. 9 | // Arrays are perfectly OK. 10 | 'doge' => [ 11 | 'name' => 'Doge', 12 | 'tag' => 'Much wow so config', 13 | ], 14 | ]; 15 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phanan/cascading-config", 3 | "description": "Bringing the cascading configuration system back to Laravel 5.", 4 | "keywords": [ 5 | "laravel", 6 | "laravel 5", 7 | "configuration", 8 | "config", 9 | "cascading", 10 | "cascade" 11 | ], 12 | "homepage": "https://github.com/phanan/cascading-config", 13 | "license": "MIT", 14 | "authors": [ 15 | { 16 | "name": "Phan An", 17 | "email": "me@phanan.net", 18 | "homepage": "http://phanan.net" 19 | } 20 | ], 21 | "require": { 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": "~5.0", 25 | "laravel/lumen-framework": "~5.1.6", 26 | "laravel/framework": "~5.1" 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "PhanAn\\CascadingConfig\\": "src" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/LaravelTest.php: -------------------------------------------------------------------------------- 1 | app = new Application(); 27 | $this->app['env'] = 'foo'; 28 | $this->app->setBasePath(sys_get_temp_dir()); 29 | $this->app->instance('config', new Repository()); 30 | 31 | $this->app['config']->set('app', [ 32 | 'url' => 'http://origin.dev', 33 | ]); 34 | 35 | $this->f = new Filesystem(); 36 | } 37 | 38 | public function tearDown() 39 | { 40 | $this->f->delete($this->app->configPath().'/../config.foo'); 41 | } 42 | 43 | public function testConfigCascaded() 44 | { 45 | $this->f->makeDirectory($this->app->configPath().'/../config.foo', 0755, true, true); 46 | $this->f->put($this->app->configPath().'/../config.foo/app.php', " 'http://cascaded.dev', 'foo' => 'bar'];"); 47 | $this->setupServiceProvider(); 48 | 49 | $this->assertEquals($this->app['config']->get('app.url'), 'http://cascaded.dev'); 50 | $this->assertEquals($this->app['config']->get('app.foo'), 'bar'); 51 | } 52 | 53 | public function testNestedConfigSupported() 54 | { 55 | $this->f->makeDirectory($this->app->configPath().'/../config.foo/nested', 0755, true, true); 56 | $this->f->put($this->app->configPath().'/../config.foo/nested/sample.php', " 'bar'];"); 57 | $this->setupServiceProvider(); 58 | 59 | $this->assertEquals($this->app['config']->get('nested.sample.foo'), 'bar'); 60 | } 61 | 62 | protected function setupServiceProvider() 63 | { 64 | $provider = new CascadingConfigServiceProvider($this->app); 65 | $this->app->register($provider); 66 | 67 | return $provider; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/LumenTest.php: -------------------------------------------------------------------------------- 1 | app = new Application(sys_get_temp_dir()); 27 | 28 | putenv('APP_ENV=foo'); 29 | 30 | $this->app->instance('config', new Repository()); 31 | 32 | $this->app['config']->set('app', [ 33 | 'url' => 'http://origin.dev', 34 | ]); 35 | 36 | $this->f = new Filesystem(); 37 | } 38 | 39 | public function tearDown() 40 | { 41 | $this->f->delete($this->app->getConfigurationPath().'/../config.foo'); 42 | } 43 | 44 | public function testConfigCascaded() 45 | { 46 | $this->f->makeDirectory($this->app->getConfigurationPath().'/../config.foo', 0755, true, true); 47 | $this->f->put($this->app->getConfigurationPath().'/../config.foo/app.php', " 'http://cascaded.dev', 'foo' => 'bar'];"); 48 | $this->setupServiceProvider(); 49 | 50 | $this->assertEquals($this->app['config']->get('app.url'), 'http://cascaded.dev'); 51 | $this->assertEquals($this->app['config']->get('app.foo'), 'bar'); 52 | } 53 | 54 | public function testNestedConfigSupported() 55 | { 56 | $this->f->makeDirectory($this->app->getConfigurationPath().'/../config.foo/nested', 0755, true, true); 57 | $this->f->put($this->app->getConfigurationPath().'/../config.foo/nested/sample.php', " 'bar'];"); 58 | $this->setupServiceProvider(); 59 | 60 | $this->assertEquals($this->app['config']->get('nested.sample.foo'), 'bar'); 61 | } 62 | 63 | protected function setupServiceProvider() 64 | { 65 | $provider = new CascadingConfigServiceProvider($this->app); 66 | $this->app->register($provider); 67 | 68 | return $provider; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cascading Config 2 | 3 | [![Build Status](https://travis-ci.org/phanan/cascading-config.svg?branch=master)](https://travis-ci.org/phanan/cascading-config) 4 | [![Dependency Status](https://gemnasium.com/phanan/cascading-config.svg)](https://gemnasium.com/phanan/cascading-config) 5 | [![License](https://poser.pugx.org/phanan/cascading-config/license.svg)](https://packagist.org/packages/phanan/cascading-config) 6 | 7 | A simple package that brings the cascading configuration system back into Laravel 5 and its sister project, Lumen. 8 | 9 | ## Requirements 10 | 11 | * Laravel 5, or 12 | * Lumen >=5.1 13 | 14 | ## Features 15 | * Laravel-4 style cascading config (can't believe I'm writing this) 16 | * [Nested configuration](https://github.com/laravel/framework/commit/fee982004a795058ab6a66e1600c11aac6748acf) is fully supported 17 | 18 | ## Installation 19 | 20 | First, require `phanan/cascading-config` into your `composer.json` and run `composer update`: 21 | 22 | ``` 23 | "require": { 24 | "phanan/cascading-config": "~2.0" 25 | }, 26 | ``` 27 | 28 | An environment-based configuration directory should have a name with this format `config.{APP_ENV}`, and reside in the same directory as the default `config` dir. For Laravel, `php artisan vendor:publish` 29 | will create a sample directory for your `local` environment. For Lumen, you'll have to create the directories manually. 30 | 31 | Your application structure now should have something like this: 32 | 33 | ``` 34 | config 35 | ├── app.php 36 | ├── auth.php 37 | ├── cache.php 38 | ├── compile.php 39 | ├── database.php 40 | ├── mail.php 41 | └── ... 42 | config.local 43 | ├── app.php 44 | ├── auth.php 45 | ├── cache.php 46 | ├── mail.php 47 | └── nested 48 | └── app.php 49 | ``` 50 | 51 | Fill the configuration into your environment-based config directory (`config.local`, `config.staging`, `config.production`), just like what you've always done in Laravel 4, 52 | 53 | ## Usage 54 | 55 | ### For Laravel 56 | 57 | 1. Add the package's service provider class into `config/app.php`: 58 | 59 | ``` php 60 | 'providers' => [ 61 | /* 62 | * ... 63 | */ 64 | App\Providers\AppServiceProvider::class, 65 | App\Providers\EventServiceProvider::class, 66 | App\Providers\RouteServiceProvider::class, 67 | 68 | PhanAn\CascadingConfig\CascadingConfigServiceProvider::class, 69 | ], 70 | ``` 71 | 72 | 1. Call `config($key)` 73 | 74 | ### For Lumen 75 | 76 | 1. Register the service provider class in `bootstrap/app.php`: 77 | 78 | ```php 79 | // $app->register(App\Providers\AppServiceProvider::class); 80 | // $app->register(App\Providers\EventServiceProvider::class); 81 | $app->register(PhanAn\CascadingConfig\CascadingConfigServiceProvider::class); 82 | ``` 83 | 84 | 1. Enable multi-environment support by uncommenting this line: 85 | 86 | ```php 87 | Dotenv::load(__DIR__.'/../'); 88 | ``` 89 | 90 | 1. Manually register the configuration arrays: 91 | 92 | ```php 93 | $app->configure('auth'); 94 | $app->configure('cache'); 95 | $app->configure('mail'); 96 | ``` 97 | 98 | 1. Call `config($key)` 99 | 100 | ## Notes 101 | 102 | Because of the way `array_merge_recursive()` works, a config key with value being an indexed (non-associative) array (for instance, `app.providers`) will have the value's items overridden. See [#6](https://github.com/phanan/cascading-config/issues/6) for more details on this behavior, and how to work around it. 103 | 104 | ## License 105 | 106 | MIT © [Phan An](http://phanan.net) 107 | -------------------------------------------------------------------------------- /src/CascadingConfigServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 18 | __DIR__.'/config.local' => $this->getConfigPath('../config.local'), 19 | ]); 20 | } 21 | 22 | /** 23 | * Register the service provider. 24 | */ 25 | public function register() 26 | { 27 | $env = $this->app->environment(); 28 | 29 | $envConfigPath = (new SysSplFileInfo(dirname($this->getConfigPath())."/config.$env"))->getRealPath(); 30 | 31 | if (!file_exists($envConfigPath) || !is_dir($envConfigPath)) { 32 | // Nothing to do here 33 | return; 34 | } 35 | 36 | $config = $this->app->make('config'); 37 | 38 | foreach (Finder::create()->files()->name('*.php')->in($envConfigPath) as $file) { 39 | // Run through all PHP files in the current environment's config directory. 40 | // With each file, check if there's a current config key with the name. 41 | // If there's not, initialize it as an empty array. 42 | // Then, use array_replace_recursive() to merge the environment config values 43 | // into the base values. 44 | 45 | $keyName = $this->getConfigurationNesting($envConfigPath, $file).basename($file->getRealPath(), '.php'); 46 | 47 | $oldValues = $config->get($keyName) ?: []; 48 | $newValues = require $file->getRealPath(); 49 | 50 | // Replace any matching values in the old config with the new ones. 51 | $config->set($keyName, array_replace_recursive($oldValues, $newValues)); 52 | } 53 | } 54 | 55 | /** 56 | * Get the configuration file nesting path. 57 | * This method is shamelessly copied from \Illuminate\Foundation\Bootstrap\LoadConfiguration.php. 58 | * 59 | * @param string $envConfigPath 60 | * @param \Symfony\Component\Finder\SplFileInfo $file 61 | * 62 | * @return string 63 | */ 64 | protected function getConfigurationNesting($envConfigPath, SplFileInfo $file) 65 | { 66 | $directory = dirname($file->getRealPath()); 67 | 68 | if ($tree = trim(str_replace($envConfigPath, '', $directory), DIRECTORY_SEPARATOR)) { 69 | $tree = str_replace(DIRECTORY_SEPARATOR, '.', $tree).'.'; 70 | } 71 | 72 | return $tree; 73 | } 74 | 75 | /** 76 | * Get the path to the config directory. 77 | * 78 | * @param string $path 79 | * 80 | * @throws \Exception 81 | * 82 | * @return string 83 | */ 84 | protected function getConfigPath($path = null) 85 | { 86 | // Lumen >=5.1.6 exposes a getConfigurationPath() method. 87 | if ($this->isLumen()) { 88 | return $this->app->getConfigurationPath().$path; 89 | } 90 | 91 | // Laravel comes with a config_path() helper. 92 | if (function_exists('config_path')) { 93 | return config_path($path); 94 | } 95 | 96 | throw new \Exception('CascadingConfig error: Unsupported Laravel/Lumen version.'); 97 | } 98 | 99 | /** 100 | * Check if the current application is a Lumen instance. 101 | * 102 | * @return bool 103 | */ 104 | protected function isLumen() 105 | { 106 | return is_a($this->app, 'Laravel\Lumen\Application'); 107 | } 108 | } 109 | --------------------------------------------------------------------------------