├── .gitignore ├── docs ├── README.md └── quickStart.md ├── src ├── UnserializerInterface.php ├── Unserializer.php ├── JsonUnserializer.php ├── QueryInterface.php ├── DataProvider.php ├── ModelInterface.php ├── Model.php └── Query.php ├── composer.json ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | 3 | /vendor/ 4 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Coming soon... 2 | 3 | See [Quick start](quickStart.md) -------------------------------------------------------------------------------- /src/UnserializerInterface.php: -------------------------------------------------------------------------------- 1 | =5.5.0", 20 | "yiisoft/yii2": ">=2.0.5", 21 | "guzzlehttp/guzzle": "^6.1" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "chsergey\\rest\\": "src/" 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sergey 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | REST Client for Yii 2 (ActiveRecord-like model) 2 | =============================================== 3 | [![Latest Stable Version](https://poser.pugx.org/chsergey/yii2-rest-client/v/stable)](https://packagist.org/packages/chsergey/yii2-rest-client) [![Total Downloads](https://poser.pugx.org/chsergey/yii2-rest-client/downloads)](https://packagist.org/packages/chsergey/yii2-rest-client) [![Latest Unstable Version](https://poser.pugx.org/chsergey/yii2-rest-client/v/unstable)](https://packagist.org/packages/chsergey/yii2-rest-client) [![License](https://poser.pugx.org/chsergey/yii2-rest-client/license)](https://packagist.org/packages/chsergey/yii2-rest-client) 4 | [![Code Climate](https://codeclimate.com/github/chsergey/yii2-rest-client/badges/gpa.svg)](https://codeclimate.com/github/chsergey/yii2-rest-client) 5 | 6 | This extension provides an interface to work with RESTful API via ActiveRecord-like model in Yii 2. 7 | 8 | For HTTP requests thanks to [GuzzleHttp](https://packagist.org/packages/guzzlehttp/guzzle) 9 | 10 | ### Welcome to PR 11 | 12 | Installation 13 | ------------ 14 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 15 | 16 | Either run 17 | 18 | ``` 19 | php composer.phar require --prefer-dist chsergey/yii2-rest-client:0.1.1 20 | ``` 21 | 22 | or add 23 | 24 | ```json 25 | "chsergey/yii2-rest-client": "0.1.1" 26 | ``` 27 | 28 | to the `require` section of your composer.json. 29 | 30 | Usage 31 | ----- 32 | 33 | * [Quick start](docs/quickStart.md) 34 | * [Documentation](docs/README.md) 35 | -------------------------------------------------------------------------------- /src/QueryInterface.php: -------------------------------------------------------------------------------- 1 | query instanceof QueryInterface) { 27 | throw new InvalidConfigException( 28 | 'The "query" property must be an instance of a class that implements the '. 29 | 'chsergey\rest\QueryInterface or its subclasses.' 30 | ); 31 | } 32 | 33 | $query = clone $this->query; 34 | 35 | $this->setPagination([ 36 | 'pageSizeLimit' => [1, 1000] 37 | ]); 38 | 39 | if (($pagination = $this->getPagination()) !== false) { 40 | $pagination->totalCount = $this->getTotalCount(); 41 | $query->limit($pagination->getLimit()) 42 | ->offset($pagination->getOffset()); 43 | } 44 | 45 | return $query->all(); 46 | } 47 | 48 | /** 49 | * Prepares the keys associated with the currently available data models. 50 | * @param Model[] $models the available data models 51 | * @return array the keys 52 | */ 53 | protected function prepareKeys($models) { 54 | $keys = []; 55 | foreach ($models as $model) { 56 | $keys[] = $model->getPrimaryKey(); 57 | } 58 | return $keys; 59 | } 60 | 61 | /** 62 | * Returns a value indicating the total number of data models in this data provider. 63 | * @return integer total number of data models in this data provider. 64 | */ 65 | protected function prepareTotalCount() { 66 | 67 | return $this->query->count(); 68 | } 69 | } -------------------------------------------------------------------------------- /docs/quickStart.md: -------------------------------------------------------------------------------- 1 | ## Quick Start 2 | 3 | Create you own model by extending abstract class chsergey\rest\Model and configure it with API URL and resource name: 4 | 5 | ```php 6 | setAttribute('name', 'John Smith'); 55 | $employee->setScenario(Model::SCENARIO_UPDATE); // PUT-request to update 56 | //$employee->setScenario(Model::SCENARIO_CREATE); // POST-request to create 57 | $employee->save(); 58 | ``` 59 | 60 | ### Working with collection 61 | To get collection: 62 | ```php 63 | $employees = Employee::find()->where(['age' => 30])->all(); 64 | // $employee is array of instances of app\models\Employee 65 | ``` 66 | 67 | In this usage example class chsergey\rest\Query will be executed GET-request to collection resource with filteration parameter `age`: 68 | ``` 69 | GET http://server/rest/v1/employees?age=30 70 | ``` 71 | 72 | REST API must return collection of elements. For each element in collection will be created instance of chsergey\rest\Model with own attributes. -------------------------------------------------------------------------------- /src/ModelInterface.php: -------------------------------------------------------------------------------- 1 | 'total', 39 | 'pageCount' => 'pages', 40 | 'currPage' => 'offset', 41 | 'perPageCount' => 'limit', 42 | 'links' => 'links', 43 | ]; 44 | /** 45 | * Request LIMIT param name 46 | * @var string 47 | */ 48 | public static $limitKey = 'per-page'; 49 | /** 50 | * Request OFFSET param name 51 | * @var string 52 | */ 53 | public static $offsetKey = 'page'; 54 | /** 55 | * @var string 56 | */ 57 | public static $primaryKey; 58 | /** 59 | * Primary key value 60 | * @var 61 | */ 62 | public $id; 63 | /** 64 | * Model errors 65 | * @var array 66 | */ 67 | protected $_errors = []; 68 | /** 69 | * Model attributes with values 70 | * @var array 71 | */ 72 | private $_attributes = []; 73 | 74 | 75 | /** 76 | * @inheritdoc 77 | */ 78 | public static function staticInit() { 79 | 80 | } 81 | 82 | /** 83 | * @inheritdoc 84 | * @return string 85 | */ 86 | public static function primaryKey() { 87 | 88 | return [static::$primaryKey]; 89 | } 90 | 91 | /** 92 | * @inheritdoc 93 | * @return string 94 | */ 95 | public static function getApiUrl() { 96 | 97 | return static::$apiUrl; 98 | } 99 | 100 | /** 101 | * @inheritdoc 102 | * @return string 103 | */ 104 | public static function getResourceName() { 105 | 106 | return static::$resourceName; 107 | } 108 | 109 | /** 110 | * @inheritdoc 111 | * @throws \yii\base\InvalidConfigException 112 | */ 113 | public static function find() { 114 | 115 | return \Yii::createObject(Query::className(), [get_called_class()]); 116 | } 117 | 118 | /** 119 | * @inheritdoc 120 | */ 121 | public static function findAll(array $conditions) { 122 | 123 | return static::find()->where($conditions)->all(); 124 | } 125 | 126 | /** 127 | * @inheritdoc 128 | */ 129 | public static function findOne($id) { 130 | 131 | return static::find()->one($id); 132 | } 133 | 134 | /** 135 | * @inheritdoc 136 | */ 137 | public function getPrimaryKey() { 138 | 139 | return $this->id !== null ? $this->id : $this->getAttribute(static::$primaryKey)[0]; 140 | } 141 | 142 | /** 143 | * @inheritdoc 144 | */ 145 | public function setId($id) { 146 | $this->id = is_array($id) ? reset($id) : $id; 147 | 148 | return $this; 149 | } 150 | 151 | /** 152 | * @inheritdoc 153 | */ 154 | public function save() { 155 | 156 | if(static::SCENARIO_CREATE === $this->getScenario()) { 157 | return static::find()->create($this); 158 | } 159 | 160 | if(static::SCENARIO_UPDATE === $this->getScenario()) { 161 | return static::find()->update($this); 162 | } 163 | 164 | return false; 165 | } 166 | 167 | /** 168 | * @inheritdoc 169 | * @return array 170 | */ 171 | public function fields() { 172 | $fields = array_keys($this->_attributes); 173 | return array_combine($fields, $fields); 174 | } 175 | 176 | /** 177 | * @inheritdoc 178 | */ 179 | public function getAttributes() { 180 | 181 | return $this->_attributes; 182 | } 183 | 184 | /** 185 | * @inheritdoc 186 | */ 187 | public function setAttributes(array $attributes, $useForce = false) { 188 | if($useForce) { 189 | $this->_attributes = $attributes; 190 | } else { 191 | foreach($attributes as $key => $val) { 192 | $this->_attributes[$key] = $val; 193 | } 194 | } 195 | 196 | return $this; 197 | } 198 | 199 | /** 200 | * @inheritdoc 201 | */ 202 | public function getAttribute($name) { 203 | 204 | return isset($this->_attributes[$name]) 205 | ? $this->_attributes[$name] 206 | : null; 207 | } 208 | 209 | /** 210 | * @inheritdoc 211 | */ 212 | public function setAttribute($name, $value) { 213 | $this->_attributes[$name] = $value; 214 | 215 | return $this; 216 | } 217 | 218 | /** 219 | * @inheritdoc 220 | */ 221 | public function unsetAttribute($name) { 222 | unset($this->_attributes[$name]); 223 | 224 | return $this; 225 | } 226 | 227 | /** 228 | * @inheritdoc 229 | */ 230 | public function hasAttribute($name) { 231 | 232 | return isset($this->_attributes[$name]) || in_array($name, $this->getAttributes()); 233 | } 234 | 235 | /** 236 | * @inheritdoc 237 | */ 238 | public static function instantiate() { 239 | 240 | return new static; 241 | } 242 | 243 | /** 244 | * PHP getter magic method. 245 | * This method is overridden so that attributes and related objects can be accessed like properties. 246 | * 247 | * @param string $name property name 248 | * @throws \yii\base\InvalidParamException if relation name is wrong 249 | * @return mixed property value 250 | * @see getAttribute() 251 | */ 252 | public function __get($name) { 253 | if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) { 254 | return $this->_attributes[$name]; 255 | } elseif ($this->hasAttribute($name)) { 256 | return null; 257 | } else { 258 | return parent::__get($name); 259 | } 260 | } 261 | 262 | /** 263 | * PHP setter magic method. 264 | * This method is overridden so that AR attributes can be accessed like properties. 265 | * @param string $name property name 266 | * @param mixed $value property value 267 | */ 268 | public function __set($name, $value) 269 | { 270 | if ($this->hasAttribute($name)) { 271 | $this->_attributes[$name] = $value; 272 | } else { 273 | parent::__set($name, $value); 274 | } 275 | } 276 | } -------------------------------------------------------------------------------- /src/Query.php: -------------------------------------------------------------------------------- 1 | 'X-Pagination-Total-Count', 41 | 'pageCount' => 'X-Pagination-Page-Count', 42 | 'currPage' => 'X-Pagination-Current-Page', 43 | 'perPageCount' => 'X-Pagination-Per-Page', 44 | 'links' => 'Link', 45 | ]; 46 | /** 47 | * Response unserializer class 48 | * @var array|object 49 | */ 50 | public $unserializers = [ 51 | self::JSON_TYPE => [ 52 | 'class' => 'chsergey\rest\JsonUnserializer' 53 | ] 54 | ]; 55 | /** 56 | * HTTP client that performs HTTP requests 57 | * @var object 58 | */ 59 | public $httpClient; 60 | /** 61 | * Configuration to be supplied to the HTTP client 62 | * @var array 63 | */ 64 | public $httpClientExtraConfig = []; 65 | /** 66 | * Model class 67 | * @var Model 68 | */ 69 | public $modelClass; 70 | /** 71 | * Get param name for select fields 72 | * @var string 73 | */ 74 | public $selectFieldsKey = 'fields'; 75 | /** 76 | * Request LIMIT param name 77 | * @see chsergey\rest\Model::$limitKey 78 | * @var string 79 | */ 80 | public $limitKey; 81 | /** 82 | * Request OFFSET param name 83 | * @see chsergey\rest\Model::$offsetKey 84 | * @var string 85 | */ 86 | public $offsetKey; 87 | /** 88 | * Model class envelope 89 | * @see chsergey\rest\Model::$collectionEnvelope 90 | * @var string 91 | */ 92 | protected $_collectionEnvelope; 93 | /** 94 | * Model class pagination envelope 95 | * @see chsergey\rest\Model::$paginationEnvelope 96 | * @var string 97 | */ 98 | protected $_paginationEnvelope; 99 | /** 100 | * Model class pagination envelope keys mapping 101 | * @see chsergey\rest\Model::$paginationEnvelopeKeys 102 | * @var array 103 | */ 104 | private $_paginationEnvelopeKeys; 105 | /** 106 | * Pagination data from pagination envelope in GET request 107 | * @var array 108 | */ 109 | private $_pagination; 110 | /** 111 | * Array of fields to select from REST 112 | * @var array 113 | */ 114 | private $_select = []; 115 | /** 116 | * Conditions 117 | * @var array 118 | */ 119 | private $_where; 120 | /** 121 | * Query limit 122 | * @var int 123 | */ 124 | private $_limit; 125 | /** 126 | * Query offset 127 | * @var int 128 | */ 129 | private $_offset; 130 | /** 131 | * Flag Is this query is sub-query 132 | * to prevent recursive requests 133 | * for get enveloped pagination 134 | * @see Query::count() 135 | * @var bool 136 | */ 137 | private $_subQuery = false; 138 | 139 | 140 | /** 141 | * Constructor. Really. 142 | * @param Model $modelClass 143 | * @param array $config 144 | */ 145 | public function __construct($modelClass, $config = []) { 146 | $modelClass::staticInit(); 147 | $this->modelClass = $modelClass; 148 | $this->_collectionEnvelope = $modelClass::$collectionEnvelope; 149 | $this->_paginationEnvelope = $modelClass::$paginationEnvelope; 150 | $this->_paginationEnvelopeKeys = $modelClass::$paginationEnvelopeKeys; 151 | $this->offsetKey = $modelClass::$offsetKey; 152 | $this->limitKey = $modelClass::$limitKey; 153 | 154 | $httpClientConfig = array_merge( 155 | [ 156 | /* @link http://docs.guzzlephp.org/en/latest/quickstart.html */ 157 | 'base_uri' => $this->_getUrl('api'), 158 | /* @link http://docs.guzzlephp.org/en/latest/request-options.html#headers */ 159 | 'headers' => $this->_getRequestHeaders(), 160 | ], 161 | $this->httpClientExtraConfig 162 | ); 163 | $this->httpClient = new Client($httpClientConfig); 164 | 165 | parent::__construct($config); 166 | } 167 | 168 | /** 169 | * GET resource collection request 170 | * @inheritdoc 171 | */ 172 | public function all() { 173 | 174 | return $this->_populate( 175 | $this->_request('get', 176 | $this->_getUrl('collection'), 177 | [ 178 | 'query' => $this->_buildQueryParams() 179 | ] 180 | ) 181 | ); 182 | } 183 | 184 | /** 185 | * Get collection count 186 | * If $this->_pagination isset (from get request before call this method) return count from it 187 | * else execute HEAD request to collection and get count from X-Pagination-Total-Count(default) response header 188 | * If header is empty and isset pagination envelope - do get collection request with limit 1 to get pagination data 189 | * @see Query::$_subQuery 190 | * @inheritdoc 191 | */ 192 | public function count() { 193 | 194 | if($this->_pagination) { 195 | return isset($this->_pagination['totalCount']) ? (int) $this->_pagination['totalCount'] : 0; 196 | } 197 | 198 | if($this->_subQuery) { 199 | return 0; 200 | } 201 | 202 | // try to get count by HEAD request 203 | $count = $this->_request('head', $this->_getUrl('collection'), ['query' => $this->_buildQueryParams()]) 204 | ->getHeaderLine($this->responseHeaders['totalCount']); 205 | 206 | // REST server not allow HEAD query and X-Total header is empty 207 | if($count === '' && $this->_paginationEnvelope) { 208 | $query = clone $this; 209 | $query->_setSubQueryFlag()->offset(0)->limit(1)->all(); 210 | return $query->count(); 211 | } 212 | 213 | return (int) $count; 214 | } 215 | 216 | /** 217 | * GET resource element request 218 | * @inheritdoc 219 | */ 220 | public function one($id) { 221 | 222 | if($this->_where) { 223 | throw new InvalidCallException(__METHOD__.'() can not be called with "where" clause'); 224 | } 225 | 226 | $model = $this->_populate( 227 | $this->_request('get', 228 | $this->_getUrl('element', $id), 229 | [ 230 | 'query' => $this->_buildQueryParams() 231 | ] 232 | ), 233 | false 234 | ); 235 | 236 | return $model; 237 | } 238 | 239 | /** 240 | * POST request 241 | * @inheritdoc 242 | */ 243 | public function create(Model $model) { 244 | 245 | return $this->_populate( 246 | $this->_request('post', $this->_getUrl('collection'), [ 247 | 'json' => $model->getAttributes() 248 | ]), 249 | false 250 | ); 251 | } 252 | 253 | /** 254 | * PUT request 255 | * // TODO non-json (i.e. form-data) payload 256 | * @inheritdoc 257 | */ 258 | public function update(Model $model) { 259 | 260 | return $this->_populate( 261 | $this->_request('put', $this->_getUrl('element', $model->getPrimaryKey()), [ 262 | 'json' => $model->getAttributes() 263 | ]), 264 | false 265 | ); 266 | } 267 | 268 | /** 269 | * @inheritdoc 270 | */ 271 | public function select(array $fields) { 272 | $this->_select = $fields; 273 | return $this; 274 | } 275 | 276 | /** 277 | * @inheritdoc 278 | */ 279 | public function where(array $conditions) { 280 | $this->_where = $conditions; 281 | return $this; 282 | } 283 | 284 | /** 285 | * @inheritdoc 286 | */ 287 | public function limit($limit) { 288 | $this->_limit = (int) $limit; 289 | return $this; 290 | } 291 | 292 | /** 293 | * @inheritdoc 294 | */ 295 | public function offset($offset) { 296 | $this->_offset = (int) $offset; 297 | return $this; 298 | } 299 | 300 | /** 301 | * HTTP request 302 | * @param string $method 303 | * @param string $url 304 | * @param array $options 305 | * @return ResponseInterface 306 | * @throws ServerErrorHttpException 307 | */ 308 | private function _request($method, $url, array $options) { 309 | try { 310 | $response = $this->httpClient->{$method}($url, $options); 311 | } catch(ClientException $e) { 312 | $response = $e->getResponse(); 313 | } catch(ConnectException $e) { 314 | $this->_throwServerError($e); 315 | } catch (RequestException $e) { 316 | $this->_throwServerError($e); 317 | } 318 | 319 | return $response; 320 | } 321 | 322 | /** 323 | * Throw 500 error exception 324 | * @param \Exception $e 325 | * @throws ServerErrorHttpException 326 | */ 327 | private function _throwServerError(\Exception $e) { 328 | $uri = (string) $this->httpClient->getConfig('base_uri'); 329 | 330 | throw new ServerErrorHttpException(get_class($e).': url='.$uri .' '. $e->getMessage(), 500); 331 | } 332 | 333 | /** 334 | * Unserialize and create models 335 | * @param ResponseInterface $response 336 | * @param bool $asCollection 337 | * @return $this|Model|array|void 338 | * @throws HttpException 339 | */ 340 | protected function _populate(ResponseInterface $response, $asCollection = true) { 341 | $models = []; 342 | $statusCode = $response->getStatusCode(); 343 | $data = $this->_unserializeResponseBody($response); 344 | 345 | // errors 346 | if($statusCode >= 400) { 347 | 348 | throw new HttpException( 349 | $statusCode, 350 | is_string($data) ? $data : $data->message, 351 | $statusCode 352 | ); 353 | } 354 | 355 | // array of objects or arrays - probably resource collection 356 | if(is_array($data)) { 357 | $models = $this->_createModels($data); 358 | } 359 | // collection with data envelope or single element 360 | if(is_object($data)) { 361 | if($asCollection) { 362 | if($this->_collectionEnvelope) { 363 | $elements = isset($data->{$this->_collectionEnvelope}) 364 | ? $data->{$this->_collectionEnvelope} 365 | : []; 366 | } else { 367 | $elements = []; 368 | } 369 | if($this->_paginationEnvelope && isset($data->{$this->_paginationEnvelope})) { 370 | $this->_setPagination( 371 | $this->_getProps($data->{$this->_paginationEnvelope}) 372 | ); 373 | } 374 | $models = $this->_createModels($elements); 375 | } else { 376 | $models = $this->_createModels([$data])[0]; 377 | } 378 | } 379 | 380 | return $models; 381 | } 382 | 383 | /** 384 | * Create models from array of elements 385 | * @param array $elements 386 | * @return array 387 | */ 388 | protected function _createModels(array $elements) { 389 | $modelClass = $this->modelClass; 390 | $models = []; 391 | foreach ($elements as $element) { 392 | $model = $modelClass::instantiate()->setAttributes($this->_getProps($element)); 393 | $models[] = $model->setId( 394 | $model->getAttribute($modelClass::primaryKey()[0]) 395 | ); 396 | } 397 | 398 | return $models; 399 | } 400 | 401 | /** 402 | * Try to unserialize response body data 403 | * @param ResponseInterface $response 404 | * @return object[]|object|string 405 | * @throws \yii\base\InvalidConfigException 406 | */ 407 | protected function _unserializeResponseBody(ResponseInterface $response) { 408 | 409 | $body = (string) $response->getBody(); 410 | $contentType = $response->getHeaderLine('Content-type'); 411 | 412 | try { 413 | if(false !== stripos($contentType, $this->dataType) 414 | && isset($this->unserializers[$this->dataType])) { 415 | /** @var UnserializerInterface $unserializer */ 416 | $unserializer = \Yii::createObject($this->unserializers[$this->dataType]); 417 | if($unserializer instanceof UnserializerInterface) { 418 | return $unserializer->unserialize($body, false); 419 | } 420 | } 421 | 422 | return $body; 423 | 424 | } catch(InvalidParamException $e) { 425 | 426 | return $body; 427 | } 428 | } 429 | 430 | /** 431 | * Pagination data setter 432 | * If pagination data isset in GET request result 433 | * @param array $pagination 434 | * @return $this 435 | */ 436 | private function _setPagination(array $pagination) { 437 | foreach ($this->_paginationEnvelopeKeys as $key => $name) { 438 | $this->_pagination[$key] = isset($pagination[$name]) 439 | ? $pagination[$name] 440 | : null; 441 | } 442 | 443 | return $this; 444 | } 445 | 446 | /** 447 | * Get array of properties from object 448 | * @param $object 449 | * @return array 450 | */ 451 | private function _getProps($object) { 452 | 453 | return is_object($object) ? get_object_vars($object) : $object; 454 | } 455 | 456 | /** 457 | * Build query params 458 | * @return array 459 | */ 460 | private function _buildQueryParams() { 461 | $query = []; 462 | 463 | $this->_where = is_array($this->_where) ? $this->_where : []; 464 | foreach ($this->_where as $key => $val) { 465 | $query[$key] = is_numeric($val) ? (int) $val : $val; 466 | } 467 | 468 | if(count($this->_select)) { 469 | $query[$this->selectFieldsKey] = implode(',', $this->_select); 470 | } 471 | if($this->_limit !== null) { 472 | $query[$this->limitKey] = $this->_limit; 473 | } 474 | if($this->_offset !== null) { 475 | $query[$this->offsetKey] = $this->_offset; 476 | } 477 | 478 | return $query; 479 | } 480 | 481 | /** 482 | * Get headers for request 483 | * @return array 484 | */ 485 | private function _getRequestHeaders() { 486 | 487 | return $this->requestHeaders ?: ['Accept' => $this->dataType]; 488 | } 489 | 490 | /** 491 | * Get url to collection or element of resource 492 | * with check base url trailing slash 493 | * @param string $type api|collection|element 494 | * @param string $id 495 | * @return string 496 | */ 497 | private function _getUrl($type = 'base', $id = 'null') { 498 | $modelClass = $this->modelClass; 499 | $collection = $modelClass::getResourceName(); 500 | 501 | switch($type) { 502 | case 'api': 503 | return $this->_trailingSlash($modelClass::getApiUrl()); 504 | break; 505 | case 'collection': 506 | return $this->_trailingSlash($collection, false); 507 | break; 508 | case 'element': 509 | return $this->_trailingSlash($collection) . $this->_trailingSlash($id, false); 510 | break; 511 | } 512 | 513 | return ''; 514 | } 515 | 516 | /** 517 | * Check trailing slash 518 | * if $add - add trailing slash 519 | * if not $add - remove trailing slash 520 | * @param $string 521 | * @param bool $add 522 | * @return string 523 | */ 524 | private function _trailingSlash($string, $add = true) { 525 | 526 | return substr($string, -1) === '/' 527 | ? ($add ? $string : substr($string, 0, strlen($string) - 1)) 528 | : ($add ? $string . '/' : $string); 529 | } 530 | 531 | /** 532 | * Mark query as subquery to prevent queries recursion 533 | * @see count() 534 | * @return Query 535 | */ 536 | private function _setSubQueryFlag() { 537 | $this->_subQuery = true; 538 | return $this; 539 | } 540 | } --------------------------------------------------------------------------------