├── .gitattributes
├── .gitignore
├── .phpstorm.meta.php
├── src
├── Exceptions
│ ├── Exception.php
│ ├── HttpException.php
│ └── InvalidArgumentException.php
├── ConfigProvider.php
├── IM.php
├── Traits
│ └── HasHttpRequest.php
└── Support
│ └── Config.php
├── publish
└── im.php
├── phpunit.xml
├── .travis.yml
├── LICENSE
├── composer.json
├── .php_cs
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | /tests export-ignore
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor/
2 | composer.lock
3 | *.cache
4 | *.log
5 | .idea/
--------------------------------------------------------------------------------
/.phpstorm.meta.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Hedeqiang\IM\Exceptions;
13 |
14 | class Exception extends \Exception
15 | {
16 | }
17 |
--------------------------------------------------------------------------------
/src/Exceptions/HttpException.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Hedeqiang\IM\Exceptions;
13 |
14 | class HttpException extends Exception
15 | {
16 | }
17 |
--------------------------------------------------------------------------------
/src/Exceptions/InvalidArgumentException.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Hedeqiang\IM\Exceptions;
13 |
14 | class InvalidArgumentException extends Exception
15 | {
16 | }
17 |
--------------------------------------------------------------------------------
/publish/im.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | return [
13 | 'sdk_app_id' => env('SDK_APP_ID', ''),
14 | 'identifier' => env('IDENTIFIER', ''),
15 | 'secret_key' => env('SECRET_KEY', ''),
16 | ];
17 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 | ./tests/
14 |
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | sudo: required
4 |
5 | matrix:
6 | include:
7 | - php: 7.2
8 | env: SW_VERSION="4.4.7"
9 | - php: 7.3
10 | env: SW_VERSION="4.4.7"
11 | - php: master
12 | env: SW_VERSION="4.4.7"
13 |
14 | allow_failures:
15 | - php: master
16 |
17 | services:
18 | - mysql
19 | - redis-server
20 | - docker
21 |
22 | before_install:
23 | - export PHP_MAJOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 1)"
24 | - export PHP_MINOR="$(`phpenv which php` -r 'echo phpversion();' | cut -d '.' -f 2)"
25 | - echo $PHP_MAJOR
26 | - echo $PHP_MINOR
27 |
28 | install:
29 | - cd $TRAVIS_BUILD_DIR
30 | - bash ./tests/swoole.install.sh
31 | - phpenv config-rm xdebug.ini || echo "xdebug not available"
32 | - phpenv config-add ./tests/ci.ini
33 |
34 | before_script:
35 | - cd $TRAVIS_BUILD_DIR
36 | - composer config -g process-timeout 900 && composer update
37 |
38 | script:
39 | - composer analyze
40 | - composer test
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 hedeqiang/im
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, 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,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/ConfigProvider.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace Hedeqiang\IM;
15 |
16 | class ConfigProvider
17 | {
18 | public function __invoke(): array
19 | {
20 | return [
21 | 'dependencies' => [
22 | ],
23 | 'commands' => [
24 | ],
25 | 'annotations' => [
26 | 'scan' => [
27 | 'paths' => [
28 | __DIR__,
29 | ],
30 | ],
31 | ],
32 | 'publish' => [
33 | [
34 | 'id' => 'config',
35 | 'description' => 'The config of im.',
36 | 'source' => __DIR__.'/../publish/im.php',
37 | 'destination' => BASE_PATH.'/config/autoload/im.php',
38 | ],
39 | ],
40 | ];
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hedeqiang/im",
3 | "type": "library",
4 | "license": "MIT",
5 | "keywords": [
6 | "php",
7 | "hyperf",
8 | "IM",
9 | "腾讯云",
10 | "即时通信"
11 | ],
12 | "description": "腾讯云即时通信 SDK for Hyperf",
13 | "autoload": {
14 | "psr-4": {
15 | "Hedeqiang\\IM\\": "./src/"
16 | }
17 | },
18 | "autoload-dev": {
19 | "psr-4": {
20 | "HyperfTest\\": "tests"
21 | }
22 | },
23 | "require": {
24 | "php": ">=7.2",
25 | "ext-swoole": ">=4.5",
26 | "tencent/tls-sig-api-v2": "^1.0"
27 | },
28 | "require-dev": {
29 | "friendsofphp/php-cs-fixer": "^2.14",
30 | "phpstan/phpstan": "^0.12",
31 | "hyperf/testing": "2.0.*",
32 | "swoft/swoole-ide-helper": "dev-master"
33 | },
34 | "config": {
35 | "sort-packages": true
36 | },
37 | "scripts": {
38 | "test": "co-phpunit -c phpunit.xml --colors=always",
39 | "analyze": "phpstan analyse --memory-limit 1024M -l 0 ./src",
40 | "cs-fix": "php-cs-fixer fix $1"
41 | },
42 | "extra": {
43 | "hyperf": {
44 | "config": "Hedeqiang\\IM\\ConfigProvider"
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/.php_cs:
--------------------------------------------------------------------------------
1 | setRiskyAllowed(true)
14 | ->setRules([
15 | '@PSR2' => true,
16 | '@Symfony' => true,
17 | '@DoctrineAnnotation' => true,
18 | '@PhpCsFixer' => true,
19 | 'header_comment' => [
20 | 'commentType' => 'PHPDoc',
21 | 'header' => $header,
22 | 'separate' => 'none',
23 | 'location' => 'after_declare_strict',
24 | ],
25 | 'array_syntax' => [
26 | 'syntax' => 'short'
27 | ],
28 | 'list_syntax' => [
29 | 'syntax' => 'short'
30 | ],
31 | 'concat_space' => [
32 | 'spacing' => 'one'
33 | ],
34 | 'blank_line_before_statement' => [
35 | 'statements' => [
36 | 'declare',
37 | ],
38 | ],
39 | 'general_phpdoc_annotation_remove' => [
40 | 'annotations' => [
41 | 'author'
42 | ],
43 | ],
44 | 'ordered_imports' => [
45 | 'imports_order' => [
46 | 'class', 'function', 'const',
47 | ],
48 | 'sort_algorithm' => 'alpha',
49 | ],
50 | 'single_line_comment_style' => [
51 | 'comment_types' => [
52 | ],
53 | ],
54 | 'yoda_style' => [
55 | 'always_move_variable' => false,
56 | 'equal' => false,
57 | 'identical' => false,
58 | ],
59 | 'phpdoc_align' => [
60 | 'align' => 'left',
61 | ],
62 | 'multiline_whitespace_before_semicolons' => [
63 | 'strategy' => 'no_multi_line',
64 | ],
65 | 'class_attributes_separation' => true,
66 | 'combine_consecutive_unsets' => true,
67 | 'declare_strict_types' => true,
68 | 'linebreak_after_opening_tag' => true,
69 | 'lowercase_constants' => true,
70 | 'lowercase_static_reference' => true,
71 | 'no_useless_else' => true,
72 | 'no_unused_imports' => true,
73 | 'not_operator_with_successor_space' => true,
74 | 'not_operator_with_space' => false,
75 | 'ordered_class_elements' => true,
76 | 'php_unit_strict' => false,
77 | 'phpdoc_separation' => false,
78 | 'single_quote' => true,
79 | 'standardize_not_equals' => true,
80 | 'multiline_comment_opening_closing' => true,
81 | ])
82 | ->setFinder(
83 | PhpCsFixer\Finder::create()
84 | ->exclude('vendor')
85 | ->in(__DIR__)
86 | )
87 | ->setUsingCache(false);
88 |
--------------------------------------------------------------------------------
/src/IM.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Hedeqiang\IM;
13 |
14 | use Hedeqiang\IM\Exceptions\Exception;
15 | use Hedeqiang\IM\Exceptions\HttpException;
16 | use Hedeqiang\IM\Support\Config;
17 | use Hedeqiang\IM\Traits\HasHttpRequest;
18 | use Psr\SimpleCache\CacheInterface;
19 | use Tencent\TLSSigAPIv2;
20 | use Hyperf\Utils\ApplicationContext;
21 |
22 | class IM
23 | {
24 | use HasHttpRequest;
25 |
26 | const ENDPOINT_TEMPLATE = 'https://console.tim.qq.com/%s/%s/%s?%s';
27 |
28 | const ENDPOINT_VERSION = 'v4';
29 |
30 | const ENDPOINT_FORMAT = 'json';
31 |
32 | /**
33 | * @var Config
34 | */
35 | protected $config;
36 |
37 | public function __construct(array $config)
38 | {
39 | $this->config = new Config($config);
40 | }
41 |
42 | /**
43 | * @param string $servername
44 | * @param string $command
45 | *
46 | * @return array
47 | *
48 | * @throws Exception
49 | * @throws HttpException
50 | */
51 | public function send($servername, $command, array $params = [])
52 | {
53 | try {
54 | $result = $this->postJson($this->buildEndpoint($servername, $command), $params);
55 | } catch (\Exception $e) {
56 | throw new HttpException($e->getMessage(), $e->getCode(), $e);
57 | }
58 |
59 | if (0 === $result['ErrorCode'] && 'OK' === $result['ActionStatus']) {
60 | return $result;
61 | }
62 |
63 | throw new Exception('Tim REST API error: '.json_encode($result));
64 | }
65 |
66 | /**
67 | * Build endpoint url.
68 | *
69 | * @throws \Exception
70 | */
71 | protected function buildEndpoint(string $servername, string $command): string
72 | {
73 | $query = http_build_query([
74 | 'sdkappid' => $this->config->get('sdk_app_id'),
75 | 'identifier' => $this->config->get('identifier'),
76 | 'usersig' => $this->generateSign($this->config->get('identifier')),
77 | 'random' => mt_rand(0, 4294967295),
78 | 'contenttype' => self::ENDPOINT_FORMAT,
79 | ]);
80 |
81 | return \sprintf(self::ENDPOINT_TEMPLATE, self::ENDPOINT_VERSION, $servername, $command, $query);
82 | }
83 |
84 | /**
85 | * Generate Sign.
86 | *
87 | * @throws \Exception
88 | */
89 | protected function generateSign(string $identifier, int $expires = 15552000): string
90 | {
91 | $cache = $this->di()->get(CacheInterface::class);
92 |
93 | if (!$cache->has($identifier.'_cache')) {
94 | $api = new TLSSigAPIv2($this->config->get('sdk_app_id'), $this->config->get('secret_key'));
95 | $sign = $api->genUserSig($identifier, $expires);
96 | $cache->set($identifier.'_cache', $sign, $expires);
97 |
98 | return $sign;
99 | }
100 |
101 | return $cache->get($identifier.'_cache');
102 | }
103 |
104 | /**
105 | * Finds an entry of the container by its identifier and returns it.
106 | *
107 | * @param mixed|null $id
108 | *
109 | * @return mixed|\Psr\Container\ContainerInterface
110 | */
111 | protected function di($id = null)
112 | {
113 | $container = ApplicationContext::getContainer();
114 | if ($id) {
115 | return $container->get($id);
116 | }
117 |
118 | return $container;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/Traits/HasHttpRequest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Hedeqiang\IM\Traits;
13 |
14 | use GuzzleHttp\Client;
15 | use Psr\Http\Message\ResponseInterface;
16 |
17 | /**
18 | * Trait HasHttpRequest.
19 | */
20 | trait HasHttpRequest
21 | {
22 | /**
23 | * @var HandlerStack
24 | */
25 | protected $stack;
26 |
27 | /**
28 | * Make a get request.
29 | *
30 | * @param string $endpoint
31 | * @param array $query
32 | * @param array $headers
33 | *
34 | * @return array
35 | */
36 | protected function get($endpoint, $query = [], $headers = [])
37 | {
38 | return $this->request('get', $endpoint, [
39 | 'headers' => $headers,
40 | 'query' => $query,
41 | ]);
42 | }
43 |
44 | /**
45 | * Make a post request.
46 | *
47 | * @param string $endpoint
48 | * @param array $params
49 | * @param array $headers
50 | *
51 | * @return array
52 | */
53 | protected function post($endpoint, $params = [], $headers = [])
54 | {
55 | return $this->request('post', $endpoint, [
56 | 'headers' => $headers,
57 | 'form_params' => $params,
58 | ]);
59 | }
60 |
61 | /**
62 | * Make a post request with json params.
63 | *
64 | * @param $endpoint
65 | * @param array $params
66 | * @param array $headers
67 | *
68 | * @return array
69 | */
70 | protected function postJson($endpoint, $params = [], $headers = [])
71 | {
72 | return $this->request('post', $endpoint, [
73 | 'headers' => $headers,
74 | 'json' => $params,
75 | ]);
76 | }
77 |
78 | /**
79 | * Make a http request.
80 | *
81 | * @param string $method
82 | * @param string $endpoint
83 | * @param array $options http://docs.guzzlephp.org/en/latest/request-options.html
84 | *
85 | * @return array
86 | */
87 | protected function request($method, $endpoint, $options = [])
88 | {
89 | return $this->unwrapResponse($this->getHttpClient($this->getBaseOptions())->{$method}($endpoint, $options));
90 | }
91 |
92 | /**
93 | * Return base Guzzle options.
94 | *
95 | * @return array
96 | */
97 | protected function getBaseOptions()
98 | {
99 | $options = [
100 | 'base_uri' => method_exists($this, 'getBaseUri') ? $this->getBaseUri() : '',
101 | 'timeout' => method_exists($this, 'getTimeout') ? $this->getTimeout() : 10.0,
102 | ];
103 |
104 | return $options;
105 | }
106 |
107 | /**
108 | * Return http client.
109 | *
110 | * @return \GuzzleHttp\Client
111 | *
112 | * @codeCoverageIgnore
113 | */
114 | protected function getHttpClient(array $options = [])
115 | {
116 | return make(Client::class, $options);
117 | }
118 |
119 | protected function getHandlerStack(): HandlerStack
120 | {
121 | if ($this->stack instanceof HandlerStack) {
122 | return $this->stack;
123 | }
124 |
125 | return $this->stack = di()->get(HandlerStackFactory::class)->create();
126 | }
127 |
128 | /**
129 | * Convert response contents to json.
130 | *
131 | * @return ResponseInterface|array|string
132 | */
133 | protected function unwrapResponse(ResponseInterface $response)
134 | {
135 | $contentType = $response->getHeaderLine('Content-Type');
136 | $contents = $response->getBody()->getContents();
137 | if (false !== stripos($contentType, 'json') || stripos($contentType, 'javascript')) {
138 | return json_decode($contents, true);
139 | } elseif (false !== stripos($contentType, 'xml')) {
140 | return json_decode(json_encode(simplexml_load_string($contents)), true);
141 | }
142 |
143 | return $contents;
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
腾讯云 IM 服务端 SDK for Hyperf
2 |
3 | 腾讯云 IM.
4 |
5 | > 普通 PHP 请使用 [hedeqiang/ten-im](https://github.com/hedeqiang/IM) 扩展
6 | ## 前提
7 | 使用本扩展前需要登录 [即时通信 IM 控制台](https://console.cloud.tencent.com/avc) 创建应用,配置管理员、获取 app_id、Key 等关键信息
8 |
9 | 更多请查看并熟读 [即时通信 IM 服务端API](https://cloud.tencent.com/document/product/269/32688)
10 |
11 | [REST API 接口列表](https://cloud.tencent.com/document/product/269/1520)
12 | ## 安装
13 |
14 | ```shell
15 | $ composer require hedeqiang/im -vvv
16 | ```
17 |
18 | ## 发布配置
19 | ```php
20 | php bin/hyperf.php vendor:publish hedeqiang/im
21 | ```
22 |
23 | 编辑 `.env` 文件
24 | ```php
25 | SDK_APP_ID=
26 | IDENTIFIER=
27 | SECRET_KEY=
28 | ```
29 |
30 | ## 使用
31 | ### 获取用户在线状态
32 | ```php
33 | config;
54 | $im = new IM($config);
55 | $params = [
56 | 'To_Account' => ['hedeqiang']
57 | ];
58 |
59 | print_r($im->send('openim','querystate',$params));
60 | }
61 | }
62 |
63 | ```
64 | 返回示例
65 | ```php
66 | {
67 | "ActionStatus": "OK",
68 | "ErrorInfo": "",
69 | "ErrorCode": 0,
70 | "QueryResult": [{
71 | "To_Account": "hedeqiang",
72 | "State": "Offline"
73 | }]
74 | }
75 | ```
76 | ### 设置资料
77 | ```php
78 | $params = [
79 | 'From_Account' => 'hedeqiang',
80 | 'ProfileItem' => [
81 | ['Tag' => 'Tag_Profile_IM_Nick', 'Value' => 'hedeqiang'],
82 | ['Tag' => 'Tag_Profile_IM_Gender', 'Value' => 'Gender_Type_Male'],
83 | ['Tag' => 'Tag_Profile_IM_BirthDay', 'Value' => 19940410],
84 | ['Tag' => 'Tag_Profile_IM_SelfSignature', 'Value' => '程序人生的寂静欢喜'],
85 | ['Tag' => 'Tag_Profile_IM_Image', 'Value' => 'https://upyun.laravelcode.cn/upload/avatar/1524205770e4fbfbff-86ae-3bf9-b7b8-e0e70ce14553.png'],
86 | ],
87 | ];
88 |
89 | print_r($im->send('profile','portrait_set',$params));
90 | ```
91 |
92 | 返回示例:
93 | ```php
94 | {
95 | "ActionStatus": "OK",
96 | "ErrorCode": 0,
97 | "ErrorInfo": "",
98 | "ErrorDisplay": ""
99 | }
100 | ```
101 |
102 | ### 单发单聊消息
103 | ```php
104 | $params = [
105 | 'SyncOtherMachine' => 1, // 消息不同步至发送方
106 | 'From_Account' => 'hedeqiang',
107 | 'To_Account' => 'zhangsan',
108 | 'MsgRandom' => 1287657,
109 | 'MsgTimeStamp' => 1557387418,
110 | 'MsgBody' => [
111 | [
112 | 'MsgType' => 'TIMTextElem',
113 | 'MsgContent' => [
114 | 'Text' => '晚上去撸串啊'
115 | ]
116 | ]
117 | ]
118 | ];
119 |
120 | print_r($im->send('openim','sendmsg',$params));
121 | ```
122 |
123 | 返回示例:
124 | ```php
125 | {
126 | "ActionStatus":"OK",
127 | "ErrorInfo":"",
128 | "ErrorCode":0,
129 | "MsgTime":1573179125,
130 | "MsgKey":"748144182_1287657_1573179125"
131 | }
132 | ```
133 |
134 | > 其中 `send` 方法接收三个参数。第一个参数 $servicename : 内部服务名,不同的 servicename 对应不同的服务类型;第二个参数 `$command`:命令字,与 servicename 组合用来标识具体的业务功能;第三个参数为请求包主体
135 |
136 | > 示例:`v4/im_open_login_svc/account_import`,其中 `im_open_login_svc` 为 `servicename`; `account_import` 为 `command`
137 |
138 | 请求包示例:
139 | ```php
140 | {
141 | "From_Account":"id",
142 | "ProfileItem":
143 | [
144 | {
145 | "Tag":"Tag_Profile_IM_Nick",
146 | "Value":"MyNickName"
147 | }
148 | ]
149 | }
150 | ```
151 |
152 | 更多用法请参考 [REST API 接口列表](https://cloud.tencent.com/document/product/269/1520)
153 |
154 | TODO
155 |
156 | ## Contributing
157 |
158 | You can contribute in one of three ways:
159 |
160 | 1. File bug reports using the [issue tracker](https://github.com/hedeqiang/hyperf-im/issues).
161 | 2. Answer questions or fix bugs on the [issue tracker](https://github.com/hedeqiang/hyperf-im/issues).
162 | 3. Contribute new features or update the wiki.
163 |
164 | _The code contribution process is not very formal. You just need to make sure that you follow the PSR-0, PSR-1, and PSR-2 coding guidelines. Any new code contributions must be accompanied by unit tests where applicable._
165 |
166 | ## License
167 |
168 | MIT
169 |
--------------------------------------------------------------------------------
/src/Support/Config.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * This source file is subject to the MIT license that is bundled
9 | * with this source code in the file LICENSE.
10 | */
11 |
12 | namespace Hedeqiang\IM\Support;
13 |
14 | use ArrayAccess;
15 | use InvalidArgumentException;
16 |
17 | /**
18 | * Class Config.
19 | */
20 | class Config implements ArrayAccess
21 | {
22 | /**
23 | * @var array
24 | */
25 | protected $config;
26 |
27 | /**
28 | * Config constructor.
29 | */
30 | public function __construct(array $config)
31 | {
32 | $this->config = $config;
33 | }
34 |
35 | /**
36 | * Get an item from an array using "dot" notation.
37 | *
38 | * @param string $key
39 | * @param mixed $default
40 | *
41 | * @return mixed
42 | */
43 | public function get($key, $default = null)
44 | {
45 | $config = $this->config;
46 | if (is_null($key)) {
47 | return $config;
48 | }
49 | if (isset($config[$key])) {
50 | return $config[$key];
51 | }
52 | foreach (explode('.', $key) as $segment) {
53 | if (!is_array($config) || !array_key_exists($segment, $config)) {
54 | return $default;
55 | }
56 | $config = $config[$segment];
57 | }
58 |
59 | return $config;
60 | }
61 |
62 | /**
63 | * Set an array item to a given value using "dot" notation.
64 | *
65 | * @param string $key
66 | * @param mixed $value
67 | *
68 | * @return array
69 | */
70 | public function set($key, $value)
71 | {
72 | if (is_null($key)) {
73 | throw new InvalidArgumentException('Invalid config key.');
74 | }
75 | $keys = explode('.', $key);
76 | $config = &$this->config;
77 | while (count($keys) > 1) {
78 | $key = array_shift($keys);
79 | if (!isset($config[$key]) || !is_array($config[$key])) {
80 | $config[$key] = [];
81 | }
82 | $config = &$config[$key];
83 | }
84 | $config[array_shift($keys)] = $value;
85 |
86 | return $config;
87 | }
88 |
89 | /**
90 | * Determine if the given configuration value exists.
91 | *
92 | * @param string $key
93 | *
94 | * @return bool
95 | */
96 | public function has($key)
97 | {
98 | return (bool) $this->get($key);
99 | }
100 |
101 | /**
102 | * Whether a offset exists.
103 | *
104 | * @see http://php.net/manual/en/arrayaccess.offsetexists.php
105 | *
106 | * @param mixed $offset
107 | * An offset to check for.
108 | *
109 | *
110 | * @return bool true on success or false on failure.
111 | *
112 | *
113 | * The return value will be casted to boolean if non-boolean was returned
114 | *
115 | * @since 5.0.0
116 | */
117 | public function offsetExists($offset)
118 | {
119 | return array_key_exists($offset, $this->config);
120 | }
121 |
122 | /**
123 | * Offset to retrieve.
124 | *
125 | * @see http://php.net/manual/en/arrayaccess.offsetget.php
126 | *
127 | * @param mixed $offset
128 | * The offset to retrieve.
129 | *
130 | *
131 | * @return mixed Can return all value types
132 | *
133 | * @since 5.0.0
134 | */
135 | public function offsetGet($offset)
136 | {
137 | return $this->get($offset);
138 | }
139 |
140 | /**
141 | * Offset to set.
142 | *
143 | * @see http://php.net/manual/en/arrayaccess.offsetset.php
144 | *
145 | * @param mixed $offset
146 | * The offset to assign the value to.
147 | *
148 | * @param mixed $value
149 | * The value to set.
150 | *
151 | *
152 | * @since 5.0.0
153 | */
154 | public function offsetSet($offset, $value)
155 | {
156 | $this->set($offset, $value);
157 | }
158 |
159 | /**
160 | * Offset to unset.
161 | *
162 | * @see http://php.net/manual/en/arrayaccess.offsetunset.php
163 | *
164 | * @param mixed $offset
165 | * The offset to unset.
166 | *
167 | *
168 | * @since 5.0.0
169 | */
170 | public function offsetUnset($offset)
171 | {
172 | $this->set($offset, null);
173 | }
174 | }
175 |
--------------------------------------------------------------------------------