1: <?php
2:
3: /**
4: * This file is part of the Nette Framework (https://nette.org)
5: *
6: * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
7: *
8: * For the full copyright and license information, please view
9: * the file license.txt that was distributed with this source code.
10: * @package Nette\Collections
11: */
12:
13:
14:
15: /**
16: * SPL ArrayObject customization.
17: *
18: * @author David Grudl
19: *
20: * @property-read bool $frozen
21: * @package Nette\Collections
22: */
23: abstract class NCollection extends ArrayObject implements ICollection
24: {
25: /** @var string type (class, interface, PHP type) */
26: private $itemType;
27:
28: /** @var string function to verify type */
29: private $checkFunc;
30:
31: /** @var bool */
32: private $frozen = FALSE;
33:
34:
35:
36: /**
37: * @param array to wrap
38: * @param string class/interface name or ':type'
39: * @throws InvalidArgumentException
40: */
41: public function __construct($arr = NULL, $type = NULL)
42: {
43: if (substr($type, 0, 1) === ':') {
44: $this->itemType = substr($type, 1);
45: $this->checkFunc = 'is_' . $this->itemType;
46: } else {
47: $this->itemType = $type;
48: }
49:
50: if ($arr !== NULL) {
51: $this->import($arr);
52: }
53: }
54:
55:
56:
57: /**
58: * Appends the specified element to the end of this collection.
59: * @param mixed
60: * @return void
61: * @throws InvalidArgumentException
62: */
63: public function append($item)
64: {
65: $this->beforeAdd($item);
66: parent::append($item);
67: }
68:
69:
70:
71: /**
72: * Removes the first occurrence of the specified element.
73: * @param mixed
74: * @return bool true if this collection changed as a result of the call
75: * @throws NotSupportedException
76: */
77: public function remove($item)
78: {
79: $this->updating();
80: $index = $this->search($item);
81: if ($index === FALSE) {
82: return FALSE;
83: } else {
84: parent::offsetUnset($index);
85: return TRUE;
86: }
87: }
88:
89:
90:
91: /**
92: * Returns the index of the first occurrence of the specified element,.
93: * or FALSE if this collection does not contain this element.
94: * @param mixed
95: * @return int|FALSE
96: */
97: protected function search($item)
98: {
99: return array_search($item, $this->getArrayCopy(), TRUE);
100: }
101:
102:
103:
104: /**
105: * Removes all of the elements from this collection.
106: * @return void
107: * @throws NotSupportedException
108: */
109: public function clear()
110: {
111: $this->updating();
112: parent::exchangeArray(array());
113: }
114:
115:
116:
117: /**
118: * Returns true if this collection contains the specified item.
119: * @param mixed
120: * @return bool
121: */
122: public function contains($item)
123: {
124: return $this->search($item) !== FALSE;
125: }
126:
127:
128:
129: /**
130: * Import from array or any traversable object.
131: * @param array|Traversable
132: * @return void
133: * @throws InvalidArgumentException
134: */
135: public function import($arr)
136: {
137: if (!(is_array($arr) || $arr instanceof Traversable)) {
138: throw new InvalidArgumentException("Argument must be traversable.");
139: }
140:
141: $this->clear();
142: foreach ($arr as $item) {
143: $this->offsetSet(NULL, $item);
144: }
145: }
146:
147:
148:
149: /**
150: * Returns the item type.
151: * @return string
152: */
153: public function getItemType()
154: {
155: return $this->itemType;
156: }
157:
158:
159:
160: /**
161: * @deprecated
162: */
163: public function setReadOnly()
164: {
165: throw new DeprecatedException(__METHOD__ . '() is deprecated; use freeze() instead.');
166: }
167:
168:
169:
170: /**
171: * @deprecated
172: */
173: public function isReadOnly()
174: {
175: throw new DeprecatedException(__METHOD__ . '() is deprecated; use isFrozen() instead.');
176: }
177:
178:
179:
180: /********************* internal notifications ****************d*g**/
181:
182:
183:
184: /**
185: * Responds when the item is about to be added to the collection.
186: * @param mixed
187: * @return void
188: * @throws InvalidArgumentException, NotSupportedException
189: */
190: protected function beforeAdd($item)
191: {
192: $this->updating();
193:
194: if ($this->itemType !== NULL) {
195: if ($this->checkFunc === NULL) {
196: if (!($item instanceof $this->itemType)) {
197: throw new InvalidArgumentException("Item must be '$this->itemType' object.");
198: }
199: } else {
200: $fnc = $this->checkFunc;
201: if (!$fnc($item)) {
202: throw new InvalidArgumentException("Item must be $this->itemType type.");
203: }
204: }
205: }
206: }
207:
208:
209:
210: /********************* ArrayObject cooperation ****************d*g**/
211:
212:
213:
214: /**
215: * Returns the iterator.
216: * @return NArrayIterator
217: */
218: public function getIterator()
219: {
220: return new ArrayIterator($this->getArrayCopy());
221: }
222:
223:
224:
225: /**
226: * Not supported. Use import().
227: */
228: public function exchangeArray($array)
229: {
230: throw new NotSupportedException('Use ' . __CLASS__ . '::import()');
231: }
232:
233:
234:
235: /**
236: * Protected exchangeArray().
237: * @param array new array
238: * @return NCollection provides a fluent interface
239: */
240: protected function setArray($array)
241: {
242: parent::exchangeArray($array);
243: return $this;
244: }
245:
246:
247:
248: /********************* NObject behaviour ****************d*g**/
249:
250:
251:
252: /**
253: * @return NClassReflection
254: */
255: public function getReflection()
256: {
257: return new NClassReflection($this);
258: }
259:
260:
261:
262: /**
263: * Call to undefined method.
264: * @throws MemberAccessException
265: */
266: public function __call($name, $args)
267: {
268: return NObjectMixin::call($this, $name, $args);
269: }
270:
271:
272:
273: /**
274: * Call to undefined static method.
275: * @throws MemberAccessException
276: */
277: public static function __callStatic($name, $args)
278: {
279: $class = __CLASS__;
280: throw new MemberAccessException("Call to undefined static method $class::$name().");
281: }
282:
283:
284:
285: /**
286: * Returns property value. Do not call directly.
287: * @throws MemberAccessException if the property is not defined.
288: */
289: public function &__get($name)
290: {
291: return NObjectMixin::get($this, $name);
292: }
293:
294:
295:
296: /**
297: * Sets value of a property. Do not call directly.
298: * @throws MemberAccessException if the property is not defined or is read-only
299: */
300: public function __set($name, $value)
301: {
302: return NObjectMixin::set($this, $name, $value);
303: }
304:
305:
306:
307: /**
308: * Is property defined?
309: * @param string property name
310: * @return bool
311: */
312: public function __isset($name)
313: {
314: return NObjectMixin::has($this, $name);
315: }
316:
317:
318:
319: /**
320: * Access to undeclared property.
321: * @throws MemberAccessException
322: */
323: public function __unset($name)
324: {
325: throw new MemberAccessException("Cannot unset the property {$this->reflection->name}::\$$name.");
326: }
327:
328:
329:
330: /********************* NFreezableObject behaviour ****************d*g**/
331:
332:
333:
334: /**
335: * Makes the object unmodifiable.
336: * @return void
337: */
338: public function freeze()
339: {
340: $this->frozen = TRUE;
341: }
342:
343:
344:
345: /**
346: * Is the object unmodifiable?
347: * @return bool
348: */
349: final public function isFrozen()
350: {
351: return $this->frozen;
352: }
353:
354:
355:
356: /**
357: * Creates a modifiable clone of the object.
358: * @return void
359: */
360: public function __clone()
361: {
362: $this->frozen = FALSE;
363: }
364:
365:
366:
367: /**
368: * @return void
369: */
370: protected function updating()
371: {
372: if ($this->frozen) {
373: $class = get_class($this);
374: throw new InvalidStateException("Cannot modify a frozen object '$class'.");
375: }
376: }
377:
378: }
379: