├── .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)
4 | [](https://packagist.org/packages/phppkg/config)
5 | [](https://packagist.org/packages/phppkg/config)
6 | [](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)
4 | [](https://packagist.org/packages/phppkg/config)
5 | [](https://packagist.org/packages/phppkg/config)
6 | [](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 |
--------------------------------------------------------------------------------