From 664c4a127492349f34b8703f1000d12decd63fa0 Mon Sep 17 00:00:00 2001 From: overtrue Date: Sun, 22 Oct 2017 12:21:55 +0800 Subject: [PATCH] Fixed response formatting feature. #661 --- phpunit.xml | 2 +- src/Kernel/BaseClient.php | 2 +- src/Kernel/Contracts/Arrayable.php | 29 +++++ src/Kernel/Helpers.php | 43 +++++++ src/Kernel/Support/ArrayAccessible.php | 66 ++++++++++ src/Kernel/Support/Collection.php | 3 +- src/Kernel/Support/LICENSE | 22 ---- src/Kernel/Support/README.md | 2 - src/Kernel/Support/composer.json | 26 ---- src/Kernel/Traits/HasHttpRequests.php | 21 ++-- src/OfficialAccount/Material/Client.php | 2 +- src/OpenPlatform/Application.php | 2 +- src/Payment/BaseClient.php | 2 +- src/Payment/LICENSE | 22 ---- src/Payment/README.md | 2 - src/Payment/composer.json | 25 ---- tests/Kernel/HelpersTest.php | 128 ++++++++++++++++++++ tests/Kernel/Traits/HasHttpRequestsTest.php | 11 +- 18 files changed, 293 insertions(+), 117 deletions(-) create mode 100644 src/Kernel/Contracts/Arrayable.php create mode 100644 src/Kernel/Support/ArrayAccessible.php delete mode 100644 src/Kernel/Support/LICENSE delete mode 100644 src/Kernel/Support/README.md delete mode 100644 src/Kernel/Support/composer.json delete mode 100644 src/Payment/LICENSE delete mode 100644 src/Payment/README.md delete mode 100644 src/Payment/composer.json create mode 100644 tests/Kernel/HelpersTest.php diff --git a/phpunit.xml b/phpunit.xml index 36bc2f846..2ef6bfd3b 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -20,7 +20,7 @@ src/ src/ - src/ + src/Kernel/Support src/Encryption src/Support diff --git a/src/Kernel/BaseClient.php b/src/Kernel/BaseClient.php index a6facc474..02a5837e4 100644 --- a/src/Kernel/BaseClient.php +++ b/src/Kernel/BaseClient.php @@ -160,7 +160,7 @@ public function request(string $url, string $method = 'GET', array $options = [] $response = $this->performRequest($url, $method, $options); - return $returnRaw ? $response : $this->resolveResponse($response, $this->app->config->get('response_type', 'array')); + return $returnRaw ? $response : $this->resolveResponse($response, $this->app->config->get('response_type')); } /** diff --git a/src/Kernel/Contracts/Arrayable.php b/src/Kernel/Contracts/Arrayable.php new file mode 100644 index 000000000..d947f8f02 --- /dev/null +++ b/src/Kernel/Contracts/Arrayable.php @@ -0,0 +1,29 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Contracts; + +use ArrayAccess; + +/** + * Interface Arrayable. + * + * @author overtrue + */ +interface Arrayable extends ArrayAccess +{ + /** + * Get the instance as an array. + * + * @return array + */ + public function toArray(); +} diff --git a/src/Kernel/Helpers.php b/src/Kernel/Helpers.php index 347c23c78..ed05386cc 100644 --- a/src/Kernel/Helpers.php +++ b/src/Kernel/Helpers.php @@ -10,3 +10,46 @@ */ namespace EasyWeChat\Kernel; + +use EasyWeChat\Kernel\Contracts\Arrayable; +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Support\Arr; +use EasyWeChat\Kernel\Support\Collection; + +function data_get($data, $key, $default = null) +{ + switch (true) { + case is_array($data): + return Arr::get($data, $key, $default); + case $data instanceof Collection: + return $data->get($key, $default); + case $data instanceof Arrayable: + return Arr::get($data->toArray(), $key, $default); + case $data instanceof \ArrayIterator: + return $data->getArrayCopy()[$key] ?? $default; + case $data instanceof \ArrayAccess: + return $data[$key] ?? $default; + case $data instanceof \IteratorAggregate: + return $data->getIterator()->getArrayCopy()[$key] ?? $default; + default: + throw new RuntimeException(sprintf('Can\'t access data with key "%s"', $key)); + } +} + +function data_to_array($data) +{ + switch (true) { + case is_array($data): + return $data; + case $data instanceof Collection: + return $data->all(); + case $data instanceof Arrayable: + return $data->toArray(); + case $data instanceof \IteratorAggregate: + return $data->getIterator()->getArrayCopy(); + case $data instanceof \ArrayIterator: + return $data->getArrayCopy(); + default: + throw new RuntimeException(sprintf('Can\'t transform data to array')); + } +} diff --git a/src/Kernel/Support/ArrayAccessible.php b/src/Kernel/Support/ArrayAccessible.php new file mode 100644 index 000000000..72e0b0446 --- /dev/null +++ b/src/Kernel/Support/ArrayAccessible.php @@ -0,0 +1,66 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Kernel\Support; + +use ArrayAccess; +use ArrayIterator; +use EasyWeChat\Kernel\Contracts\Arrayable; +use IteratorAggregate; + +/** + * Class ArrayAccessible. + * + * @author overtrue + */ +class ArrayAccessible implements ArrayAccess, IteratorAggregate, Arrayable +{ + private $array; + + public function __construct(array $array = []) + { + $this->array = $array; + } + + public function offsetExists($offset) + { + return array_key_exists($offset, $this->array); + } + + public function offsetGet($offset) + { + return $this->array[$offset]; + } + + public function offsetSet($offset, $value) + { + if (null === $offset) { + $this->array[] = $value; + } else { + $this->array[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + unset($this->array[$offset]); + } + + public function getIterator() + { + return new ArrayIterator($this->array); + } + + public function toArray() + { + return $this->array; + } +} diff --git a/src/Kernel/Support/Collection.php b/src/Kernel/Support/Collection.php index bbb27313c..b289a7915 100644 --- a/src/Kernel/Support/Collection.php +++ b/src/Kernel/Support/Collection.php @@ -14,6 +14,7 @@ use ArrayAccess; use ArrayIterator; use Countable; +use EasyWeChat\Kernel\Contracts\Arrayable; use IteratorAggregate; use JsonSerializable; use Serializable; @@ -21,7 +22,7 @@ /** * Class Collection. */ -class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Serializable +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Serializable, Arrayable { /** * The collection data. diff --git a/src/Kernel/Support/LICENSE b/src/Kernel/Support/LICENSE deleted file mode 100644 index c5251b826..000000000 --- a/src/Kernel/Support/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 overtrue - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/src/Kernel/Support/README.md b/src/Kernel/Support/README.md deleted file mode 100644 index 081c6ff5b..000000000 --- a/src/Kernel/Support/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# support -Tools for Wechat SDK. diff --git a/src/Kernel/Support/composer.json b/src/Kernel/Support/composer.json deleted file mode 100644 index a33ca27fa..000000000 --- a/src/Kernel/Support/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "easywechat/support", - "description": "support module for EasyWeChat SDK.", - "keywords": ["wechat", "weixin", "SDK", "support", "easywechat"], - "license": "MIT", - "authors": [ - { - "name": "overtrue", - "email": "anzhengchao@gmail.com" - } - ], - "autoload": { - "psr-4": { - "EasyWeChat\\Support\\": "." - } - }, - "minimum-stability":"dev", - "require-dev": { - "phpunit/phpunit": "*", - "mockery/mockery": "^1.0@dev" - }, - "require": { - "monolog/monolog": "1.*", - "ext-SimpleXML": "*" - } -} diff --git a/src/Kernel/Traits/HasHttpRequests.php b/src/Kernel/Traits/HasHttpRequests.php index 4d2eae8d1..18a3a5b50 100644 --- a/src/Kernel/Traits/HasHttpRequests.php +++ b/src/Kernel/Traits/HasHttpRequests.php @@ -11,6 +11,8 @@ namespace EasyWeChat\Kernel\Traits; +use EasyWeChat\Kernel\Contracts\Arrayable; +use EasyWeChat\Kernel\Exceptions\InvalidConfigException; use EasyWeChat\Kernel\Http\Response; use EasyWeChat\Kernel\Support\Collection; use GuzzleHttp\Client; @@ -187,15 +189,18 @@ public function getHandlerStack(): HandlerStack /** * @param \Psr\Http\Message\ResponseInterface $response - * @param string $type + * @param string|null $type * - * @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string + * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string + * + * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException */ - protected function resolveResponse(ResponseInterface $response, string $type) + protected function resolveResponse(ResponseInterface $response, $type = null) { $response = Response::buildFromPsrResponse($response); + $response->getBody()->rewind(); - switch ($type) { + switch ($type ?? 'array') { case 'collection': return $response->toCollection(); case 'array': @@ -203,13 +208,13 @@ protected function resolveResponse(ResponseInterface $response, string $type) case 'object': return $response->toObject(); case 'raw': + return $response; default: - $response->getBody()->rewind(); - if (class_exists($type)) { - return new $type($response); + if (!is_subclass_of($type, Arrayable::class)) { + throw new InvalidConfigException(sprintf('Config key "response_type" classname must be an instanceof %s', Arrayable::class)); } - return $response; + return new $type($response); } } diff --git a/src/OfficialAccount/Material/Client.php b/src/OfficialAccount/Material/Client.php index 8a56dd8e3..402af36d3 100644 --- a/src/OfficialAccount/Material/Client.php +++ b/src/OfficialAccount/Material/Client.php @@ -163,7 +163,7 @@ public function get(string $mediaId) return StreamResponse::buildFromPsrResponse($response); } - return $this->resolveResponse($response, $this->app['config']->get('response_type', 'array')); + return $this->resolveResponse($response, $this->app['config']->get('response_type')); } /** diff --git a/src/OpenPlatform/Application.php b/src/OpenPlatform/Application.php index d5157147f..50f1d8323 100644 --- a/src/OpenPlatform/Application.php +++ b/src/OpenPlatform/Application.php @@ -128,7 +128,7 @@ protected function getAuthorizerConfig(string $appId, string $refreshToken): arr { return [ 'debug' => $this['config']->get('debug', false), - 'response_type' => $this['config']->get('response_type', 'array'), + 'response_type' => $this['config']->get('response_type'), 'log' => $this['config']->get('log', []), 'app_id' => $appId, 'refresh_token' => $refreshToken, diff --git a/src/Payment/BaseClient.php b/src/Payment/BaseClient.php index 741513b60..3e21ba5a4 100644 --- a/src/Payment/BaseClient.php +++ b/src/Payment/BaseClient.php @@ -77,7 +77,7 @@ protected function request($api, array $params, $method = 'post', array $options $response = $this->performRequest($api, $method, $options); - return $returnResponse ? $response : $this->resolveResponse($response, $this->app->config->get('response_type', 'array')); + return $returnResponse ? $response : $this->resolveResponse($response, $this->app->config->get('response_type')); } /** diff --git a/src/Payment/LICENSE b/src/Payment/LICENSE deleted file mode 100644 index 307d3f4da..000000000 --- a/src/Payment/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 wechat-sdk - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - diff --git a/src/Payment/README.md b/src/Payment/README.md deleted file mode 100644 index f63eff391..000000000 --- a/src/Payment/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# payment -微信支付 diff --git a/src/Payment/composer.json b/src/Payment/composer.json deleted file mode 100644 index b37a2d276..000000000 --- a/src/Payment/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "easywechat/payment", - "description": "payment module for EasyWeChat SDK.", - "keywords": ["wechat", "weixin", "SDK", "payment", "easywechat"], - "license": "MIT", - "authors": [ - { - "name": "overtrue", - "email": "anzhengchao@gmail.com" - } - ], - "autoload": { - "psr-4": { - "EasyWeChat\\Payment\\": "." - }, - "files": ["helpers.php"] - }, - "require-dev": { - "phpunit/phpunit": "~4.0", - "mockery/mockery": "^1.0@dev" - }, - "require": { - "easywechat/core" : "dev-master" - } -} diff --git a/tests/Kernel/HelpersTest.php b/tests/Kernel/HelpersTest.php new file mode 100644 index 000000000..7431d0078 --- /dev/null +++ b/tests/Kernel/HelpersTest.php @@ -0,0 +1,128 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace EasyWeChat\Tests\Kernel; + +use EasyWeChat\Kernel\Exceptions\RuntimeException; +use EasyWeChat\Kernel\Support\ArrayAccessible; +use EasyWeChat\Kernel\Support\Collection; +use EasyWeChat\Tests\TestCase; +use function EasyWeChat\Kernel\data_get; +use function EasyWeChat\Kernel\data_to_array; + +class HelpersTest extends TestCase +{ + public function testDataGet() + { + // array + $this->assertSame('foo', data_get(['name' => 'foo'], 'name')); + $this->assertNull(data_get(['name' => 'foo'], 'age')); + $this->assertSame(27, data_get(['name' => 'foo'], 'age', 27)); + + // Arrayable + $array = new ArrayAccessible(['name' => 'overtrue']); + $this->assertSame('overtrue', data_get($array, 'name')); + + // ArrayAccess + $array = new DummyArrayAccessClassForHelpersTest(['name' => 'overtrue']); + $this->assertSame('overtrue', data_get($array, 'name')); + + // Collection + $array = new Collection(['name' => 'overtrue']); + $this->assertSame('overtrue', data_get($array, 'name')); + + // IteratorAggregate + $array = new DummyIteratorAggregateClassForHelpersTest(['name' => 'overtrue']); + $this->assertSame('overtrue', data_get($array, 'name')); + + // ArrayIterator + $array = new DummyIteratorAggregateClassForHelpersTest(['name' => 'overtrue']); + $this->assertSame('overtrue', data_get($array->getIterator(), 'name')); + + $this->expectException(RuntimeException::class); + data_get('not an array accessible data', 'foo'); + + $this->fail('Failed assert that data_get should throw an exception.'); + } + + public function testDataToArray() + { + $array = ['name' => 'overtrue']; + // array + $this->assertSame($array, data_to_array($array)); + + // Arrayable + $data = new ArrayAccessible($array); + $this->assertSame($array, data_to_array($data)); + + // Collection + $data = new Collection($array); + $this->assertSame($array, data_to_array($data)); + + // IteratorAggregate + $data = new DummyIteratorAggregateClassForHelpersTest($array); + $this->assertSame($array, data_to_array($data)); + + // ArrayIterator + $data = new DummyIteratorAggregateClassForHelpersTest($array); + $this->assertSame($array, data_to_array($data->getIterator())); + + $this->expectException(RuntimeException::class); + data_to_array('not an arrayable data', 'foo'); + + $this->fail('Failed assert that data_to_array should throw an exception.'); + } +} + +class DummyIteratorAggregateClassForHelpersTest implements \IteratorAggregate +{ + private $array; + + public function __construct($array) + { + $this->array = $array; + } + + public function getIterator() + { + return new \ArrayIterator($this->array); + } +} + +class DummyArrayAccessClassForHelpersTest implements \ArrayAccess +{ + private $array; + + public function __construct($array) + { + $this->array = $array; + } + + public function offsetExists($offset) + { + return isset($this->array[$offset]); + } + + public function offsetGet($offset) + { + return $this->array[$offset] ?? null; + } + + public function offsetSet($offset, $value) + { + $this->array[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->array[$offset]); + } +} diff --git a/tests/Kernel/Traits/HasHttpRequestsTest.php b/tests/Kernel/Traits/HasHttpRequestsTest.php index 7a6a6431e..6d791980a 100644 --- a/tests/Kernel/Traits/HasHttpRequestsTest.php +++ b/tests/Kernel/Traits/HasHttpRequestsTest.php @@ -11,7 +11,9 @@ namespace EasyWeChat\Tests\Kernel\Traits; +use EasyWeChat\Kernel\Exceptions\InvalidConfigException; use EasyWeChat\Kernel\Http\Response; +use EasyWeChat\Kernel\Support\ArrayAccessible; use EasyWeChat\Kernel\Support\Collection; use EasyWeChat\Kernel\Traits\HasHttpRequests; use EasyWeChat\Tests\TestCase; @@ -142,9 +144,9 @@ public function testResolveResponse() $this->assertInstanceOf(Response::class, $dummyResponse->response); // 2. not exists - $resp = $cls->resolveResponse($response, 'Not\Exists\ClassName'); - $this->assertInstanceOf(Response::class, $resp); - $this->assertSame('{"foo": "bar"}', $resp->getBody()->getContents()); + $this->expectException(InvalidConfigException::class); + $cls->resolveResponse($response, 'Not\Exists\ClassName'); + $this->fail('failed to assert resolveResponse should throw an exception.'); } public function testTransformResponseToType() @@ -212,13 +214,14 @@ public function testFixJsonIssue() } } -class DummyResponseClassForHasHttpRequestTest +class DummyResponseClassForHasHttpRequestTest extends ArrayAccessible { public $response; public function __construct($response) { $this->response = $response; + parent::__construct([]); } }