├── .github ├── changelog.yml ├── dependabot.yml └── workflows │ ├── php.yml │ ├── release.yml │ └── static.yml ├── .gitignore ├── .nojekyll ├── LICENSE ├── README.md ├── README.zh-CN.md ├── composer.json ├── index.html ├── phpunit.xml ├── src ├── Collection.php ├── CollectionInterface.php ├── ConfigBox.php ├── ConfigUtil.php └── Language.php └── test ├── ConfigBoxTest.php ├── LanguageTest.php ├── _navbar.md ├── bootstrap.php └── testdata ├── config.ini ├── config.json ├── config.json5 ├── config.neon ├── config.php ├── config.toml ├── config.yml └── language ├── en └── response.php └── zh-CN └── response.php /.github/changelog.yml: -------------------------------------------------------------------------------- 1 | title: '## Change Log' 2 | # style allow: simple, markdown(mkdown), ghr(gh-release) 3 | style: gh-release 4 | # group names 5 | names: [Refactor, Fixed, Feature, Update, Other] 6 | #repo_url: https://github.com/gookit/gcli 7 | 8 | filters: 9 | # message length should >= 12 10 | - name: msg_len 11 | min_len: 12 12 | # message words should >= 3 13 | - name: words_len 14 | min_len: 3 15 | - name: keyword 16 | keyword: format code 17 | exclude: true 18 | - name: keywords 19 | keywords: format code, action test 20 | exclude: true 21 | 22 | # group match rules 23 | # not matched will use 'Other' group. 24 | rules: 25 | - name: Refactor 26 | start_withs: [refactor, break] 27 | contains: ['refactor:', 'break:'] 28 | - name: Fixed 29 | start_withs: [fix] 30 | contains: ['fix:'] 31 | - name: Feature 32 | start_withs: [feat, new] 33 | contains: ['feat:', 'new:'] 34 | - name: Update 35 | start_withs: [up] 36 | contains: ['update:', 'up:'] 37 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | # Check for updates to GitHub Actions every weekday 13 | interval: "daily" 14 | -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: Unit-Tests 2 | 3 | # https://docs.github.com/cn/actions/reference/workflow-syntax-for-github-actions 4 | on: 5 | push: 6 | paths: 7 | - '**.php' 8 | - 'composer.json' 9 | - '**.yml' 10 | 11 | jobs: 12 | test: 13 | name: Test on php ${{ matrix.php}} and ${{ matrix.os }} 14 | runs-on: ${{ matrix.os }} 15 | timeout-minutes: 10 16 | strategy: 17 | fail-fast: true 18 | matrix: 19 | php: [8.1, 8.2, 8.3, 8.4] 20 | os: [ubuntu-latest, macOS-latest] # windows-latest, 21 | # include: # will not testing on php 7.2 22 | # - os: 'ubuntu-latest' 23 | # php: '7.2' 24 | # phpunit: '8.5.13' 25 | 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | 30 | - name: Set ENV vars 31 | # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable 32 | run: | 33 | echo "RELEASE_TAG=${GITHUB_REF:10}" >> $GITHUB_ENV 34 | echo "RELEASE_NAME=$GITHUB_WORKFLOW" >> $GITHUB_ENV 35 | 36 | - name: Display Env 37 | run: env 38 | 39 | # usage refer https://github.com/shivammathur/setup-php 40 | - name: Setup PHP 41 | timeout-minutes: 5 42 | uses: shivammathur/setup-php@v2 43 | with: 44 | php-version: ${{ matrix.php}} 45 | tools: pecl, php-cs-fixer, phpunit:${{ matrix.phpunit }} 46 | extensions: mbstring, dom, fileinfo, mysql, openssl, igbinary, redis # , swoole-4.4.19 #optional, setup extensions 47 | ini-values: post_max_size=56M, short_open_tag=On #optional, setup php.ini configuration 48 | coverage: none #optional, setup coverage driver: xdebug, none 49 | 50 | - name: Install dependencies 51 | run: composer update --no-progress 52 | 53 | # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" 54 | # Docs: https://getcomposer.org/doc/articles/scripts.md 55 | 56 | - name: Run test suite 57 | run: phpunit 58 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Tag-release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | release: 10 | name: Tag release 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 10 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Set ENV for github-release 21 | # https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable 22 | run: | 23 | echo "RELEASE_TAG=${GITHUB_REF:10}" >> $GITHUB_ENV 24 | echo "RELEASE_NAME=$GITHUB_WORKFLOW" >> $GITHUB_ENV 25 | 26 | - name: Generate changelog 27 | run: | 28 | curl https://github.com/gookit/gitw/releases/latest/download/chlog-linux-amd64 -L -o /usr/local/bin/chlog 29 | chmod a+x /usr/local/bin/chlog 30 | chlog -c .github/changelog.yml -o changelog.md prev last 31 | 32 | # https://github.com/softprops/action-gh-release 33 | - name: Create release and upload assets 34 | uses: softprops/action-gh-release@v2 35 | with: 36 | name: ${{ env.RELEASE_TAG }} 37 | tag_name: ${{ env.RELEASE_TAG }} 38 | body_path: changelog.md 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Setup Pages 35 | uses: actions/configure-pages@v5 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v3 38 | with: 39 | # Upload entire repository 40 | path: '.' 41 | - name: Deploy to GitHub Pages 42 | id: deployment 43 | uses: actions/deploy-pages@v4 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .phpintel/ 3 | !README.md 4 | !.gitkeep 5 | composer.lock 6 | *.swp 7 | *.log 8 | *.pid 9 | *.patch 10 | *.cache 11 | .DS_Store 12 | test/testdata/ 13 | vendor/ 14 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phppkg/config/ff153409aa87bcc2e55f9d2aa95ef0dbb7ac4385/.nojekyll -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 inhere 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Config 2 | 3 | [![License](https://img.shields.io/packagist/l/phppkg/config.svg?style=flat-square)](LICENSE) 4 | [![Php Version](https://img.shields.io/packagist/php-v/phppkg/config?maxAge=2592000)](https://packagist.org/packages/phppkg/config) 5 | [![Latest Stable Version](http://img.shields.io/packagist/v/phppkg/config.svg)](https://packagist.org/packages/phppkg/config) 6 | [![Actions Status](https://github.com/phppkg/config/workflows/Unit-Tests/badge.svg)](https://github.com/phppkg/config/actions) 7 | 8 | 🗂 Config load, management, merge, get, set and more. 9 | 10 | - Config data load, management 11 | - Support load multi config data, will auto merge 12 | - Supports INI,JSON,YAML,TOML,NEON,PHP format file 13 | - Support for exporting configuration data to file 14 | - Language data management 15 | 16 | > **[中文说明](README.zh-CN.md)** 17 | 18 | ## Install 19 | 20 | **composer** 21 | 22 | ```bash 23 | composer require phppkg/config 24 | ``` 25 | 26 | ## Usage 27 | 28 | create and load config data. load multi file, will auto merge data. 29 | 30 | ```php 31 | use PhpPkg\Config\ConfigBox; 32 | 33 | $config = ConfigBox::new(); 34 | $config->loadFromFiles([ 35 | __DIR__ . '/test/testdata/config.ini', 36 | __DIR__ . '/test/testdata/config.neon', 37 | __DIR__ . '/test/testdata/config.yml', 38 | __DIR__ . '/test/testdata/config.toml', 39 | ]); 40 | ``` 41 | 42 | ### Created in other ways 43 | 44 | ```php 45 | use PhpPkg\Config\ConfigBox; 46 | 47 | $config = ConfigBox::newFromFiles([ 48 | // ... config file list 49 | ]); 50 | 51 | $config->loadIniFile('path/to/my.ini') 52 | ``` 53 | 54 | ### More load methods 55 | 56 | - `loadFromFiles(array $filePaths, string $format = '')` 57 | - `loadFromStrings(string $format, string ...$strings)` 58 | - `loadFromSteam(string $format, resource $stream)` 59 | - `loadIniFile(string $filepath)` 60 | - `loadJsonFile(string $filepath)` 61 | - `loadJson5File(string $filepath)` 62 | - `loadYamlFile(string $filepath)` 63 | - `loadPhpFile(string $filepath)` 64 | 65 | ### Dump data 66 | 67 | ```php 68 | // dump config 69 | vdump($config->getData()); 70 | ``` 71 | 72 | **Output**: 73 | 74 | ```php 75 | CALL ON PhpPkg\ConfigTest\ConfigBoxTest(24): 76 | array(7) { 77 | ["name"]=> string(6) "inhere" 78 | ["age"]=> int(89) 79 | ["atIni"]=> string(6) "value0" 80 | ["arr0"]=> array(3) { 81 | [0]=> string(2) "ab" 82 | [1]=> int(23) 83 | [2]=> string(2) "de" 84 | } 85 | ["map0"]=> array(2) { 86 | ["key0"]=> string(4) "val0" 87 | ["key1"]=> string(4) "val1" 88 | } 89 | ["atNeon"]=> string(6) "value1" 90 | ["atYaml"]=> string(6) "value2" 91 | ["atToml"]=> string(6) "val at toml" 92 | } 93 | ``` 94 | 95 | ## Get value 96 | 97 | ```php 98 | /** @var PhpPkg\Config\ConfigBox $config */ 99 | $config->getInt('age'); // int(89) 100 | $config->getString('name'); // string('inhere') 101 | $config->get('arr0'); 102 | $config->get('map0'); 103 | 104 | // get value by key-path. 105 | $config->getInt('arr0.1'); // int(23) 106 | $config->getString('map0.key0'); // string('val0') 107 | ``` 108 | 109 | ## Set value 110 | 111 | ```php 112 | /** @var PhpPkg\Config\ConfigBox $config */ 113 | $config->set('name', 'INHERE'); 114 | $config->set('map0.key0', 'new value'); 115 | 116 | // set multi at once 117 | $config->sets([ 118 | 'key1' => 'value1', 119 | 'key2' => 'value2', 120 | // ... 121 | ]); 122 | ``` 123 | 124 | ## Export to file 125 | 126 | Export config data to file. 127 | 128 | ```php 129 | use PhpPkg\Config\ConfigBox; 130 | 131 | /** @var ConfigBox $config */ 132 | $config->exportTo('/path/to/file.json'); 133 | $config->exportTo('/path/to/my.conf', ConfigBox::FORMAT_YAML); 134 | ``` 135 | 136 | ## PHPPkg Projects 137 | 138 | - [phppkg/config](https://github.com/phppkg/config) - 🗂 Config load, management, merge, get, set and more. 139 | - [phppkg/easytpl](https:://github.com/phppkg/easytpl) - ⚡️ Simple and fastly template engine for PHP 140 | - [phppkg/http-client](https:://github.com/phppkg/http-client) - An easy-to-use HTTP client library for PHP 141 | - [phppkg/ini](https:://github.com/phppkg/ini) - 💪 An enhanced `INI` format parser written in PHP. 142 | - [phppkg/jenkins-client](https:://github.com/phppkg/jenkins-client) - Designed to interact with Jenkins CI using its API. 143 | - [phppkg/phpgit](https:://github.com/phppkg/phpgit) - A Git wrapper library for PHP 144 | 145 | ## License 146 | 147 | [MIT](LICENSE) 148 | -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # Config 2 | 3 | [![License](https://img.shields.io/packagist/l/phppkg/config.svg?style=flat-square)](LICENSE) 4 | [![Php Version](https://img.shields.io/packagist/php-v/phppkg/config?maxAge=2592000)](https://packagist.org/packages/phppkg/config) 5 | [![Latest Stable Version](http://img.shields.io/packagist/v/phppkg/config.svg)](https://packagist.org/packages/phppkg/config) 6 | [![Actions Status](https://github.com/phppkg/easytpl/workflows/Unit-Tests/badge.svg)](https://github.com/phppkg/easytpl/actions) 7 | 8 | PHP的配置数据加载,管理,获取,支持多种数据格式. 9 | 10 | - 配置数据加载,管理,获取 11 | - 支持加载多个配置数据,会自动合并 12 | - 支持 INI,JSON,YAML,TOML,NEON,PHP 等格式的文件内容 13 | - 支持导出整个配置数据到文件 14 | - 简单的多语言配置数据管理 15 | 16 | > **[EN README](README.md)** 17 | 18 | ## 安装 19 | 20 | **composer** 21 | 22 | ```bash 23 | composer require phppkg/config 24 | ``` 25 | 26 | ## 快速开始 27 | 28 | 先创建一个配置实例,就可以加载指定的配置数据了. 29 | 30 | ```php 31 | use PhpPkg\Config\ConfigBox; 32 | 33 | $config = ConfigBox::new(); 34 | $config->loadFromFiles([ 35 | __DIR__ . '/test/testdata/config.ini', 36 | __DIR__ . '/test/testdata/config.neon', 37 | __DIR__ . '/test/testdata/config.yml', 38 | __DIR__ . '/test/testdata/config.toml', 39 | ]); 40 | ``` 41 | 42 | ### 其他方式创建 43 | 44 | ### 更多加载方法 45 | 46 | - `loadFromFiles(array $filePaths, string $format = '')` 47 | - `loadFromStrings(string $format, string ...$strings)` 48 | - `loadFromSteam(string $format, resource $stream)` 49 | - `loadIniFile(string $filepath)` 50 | - `loadJsonFile(string $filepath)` 51 | - `loadJson5File(string $filepath)` 52 | - `loadYamlFile(string $filepath)` 53 | - `loadPhpFile(string $filepath)` 54 | 55 | ### 查看加载的数据 56 | 57 | ```php 58 | // dump config 59 | vdump($config->getData()); 60 | ``` 61 | 62 | **Output**: 63 | 64 | ```php 65 | CALL ON PhpPkg\ConfigTest\ConfigBoxTest(24): 66 | array(7) { 67 | ["name"]=> string(6) "inhere" 68 | ["age"]=> int(89) 69 | ["atIni"]=> string(6) "value0" 70 | ["arr0"]=> array(3) { 71 | [0]=> string(2) "ab" 72 | [1]=> int(23) 73 | [2]=> string(2) "de" 74 | } 75 | ["map0"]=> array(2) { 76 | ["key0"]=> string(4) "val0" 77 | ["key1"]=> string(4) "val1" 78 | } 79 | ["atNeon"]=> string(6) "value1" 80 | ["atYaml"]=> string(6) "value2" 81 | ["atToml"]=> string(6) "val at toml" 82 | } 83 | ``` 84 | 85 | ## 获取值 86 | 87 | 可以获取指定类型的返回值,同时支持链式key方式获取值 88 | 89 | ```php 90 | /** @var PhpPkg\Config\ConfigBox $config */ 91 | $config->getInt('age'); // int(89) 92 | $config->getString('name'); // string('inhere') 93 | $config->get('arr0'); 94 | $config->get('map0'); 95 | 96 | // get value by key-path. 97 | $config->getInt('arr0.1'); // int(23) 98 | $config->getString('map0.key0'); // string('val0') 99 | ``` 100 | 101 | ## 设置值 102 | 103 | ```php 104 | /** @var PhpPkg\Config\ConfigBox $config */ 105 | $config->set('name', 'INHERE'); 106 | $config->set('map0.key0', 'new value'); 107 | 108 | // set multi at once 109 | $config->sets([ 110 | 'key1' => 'value1', 111 | 'key2' => 'value2', 112 | // ... 113 | ]); 114 | ``` 115 | 116 | ## 导出到文件 117 | 118 | 支持导出整个配置数据到文件. 119 | 120 | ```php 121 | use PhpPkg\Config\ConfigBox; 122 | 123 | /** @var ConfigBox $config */ 124 | $config->exportTo('/path/to/file.json'); 125 | $config->exportTo('/path/to/my.conf', ConfigBox::FORMAT_YAML); 126 | ``` 127 | 128 | ## License 129 | 130 | [MIT](LICENSE) 131 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phppkg/config", 3 | "type": "library", 4 | "description": "Config manage, load, get. Supports INI,JSON,YAML,NEON,PHP format file", 5 | "keywords": [ 6 | "library", 7 | "config", 8 | "config-manage", 9 | "config-load", 10 | "ini", 11 | "json", 12 | "json5", 13 | "yaml", 14 | "toml", 15 | "php" 16 | ], 17 | "homepage": "https://github.com/phppkg/config", 18 | "license": "MIT", 19 | "authors": [ 20 | { 21 | "name": "inhere", 22 | "email": "in.798@qq.com", 23 | "homepage": "https://github/inhere" 24 | } 25 | ], 26 | "require": { 27 | "php": ">=8.1", 28 | "phppkg/ini": "~0.1", 29 | "colinodell/json5": "^2.2 || ^3.0", 30 | "nette/neon": "^3.3", 31 | "symfony/yaml": "^6.0 || ^7.0", 32 | "yosymfony/toml": "^1.0", 33 | "toolkit/fsutil": "^2.0", 34 | "toolkit/stdlib": "^2.0" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "PhpPkg\\Config\\": "src/" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Config - Config load, management, merge, get, set and more... 9 | 10 | 11 |
12 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | src 6 | 7 | 8 | 9 | 10 | test/ 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Collection.php: -------------------------------------------------------------------------------- 1 | [ 31 | * 'bar' => [ 32 | * 'yoo' => 'value' 33 | * ] 34 | * ] 35 | * ]; 36 | * 37 | * $config = new Collection(); 38 | * $config->get('foo.bar.yoo')` equals to $data['foo']['bar']['yoo']; 39 | * ``` 40 | */ 41 | class Collection extends \Toolkit\Stdlib\Std\Collection 42 | { 43 | /** 44 | * @var int 45 | */ 46 | public int $mergeDepth = 3; 47 | 48 | /** 49 | * The key path separator. 50 | * 51 | * @var string 52 | */ 53 | public string $keyPathSep = '.'; 54 | 55 | /** 56 | * set config value by key/path 57 | * 58 | * @param string $key 59 | * @param mixed $value 60 | * 61 | * @return mixed 62 | */ 63 | public function set(string $key, mixed $value): static 64 | { 65 | if ($this->keyPathSep && strpos($key, $this->keyPathSep) > 0) { 66 | Arr::setByPath($this->data, $key, $value, $this->keyPathSep); 67 | return $this; 68 | } 69 | 70 | return parent::set($key, $value); 71 | } 72 | 73 | /** 74 | * @param string $key 75 | * @param mixed|null $default 76 | * 77 | * @return mixed 78 | */ 79 | public function get(string $key, mixed $default = null): mixed 80 | { 81 | if ($this->keyPathSep && strpos($key, $this->keyPathSep) > 0) { 82 | return Arr::getByPath($this->data, $key, $default, $this->keyPathSep); 83 | } 84 | 85 | return parent::get($key, $default); 86 | } 87 | 88 | /** 89 | * @param string $key 90 | * 91 | * @return bool 92 | */ 93 | public function exists(string $key): bool 94 | { 95 | return $this->get($key) !== null; 96 | } 97 | 98 | /** 99 | * @param string $key 100 | * 101 | * @return bool 102 | */ 103 | public function has(string $key): bool 104 | { 105 | return $this->exists($key); 106 | } 107 | 108 | /** 109 | * @return string 110 | */ 111 | public function getKeyPathSep(): string 112 | { 113 | return $this->keyPathSep; 114 | } 115 | 116 | /** 117 | * @param string $keyPathSep 118 | */ 119 | public function setKeyPathSep(string $keyPathSep): void 120 | { 121 | $this->keyPathSep = $keyPathSep; 122 | } 123 | 124 | /** 125 | * @param iterable $data 126 | * 127 | * @return $this 128 | */ 129 | public function load(iterable $data): static 130 | { 131 | $this->bindData($this->data, $data); 132 | return $this; 133 | } 134 | 135 | /** 136 | * @param iterable $data 137 | * 138 | * @return $this 139 | */ 140 | public function loadData(iterable $data): static 141 | { 142 | $this->bindData($this->data, $data); 143 | return $this; 144 | } 145 | 146 | /** 147 | * @param array $parent 148 | * @param iterable $data 149 | * @param int $depth 150 | */ 151 | protected function bindData(array &$parent, iterable $data, int $depth = 1): void 152 | { 153 | foreach ($data as $key => $value) { 154 | if ($value === null) { 155 | continue; 156 | } 157 | 158 | if (is_array($value) && isset($parent[$key]) && is_array($parent[$key])) { 159 | if ($depth > $this->mergeDepth) { 160 | $parent[$key] = $value; 161 | } else { 162 | $this->bindData($parent[$key], $value, ++$depth); 163 | } 164 | } else { 165 | $parent[$key] = $value; 166 | } 167 | } 168 | } 169 | 170 | /** 171 | * @param string $key 172 | * @param class-string|object $obj 173 | * 174 | * @return object 175 | */ 176 | public function bindTo(string $key, string|object $obj): object 177 | { 178 | // is class string 179 | if (is_string($obj)) { 180 | $obj = new $obj(); 181 | } 182 | 183 | if ($data = $this->getArray($key)) { 184 | Php::initObject($obj, $data); 185 | } 186 | 187 | return $obj; 188 | } 189 | 190 | /** 191 | * @return array 192 | */ 193 | public function getKeys(): array 194 | { 195 | return array_keys($this->data); 196 | } 197 | 198 | /** 199 | * @return RecursiveArrayIterator 200 | */ 201 | public function getIterator(): Traversable 202 | { 203 | return new RecursiveArrayIterator($this->data); 204 | } 205 | 206 | /** 207 | * Unset an offset in the iterator. 208 | * 209 | * @param mixed $offset 210 | */ 211 | public function offsetUnset($offset): void 212 | { 213 | $this->set($offset, null); 214 | } 215 | 216 | public function __clone(): void 217 | { 218 | $this->data = unserialize(serialize($this->data), [ 219 | 'allowed_classes' => self::class 220 | ]); 221 | } 222 | 223 | /** 224 | * @return string 225 | */ 226 | public function toString(): string 227 | { 228 | return Php::dumpVars($this->data); 229 | } 230 | 231 | /** 232 | * @return string 233 | */ 234 | public function __toString(): string 235 | { 236 | return $this->toString(); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/CollectionInterface.php: -------------------------------------------------------------------------------- 1 | JSON_PRETTY_PRINT, 40 | self::FORMAT_JSON5 => JSON_PRETTY_PRINT, 41 | ]; 42 | 43 | // 44 | // ============================== Global instance =============================== 45 | // 46 | 47 | /** 48 | * @var ConfigBox|null 49 | */ 50 | private static ?self $std; 51 | 52 | /** 53 | * @return ConfigBox 54 | */ 55 | public static function std(): ConfigBox 56 | { 57 | if (!self::$std) { 58 | self::$std = new ConfigBox(); 59 | } 60 | return self::$std; 61 | } 62 | 63 | /** 64 | * reset global instance 65 | */ 66 | public static function resetStd(): void 67 | { 68 | self::$std = null; 69 | } 70 | 71 | // 72 | // ============================== Quickly create =============================== 73 | // 74 | 75 | /** 76 | * @param string $filepath 77 | * @param string $format 78 | * @param bool $onExists 79 | * 80 | * @return static 81 | */ 82 | public static function newFromFile(string $filepath, string $format = '', bool $onExists = false): self 83 | { 84 | return (new static())->loadFromFile($filepath, $format, $onExists); 85 | } 86 | 87 | /** 88 | * @param string[] $filePaths 89 | * @param string $format 90 | * @param bool $onExists 91 | * 92 | * @return static 93 | */ 94 | public static function newFromFiles(array $filePaths, string $format = '', bool $onExists = false): self 95 | { 96 | return (new static())->loadFromFiles($filePaths, $format, $onExists); 97 | } 98 | 99 | /** 100 | * @param string $format 101 | * @param resource $stream 102 | * 103 | * @return static 104 | */ 105 | public static function newFromStream(string $format, $stream): self 106 | { 107 | return (new static())->loadFromStream($format, $stream); 108 | } 109 | 110 | /** 111 | * @param string $format 112 | * @param string $str 113 | * 114 | * @return static 115 | */ 116 | public static function newFromString(string $format, string $str): self 117 | { 118 | return self::newFromStrings($format, $str); 119 | } 120 | 121 | /** 122 | * @param string $format 123 | * @param string ...$strings 124 | * 125 | * @return static 126 | */ 127 | public static function newFromStrings(string $format, string ...$strings): self 128 | { 129 | return (new static())->loadFromStrings($format, ...$strings); 130 | } 131 | 132 | // 133 | // ============================== load config data =============================== 134 | // 135 | 136 | /** 137 | * @param string $format 138 | * @param resource $stream 139 | * 140 | * @return static 141 | */ 142 | public function loadFromStream(string $format, $stream): self 143 | { 144 | $data = ConfigUtil::readFromStream($format, $stream); 145 | 146 | return $this->load($data); 147 | } 148 | 149 | /** 150 | * @param string $format 151 | * @param string ...$strings 152 | * 153 | * @return static 154 | */ 155 | public function loadFromStrings(string $format, string ...$strings): self 156 | { 157 | foreach ($strings as $str) { 158 | $data = ConfigUtil::readFromString($format, $str); 159 | 160 | // load and merge to data. 161 | $this->load($data); 162 | } 163 | 164 | return $this; 165 | } 166 | 167 | /** 168 | * @param string[] $filePaths 169 | * @param string $format If is empty, will parse from filepath. 170 | * @param bool $onExists Only load exists files, otherwise will load all files. 171 | * 172 | * @return $this 173 | */ 174 | public function loadFromFiles(array $filePaths, string $format = '', bool $onExists = false): self 175 | { 176 | foreach ($filePaths as $filePath) { 177 | $this->loadFromFile($filePath, $format, $onExists); 178 | } 179 | return $this; 180 | } 181 | 182 | /** 183 | * @param string $filepath 184 | * @param string $format If is empty, will parse from filepath. 185 | * @param bool $onExists Only load exists files, otherwise will direct read file. 186 | * 187 | * @return $this 188 | */ 189 | public function loadFromFile(string $filepath, string $format = '', bool $onExists = false): self 190 | { 191 | if ($onExists && !file_exists($filepath)) { 192 | return $this; 193 | } 194 | 195 | $data = ConfigUtil::readFromFile($filepath, $format); 196 | return $this->load($data); 197 | } 198 | 199 | /** 200 | * @param string $filepath 201 | * 202 | * @return $this 203 | */ 204 | public function loadIniFile(string $filepath): self 205 | { 206 | return $this->load(ConfigUtil::readIniData($filepath)); 207 | } 208 | 209 | /** 210 | * @param string $filepath 211 | * 212 | * @return $this 213 | */ 214 | public function loadPhpFile(string $filepath): self 215 | { 216 | return $this->load(ConfigUtil::readPhpData($filepath)); 217 | } 218 | 219 | /** 220 | * @param string $filepath 221 | * 222 | * @return $this 223 | */ 224 | public function loadNeonFile(string $filepath): self 225 | { 226 | return $this->load(ConfigUtil::readNeonData($filepath)); 227 | } 228 | 229 | /** 230 | * @param string $filepath 231 | * 232 | * @return $this 233 | */ 234 | public function loadJsonFile(string $filepath): self 235 | { 236 | return $this->load(ConfigUtil::readJsonData($filepath)); 237 | } 238 | 239 | /** 240 | * @param string $filepath 241 | * 242 | * @return $this 243 | */ 244 | public function loadJson5File(string $filepath): self 245 | { 246 | return $this->load(ConfigUtil::readJson5Data($filepath)); 247 | } 248 | 249 | /** 250 | * @param string $filepath 251 | * 252 | * @return $this 253 | */ 254 | public function loadYamlFile(string $filepath): self 255 | { 256 | return $this->load(ConfigUtil::readYamlData($filepath)); 257 | } 258 | 259 | /** 260 | * @param string $filepath 261 | * 262 | * @return $this 263 | */ 264 | public function loadTomlFile(string $filepath): self 265 | { 266 | return $this->load(ConfigUtil::parseTomlFile($filepath)); 267 | } 268 | 269 | // 270 | // ============================== helper methods =============================== 271 | // 272 | 273 | /** 274 | * Export config data to file 275 | * 276 | * @param string $filepath 277 | * @param string $format If is empty, will parse from filepath. 278 | * 279 | * @return void 280 | */ 281 | public function exportTo(string $filepath, string $format = ''): void 282 | { 283 | $format = $format ?: File::getExtension($filepath, true); 284 | $encFlag = $this->encodeFlags[$format] ?? 0; 285 | 286 | try { 287 | $string = ConfigUtil::encodeToString($this->data, $format, $encFlag); 288 | } catch (JsonException $e) { 289 | throw new RuntimeException('export data error: ' . $e->getMessage(), $e->getCode(), $e); 290 | } 291 | 292 | File::mkdirSave($filepath, $string); 293 | } 294 | 295 | /** 296 | * @return string 297 | */ 298 | public function getName(): string 299 | { 300 | return $this->name; 301 | } 302 | 303 | /** 304 | * @param string $name 305 | */ 306 | public function setName(string $name): void 307 | { 308 | $this->name = $name; 309 | } 310 | 311 | /** 312 | * @param string $format 313 | * @param int $encodeFlag 314 | */ 315 | public function setEncodeFlag(string $format, int $encodeFlag): void 316 | { 317 | ConfigUtil::assertFormat($format); 318 | $this->encodeFlags[$format] = $encodeFlag; 319 | } 320 | 321 | /** 322 | * @param array $encodeFlags 323 | */ 324 | public function setEncodeFlags(array $encodeFlags): void 325 | { 326 | $this->encodeFlags = $encodeFlags; 327 | } 328 | } 329 | -------------------------------------------------------------------------------- /src/ConfigUtil.php: -------------------------------------------------------------------------------- 1 | Ini::decode($str), 108 | // ConfigBox::FORMAT_PHP => self::readPhpData($filepath), 109 | ConfigBox::FORMAT_NEON => Neon::decode($str), 110 | ConfigBox::FORMAT_JSON => json_decode($str, true, 512, self::$jsonFlags), 111 | ConfigBox::FORMAT_JSON5 => json5_decode($str, true, 512, self::$jsonFlags), 112 | ConfigBox::FORMAT_TOML => self::parseTomlString($str), 113 | ConfigBox::FORMAT_YML, ConfigBox::FORMAT_YAML => self::parseYamlString($str), 114 | default => throw new InvalidArgumentException('unknown config format: ' . $format), 115 | }; 116 | } 117 | 118 | /** 119 | * @param array $data 120 | * @param string $format 121 | * @param int $flags 122 | * 123 | * @return string 124 | * @throws \JsonException 125 | */ 126 | public static function encodeToString(array $data, string $format, int $flags = 0): string 127 | { 128 | switch ($format) { 129 | case ConfigBox::FORMAT_INI: 130 | return Ini::encode($data, $flags); 131 | case ConfigBox::FORMAT_TOML: 132 | throw new RuntimeException('not support encode data to ' . $format); 133 | case ConfigBox::FORMAT_PHP: 134 | $codes = rtrim(Php::exportVar($data)); 135 | return "parseFile($filepath, $flags); 233 | } 234 | 235 | /** 236 | * @param string $yaml 237 | * @param int $flags 238 | * 239 | * @return array 240 | */ 241 | public static function parseYamlString(string $yaml, int $flags = 0): array 242 | { 243 | return (new Parser())->parse($yaml, $flags); 244 | } 245 | 246 | /** 247 | * @param string $filepath 248 | * 249 | * @return array 250 | */ 251 | public static function parseTomlFile(string $filepath): array 252 | { 253 | return Toml::parseFile($filepath); 254 | } 255 | 256 | /** 257 | * @param string $toml 258 | * 259 | * @return array 260 | */ 261 | public static function parseTomlString(string $toml): array 262 | { 263 | return Toml::parse($toml); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/Language.php: -------------------------------------------------------------------------------- 1 | tl('app.createPage'); 91 | * 92 | * @var string 93 | */ 94 | private string $separator = '.'; 95 | 96 | /** 97 | * e.g. 98 | * [ 99 | * 'user' => '{basePath}/zh-CN/user.yml' 100 | * 'admin' => '{basePath}/zh-CN/admin.yml' 101 | * ] 102 | * 103 | * @var array 104 | */ 105 | private array $langFiles = []; 106 | 107 | /** 108 | * loaded language file list. 109 | * 110 | * @var array 111 | */ 112 | private array $loadedFiles = []; 113 | 114 | /** 115 | * whether ignore not exists lang file when addLangFile() 116 | * 117 | * @var bool 118 | */ 119 | private bool $ignoreError = false; 120 | 121 | public const DEFAULT_FILE_KEY = '__default'; 122 | 123 | /** 124 | * @param array $settings 125 | * 126 | * @throws InvalidArgumentException 127 | * @throws RangeException 128 | */ 129 | public function __construct(array $settings = []) 130 | { 131 | Obj::init($this, $settings); 132 | 133 | $this->data = new Collection(); 134 | 135 | if ($this->defaultFile) { 136 | $file = $this->buildLangFilePath($this->defaultFile . '.' . $this->format); 137 | 138 | if (is_file($file)) { 139 | $this->data->load(ConfigUtil::readFromFile($file, $this->format)); 140 | } 141 | } 142 | } 143 | 144 | /** 145 | * @see translate() 146 | */ 147 | public function t(string $key, array $args = [], $lang = ''): string 148 | { 149 | return $this->translate($key, $args, $lang); 150 | } 151 | 152 | /** 153 | * @see translate() 154 | */ 155 | public function tl(string $key, array $args = [], $lang = ''): string 156 | { 157 | return $this->translate($key, $args, $lang); 158 | } 159 | 160 | /** 161 | * @see translate() 162 | */ 163 | public function trans(string $key, array $args = [], $lang = ''): string 164 | { 165 | return $this->translate($key, $args, $lang); 166 | } 167 | 168 | /** 169 | * how to use language translate ? please see '/doc/language.md' 170 | * 171 | * @param string $key 172 | * @param array $args 173 | * @param string $lang 174 | * 175 | * @return string 176 | */ 177 | public function translate(string $key, array $args = [], string $lang = ''): string 178 | { 179 | $key = trim($key, ' ' . $this->separator); 180 | if ($key === '') { // '0' is equals False 181 | throw new InvalidArgumentException('Cannot translate the empty key'); 182 | } 183 | 184 | [$lang, $key] = $this->parseKey($key, $lang); 185 | 186 | // translate form current language. if not found, translate form fallback language. 187 | if (($value = $this->findTranslationText($key)) === null) { 188 | $value = $this->transByFallbackLang($key); 189 | 190 | // no translate text 191 | if ($value === '') { 192 | if (!empty($args['__default'])) { 193 | return $args['__default']; 194 | } 195 | 196 | return ucfirst(Str::toSnake(str_replace(['-', '_'], ' ', $key), ' ')); 197 | } 198 | } 199 | 200 | return $args ? sprintf($value, ...$args) : $value; 201 | } 202 | 203 | /********************************************************************************* 204 | * handle current language translate 205 | *********************************************************************************/ 206 | 207 | /** 208 | * @param string $key 209 | * @param mixed $value 210 | * 211 | * @return self 212 | */ 213 | public function set(string $key, mixed $value): self 214 | { 215 | $this->data->set($key, $value); 216 | return $this; 217 | } 218 | 219 | /** 220 | * @param string $key 221 | * 222 | * @return mixed 223 | */ 224 | public function get(string $key): mixed 225 | { 226 | return $this->data->get($key); 227 | } 228 | 229 | /** 230 | * @param string $key 231 | * 232 | * @return bool 233 | */ 234 | public function has(string $key): bool 235 | { 236 | return $this->data->has($key); 237 | } 238 | 239 | /** 240 | * @param string $key 241 | * 242 | * @return string 243 | */ 244 | protected function findTranslationText(string $key): string 245 | { 246 | if ($val = $this->data->getString($key)) { 247 | return $val; 248 | } 249 | 250 | if (strpos($key, $this->separator)) { 251 | [$fileKey,] = explode($this->separator, $key); 252 | } else { 253 | $fileKey = $key; 254 | } 255 | 256 | // at first, load language data to collection. 257 | if ($file = $this->getLangFile($fileKey)) { 258 | $this->loadedFiles[] = $file; 259 | $this->data->set($fileKey, ConfigUtil::readFromFile($file, $this->format)); 260 | 261 | return $this->data->getString($key); 262 | } 263 | 264 | return ''; 265 | } 266 | 267 | /********************************************************************************* 268 | * fallback language translate handle 269 | *********************************************************************************/ 270 | 271 | /** 272 | * @param string $key 273 | * 274 | * @return string 275 | */ 276 | protected function transByFallbackLang(string $key): string 277 | { 278 | if ($this->lang === $this->fallbackLang || !$this->fallbackLang) { 279 | return ''; 280 | } 281 | 282 | // init fallbackData 283 | if (!$this->fallbackData) { 284 | $this->fallbackData = new Collection(); 285 | 286 | if ($this->defaultFile) { 287 | $file = $this->buildLangFilePath($this->defaultFile . '.' . $this->format, $this->fallbackLang); 288 | 289 | if (is_file($file)) { 290 | $this->fallbackData->load(ConfigUtil::readFromFile($file, $this->format)); 291 | } 292 | } 293 | } 294 | 295 | if ($val = $this->fallbackData->getString($key)) { 296 | return $val; 297 | } 298 | 299 | if (strpos($key, $this->separator)) { 300 | [$fileKey,] = explode($this->separator, $key); 301 | } else { 302 | $fileKey = $key; 303 | } 304 | 305 | // the first times fetch, instantiate lang data 306 | if ($file = $this->getLangFile($fileKey)) { 307 | $lang = $this->lang; 308 | $fbLa = $this->fallbackLang; 309 | $file = str_replace("/$lang/", "/$fbLa/", $file); 310 | 311 | if (is_file($file)) { 312 | $this->loadedFiles[] = $file; 313 | $this->fallbackData->set($fileKey, ConfigUtil::readFromFile($file, $this->format)); 314 | 315 | return $this->fallbackData->getString($key); 316 | } 317 | } 318 | 319 | return ''; 320 | } 321 | 322 | /********************************************************************************* 323 | * helper 324 | *********************************************************************************/ 325 | 326 | /** 327 | * @param string $key 328 | * @param string $lang 329 | * 330 | * @return array 331 | */ 332 | private function parseKey(string $key, string $lang = ''): array 333 | { 334 | if ($lang) { 335 | return [$lang, $key]; 336 | } 337 | 338 | $langSep = ':'; 339 | if (strpos($key, $langSep)) { 340 | $info = explode($langSep, $key, 2); 341 | if ($this->isLang($info[0])) { 342 | return $info; 343 | } 344 | } 345 | 346 | return [$this->lang, $key]; 347 | } 348 | 349 | /** 350 | * @param string $filename 351 | * @param string $lang 352 | * 353 | * @return string 354 | */ 355 | protected function buildLangFilePath(string $filename, string $lang = ''): string 356 | { 357 | $path = ($lang ?: $this->lang) . DIRECTORY_SEPARATOR . trim($filename); 358 | 359 | return $this->basePath . DIRECTORY_SEPARATOR . $path; 360 | } 361 | 362 | /********************************************************************************* 363 | * language file handle 364 | *********************************************************************************/ 365 | 366 | /** 367 | * @param string $fileKey 368 | * 369 | * @return string|null 370 | */ 371 | public function getLangFile(string $fileKey): ?string 372 | { 373 | return $this->langFiles[$fileKey] ?? null; 374 | } 375 | 376 | /** 377 | * @param string $fileKey 378 | * 379 | * @return bool 380 | */ 381 | public function hasLangFile(string $fileKey): bool 382 | { 383 | return isset($this->langFiles[$fileKey]); 384 | } 385 | 386 | /** 387 | * @param string $file 388 | * @param string $fileKey 389 | * 390 | * @throws InvalidArgumentException 391 | */ 392 | public function addLangFile(string $file, string $fileKey = ''): void 393 | { 394 | if (!FileSystem::isAbsPath($file)) { 395 | $file = $this->buildLangFilePath($file); 396 | } 397 | 398 | if (!is_file($file)) { 399 | if ($this->ignoreError) { 400 | return; 401 | } 402 | 403 | throw new InvalidArgumentException("The language file don't exists. FILE: $file"); 404 | } 405 | 406 | $fileKey = $fileKey ?: basename($file, '.' . $this->format); 407 | 408 | if (!preg_match('/^[a-z][\w-]+$/i', $fileKey)) { 409 | throw new InvalidArgumentException("language file key [$fileKey] naming format error!!"); 410 | } 411 | 412 | if ($this->hasLangFile($fileKey)) { 413 | if ($this->ignoreError) { 414 | return; 415 | } 416 | 417 | throw new InvalidArgumentException("language file key [$fileKey] have been exists, don't allow override!!"); 418 | } 419 | 420 | $this->langFiles[$fileKey] = $file; 421 | } 422 | 423 | /** 424 | * @param string $fileKey 425 | * 426 | * @return bool 427 | */ 428 | public function hasLangFileData(string $fileKey): bool 429 | { 430 | return isset($this->data[$fileKey]); 431 | } 432 | 433 | /********************************************************************************* 434 | * getter/setter 435 | *********************************************************************************/ 436 | 437 | /** 438 | * @param string $lang 439 | * 440 | * @return bool 441 | */ 442 | public function hasLang(string $lang): bool 443 | { 444 | return $this->isLang($lang); 445 | } 446 | 447 | /** 448 | * @param string $lang 449 | * 450 | * @return bool 451 | */ 452 | public function isLang(string $lang): bool 453 | { 454 | return $lang && in_array($lang, $this->allowed, true); 455 | } 456 | 457 | /** 458 | * Allow quick access default file translate by `$lang->key`, 459 | * is equals to `$lang->tl('key')`. 460 | * 461 | * @param string $name 462 | * 463 | * @return string 464 | */ 465 | public function __get(string $name) 466 | { 467 | return $this->translate($name); 468 | } 469 | 470 | public function __isset(string $name) 471 | { 472 | } 473 | 474 | public function __set(string $name, $value) 475 | { 476 | } 477 | 478 | /** 479 | * Allow quick access default file translate by `$lang->key()`, 480 | * is equals to `$lang->tl('key')`. 481 | * 482 | * @param string $name 483 | * @param array $args 484 | * 485 | * @return mixed 486 | */ 487 | public function __call(string $name, array $args): mixed 488 | { 489 | return $this->translate($name); 490 | } 491 | 492 | /** 493 | * @return string 494 | */ 495 | public function getLang(): string 496 | { 497 | return $this->lang; 498 | } 499 | 500 | /** 501 | * @param string $lang 502 | */ 503 | public function setLang(string $lang): void 504 | { 505 | $this->lang = trim($lang); 506 | } 507 | 508 | /** 509 | * @return string[] 510 | */ 511 | public function getAllowed(): array 512 | { 513 | return $this->allowed; 514 | } 515 | 516 | /** 517 | * @param string[] $allowed 518 | */ 519 | public function setAllowed(array $allowed): void 520 | { 521 | $this->allowed = $allowed; 522 | } 523 | 524 | /** 525 | * @return string 526 | */ 527 | public function getBasePath(): string 528 | { 529 | return $this->basePath; 530 | } 531 | 532 | /** 533 | * @param string $path 534 | * 535 | * @throws InvalidArgumentException 536 | */ 537 | public function setBasePath(string $path): void 538 | { 539 | if ($path && is_dir($path)) { 540 | $this->basePath = $path; 541 | } else { 542 | throw new InvalidArgumentException("The language files path: $path is not exists."); 543 | } 544 | } 545 | 546 | /** 547 | * @return Collection 548 | */ 549 | public function getData(): Collection 550 | { 551 | return $this->data; 552 | } 553 | 554 | /** 555 | * @return array 556 | */ 557 | public function getLangFiles(): array 558 | { 559 | return $this->langFiles; 560 | } 561 | 562 | /** 563 | * @param array $langFiles 564 | * 565 | * @throws InvalidArgumentException 566 | */ 567 | public function setLangFiles(array $langFiles): void 568 | { 569 | foreach ($langFiles as $fileKey => $file) { 570 | $this->addLangFile($file, is_numeric($fileKey) ? '' : $fileKey); 571 | } 572 | } 573 | 574 | /** 575 | * @param bool $full 576 | * 577 | * @return string 578 | */ 579 | public function getDefaultFile(bool $full = false): string 580 | { 581 | return $full ? $this->getLangFile(self::DEFAULT_FILE_KEY) : $this->defaultFile; 582 | } 583 | 584 | /** 585 | * @return string 586 | */ 587 | public function getFallbackLang(): string 588 | { 589 | return $this->fallbackLang; 590 | } 591 | 592 | /** 593 | * @param string $fallbackLang 594 | */ 595 | public function setFallbackLang(string $fallbackLang): void 596 | { 597 | $this->fallbackLang = $fallbackLang; 598 | } 599 | 600 | /** 601 | * @return Collection 602 | */ 603 | public function getFallbackData(): Collection 604 | { 605 | return $this->fallbackData; 606 | } 607 | 608 | /** 609 | * @return string 610 | */ 611 | public function getFormat(): string 612 | { 613 | return $this->format; 614 | } 615 | 616 | /** 617 | * @param string $format 618 | */ 619 | public function setFormat(string $format): void 620 | { 621 | if (in_array($format, ConfigUtil::getFormats(), true)) { 622 | $this->format = $format; 623 | } 624 | } 625 | 626 | /** 627 | * @return string 628 | */ 629 | public function getSeparator(): string 630 | { 631 | return $this->separator; 632 | } 633 | 634 | /** 635 | * @param string $separator 636 | */ 637 | public function setSeparator(string $separator): void 638 | { 639 | $this->separator = $separator; 640 | } 641 | 642 | /** 643 | * @return array 644 | */ 645 | public function getLoadedFiles(): array 646 | { 647 | return $this->loadedFiles; 648 | } 649 | 650 | /** 651 | * @return bool 652 | */ 653 | public function isIgnoreError(): bool 654 | { 655 | return $this->ignoreError; 656 | } 657 | 658 | /** 659 | * @param bool $ignoreError 660 | */ 661 | public function setIgnoreError(bool $ignoreError): void 662 | { 663 | $this->ignoreError = $ignoreError; 664 | } 665 | 666 | /********************************************************************************* 667 | * interface implementing 668 | *********************************************************************************/ 669 | 670 | /** 671 | * Retrieve an external iterator 672 | * 673 | * @link http://php.net/manual/en/iteratoraggregate.getiterator.php 674 | * @return Traversable An instance of an object implementing Iterator or 675 | * Traversable 676 | * @since 5.0.0 677 | */ 678 | public function getIterator(): Traversable 679 | { 680 | return $this->data->getIterator(); 681 | } 682 | 683 | /** 684 | * Whether a offset exists 685 | * 686 | * @link http://php.net/manual/en/arrayaccess.offsetexists.php 687 | * 688 | * @param mixed $offset An offset to check for. 689 | * 690 | * @return bool true on success or false on failure. 691 | * 692 | * The return value will be casted to boolean if non-boolean was returned. 693 | * @since 5.0.0 694 | */ 695 | public function offsetExists($offset): bool 696 | { 697 | return $this->has($offset); 698 | } 699 | 700 | /** 701 | * Offset to retrieve 702 | * 703 | * @link http://php.net/manual/en/arrayaccess.offsetget.php 704 | * 705 | * @param mixed $offset The offset to retrieve. 706 | * 707 | * @return mixed Can return all value types. 708 | * @since 5.0.0 709 | * @throws InvalidArgumentException 710 | */ 711 | public function offsetGet($offset): mixed 712 | { 713 | return $this->translate($offset); 714 | } 715 | 716 | /** 717 | * Offset to set 718 | * 719 | * @link http://php.net/manual/en/arrayaccess.offsetset.php 720 | * 721 | * @param mixed $offset The offset to assign the value to. 722 | * @param mixed $value The value to set. 723 | * 724 | * @return void 725 | * @since 5.0.0 726 | */ 727 | public function offsetSet($offset, $value): void 728 | { 729 | $this->set($offset, $value); 730 | } 731 | 732 | /** 733 | * Offset to unset 734 | * 735 | * @link http://php.net/manual/en/arrayaccess.offsetunset.php 736 | * 737 | * @param mixed $offset The offset to unset. 738 | * 739 | * @return void 740 | * @since 5.0.0 741 | */ 742 | public function offsetUnset($offset): void 743 | { 744 | unset($this->data[$offset]); 745 | } 746 | 747 | /** 748 | * Count elements of an object 749 | * 750 | * @link http://php.net/manual/en/countable.count.php 751 | * @return int The custom count as an integer. 752 | * The return value is cast to an integer. 753 | */ 754 | public function count(): int 755 | { 756 | return count($this->data); 757 | } 758 | } 759 | -------------------------------------------------------------------------------- /test/ConfigBoxTest.php: -------------------------------------------------------------------------------- 1 | getData()); 31 | $this->assertEquals(89, $c->getInt('age')); 32 | $this->assertEquals('inhere', $c->get('name')); 33 | 34 | $this->assertTrue($c->has('atIni')); 35 | $this->assertTrue($c->has('atPhp')); 36 | $this->assertTrue($c->has('atNeon')); 37 | $this->assertTrue($c->has('atYaml')); 38 | $this->assertTrue($c->has('atJson')); 39 | $this->assertTrue($c->has('atJson5')); 40 | $this->assertTrue($c->has('atToml')); 41 | 42 | // get by path 43 | $this->assertEquals(23, $c->getInt('arr0.1')); 44 | $this->assertEquals('val0', $c->getString('map0.key0')); 45 | } 46 | 47 | public function testNewFromStrings(): void 48 | { 49 | $c = ConfigBox::newFromStrings('ini', ' 50 | # comments 51 | key0 = val0 52 | '); 53 | 54 | $c->loadFromStrings(ConfigBox::FORMAT_JSON5, "{ 55 | // comments 56 | key1: 'val1' 57 | }"); 58 | 59 | $c->loadFromStrings(ConfigBox::FORMAT_JSON, '{"key2": "val2"}'); 60 | $c->loadFromStrings(ConfigBox::FORMAT_YAML, 'key3: val3'); 61 | $c->loadFromStrings(ConfigBox::FORMAT_NEON, 'key4: val4'); 62 | $c->set('arrKey', ['abc', 'def']); 63 | 64 | vdump($data = $c->toArray()); 65 | $this->assertNotEmpty($data); 66 | $this->assertEquals('val0', $c->get('key0')); 67 | $this->assertEquals('val1', $c->get('key1')); 68 | $this->assertEquals('val2', $c->get('key2')); 69 | $this->assertEquals('val4', $c->get('key4')); 70 | 71 | $c->exportTo(__DIR__ . '/testdata/export.php'); 72 | $c->exportTo(__DIR__ . '/testdata/export.json'); 73 | } 74 | 75 | public function testLoadFromStream(): void 76 | { 77 | $s1 = fopen(__DIR__ . '/testdata/config.json', 'rb+'); 78 | $c = ConfigBox::newFromStream(ConfigBox::FORMAT_JSON, $s1); 79 | fclose($s1); 80 | 81 | $this->assertNotEmpty($c->all()); 82 | $this->assertEquals('val at json', $c->get('atJson')); 83 | 84 | $s2 = fopen(__DIR__ . '/testdata/config.yml', 'rb+'); 85 | $c->loadFromStream(ConfigBox::FORMAT_YML, $s2); 86 | fclose($s2); 87 | $this->assertEquals('val at yaml', $c->get('atYaml')); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/LanguageTest.php: -------------------------------------------------------------------------------- 1 | 'en', 25 | 'allowed' => ['en', 'zh-CN'], 26 | 'basePath' => __DIR__ . '/testdata/language', 27 | 'langFiles' => [ 28 | 'response.php' 29 | ], 30 | ]); 31 | 32 | $arr = [ 33 | 0 => 'a', 34 | 1 => 'b', 35 | 'k' => 'c', 36 | ]; 37 | 38 | $msg = $l->tl('response.key'); 39 | $this->assertEquals('message', $msg); 40 | $this->assertTrue($l->has('response.key')); 41 | $this->assertEquals('successful', $l->trans('response.0')); 42 | $this->assertEquals('error', $l->trans('response.2')); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/_navbar.md: -------------------------------------------------------------------------------- 1 | * [English](/README.md) 2 | * [中文说明](/README.zh-CN.md) 3 | * **[PHPPkg](https://github.com/phppkg)** 4 | * [Config](https://phppkg.github.io/config/ "🗂 Config load, management, merge, get, set and more.") 5 | * [EasyTpl](https://phppkg.github.io/easytpl/ "⚡️ Simple and fastly template engine for PHP") 6 | * [Http-client](https://phppkg.github.io/http-client/ "An easy-to-use HTTP client library for PHP") 7 | * [PhpGit](https://phppkg.github.io/phpgit/ "A Git wrapper library for PHP") 8 | * [Ini](https://phppkg.github.io/ini/ "💪 An enhanced INI format parser written in PHP") 9 | * [Jenkins-client](https://phppkg.github.io/jenkins-client/ "Designed to interact with Jenkins CI using its API") 10 | * [Console](https://inhere.github.io/php-console/ "🖥 PHP CLI library, provide console options, arguments parse") 11 | * [Validate](https://inhere.github.io/php-validate/ "php data validate engine") 12 | * **[Toolkit](https://github.com/php-toolkit)** 13 | * [PFlag](https://php-toolkit.github.io/pflag/ "console option and argument parse") 14 | * [Stdlib](https://php-toolkit.github.io/stdlib/ "Useful basic tools library for PHP development.") 15 | -------------------------------------------------------------------------------- /test/bootstrap.php: -------------------------------------------------------------------------------- 1 | $libDir . '/test/', 12 | 'PhpPkg\\Config\\' => $libDir . '/src/', 13 | ]; 14 | 15 | spl_autoload_register(static function ($class) use ($npMap) { 16 | foreach ($npMap as $np => $dir) { 17 | $file = $dir . str_replace('\\', '/', substr($class, strlen($np))) . '.php'; 18 | 19 | if (file_exists($file)) { 20 | include $file; 21 | } 22 | } 23 | }); 24 | 25 | if (is_file(dirname(__DIR__, 3) . '/autoload.php')) { 26 | require dirname(__DIR__, 3) . '/autoload.php'; 27 | } elseif (is_file(dirname(__DIR__) . '/vendor/autoload.php')) { 28 | require dirname(__DIR__) . '/vendor/autoload.php'; 29 | } 30 | -------------------------------------------------------------------------------- /test/testdata/config.ini: -------------------------------------------------------------------------------- 1 | name=inhere 2 | age=89 3 | atIni=value0 4 | arr0 = [ab, 23, de] 5 | [map0] 6 | key0 = val0 7 | key1 = val1 8 | -------------------------------------------------------------------------------- /test/testdata/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "atJson": "val at json" 3 | } 4 | -------------------------------------------------------------------------------- /test/testdata/config.json5: -------------------------------------------------------------------------------- 1 | { 2 | atJson5: 'val in json5' 3 | } 4 | -------------------------------------------------------------------------------- /test/testdata/config.neon: -------------------------------------------------------------------------------- 1 | name: inhere 2 | age: 89 3 | atNeon: value1 -------------------------------------------------------------------------------- /test/testdata/config.php: -------------------------------------------------------------------------------- 1 | 'val at php', 4 | ]; 5 | -------------------------------------------------------------------------------- /test/testdata/config.toml: -------------------------------------------------------------------------------- 1 | name = "inhere" 2 | age = 89 3 | atToml = "val at toml" 4 | 5 | -------------------------------------------------------------------------------- /test/testdata/config.yml: -------------------------------------------------------------------------------- 1 | name: inhere 2 | age: 89 3 | atYaml: val at yaml 4 | 5 | -------------------------------------------------------------------------------- /test/testdata/language/en/response.php: -------------------------------------------------------------------------------- 1 | 'successful', 11 | 2 => 'error', 12 | 'key' => 'message', 13 | ]; 14 | -------------------------------------------------------------------------------- /test/testdata/language/zh-CN/response.php: -------------------------------------------------------------------------------- 1 | '操作成功', 12 | 2 => '发生错误', 13 | 'key' => '消息', 14 | ]; 15 | --------------------------------------------------------------------------------