├── .gitignore ├── src ├── Middleware │ ├── MiddlewareInterface.php │ ├── RetryMiddleware.php │ ├── LogMiddleware.php │ └── TokenMiddleware.php ├── Utils.php ├── SDK.php └── AbstractApi.php ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | composer.lock 3 | .idea/ 4 | -------------------------------------------------------------------------------- /src/Middleware/MiddlewareInterface.php: -------------------------------------------------------------------------------- 1 | =5.6", 14 | "guzzlehttp/guzzle": "^6.3", 15 | "psr/log": "^1.0" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "Leo108\\SDK\\": "src/" 20 | } 21 | }, 22 | "repositories": { 23 | "packagist": { 24 | "type": "composer", 25 | "url": "https://packagist.org" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Middleware/RetryMiddleware.php: -------------------------------------------------------------------------------- 1 | decider = $decider; 32 | $this->delay = $delay; 33 | } 34 | 35 | public function __invoke() 36 | { 37 | return Middleware::retry($this->decider, $this->delay); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP SDK Skeleton 2 | 3 | A skeleton for PHP SDK development. 4 | 5 | ## Installation 6 | 7 | `composer require leo108/php_sdk_skeleton -vvv` 8 | 9 | ## Quick Start 10 | 11 | Let's get started with creating a simple github sdk using [REST Api](https://developer.github.com/v3/). 12 | 13 | Create a class extends `Leo108\SDK\AbstractApi`, 14 | override the `getFullApiUrl` method. 15 | 16 | ``` 17 | class RepositoryApi extends Leo108\SDK\AbstractApi { 18 | protected function getFullApiUrl($api) 19 | { 20 | return 'https://api.github.com/'.$api; 21 | } 22 | } 23 | ``` 24 | 25 | Create a method called `list` which will list all repos of a user. 26 | 27 | ``` 28 | class RepositoryApi extends Leo108\SDK\AbstractApi { 29 | public function list($username) 30 | { 31 | return $this->apiGet('users/'.$username.'/repos'); 32 | } 33 | } 34 | ``` 35 | 36 | Create a class extends `Leo108\SDK\SDK`, implement the `getApiMap` method. 37 | 38 | ``` 39 | class GithubSDK extends Leo108\SDK\SDK { 40 | protected function getApiMap() 41 | { 42 | return [ 43 | 'repository' => RepositoryApi::class, 44 | ]; 45 | } 46 | } 47 | ``` 48 | 49 | All Done. Let's try it out. 50 | 51 | ``` 52 | $sdk = new GithubSDK(); 53 | // $resp is a Psr\Http\Message\ResponseInterface object 54 | $resp = $sdk->repository->list('leo108'); 55 | var_dump($resp->getBody()->getContents()); 56 | ``` 57 | 58 | ## Work with 59 | -------------------------------------------------------------------------------- /src/Middleware/LogMiddleware.php: -------------------------------------------------------------------------------- 1 | requestLogger = $requestLogger; 32 | $this->responseLogger = $responseLogger; 33 | } 34 | 35 | public function __invoke() 36 | { 37 | return function (callable $handler) { 38 | return function (RequestInterface $request, array $options) use ($handler) { 39 | if (is_callable($this->requestLogger)) { 40 | call_user_func($this->requestLogger, $request, $options); 41 | } 42 | $response = $handler($request, $options); 43 | if (is_callable($this->responseLogger)) { 44 | call_user_func($this->responseLogger, $request, $options, $response); 45 | } 46 | 47 | return $response; 48 | }; 49 | }; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Middleware/TokenMiddleware.php: -------------------------------------------------------------------------------- 1 | isRequireToken = $isRequireToken; 32 | $this->attachToken = $attachToken; 33 | } 34 | 35 | public function __invoke() 36 | { 37 | return function (callable $handler) { 38 | return function (RequestInterface $request, array $options) use ($handler) { 39 | if (is_bool($this->isRequireToken)) { 40 | $requireToken = $this->isRequireToken; 41 | } else { 42 | $requireToken = call_user_func($this->isRequireToken, $request); 43 | } 44 | 45 | if ($requireToken) { 46 | $request = call_user_func($this->attachToken, $request); 47 | } 48 | 49 | return $handler($request, $options); 50 | }; 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Utils.php: -------------------------------------------------------------------------------- 1 | offsetExists($key); 45 | } 46 | 47 | return array_key_exists($key, $array); 48 | } 49 | 50 | /** 51 | * Get an item from an array using "dot" notation. 52 | * 53 | * @param \ArrayAccess|array $array 54 | * @param string $key 55 | * @param mixed $default 56 | * @return mixed 57 | */ 58 | public static function get($array, $key, $default = null) 59 | { 60 | if (!static::accessible($array)) { 61 | return static::value($default); 62 | } 63 | 64 | if (is_null($key)) { 65 | return $array; 66 | } 67 | 68 | if (static::exists($array, $key)) { 69 | return $array[$key]; 70 | } 71 | 72 | foreach (explode('.', $key) as $segment) { 73 | if (static::accessible($array) && static::exists($array, $segment)) { 74 | $array = $array[$segment]; 75 | } else { 76 | return static::value($default); 77 | } 78 | } 79 | 80 | return $array; 81 | } 82 | 83 | /** 84 | * Return the default value of the given value. 85 | * 86 | * @param mixed $value 87 | * @return mixed 88 | */ 89 | public static function value($value) 90 | { 91 | return $value instanceof Closure ? $value() : $value; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/SDK.php: -------------------------------------------------------------------------------- 1 | config = $config; 50 | $this->setLogger($logger); 51 | $this->setHttpClient($httpClient); 52 | } 53 | 54 | /** 55 | * @return ClientInterface 56 | */ 57 | public function getHttpClient() 58 | { 59 | return $this->httpClient; 60 | } 61 | 62 | /** 63 | * @return LoggerInterface 64 | */ 65 | public function getLogger() 66 | { 67 | return $this->logger; 68 | } 69 | 70 | /** 71 | * @param LoggerInterface $logger 72 | * 73 | * @return $this 74 | */ 75 | public function setLogger(LoggerInterface $logger = null) 76 | { 77 | $this->logger = $logger ?: new NullLogger(); 78 | 79 | return $this; 80 | } 81 | 82 | /** 83 | * @param ClientInterface $httpClient 84 | * 85 | * @return $this 86 | */ 87 | public function setHttpClient(ClientInterface $httpClient = null) 88 | { 89 | $this->httpClient = $httpClient ?: new Client([RequestOptions::HTTP_ERRORS => false]); 90 | 91 | return $this; 92 | } 93 | 94 | 95 | /** 96 | * @param null|string $key 97 | * @param mixed $default 98 | * @return mixed 99 | */ 100 | public function getConfig($key = null, $default = null) 101 | { 102 | if (is_null($key)) { 103 | return $this->config; 104 | } 105 | 106 | return Utils::get($this->config, $key, $default); 107 | } 108 | 109 | /** 110 | * @param string $name 111 | * 112 | * @return AbstractApi|null 113 | */ 114 | public function __get($name) 115 | { 116 | $apiMap = $this->getApiMap(); 117 | if (!isset($apiMap[$name])) { 118 | return null; 119 | } 120 | 121 | if (!isset($this->apiInstances[$name])) { 122 | $this->apiInstances[$name] = (new ReflectionClass($apiMap[$name]))->newInstanceArgs([$this]); 123 | } 124 | 125 | return $this->apiInstances[$name]; 126 | } 127 | 128 | /** 129 | * override this method to register api 130 | * 131 | * @return array 132 | */ 133 | abstract protected function getApiMap(); 134 | } 135 | -------------------------------------------------------------------------------- /src/AbstractApi.php: -------------------------------------------------------------------------------- 1 | sdk = $sdk; 31 | } 32 | 33 | /** 34 | * send a Get api request 35 | * 36 | * @param string $api api endpoint 37 | * @param array $queries api queries 38 | * @param array $options extra options provided to guzzle 39 | * 40 | * @return ResponseInterface 41 | */ 42 | protected function apiGet($api, array $queries = [], array $options = []) 43 | { 44 | $options[RequestOptions::QUERY] = $queries; 45 | 46 | return $this->apiRequest('GET', $this->getFullApiUrl($api), $options); 47 | } 48 | 49 | /** 50 | * send a Post api request 51 | * 52 | * @param string $api api endpoint 53 | * @param array $params api params 54 | * @param array $options extra options provided to guzzle 55 | * 56 | * @return ResponseInterface 57 | */ 58 | protected function apiPost($api, array $params = [], array $options = []) 59 | { 60 | $options[RequestOptions::FORM_PARAMS] = $params; 61 | 62 | return $this->apiRequest('POST', $this->getFullApiUrl($api), $options); 63 | } 64 | 65 | /** 66 | * Upload files 67 | * 68 | * @param string $api api endpoint 69 | * @param array $files key => value format, key is the request parameter name, 70 | * value can be the content of the file or 71 | * the return value from fopen or a Psr\Http\Message\StreamInterface object 72 | * @param array $options extra options provided to guzzle 73 | * 74 | * @return ResponseInterface 75 | */ 76 | protected function apiUpload($api, array $files, array $options = []) 77 | { 78 | $options[RequestOptions::MULTIPART] = []; 79 | foreach ($files as $name => $content) { 80 | $options[RequestOptions::MULTIPART][] = [ 81 | 'name' => $name, 82 | 'content' => $content, 83 | ]; 84 | } 85 | 86 | return $this->apiRequest('POST', $this->getFullApiUrl($api), $options); 87 | } 88 | 89 | /** 90 | * send a Post api request using application/json 91 | * 92 | * @param string $api api endpoint 93 | * @param array $param api params 94 | * @param array $options extra options provided to guzzle 95 | * 96 | * @return ResponseInterface 97 | */ 98 | protected function apiJson($api, array $param, array $options = []) 99 | { 100 | $options[RequestOptions::JSON] = $param; 101 | 102 | return $this->apiRequest('POST', $this->getFullApiUrl($api), $options); 103 | } 104 | 105 | /** 106 | * send http request 107 | * 108 | * @param string $method request method 109 | * @param string $url target url 110 | * @param array $options options provided to guzzle 111 | * 112 | * @return ResponseInterface 113 | */ 114 | protected function apiRequest($method, $url, $options = []) 115 | { 116 | $options['handler'] = $this->createHandler(); 117 | 118 | return $this->sdk->getHttpClient()->request($method, $url, $options); 119 | } 120 | 121 | /** 122 | * override this method to add middleware to guzzle request 123 | * @see http://docs.guzzlephp.org/en/stable/handlers-and-middleware.html 124 | * 125 | * @return array 126 | */ 127 | protected function getHttpMiddleware() 128 | { 129 | return []; 130 | } 131 | 132 | /** 133 | * @return HandlerStack 134 | */ 135 | private function createHandler() 136 | { 137 | $stack = HandlerStack::create(); 138 | $middleware = $this->getHttpMiddleware(); 139 | foreach ($middleware as $item) { 140 | if ($item instanceof MiddlewareInterface) { 141 | $stack->push($item()); 142 | } else { 143 | $stack->push($item); 144 | } 145 | } 146 | 147 | return $stack; 148 | } 149 | 150 | /** 151 | * @param string $api 152 | * 153 | * @return string 154 | */ 155 | protected function getFullApiUrl($api) 156 | { 157 | return $api; 158 | } 159 | } 160 | --------------------------------------------------------------------------------