├── .gitignore ├── src ├── LumenServiceProvider.php ├── ServiceProvider.php ├── Repository.php └── DataWriter │ ├── FileWriter.php │ └── Rewrite.php ├── phpunit.xml ├── .github └── workflows │ └── tests.yml ├── composer.json ├── README.md └── tests ├── fixtures └── Config │ └── sample-config.php └── Config └── RewriteTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /vendor 3 | composer.phar 4 | composer.lock 5 | .DS_Store 6 | .phpunit.result.cache -------------------------------------------------------------------------------- /src/LumenServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->getConfigurationPath(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests 10 | 11 | 12 | 13 | 14 | src 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | pull_request: 8 | branches: [master] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | tests: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Setup PHP 20 | uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: 7.2 23 | 24 | - name: Validate composer.json and composer.lock 25 | run: composer validate --strict 26 | 27 | - name: Cache Composer packages 28 | id: composer-cache 29 | uses: actions/cache@v3 30 | with: 31 | path: vendor 32 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 33 | restore-keys: | 34 | ${{ runner.os }}-php- 35 | 36 | - name: Install dependencies 37 | run: composer install --prefer-dist --no-progress 38 | 39 | - name: Run test suite 40 | run: composer run-script test 41 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "daftspunk/laravel-config-writer", 3 | "description": "Laravel provider to be able to rewrite configuration", 4 | "homepage": "http://octobercms.com", 5 | "keywords": ["october cms", "october", "laravel", "config", "write", "provider"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Alexey Bobkov", 10 | "email": "aleksey.bobkov@gmail.com" 11 | }, 12 | { 13 | "name": "Samuel Georges", 14 | "email": "daftspunky@gmail.com" 15 | } 16 | ], 17 | "require": { 18 | "php": ">=7.1.0", 19 | "illuminate/config": "5.*|6.*|7.*|8.*|9.*|10.*|11.*|12.*|13.*|14.*|15.*|16.*|17.*|18.*|19.*|20.*" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "^7.0", 23 | "orchestra/testbench": "3.8" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "October\\Rain\\Config\\": "src/" 28 | } 29 | }, 30 | "scripts": { 31 | "test": "vendor/bin/phpunit" 32 | }, 33 | "extra": { 34 | "laravel": { 35 | "providers": [ 36 | "October\\Rain\\Config\\ServiceProvider" 37 | ] 38 | } 39 | }, 40 | "minimum-stability": "dev", 41 | "prefer-stable": true 42 | } 43 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton($this->repository(), function($app, $items) { 21 | $writer = new FileWriter($this->getFiles(), $this->getConfigPath()); 22 | return new Repository($writer, $items); 23 | }); 24 | 25 | $this->app->extend('config', function($config, $app) { 26 | // Capture the loaded configuration items 27 | $config_items = $config->all(); 28 | return $app->make($this->repository(), $config_items); 29 | }); 30 | } 31 | 32 | public function repository() 33 | { 34 | return Repository::class; 35 | } 36 | 37 | protected function getFiles(): Filesystem 38 | { 39 | return $this->app['files']; 40 | } 41 | 42 | protected function getConfigPath(): string 43 | { 44 | return $this->app['path.config']; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Repository.php: -------------------------------------------------------------------------------- 1 | writer = $writer; 29 | } 30 | 31 | /** 32 | * Write a given configuration value to file. 33 | * 34 | * @param string $key 35 | * @param mixed $value 36 | * @return bool 37 | */ 38 | public function write(string $key, $value): bool 39 | { 40 | list($filename, $item) = $this->parseKey($key); 41 | $result = $this->writer->write($item, $value, $filename); 42 | 43 | if(!$result) throw new Exception('File could not be written to'); 44 | 45 | $this->set($key, $value); 46 | 47 | return $result; 48 | } 49 | 50 | /** 51 | * Split key into 2 parts. The first part will be the filename 52 | * 53 | * @param string $key 54 | * @return array 55 | */ 56 | private function parseKey(string $key): array 57 | { 58 | return preg_split('/\./', $key, 2); 59 | } 60 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Config Writer 2 | 3 | Write to Laravel Config files and maintain file integrity. 4 | 5 | This library is an extension of the Config component used by Laravel. It adds the ability to write to configuration files. 6 | 7 | You can rewrite array values inside a basic configuration file that returns a single array definition (like a Laravel config file) whilst maintaining the file integrity, leaving comments and advanced settings intact. 8 | 9 | The following value types are supported for writing: strings, integers, booleans and single-dimension arrays. 10 | 11 | ## Support 12 | 13 | This provider is designed to be used in Laravel from `5.4` version. 14 | 15 | ## Setup 16 | 17 | Install through composer: 18 | ``` 19 | composer require "daftspunk/laravel-config-writer" 20 | ``` 21 | 22 | Add this to `app/config/app.php` under the 'providers' key: 23 | 24 | ```php 25 | October\Rain\Config\ServiceProvider::class, 26 | ``` 27 | 28 | ### Lumen case 29 | 30 | Add this to `bootstrap/app.php` in the 'service providers' section declaration: 31 | 32 | ```php 33 | $app->register(October\Rain\Config\ServiceProvider::class); 34 | ``` 35 | 36 | ## Usage 37 | 38 | You can write to config files like this: 39 | 40 | ```php 41 | Config::write('app.url', 'http://domain.com'); 42 | 43 | app('config')->write('app.url', 'http://domain.com'); 44 | ``` 45 | 46 | 47 | ### Outside Laravel 48 | 49 | The `Rewrite` class can be used anywhere. 50 | 51 | ```php 52 | $writeConfig = new October\Rain\Config\DataWriter\Rewrite; 53 | $writeConfig->toFile('path/to/config.php', [ 54 | 'item' => 'new value', 55 | 'nested.config.item' => 'value', 56 | 'arrayItem' => ['Single', 'Level', 'Array', 'Values'], 57 | 'numberItem' => 3, 58 | 'booleanItem' => true 59 | ]); 60 | ``` 61 | -------------------------------------------------------------------------------- /src/DataWriter/FileWriter.php: -------------------------------------------------------------------------------- 1 | files = $files; 41 | $this->defaultPath = $defaultPath; 42 | $this->rewriter = new Rewrite; 43 | } 44 | 45 | /** 46 | * Write an item value in a file. 47 | * 48 | * @param string $item 49 | * @param mixed $value 50 | * @param string $filename 51 | * @return bool 52 | */ 53 | public function write(string $item, $value, string $filename, string $fileExtension = '.php'): bool 54 | { 55 | $path = $this->getPath($item, $filename, $fileExtension); 56 | 57 | if (!$path) return false; 58 | 59 | $contents = $this->files->get($path); 60 | $contents = $this->rewriter->toContent($contents, [$item => $value]); 61 | 62 | return !($this->files->put($path, $contents) === false); 63 | } 64 | 65 | private function getPath(string $item, string $filename, string $ext = '.php'): ?string 66 | { 67 | $file = "{$this->defaultPath}/{$filename}{$ext}"; 68 | 69 | if ($this->files->exists($file) && $this->hasKey($file, $item)) { 70 | return $file; 71 | } 72 | 73 | return null; 74 | } 75 | 76 | private function hasKey(string $path, string $key): bool 77 | { 78 | $contents = file_get_contents($path); 79 | $vars = eval('?>'.$contents); 80 | 81 | $keys = explode('.', $key); 82 | 83 | $isset = false; 84 | while ($key = array_shift($keys)) { 85 | $isset = isset($vars[$key]); 86 | if (is_array($vars[$key])) $vars = $vars[$key]; 87 | } 88 | 89 | return $isset; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tests/fixtures/Config/sample-config.php: -------------------------------------------------------------------------------- 1 | true, 17 | 18 | 'debugAgain' => false, 19 | 20 | 'bullyIan' => 0, 21 | 22 | 'booLeeIan' => 1, 23 | 24 | 'aNumber' => 55, 25 | 26 | 'envMethod' => env('___KEY_FOR_ENV___', "unknown fallback value"), 27 | 28 | 'nestedEnv' => [ 29 | 'envMethod' => [ 30 | 'envChild' => env('___KEY_FOR_CHILD_ENV___', "unknown fallback child value"), 31 | ], 32 | ], 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Application URL 37 | |-------------------------------------------------------------------------- 38 | | 39 | | This URL is used by the console to properly generate URLs when using 40 | | the Artisan command line tool. You should set this to the root of 41 | | your application so that it is used when running Artisan tasks. 42 | | 43 | */ 44 | 45 | 'url' => 'http://localhost', 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Application Timezone 50 | |-------------------------------------------------------------------------- 51 | | 52 | | Here you may specify the default timezone for your application, which 53 | | will be used by the PHP date and date-time functions. We have gone 54 | | ahead and set this to a sensible default for you out of the box. 55 | | 56 | */ 57 | 58 | 'timezone' => "October's time", 59 | 60 | 'timezoneAgain' => 'Something "else"', 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Database Connections 65 | |-------------------------------------------------------------------------- 66 | | 67 | | Here are each of the database connections setup for your application. 68 | | Of course, examples of configuring each database platform that is 69 | | supported by Laravel is shown below to make development simple. 70 | | 71 | | 72 | | All database work in Laravel is done through the PHP PDO facilities 73 | | so make sure you have the driver for your particular database of 74 | | choice installed on your machine before you begin development. 75 | | 76 | */ 77 | 78 | 'connections' => [ 79 | 80 | 'sqlite' => [ 81 | 'driver' => 'sqlite', 82 | 'database' => __DIR__.'/../database/production.sqlite', 83 | 'prefix' => '', 84 | ], 85 | 86 | 'mysql' => [ 87 | 'driver' => ['rabble' => 'mysql'], 88 | 'host' => 'localhost', 89 | 'database' => 'database', 90 | 'username' => 'root', 91 | 'password' => '', 92 | 'charset' => 'utf8', 93 | 'collation' => 'utf8_unicode_ci', 94 | 'prefix' => '', 95 | ], 96 | 97 | 'pgsql' => [ 98 | 'driver' => 'pgsql', 99 | 'host' => 'localhost', 100 | 'database' => 'database', 101 | 'username' => 'root', 102 | 'password' => false, 103 | 'charset' => 'utf8', 104 | 'prefix' => '', 105 | 'schema' => 'public', 106 | ], 107 | 108 | 'sqlsrv' => [ 109 | 'driver' => 'sqlsrv', 110 | 'host' => 'localhost', 111 | 'database' => 'database', 112 | 'username' => 'root', 113 | 'password' => '', 114 | 'prefix' => '', 115 | ], 116 | 117 | ], 118 | 119 | /* 120 | |-------------------------------------------------------------------------- 121 | | Memcached Servers 122 | |-------------------------------------------------------------------------- 123 | | 124 | | Now you may specify an array of your Memcached servers that should be 125 | | used when utilizing the Memcached cache driver. All of the servers 126 | | should contain a value for "host", "port", and "weight" options. 127 | | 128 | */ 129 | 130 | 'memcached' => [ 131 | 'host' => '127.0.0.1', 132 | 'port' => 11211, 133 | 'weight' => true, 134 | ], 135 | ); 136 | -------------------------------------------------------------------------------- /tests/Config/RewriteTest.php: -------------------------------------------------------------------------------- 1 | toFile($tmpFile, ['connections.sqlite.driver' => 'sqlbite']); 18 | 19 | $result = include $tmpFile; 20 | $this->assertArrayHasKey('connections', $result); 21 | $this->assertArrayHasKey('sqlite', $result['connections']); 22 | $this->assertArrayHasKey('driver', $result['connections']['sqlite']); 23 | $this->assertEquals('sqlbite', $result['connections']['sqlite']['driver']); 24 | 25 | unlink($tmpFile); 26 | } 27 | 28 | public function testToContent() 29 | { 30 | $writer = new Rewrite; 31 | 32 | /* 33 | * Rewrite a single level string 34 | */ 35 | $contents = file_get_contents(__DIR__ . '/../fixtures/Config/sample-config.php'); 36 | $contents = $writer->toContent($contents, ['url' => 'http://octobercms.com']); 37 | $result = eval('?>'.$contents); 38 | 39 | $this->assertTrue(is_array($result)); 40 | $this->assertArrayHasKey('url', $result); 41 | $this->assertEquals('http://octobercms.com', $result['url']); 42 | 43 | /* 44 | * Rewrite a second level string 45 | */ 46 | $contents = $writer->toContent($contents, ['memcached.host' => '69.69.69.69']); 47 | $result = eval('?>'.$contents); 48 | 49 | $this->assertArrayHasKey('memcached', $result); 50 | $this->assertArrayHasKey('host', $result['memcached']); 51 | $this->assertEquals('69.69.69.69', $result['memcached']['host']); 52 | 53 | /* 54 | * Rewrite a third level string 55 | */ 56 | $contents = $writer->toContent($contents, ['connections.mysql.host' => '127.0.0.1']); 57 | $result = eval('?>'.$contents); 58 | 59 | $this->assertArrayHasKey('connections', $result); 60 | $this->assertArrayHasKey('mysql', $result['connections']); 61 | $this->assertArrayHasKey('host', $result['connections']['mysql']); 62 | $this->assertEquals('127.0.0.1', $result['connections']['mysql']['host']); 63 | 64 | /* 65 | * Test alternative quoting 66 | */ 67 | $contents = $writer->toContent($contents, ['timezone' => 'The Fifth Dimension']); 68 | $contents = $writer->toContent($contents, ['timezoneAgain' => "The \"Sixth\" Dimension"]); 69 | $result = eval('?>'.$contents); 70 | 71 | $this->assertArrayHasKey('timezone', $result); 72 | $this->assertArrayHasKey('timezoneAgain', $result); 73 | $this->assertEquals('The Fifth Dimension', $result['timezone']); 74 | $this->assertEquals("The \"Sixth\" Dimension", $result['timezoneAgain']); 75 | 76 | /* 77 | * Rewrite a boolean 78 | */ 79 | $contents = $writer->toContent($contents, ['debug' => false]); 80 | $contents = $writer->toContent($contents, ['debugAgain' => true]); 81 | $contents = $writer->toContent($contents, ['bullyIan' => true]); 82 | $contents = $writer->toContent($contents, ['booLeeIan' => false]); 83 | $contents = $writer->toContent($contents, ['memcached.weight' => false]); 84 | // FIXME: there is a bug when write `connections.pgsql.password` to true 85 | // $contents = $writer->toContent($contents, ['connections.pgsql.password' => true]); 86 | $result = eval('?>'.$contents); 87 | 88 | $this->assertArrayHasKey('debug', $result); 89 | $this->assertArrayHasKey('debugAgain', $result); 90 | $this->assertArrayHasKey('bullyIan', $result); 91 | $this->assertArrayHasKey('booLeeIan', $result); 92 | $this->assertFalse($result['debug']); 93 | $this->assertTrue($result['debugAgain']); 94 | $this->assertTrue($result['bullyIan']); 95 | $this->assertFalse($result['booLeeIan']); 96 | 97 | $this->assertArrayHasKey('memcached', $result); 98 | $this->assertArrayHasKey('weight', $result['memcached']); 99 | $this->assertFalse($result['memcached']['weight']); 100 | 101 | $this->assertArrayHasKey('connections', $result); 102 | $this->assertArrayHasKey('pgsql', $result['connections']); 103 | $this->assertArrayHasKey('password', $result['connections']['pgsql']); 104 | // $this->assertTrue($result['connections']['pgsql']['password']); 105 | 106 | /* 107 | * Rewrite an integer 108 | */ 109 | $contents = $writer->toContent($contents, ['aNumber' => 69]); 110 | $result = eval('?>'.$contents); 111 | 112 | $this->assertArrayHasKey('aNumber', $result); 113 | $this->assertEquals(69, $result['aNumber']); 114 | 115 | /* 116 | * Rewrite config with values from env 117 | */ 118 | $contents = $writer->toContent($contents, [ 119 | 'envMethod' => 'default', 120 | 'nestedEnv.envMethod.envChild' => 'default', 121 | ]); 122 | $result = eval('?>'.$contents); 123 | 124 | $this->assertEquals('default', $result['envMethod']); 125 | $this->assertEquals('default', $result['nestedEnv']['envMethod']['envChild']); 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/DataWriter/Rewrite.php: -------------------------------------------------------------------------------- 1 | toContent($contents, $newValues, $useValidation); 36 | file_put_contents($filePath, $contents); 37 | 38 | return $contents; 39 | } 40 | 41 | public function toContent(string $contents, array $newValues, bool $useValidation = true): string 42 | { 43 | $contents = $this->parseContent($contents, $newValues); 44 | 45 | if (!$useValidation) { 46 | return $contents; 47 | } 48 | 49 | $result = eval('?>'.$contents); 50 | 51 | 52 | foreach ($newValues as $key => $expectedValue) { 53 | $parts = explode('.', $key); 54 | 55 | $array = $result; 56 | foreach ($parts as $part) { 57 | 58 | if (!is_array($array) || !array_key_exists($part, $array)) { 59 | throw new Exception(sprintf('Unable to rewrite key "%s" in config, does it exist?', $key)); 60 | } 61 | if ( array_key_exists($part, $array) && is_string($array[$part])) { 62 | $array = $expectedValue; 63 | } else { 64 | $array = $array[$part]; 65 | } 66 | 67 | } 68 | $actualValue = $array; 69 | 70 | if ($actualValue != $expectedValue) { 71 | throw new Exception(sprintf('Unable to rewrite key "%s" in config, rewrite failed', $key)); 72 | } 73 | } 74 | 75 | return $contents; 76 | } 77 | 78 | protected function parseContent(string $contents, array $newValues): string 79 | { 80 | $result = $contents; 81 | 82 | foreach ($newValues as $path => $value) { 83 | $result = $this->parseContentValue($result, $path, $value); 84 | } 85 | 86 | return $result; 87 | } 88 | 89 | protected function parseContentValue(string $contents, string $path, $value): string 90 | { 91 | $result = $contents; 92 | $items = explode('.', $path); 93 | $key = array_pop($items); 94 | $replaceValue = $this->writeValueToPhp($value); 95 | 96 | $count = 0; 97 | $patterns = array(); 98 | $patterns[$this->buildStringExpression($key, $items)] = '${1}${2}'.$replaceValue; 99 | $patterns[$this->buildStringExpression($key, $items, '"')] = '${1}${2}'.$replaceValue; 100 | $patterns[$this->buildEnvCallExpression($key, $items)] = '${1}${2}${4}' . $replaceValue . '${8}'; 101 | $patterns[$this->buildConstantExpression($key, $items)] = '${1}${2}'.$replaceValue; 102 | $patterns[$this->buildArrayExpression($key, $items)] = '${1}${2}'.$replaceValue; 103 | 104 | foreach ($patterns as $pattern => $patternReplacement) { 105 | $result = preg_replace($pattern, $patternReplacement, $result, 1, $count); 106 | 107 | if ($count > 0) { 108 | break; 109 | } 110 | } 111 | 112 | return $result; 113 | } 114 | 115 | protected function writeValueToPhp($value): string 116 | { 117 | if (is_string($value) && strpos($value, "'") === false) { 118 | $replaceValue = "'".$value."'"; 119 | } 120 | elseif (is_string($value) && strpos($value, '"') === false) { 121 | $replaceValue = '"'.$value.'"'; 122 | } 123 | elseif (is_bool($value)) { 124 | $replaceValue = ($value ? 'true' : 'false'); 125 | } 126 | elseif (is_null($value)) { 127 | $replaceValue = 'null'; 128 | } 129 | elseif (is_array($value) && count($value) === count($value, COUNT_RECURSIVE)) { 130 | $replaceValue = $this->writeArrayToPhp($value); 131 | } 132 | else { 133 | $replaceValue = $value; 134 | } 135 | 136 | $replaceValue = str_replace('$', '\$', $replaceValue); 137 | 138 | return $replaceValue; 139 | } 140 | 141 | protected function writeArrayToPhp(array $array): string 142 | { 143 | $result = []; 144 | 145 | foreach ($array as $value) { 146 | if (!is_array($value)) { 147 | $result[] = $this->writeValueToPhp($value); 148 | } 149 | } 150 | 151 | return '['.implode(', ', $result).']'; 152 | } 153 | 154 | protected function buildStringExpression(string $targetKey, array $arrayItems = [], string $quoteChar = "'"): string 155 | { 156 | $expression = []; 157 | 158 | // Opening expression for array items ($1) 159 | $expression[] = $this->buildArrayOpeningExpression($arrayItems); 160 | 161 | // The target key opening 162 | $expression[] = '([\'|"]'.$targetKey.'[\'|"]\s*=>\s*)['.$quoteChar.']'; 163 | 164 | // The target value to be replaced ($2) 165 | $expression[] = '([^'.$quoteChar.']*)'; 166 | 167 | // The target key closure 168 | $expression[] = '['.$quoteChar.']'; 169 | 170 | return '/' . implode('', $expression) . '/'; 171 | } 172 | 173 | protected function buildEnvCallExpression(string $targetKey, array $arrayItems = []) 174 | { 175 | $expression = array(); 176 | 177 | // Opening expression for array items ($1) 178 | $expression[] = $this->buildArrayOpeningExpression($arrayItems); 179 | 180 | // The target key opening 181 | $expression[] = '(([\'"])' . $targetKey . '\3\s*=>\s*)'; 182 | 183 | // The method call 184 | $expression[] = '(env\(([\'"]).*\5,\s*)([\'"])(.*)\6(\))'; 185 | 186 | return '/' . implode('', $expression) . '/'; 187 | } 188 | 189 | /** 190 | * Common constants only (true, false, null, integers) 191 | */ 192 | protected function buildConstantExpression(string $targetKey, array $arrayItems = []): string 193 | { 194 | $expression = []; 195 | 196 | // Opening expression for array items ($1) 197 | $expression[] = $this->buildArrayOpeningExpression($arrayItems); 198 | 199 | // The target key opening ($2) 200 | $expression[] = '([\'|"]'.$targetKey.'[\'|"]\s*=>\s*)'; 201 | 202 | // The target value to be replaced ($3) 203 | $expression[] = '([tT][rR][uU][eE]|[fF][aA][lL][sS][eE]|[nN][uU][lL]{2}|[\d]+)'; 204 | 205 | return '/' . implode('', $expression) . '/'; 206 | } 207 | 208 | /** 209 | * Single level arrays only 210 | */ 211 | protected function buildArrayExpression(string $targetKey, array $arrayItems = []): string 212 | { 213 | $expression = []; 214 | 215 | // Opening expression for array items ($1) 216 | $expression[] = $this->buildArrayOpeningExpression($arrayItems); 217 | 218 | // The target key opening ($2) 219 | $expression[] = '([\'|"]'.$targetKey.'[\'|"]\s*=>\s*)'; 220 | 221 | // The target value to be replaced ($3) 222 | $expression[] = '(?:[aA][rR]{2}[aA][yY]\(|[\[])([^\]|)]*)[\]|)]'; 223 | 224 | return '/' . implode('', $expression) . '/'; 225 | } 226 | 227 | protected function buildArrayOpeningExpression(array $arrayItems): string 228 | { 229 | if (count($arrayItems)) { 230 | $itemOpen = []; 231 | foreach ($arrayItems as $item) { 232 | // The left hand array assignment 233 | $itemOpen[] = '[\'|"]'.$item.'[\'|"]\s*=>\s*(?:[aA][rR]{2}[aA][yY]\(|[\[])'; 234 | } 235 | 236 | // Capture all opening array (non greedy) 237 | $result = '(' . implode('[\s\S]*?', $itemOpen) . '[\s\S]*?)'; 238 | } 239 | else { 240 | // Gotta capture something for $1 241 | $result = '()'; 242 | } 243 | 244 | return $result; 245 | } 246 | 247 | } 248 | --------------------------------------------------------------------------------