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\Query;
10:
11: use PipelinerSales\ApiClient\Defaults;
12:
13: /**
14: * Represents the query parameters for loading entities.
15: *
16: * For details, see the {@link
17: * http://workspace.pipelinersales.com/community/api/data/Querying_rest.html
18: * API documentation on querying}.
19: *
20: * @method static Criteria limit($limit) Limits how many entities to load. Use the NO_LIMIT constant to disable limits.
21: * @method static Criteria offset($offset)
22: * @method static Criteria sort($sort) Set sorting of the result, either by using a raw string (see API documentation) or a Sort object
23: * @method static Criteria filter($filter) Only return entities which conform to the specified filter - either a raw string (see API documentation) or a Filter object
24: * @method static Criteria after($after) Only load entities modified after the date, specified as either a string or a DateTime object
25: * @method static Criteria loadonly($loadonly) Only load the specified fields, specified as either an array or a string separated with |
26: */
27: class Criteria
28: {
29:
30: private static $properties = array(
31: 'limit' => 'setLimit',
32: 'offset' => 'setOffset',
33: 'sort' => 'setSort',
34: 'filter' => 'setFilter',
35: 'after' => 'setAfter',
36: 'loadonly' => 'setLoadOnly'
37: );
38: private $limit = null;
39: private $offset = null;
40: private $sort = null;
41: private $filter = null;
42: private $after = null;
43: private $loadonly = null;
44: private $dateTimeFormat;
45: private $defaultLimit;
46:
47: /**
48: * Disable the limit on how many entities to load. Please use with caution.
49: */
50: const NO_LIMIT = -1;
51:
52: /**
53: * Creates a new set of criteria.
54: * @param mixed $criteria initial criteria to use, see {@see set}
55: * @param string $dateTimeFormat format to use for converting DateTime objects
56: */
57: public function __construct($criteria = array(), $dateTimeFormat = Defaults::DATE_FORMAT, $defaultLimit = Defaults::DEFAULT_LIMIT)
58: {
59: $this->dateTimeFormat = $dateTimeFormat;
60: $this->defaultLimit = $defaultLimit;
61: $this->set($criteria);
62: }
63:
64: /**
65: * Returns the resulting url-encoded query string.
66: * @return string
67: */
68: public function toUrlQuery()
69: {
70: $queryData = array();
71: foreach (self::$properties as $p => $setter) {
72: if ($this->$p !== null) {
73: $queryData[$p] = $this->$p;
74: }
75: }
76: return http_build_query($queryData);
77: }
78:
79: /**
80: * Returns the currently set limit. Null will be returned if the limit is not set.
81: * @return integer|null
82: */
83: public function getLimit()
84: {
85: return $this->limit;
86: }
87:
88: /**
89: * Returns the currently set limit, or the default limit if none is set.
90: * @return integer
91: */
92: public function getEffectiveLimit()
93: {
94: if ($this->limit === null) {
95: return $this->defaultLimit;
96: }
97: return $this->limit;
98: }
99:
100: /**
101: * @param integer $limit
102: * @return Criteria
103: */
104: private function setLimit($limit)
105: {
106: $this->limit = $limit;
107: return $this;
108: }
109:
110: /**
111: * Returns the currently set offset. Null will be returned if no offset is set.
112: * @return integer|null
113: */
114: public function getOffset()
115: {
116: return $this->offset;
117: }
118:
119: /**
120: * Returns the currently set offset, or the default offset 0 if no offset is set.
121: * @return integer
122: */
123: public function getEffectiveOffset()
124: {
125: if ($this->offset === null) {
126: return 0;
127: }
128: return $this->getOffset();
129: }
130:
131: /**
132: * @param integer $offset
133: * @return Criteria
134: */
135: private function setOffset($offset)
136: {
137: $this->offset = $offset;
138: return $this;
139: }
140:
141: /**
142: * @return string|null
143: */
144: public function getSort()
145: {
146: return $this->sort;
147: }
148:
149: /**
150: * @param string|Sort
151: * @return Criteria
152: */
153: public function setSort($sort)
154: {
155: if ($sort instanceof Sort) {
156: $this->sort = $sort->getString();
157: } else {
158: $this->sort = $sort;
159: }
160: return $this;
161: }
162:
163: /**
164: * Returns the currently set filter string.
165: * @return string|null
166: */
167: public function getFilter()
168: {
169: return $this->filter;
170: }
171:
172: /**
173: * @param Filter|string
174: * @return Criteria
175: */
176: private function setFilter($filter)
177: {
178: if ($filter instanceof Filter) {
179: $this->filter = $filter->getString();
180: } else {
181: $this->filter = $filter;
182: }
183: return $this;
184: }
185:
186: /**
187: * @return string|null
188: */
189: public function getAfter()
190: {
191: return $this->after;
192: }
193:
194: /**
195: * @param string|\DateTime $after
196: * @return Criteria
197: */
198: private function setAfter($after)
199: {
200: if (is_string($after)) {
201: $this->after = $after;
202: } elseif ($after instanceof \DateTime) {
203: $afterCopy = clone $after;
204: $afterCopy->setTimezone(new \DateTimeZone('UTC'));
205: $this->after = $afterCopy->format($this->dateTimeFormat);
206: }
207: return $this;
208: }
209:
210: /**
211: * @return string|null
212: */
213: public function getLoadOnly()
214: {
215: return $this->loadonly;
216: }
217:
218: /**
219: * @param string|array
220: * @return Criteria
221: */
222: private function setLoadOnly($loadonly)
223: {
224: if (is_string($loadonly)) {
225: $this->loadonly = $loadonly;
226: } elseif (is_array($loadonly)) {
227: $this->loadonly = implode('|', $loadonly);
228: }
229: return $this;
230: }
231:
232: /**
233: * Sets multiple parameters at once.
234: *
235: * @param mixed $criteria Can be one of the following:
236: * <ul>
237: * <li>Another Criteria object - copies that object's criteria into this one.
238: * Competely replaces the current object's values, unsetting those which have not been
239: * set in $criteria</li>
240: * <li>An array - sets the parameters present in the array. Parameters not present
241: * in the array will <b>not</b> be unset. If you wish to unset certain parameters,
242: * set them to null in the array.</li>
243: * <li>A string - the resulting query string (without a leading question mark),
244: * will be parsed back into the individual parameters and processed like an array.</li>
245: * </ul>
246: */
247: public function set($criteria)
248: {
249: if ($criteria instanceof Criteria) {
250: //this is much faster than using the same method that's used for arrays (see below)
251: $this->limit = $criteria->limit;
252: $this->offset = $criteria->offset;
253: $this->sort = $criteria->sort;
254: $this->filter = $criteria->filter;
255: $this->after = $criteria->after;
256: $this->loadonly = $criteria->loadonly;
257: } elseif (is_string($criteria)) {
258: //parse the string into an array and then parse the array
259: $criteria = $this->parseHttpQuery($criteria);
260: }
261:
262: if (is_array($criteria)) {
263: foreach (self::$properties as $p => $setter) {
264: if (isset($criteria[$p])) {
265: $this->$setter($criteria[$p]);
266: }
267: }
268: }
269: }
270:
271: /**
272: * Parses a http query into a key-value array, similar to PHP's parse_str function.
273: * @param string $query query string (without a leading question mark)
274: * @return array
275: */
276: private function parseHttpQuery($query)
277: {
278: /* can't use parse_str, because the minimum supported PHP version
279: * is 5.3, which still has the magic_quotes_gpc option that affects its
280: * result, so some users could hypothetically have that enabled */
281: if (empty($query)) {
282: return array();
283: }
284:
285: $result = array();
286: $parts = explode('&', $query);
287: foreach ($parts as $p) {
288: $keyval = explode('=', $p);
289: $result[urldecode($keyval[0])] = urldecode($keyval[1]);
290: }
291: return $result;
292: }
293:
294: public static function __callStatic($name, $arguments)
295: {
296: if (isset(self::$properties[$name])) {
297: return new Criteria(array( $name => $arguments[0] ));
298: }
299: throw new \BadMethodCallException('Call to a non-existent static method \'' . $name . '\'');
300: }
301:
302: public function __call($name, $arguments)
303: {
304: if (isset(self::$properties[$name])) {
305: $setter = self::$properties[$name];
306: $this->$setter($arguments[0]);
307: return $this;
308: }
309: throw new \BadMethodCallException('Call to a non-existent method \'' . $name . '\'');
310: }
311: }
312: