1 <?php
2
3 /*
4 * This file is part of the ICanBoogie package.
5 *
6 * (c) Olivier Laviale <olivier.laviale@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace ICanBoogie;
13
14 /**
15 * An error collector.
16 */
17 class Errors implements \ArrayAccess, \Countable, \Iterator
18 {
19 static public $message_constructor;
20
21 protected $errors = array(null => array());
22
23 /**
24 * Checks if an error is defined for an attribute.
25 *
26 * Example:
27 *
28 * <pre>
29 * $e = new Errors();
30 * $e['username'] = 'Funny username';
31 * var_dump(isset($e['username']);
32 * #=> true
33 * var_dump(isset($e['password']);
34 * #=> false
35 * </pre>
36 *
37 * @return boolean true if an error is defined for the specified attribute, false otherwise.
38 */
39 public function offsetExists($attribute)
40 {
41 return isset($this->errors[$attribute]);
42 }
43
44 /**
45 * Returns error messages.
46 *
47 * Example:
48 *
49 * <pre>
50 * $e = new Errors();
51 * var_dump($e['password']);
52 * #=> null
53 * $e['password'] = 'Invalid password';
54 * var_dump($e['password']);
55 * #=> 'Invalid password'
56 * $e['password'] = 'Ugly password';
57 * var_dump($e['password']);
58 * #=> array('Invalid password', 'Ugly password')
59 * </pre>
60 *
61 * @param string|null $attribute The attribute that caused the error, or null if the error is global.
62 *
63 * @return string|array|null Return the global error messages or the error messages attached
64 * to an attribute. If there is only one message a string is returned, otherwise an array
65 * with all the messages is returned. null is returned if there is no message defined.
66 */
67 public function offsetGet($attribute)
68 {
69 if (empty($this->errors[$attribute]))
70 {
71 return null;
72 }
73
74 $messages = $this->errors[$attribute];
75
76 return count($messages) > 1 ? $messages : current($messages);
77 }
78
79 /**
80 * Adds an error message.
81 *
82 * Example:
83 *
84 * <pre>
85 * $e = new Errors();
86 * $e['password'] = 'Invalid password';
87 * $e[] = 'Requires authentication';
88 * </pre>
89 *
90 * @param string|null $attribute If null, the message is considered as a general error message
91 * instead of an attribute message.
92 * @param string $message The error message.
93 */
94 public function offsetSet($attribute, $message)
95 {
96 $this->errors[$attribute][] = $message;
97 }
98
99 /**
100 * Removes error messages.
101 *
102 * @param string|null attribute If null, general message are removed, otherwise the message
103 * attached to the attribute are removed.
104 */
105 public function offsetUnset($attribute)
106 {
107 unset($this->errors[$attribute]);
108 }
109
110 /**
111 * Returns the number of errors defined.
112 *
113 * Example:
114 *
115 * <pre>
116 * $e = new Errors();
117 * $e['username'] = 'Funny user name';
118 * $e['password'] = 'Weak password';
119 * $e['password'] = 'should have at least one digit';
120 * count($e);
121 * #=> 3
122 * </pre>
123 */
124 public function count()
125 {
126 $n = 0;
127
128 foreach ($this->errors as $errors)
129 {
130 $n += count($errors);
131 }
132
133 return $n;
134 }
135
136 private $i;
137 private $ia;
138
139 public function current()
140 {
141 return $this->ia[$this->i][1];
142 }
143
144 public function next()
145 {
146 ++$this->i;
147 }
148
149 public function key()
150 {
151 return $this->ia[$this->i][0];
152 }
153
154 public function valid()
155 {
156 return isset($this->ia[$this->i]);
157 }
158
159 public function rewind()
160 {
161 $this->i = 0;
162 $ia = array();
163
164 foreach ($this->errors as $attribute => $errors)
165 {
166 foreach ($errors as $error)
167 {
168 $ia[] = array($attribute, $error);
169 }
170 }
171
172 $this->ia = $ia;
173 }
174
175 /**
176 * Iterates through errors using the specified callback.
177 *
178 * Example:
179 *
180 * <pre>
181 * $e = new Errors();
182 * $e['username'] = 'Funny user name';
183 * $e['password'] = 'Weak password';
184 *
185 * $e->each(function($attribute, $message) {
186 *
187 * echo "$attribute => $message<br />";
188 *
189 * });
190 * </pre>
191 *
192 * @param mixed $callback
193 */
194 public function each($callback)
195 {
196 foreach ($this->errors as $attribute => $errors)
197 {
198 foreach ($errors as $error)
199 {
200 call_user_func($callback, $attribute, $error);
201 }
202 }
203 }
204
205 /**
206 * Clears the errors.
207 */
208 public function clear()
209 {
210 $this->errors = array(null => array());
211 }
212
213 /**
214 * Formats the given string by replacing placeholders with the values provided.
215 *
216 * @param string $pattern The format pattern.
217 * @param array $args An array of replacements for the placeholders.
218 * @param array $options Options for the formatter.
219 *
220 * @return mixed A string or a stringyfiable object.
221 *
222 * @see \ICanBoogie\I18n\FormattedString
223 * @see \ICanBoogie\FormattedString
224 * @see \ICanBoogie\format
225 */
226 public function format($pattern, array $args=array(), array $options=array())
227 {
228 if (!self::$message_constructor)
229 {
230 $constructors = array('ICanBoogie\I18n\FormattedString', 'ICanBoogie\FormattedString');
231
232 foreach ($constructors as $constructor)
233 {
234 if (class_exists($constructor, true))
235 {
236 self::$message_constructor = $constructor;
237
238 break;
239 }
240 }
241 }
242
243 $constructor = self::$message_constructor;
244
245 if (!$constructor)
246 {
247 return \ICanBoogie\format($pattern, $args);
248 }
249
250 return new $constructor($pattern, $args, $options);
251 }
252 }