Collection.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. <?php
  2. namespace Stripe;
  3. /**
  4. * Class Collection.
  5. *
  6. * @property string $object
  7. * @property string $url
  8. * @property bool $has_more
  9. * @property \Stripe\StripeObject[] $data
  10. */
  11. class Collection extends StripeObject implements \Countable, \IteratorAggregate
  12. {
  13. const OBJECT_NAME = 'list';
  14. use ApiOperations\Request;
  15. /** @var array */
  16. protected $filters = [];
  17. /**
  18. * @return string the base URL for the given class
  19. */
  20. public static function baseUrl()
  21. {
  22. return Stripe::$apiBase;
  23. }
  24. /**
  25. * Returns the filters.
  26. *
  27. * @return array the filters
  28. */
  29. public function getFilters()
  30. {
  31. return $this->filters;
  32. }
  33. /**
  34. * Sets the filters, removing paging options.
  35. *
  36. * @param array $filters the filters
  37. */
  38. public function setFilters($filters)
  39. {
  40. $this->filters = $filters;
  41. }
  42. public function offsetGet($k)
  43. {
  44. if (\is_string($k)) {
  45. return parent::offsetGet($k);
  46. }
  47. $msg = "You tried to access the {$k} index, but Collection " .
  48. 'types only support string keys. (HINT: List calls ' .
  49. 'return an object with a `data` (which is the data ' .
  50. "array). You likely want to call ->data[{$k}])";
  51. throw new Exception\InvalidArgumentException($msg);
  52. }
  53. public function all($params = null, $opts = null)
  54. {
  55. self::_validateParams($params);
  56. list($url, $params) = $this->extractPathAndUpdateParams($params);
  57. list($response, $opts) = $this->_request('get', $url, $params, $opts);
  58. $obj = Util\Util::convertToStripeObject($response, $opts);
  59. if (!($obj instanceof \Stripe\Collection)) {
  60. throw new \Stripe\Exception\UnexpectedValueException(
  61. 'Expected type ' . \Stripe\Collection::class . ', got "' . \get_class($obj) . '" instead.'
  62. );
  63. }
  64. $obj->setFilters($params);
  65. return $obj;
  66. }
  67. public function create($params = null, $opts = null)
  68. {
  69. self::_validateParams($params);
  70. list($url, $params) = $this->extractPathAndUpdateParams($params);
  71. list($response, $opts) = $this->_request('post', $url, $params, $opts);
  72. return Util\Util::convertToStripeObject($response, $opts);
  73. }
  74. public function retrieve($id, $params = null, $opts = null)
  75. {
  76. self::_validateParams($params);
  77. list($url, $params) = $this->extractPathAndUpdateParams($params);
  78. $id = Util\Util::utf8($id);
  79. $extn = \urlencode($id);
  80. list($response, $opts) = $this->_request(
  81. 'get',
  82. "{$url}/{$extn}",
  83. $params,
  84. $opts
  85. );
  86. return Util\Util::convertToStripeObject($response, $opts);
  87. }
  88. /**
  89. * @return int the number of objects in the current page
  90. */
  91. public function count()
  92. {
  93. return \count($this->data);
  94. }
  95. /**
  96. * @return \ArrayIterator an iterator that can be used to iterate
  97. * across objects in the current page
  98. */
  99. public function getIterator()
  100. {
  101. return new \ArrayIterator($this->data);
  102. }
  103. /**
  104. * @return \ArrayIterator an iterator that can be used to iterate
  105. * backwards across objects in the current page
  106. */
  107. public function getReverseIterator()
  108. {
  109. return new \ArrayIterator(\array_reverse($this->data));
  110. }
  111. /**
  112. * @return \Generator|StripeObject[] A generator that can be used to
  113. * iterate across all objects across all pages. As page boundaries are
  114. * encountered, the next page will be fetched automatically for
  115. * continued iteration.
  116. */
  117. public function autoPagingIterator()
  118. {
  119. $page = $this;
  120. while (true) {
  121. $filters = $this->filters ?: [];
  122. if (\array_key_exists('ending_before', $filters)
  123. && !\array_key_exists('starting_after', $filters)) {
  124. foreach ($page->getReverseIterator() as $item) {
  125. yield $item;
  126. }
  127. $page = $page->previousPage();
  128. } else {
  129. foreach ($page as $item) {
  130. yield $item;
  131. }
  132. $page = $page->nextPage();
  133. }
  134. if ($page->isEmpty()) {
  135. break;
  136. }
  137. }
  138. }
  139. /**
  140. * Returns an empty collection. This is returned from {@see nextPage()}
  141. * when we know that there isn't a next page in order to replicate the
  142. * behavior of the API when it attempts to return a page beyond the last.
  143. *
  144. * @param null|array|string $opts
  145. *
  146. * @return Collection
  147. */
  148. public static function emptyCollection($opts = null)
  149. {
  150. return Collection::constructFrom(['data' => []], $opts);
  151. }
  152. /**
  153. * Returns true if the page object contains no element.
  154. *
  155. * @return bool
  156. */
  157. public function isEmpty()
  158. {
  159. return empty($this->data);
  160. }
  161. /**
  162. * Fetches the next page in the resource list (if there is one).
  163. *
  164. * This method will try to respect the limit of the current page. If none
  165. * was given, the default limit will be fetched again.
  166. *
  167. * @param null|array $params
  168. * @param null|array|string $opts
  169. *
  170. * @return Collection
  171. */
  172. public function nextPage($params = null, $opts = null)
  173. {
  174. if (!$this->has_more) {
  175. return static::emptyCollection($opts);
  176. }
  177. $lastId = \end($this->data)->id;
  178. $params = \array_merge(
  179. $this->filters ?: [],
  180. ['starting_after' => $lastId],
  181. $params ?: []
  182. );
  183. return $this->all($params, $opts);
  184. }
  185. /**
  186. * Fetches the previous page in the resource list (if there is one).
  187. *
  188. * This method will try to respect the limit of the current page. If none
  189. * was given, the default limit will be fetched again.
  190. *
  191. * @param null|array $params
  192. * @param null|array|string $opts
  193. *
  194. * @return Collection
  195. */
  196. public function previousPage($params = null, $opts = null)
  197. {
  198. if (!$this->has_more) {
  199. return static::emptyCollection($opts);
  200. }
  201. $firstId = $this->data[0]->id;
  202. $params = \array_merge(
  203. $this->filters ?: [],
  204. ['ending_before' => $firstId],
  205. $params ?: []
  206. );
  207. return $this->all($params, $opts);
  208. }
  209. /**
  210. * Gets the first item from the current page. Returns `null` if the current page is empty.
  211. *
  212. * @return null|\Stripe\StripeObject
  213. */
  214. public function first()
  215. {
  216. return \count($this->data) > 0 ? $this->data[0] : null;
  217. }
  218. /**
  219. * Gets the last item from the current page. Returns `null` if the current page is empty.
  220. *
  221. * @return null|\Stripe\StripeObject
  222. */
  223. public function last()
  224. {
  225. return \count($this->data) > 0 ? $this->data[\count($this->data) - 1] : null;
  226. }
  227. private function extractPathAndUpdateParams($params)
  228. {
  229. $url = \parse_url($this->url);
  230. if (!isset($url['path'])) {
  231. throw new Exception\UnexpectedValueException("Could not parse list url into parts: {$url}");
  232. }
  233. if (isset($url['query'])) {
  234. // If the URL contains a query param, parse it out into $params so they
  235. // don't interact weirdly with each other.
  236. $query = [];
  237. \parse_str($url['query'], $query);
  238. $params = \array_merge($params ?: [], $query);
  239. }
  240. return [$url['path'], $params];
  241. }
  242. }