Collection.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. <?php
  2. namespace Guzzle\Common;
  3. use Guzzle\Common\Exception\InvalidArgumentException;
  4. use Guzzle\Common\Exception\RuntimeException;
  5. /**
  6. * Key value pair collection object
  7. */
  8. class Collection implements \ArrayAccess, \IteratorAggregate, \Countable, ToArrayInterface
  9. {
  10. /** @var array Data associated with the object. */
  11. protected $data;
  12. /**
  13. * @param array $data Associative array of data to set
  14. */
  15. public function __construct(array $data = array())
  16. {
  17. $this->data = $data;
  18. }
  19. /**
  20. * Create a new collection from an array, validate the keys, and add default values where missing
  21. *
  22. * @param array $config Configuration values to apply.
  23. * @param array $defaults Default parameters
  24. * @param array $required Required parameter names
  25. *
  26. * @return self
  27. * @throws InvalidArgumentException if a parameter is missing
  28. */
  29. public static function fromConfig(array $config = array(), array $defaults = array(), array $required = array())
  30. {
  31. $data = $config + $defaults;
  32. if ($missing = array_diff($required, array_keys($data))) {
  33. throw new InvalidArgumentException('Config is missing the following keys: ' . implode(', ', $missing));
  34. }
  35. return new self($data);
  36. }
  37. public function count()
  38. {
  39. return count($this->data);
  40. }
  41. public function getIterator()
  42. {
  43. return new \ArrayIterator($this->data);
  44. }
  45. public function toArray()
  46. {
  47. return $this->data;
  48. }
  49. /**
  50. * Removes all key value pairs
  51. *
  52. * @return Collection
  53. */
  54. public function clear()
  55. {
  56. $this->data = array();
  57. return $this;
  58. }
  59. /**
  60. * Get all or a subset of matching key value pairs
  61. *
  62. * @param array $keys Pass an array of keys to retrieve only a subset of key value pairs
  63. *
  64. * @return array Returns an array of all matching key value pairs
  65. */
  66. public function getAll(array $keys = null)
  67. {
  68. return $keys ? array_intersect_key($this->data, array_flip($keys)) : $this->data;
  69. }
  70. /**
  71. * Get a specific key value.
  72. *
  73. * @param string $key Key to retrieve.
  74. *
  75. * @return mixed|null Value of the key or NULL
  76. */
  77. public function get($key)
  78. {
  79. return isset($this->data[$key]) ? $this->data[$key] : null;
  80. }
  81. /**
  82. * Set a key value pair
  83. *
  84. * @param string $key Key to set
  85. * @param mixed $value Value to set
  86. *
  87. * @return Collection Returns a reference to the object
  88. */
  89. public function set($key, $value)
  90. {
  91. $this->data[$key] = $value;
  92. return $this;
  93. }
  94. /**
  95. * Add a value to a key. If a key of the same name has already been added, the key value will be converted into an
  96. * array and the new value will be pushed to the end of the array.
  97. *
  98. * @param string $key Key to add
  99. * @param mixed $value Value to add to the key
  100. *
  101. * @return Collection Returns a reference to the object.
  102. */
  103. public function add($key, $value)
  104. {
  105. if (!array_key_exists($key, $this->data)) {
  106. $this->data[$key] = $value;
  107. } elseif (is_array($this->data[$key])) {
  108. $this->data[$key][] = $value;
  109. } else {
  110. $this->data[$key] = array($this->data[$key], $value);
  111. }
  112. return $this;
  113. }
  114. /**
  115. * Remove a specific key value pair
  116. *
  117. * @param string $key A key to remove
  118. *
  119. * @return Collection
  120. */
  121. public function remove($key)
  122. {
  123. unset($this->data[$key]);
  124. return $this;
  125. }
  126. /**
  127. * Get all keys in the collection
  128. *
  129. * @return array
  130. */
  131. public function getKeys()
  132. {
  133. return array_keys($this->data);
  134. }
  135. /**
  136. * Returns whether or not the specified key is present.
  137. *
  138. * @param string $key The key for which to check the existence.
  139. *
  140. * @return bool
  141. */
  142. public function hasKey($key)
  143. {
  144. return array_key_exists($key, $this->data);
  145. }
  146. /**
  147. * Case insensitive search the keys in the collection
  148. *
  149. * @param string $key Key to search for
  150. *
  151. * @return bool|string Returns false if not found, otherwise returns the key
  152. */
  153. public function keySearch($key)
  154. {
  155. foreach (array_keys($this->data) as $k) {
  156. if (!strcasecmp($k, $key)) {
  157. return $k;
  158. }
  159. }
  160. return false;
  161. }
  162. /**
  163. * Checks if any keys contains a certain value
  164. *
  165. * @param string $value Value to search for
  166. *
  167. * @return mixed Returns the key if the value was found FALSE if the value was not found.
  168. */
  169. public function hasValue($value)
  170. {
  171. return array_search($value, $this->data);
  172. }
  173. /**
  174. * Replace the data of the object with the value of an array
  175. *
  176. * @param array $data Associative array of data
  177. *
  178. * @return Collection Returns a reference to the object
  179. */
  180. public function replace(array $data)
  181. {
  182. $this->data = $data;
  183. return $this;
  184. }
  185. /**
  186. * Add and merge in a Collection or array of key value pair data.
  187. *
  188. * @param Collection|array $data Associative array of key value pair data
  189. *
  190. * @return Collection Returns a reference to the object.
  191. */
  192. public function merge($data)
  193. {
  194. foreach ($data as $key => $value) {
  195. $this->add($key, $value);
  196. }
  197. return $this;
  198. }
  199. /**
  200. * Over write key value pairs in this collection with all of the data from an array or collection.
  201. *
  202. * @param array|\Traversable $data Values to override over this config
  203. *
  204. * @return self
  205. */
  206. public function overwriteWith($data)
  207. {
  208. if (is_array($data)) {
  209. $this->data = $data + $this->data;
  210. } elseif ($data instanceof Collection) {
  211. $this->data = $data->toArray() + $this->data;
  212. } else {
  213. foreach ($data as $key => $value) {
  214. $this->data[$key] = $value;
  215. }
  216. }
  217. return $this;
  218. }
  219. /**
  220. * Returns a Collection containing all the elements of the collection after applying the callback function to each
  221. * one. The Closure should accept three parameters: (string) $key, (string) $value, (array) $context and return a
  222. * modified value
  223. *
  224. * @param \Closure $closure Closure to apply
  225. * @param array $context Context to pass to the closure
  226. * @param bool $static Set to TRUE to use the same class as the return rather than returning a Collection
  227. *
  228. * @return Collection
  229. */
  230. public function map(\Closure $closure, array $context = array(), $static = true)
  231. {
  232. $collection = $static ? new static() : new self();
  233. foreach ($this as $key => $value) {
  234. $collection->add($key, $closure($key, $value, $context));
  235. }
  236. return $collection;
  237. }
  238. /**
  239. * Iterates over each key value pair in the collection passing them to the Closure. If the Closure function returns
  240. * true, the current value from input is returned into the result Collection. The Closure must accept three
  241. * parameters: (string) $key, (string) $value and return Boolean TRUE or FALSE for each value.
  242. *
  243. * @param \Closure $closure Closure evaluation function
  244. * @param bool $static Set to TRUE to use the same class as the return rather than returning a Collection
  245. *
  246. * @return Collection
  247. */
  248. public function filter(\Closure $closure, $static = true)
  249. {
  250. $collection = ($static) ? new static() : new self();
  251. foreach ($this->data as $key => $value) {
  252. if ($closure($key, $value)) {
  253. $collection->add($key, $value);
  254. }
  255. }
  256. return $collection;
  257. }
  258. public function offsetExists($offset)
  259. {
  260. return isset($this->data[$offset]);
  261. }
  262. public function offsetGet($offset)
  263. {
  264. return isset($this->data[$offset]) ? $this->data[$offset] : null;
  265. }
  266. public function offsetSet($offset, $value)
  267. {
  268. $this->data[$offset] = $value;
  269. }
  270. public function offsetUnset($offset)
  271. {
  272. unset($this->data[$offset]);
  273. }
  274. /**
  275. * Set a value into a nested array key. Keys will be created as needed to set the value.
  276. *
  277. * @param string $path Path to set
  278. * @param mixed $value Value to set at the key
  279. *
  280. * @return self
  281. * @throws RuntimeException when trying to setPath using a nested path that travels through a scalar value
  282. */
  283. public function setPath($path, $value)
  284. {
  285. $current =& $this->data;
  286. $queue = explode('/', $path);
  287. while (null !== ($key = array_shift($queue))) {
  288. if (!is_array($current)) {
  289. throw new RuntimeException("Trying to setPath {$path}, but {$key} is set and is not an array");
  290. } elseif (!$queue) {
  291. $current[$key] = $value;
  292. } elseif (isset($current[$key])) {
  293. $current =& $current[$key];
  294. } else {
  295. $current[$key] = array();
  296. $current =& $current[$key];
  297. }
  298. }
  299. return $this;
  300. }
  301. /**
  302. * Gets a value from the collection using an array path (e.g. foo/baz/bar would retrieve bar from two nested arrays)
  303. * Allows for wildcard searches which recursively combine matches up to the level at which the wildcard occurs. This
  304. * can be useful for accepting any key of a sub-array and combining matching keys from each diverging path.
  305. *
  306. * @param string $path Path to traverse and retrieve a value from
  307. * @param string $separator Character used to add depth to the search
  308. * @param mixed $data Optional data to descend into (used when wildcards are encountered)
  309. *
  310. * @return mixed|null
  311. */
  312. public function getPath($path, $separator = '/', $data = null)
  313. {
  314. if ($data === null) {
  315. $data =& $this->data;
  316. }
  317. $path = is_array($path) ? $path : explode($separator, $path);
  318. while (null !== ($part = array_shift($path))) {
  319. if (!is_array($data)) {
  320. return null;
  321. } elseif (isset($data[$part])) {
  322. $data =& $data[$part];
  323. } elseif ($part != '*') {
  324. return null;
  325. } else {
  326. // Perform a wildcard search by diverging and merging paths
  327. $result = array();
  328. foreach ($data as $value) {
  329. if (!$path) {
  330. $result = array_merge_recursive($result, (array) $value);
  331. } elseif (null !== ($test = $this->getPath($path, $separator, $value))) {
  332. $result = array_merge_recursive($result, (array) $test);
  333. }
  334. }
  335. return $result;
  336. }
  337. }
  338. return $data;
  339. }
  340. /**
  341. * Inject configuration settings into an input string
  342. *
  343. * @param string $input Input to inject
  344. *
  345. * @return string
  346. * @deprecated
  347. */
  348. public function inject($input)
  349. {
  350. Version::warn(__METHOD__ . ' is deprecated');
  351. $replace = array();
  352. foreach ($this->data as $key => $val) {
  353. $replace['{' . $key . '}'] = $val;
  354. }
  355. return strtr($input, $replace);
  356. }
  357. }