Overview

Namespaces

  • PipelinerSales
    • ApiClient
      • Http
      • Query
      • Repository
        • Rest

Classes

  • RestCreatedResponse
  • RestInfoMethods
  • RestRepository
  • RestRepositoryFactory
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * This file is part of the Pipeliner API client library for PHP
  4:  *
  5:  * Copyright 2014 Pipelinersales, Inc. All Rights Reserved.
  6:  * For the full license information, see the attached LICENSE file.
  7:  */
  8: 
  9: namespace PipelinerSales\ApiClient\Repository\Rest;
 10: 
 11: use PipelinerSales\ApiClient\Entity;
 12: use PipelinerSales\ApiClient\EntityCollection;
 13: use PipelinerSales\ApiClient\EntityCollectionIterator;
 14: use PipelinerSales\ApiClient\PipelinerClientException;
 15: use PipelinerSales\ApiClient\Repository\RepositoryInterface;
 16: use PipelinerSales\ApiClient\Http\HttpInterface;
 17: use PipelinerSales\ApiClient\Http\PipelinerHttpException;
 18: use PipelinerSales\ApiClient\Http\Response;
 19: use PipelinerSales\ApiClient\Query\Criteria;
 20: use PipelinerSales\ApiClient\Query\Filter;
 21: use PipelinerSales\ApiClient\Query\Sort;
 22: 
 23: /**
 24:  * Class for retrieving and manipulating entities using Pipeliner's REST API.
 25:  */
 26: class RestRepository implements RepositoryInterface
 27: {
 28: 
 29:     private $httpClient;
 30:     private $urlPrefix;
 31:     private $entityType;
 32:     private $entityPlural;
 33:     private $dateTimeFormat;
 34: 
 35:     public function __construct($urlPrefix, $entity, $entityPlural, HttpInterface $connection, $dateTimeFormat)
 36:     {
 37:         $this->httpClient = $connection;
 38:         $this->urlPrefix = $urlPrefix;
 39:         $this->entityType = $entity;
 40:         $this->entityPlural = $entityPlural;
 41:         $this->dateTimeFormat = $dateTimeFormat;
 42:     }
 43: 
 44:     public function getById($id)
 45:     {
 46:         $result = $this->httpClient
 47:                 ->request('GET', $this->urlPrefix . '/' . $this->entityPlural . '/' . $id)
 48:                 ->decodeJson();
 49:         return $this->decodeEntity($result);
 50:     }
 51: 
 52:     public function get($criteria = null)
 53:     {
 54:         $criteriaUrl = '?';
 55:         if (is_null($criteria)) {
 56:             $criteriaUrl = '';
 57:         } elseif (is_array($criteria)) {
 58:             $criteriaUrl .= $this->arrayCriteriaToUrl($criteria);
 59:         } elseif (is_object($criteria)) {
 60:             $criteriaUrl .= $this->objectCriteriaToUrl($criteria);
 61:         } elseif (is_string($criteria)) {
 62:             $criteriaUrl .= $criteria;
 63:         } else {
 64:             throw new PipelinerClientException('Invalid criteria type');
 65:         }
 66: 
 67:         $response = $this->httpClient
 68:                 ->request('GET', $this->urlPrefix . '/' . $this->entityPlural . $criteriaUrl);
 69:         $results = $response->decodeJson();
 70:         $decoded = array_map(array($this, 'decodeEntity'), $results);
 71: 
 72:         $range = $this->getContentRange($response);
 73:         if ($range === null) {
 74:             throw new PipelinerHttpException($response, 'Content-Range header not found');
 75:         }
 76: 
 77:         return new EntityCollection($decoded, $criteria, $range['start'], $range['end'], $range['total']);
 78:     }
 79: 
 80:     public function create()
 81:     {
 82:         return new Entity($this->entityType, $this->dateTimeFormat);
 83:     }
 84: 
 85:     public function delete($entity, $flags = self::FLAG_ROLLBACK_ON_ERROR)
 86:     {
 87:         //if $entity is an array, it can either be a single entity
 88:         //represented as an associative array, or an array of multiple entities,
 89:         //i.e. [ 'ID' => 'Something' ] vs. [ [ 'ID' => 'Something1' ], [ 'ID' => 'Something2 ] ],
 90:         //so we need to distinguish between these two cases
 91:         if (is_array($entity)) {
 92:             $first = reset($entity);
 93: 
 94:             //if the first element of $entity is also an array,
 95:             //we are dealing with multiple entities
 96:             if (is_array($first) or $first instanceof Entity) {
 97:                 return $this->deleteById(array_map(function ($item) {
 98:                     return $item['ID'];
 99:                 }, $entity), $flags);
100:             }
101:         }
102: 
103:         if (!isset($entity['ID'])) {
104:             throw new PipelinerClientException('Cannot delete an entity which has no ID');
105:         }
106:         return $this->deleteById($entity['ID']);
107:     }
108: 
109:     public function deleteById($id, $flags = self::FLAG_ROLLBACK_ON_ERROR)
110:     {
111:         if (is_array($id)) {
112:             //bulk delete
113:             $json = json_encode($id);
114:             $url = $this->urlPrefix . '/deleteEntities?entityName=' . $this->entityType;
115:             if ($flags) {
116:                 $url .= '&flag=' . $flags;
117:             }
118:             return $this->httpClient->request('POST', $url, $json);
119:         } else {
120:             return $this->httpClient->request('DELETE', $this->urlPrefix . '/' . $this->entityPlural . '/' . $id);
121:         }
122:     }
123: 
124:     public function bulkUpdate($data, $flags = self::FLAG_ROLLBACK_ON_ERROR, $sendFields = self::SEND_MODIFIED_FIELDS)
125:     {
126:         $payload = array();
127: 
128:         foreach ($data as $entity) {
129:             if ($entity instanceof Entity) {
130:                 $values = (
131:                     $sendFields == self::SEND_MODIFIED_FIELDS ? $entity->getModifiedFields() : $entity->getFields()
132:                 );
133:                 $values['ID'] = $entity->getId();
134:                 $payload[] = $values;
135:             } else {
136:                 $payload[] = $entity;
137:             }
138:         }
139: 
140:         $json = json_encode($payload);
141:         $url = $this->urlPrefix . '/setEntities?entityName=' . $this->entityType;
142:         if ($flags) {
143:             $url .= '&flag=' . $flags;
144:         }
145:         return $this->httpClient->request('POST', $url, $json);
146:     }
147: 
148:     public function save($entity, $sendFields = self::SEND_MODIFIED_FIELDS)
149:     {
150:         if ($entity instanceof Entity) {
151:             $id = $entity->isFieldSet('ID') ? $entity->getId() : null;
152:             $data = ($sendFields == self::SEND_MODIFIED_FIELDS ? $entity->modifiedToJson() : $entity->allToJson());
153:         } elseif (is_array($entity)) {
154:             $id = isset($entity['ID']) ? $entity['ID'] : null;
155:             $data = json_encode($entity);
156:         } else {
157:             throw new \InvalidArgumentException('Invalid entity');
158:         }
159: 
160:         if (!empty($id)) {
161:             //The server currently interprets both of these:
162:             //  - POST to /Accounts, where the entity contains an ID
163:             //  - PUT to /Accounts/{id}
164:             //as partial updates.
165:             //Full PUT replacement is not implemented on the server.
166:             $method = 'PUT';
167:             $url = $this->urlPrefix . '/' . $this->entityPlural . '/' . $id;
168:         } else {
169:             $method = 'POST';
170:             $url = $this->urlPrefix . '/' . $this->entityPlural;
171:         }
172: 
173:         $response = $this->httpClient->request($method, $url, $data);
174: 
175:         //new entity was created, we need to get its ID
176:         if ($response->getStatusCode() == 201) {
177:             $response = new RestCreatedResponse($response);
178:             $newId = $response->getCreatedId();
179:             if ($entity instanceof Entity) {
180:                 $entity->setId($newId);
181:             }
182:         }
183: 
184:         if (($response->getStatusCode() == 200 or $response->getStatusCode() == 201)
185:                 and ( $entity instanceof Entity )) {
186:             $entity->resetModified();
187:         }
188: 
189:         return $response;
190:     }
191: 
192:     public function getEntireRangeIterator(EntityCollection $collection)
193:     {
194:         return new EntityCollectionIterator($this, $collection);
195:     }
196: 
197:     private function arrayCriteriaToUrl(array $criteria)
198:     {
199:         return http_build_query($criteria);
200:     }
201: 
202:     private function objectCriteriaToUrl($criteria)
203:     {
204:         if ($criteria instanceof Criteria) {
205:             return $criteria->toUrlQuery();
206:         } elseif ($criteria instanceof Filter) {
207:             $c = new Criteria();
208:             $c->filter($criteria);
209:             return $c->toUrlQuery();
210:         } elseif ($criteria instanceof Sort) {
211:             $c = new Criteria();
212:             $c->sort($criteria);
213:             return $c->toUrlQuery();
214:         }
215:         throw new PipelinerClientException('Invalid criteria object');
216:     }
217: 
218:     private function decodeEntity(\stdClass $object)
219:     {
220:         $entity = new Entity($this->entityType, $this->dateTimeFormat);
221: 
222:         foreach ($object as $field => $value) {
223:             $entity->setField($field, $value);
224:         }
225:         $entity->resetModified();
226: 
227:         return $entity;
228:     }
229: 
230:     private function getContentRange(Response $response)
231:     {
232:         $result = preg_match('~Content-Range: items (\d+)-([\d-]+)/(\d+)~i', $response->getHeaders(), $matches);
233:         if (!$result) {
234:             return null;
235:         }
236: 
237:         return array(
238:             'start' => intval($matches[1]),
239:             'end' => intval($matches[2]),
240:             'total' => intval($matches[3])
241:         );
242:     }
243: }
244: 
API documentation generated by ApiGen