1 <?php
2 
3 /**
4  * Hoa
5  *
6  *
7  * @license
8  *
9  * New BSD License
10  *
11  * Copyright © 2007-2017, Hoa community. All rights reserved.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions are met:
15  *     * Redistributions of source code must retain the above copyright
16  *       notice, this list of conditions and the following disclaimer.
17  *     * Redistributions in binary form must reproduce the above copyright
18  *       notice, this list of conditions and the following disclaimer in the
19  *       documentation and/or other materials provided with the distribution.
20  *     * Neither the name of the Hoa nor the names of its contributors may be
21  *       used to endorse or promote products derived from this software without
22  *       specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGE.
35  */
36 
37 namespace Hoa\Exception;
38 
39 /**
40  * Class \Hoa\Exception\Group.
41  *
42  * This is an exception that contains a group of exceptions.
43  *
44  * @copyright  Copyright © 2007-2017 Hoa community
45  * @license    New BSD License
46  */
47 class Group extends Exception implements \ArrayAccess, \IteratorAggregate, \Countable
48 {
49     /**
50      * All exceptions (stored in a stack for transactions).
51      *
52      * @var \SplStack
53      */
54     protected $_group = null;
55 
56 
57 
58     /**
59      * Create an exception.
60      *
61      * @param   string      $message      Formatted message.
62      * @param   int         $code         Code (the ID).
63      * @param   array       $arguments    Arguments to format message.
64      * @param   \Exception  $previous     Previous exception in chaining.
65      */
66     public function __construct(
67         $message,
68         $code                = 0,
69         $arguments           = [],
70         \Exception $previous = null
71     ) {
72         parent::__construct($message, $code, $arguments, $previous);
73         $this->_group = new \SplStack();
74         $this->beginTransaction();
75 
76         return;
77     }
78 
79     /**
80      * Raise an exception as a string.
81      *
82      * @param   bool    $previous    Whether raise previous exception if exists.
83      * @return  string
84      */
85     public function raise($previous = false)
86     {
87         $out = parent::raise($previous);
88 
89         if (0 >= count($this)) {
90             return $out;
91         }
92 
93         $out .= "\n\n" . 'Contains the following exceptions:';
94 
95         foreach ($this as $exception) {
96             $out .=
97                 "\n\n" . '  • ' .
98                 str_replace(
99                     "\n",
100                     "\n" . '    ',
101                     $exception->raise($previous)
102                 );
103         }
104 
105         return $out;
106     }
107 
108     /**
109      * Begin a transaction.
110      *
111      * @return  \Hoa\Exception\Group
112      */
113     public function beginTransaction()
114     {
115         $this->_group->push(new \ArrayObject());
116 
117         return $this;
118     }
119 
120     /**
121      * Rollback a transaction.
122      *
123      * @return  \Hoa\Exception\Group
124      */
125     public function rollbackTransaction()
126     {
127         if (1 >= count($this->_group)) {
128             return $this;
129         }
130 
131         $this->_group->pop();
132 
133         return $this;
134     }
135 
136     /**
137      * Commit a transaction.
138      *
139      * @return  \Hoa\Exception\Group
140      */
141     public function commitTransaction()
142     {
143         if (false === $this->hasUncommittedExceptions()) {
144             $this->_group->pop();
145 
146             return $this;
147         }
148 
149         foreach ($this->_group->pop() as $index => $exception) {
150             $this[$index] = $exception;
151         }
152 
153         return $this;
154     }
155 
156     /**
157      * Check if there is uncommitted exceptions.
158      *
159      * @return  bool
160      */
161     public function hasUncommittedExceptions()
162     {
163         return
164             1 < count($this->_group) &&
165             0 < count($this->_group->top());
166     }
167 
168     /**
169      * Check if an index in the group exists.
170      *
171      * @param   mixed  $index    Index.
172      * @return  bool
173      */
174     public function offsetExists($index)
175     {
176         foreach ($this->_group as $group) {
177             if (isset($group[$index])) {
178                 return true;
179             }
180         }
181 
182         return false;
183     }
184 
185     /**
186      * Get an exception from the group.
187      *
188      * @param   mixed  $index    Index.
189      * @return  Exception
190      */
191     public function offsetGet($index)
192     {
193         foreach ($this->_group as $group) {
194             if (isset($group[$index])) {
195                 return $group[$index];
196             }
197         }
198 
199         return null;
200     }
201 
202     /**
203      * Set an exception in the group.
204      *
205      * @param   mixed       $index        Index.
206      * @param   Exception  $exception    Exception.
207      * @return  void
208      */
209     public function offsetSet($index, $exception)
210     {
211         if (!($exception instanceof \Exception)) {
212             return null;
213         }
214 
215         $group = $this->_group->top();
216 
217         if (null === $index ||
218             true === is_int($index)) {
219             $group[] = $exception;
220         } else {
221             $group[$index] = $exception;
222         }
223 
224         return;
225     }
226 
227     /**
228      * Remove an exception in the group.
229      *
230      * @param   mixed  $index    Index.
231      * @return  void
232      */
233     public function offsetUnset($index)
234     {
235         foreach ($this->_group as $group) {
236             if (isset($group[$index])) {
237                 unset($group[$index]);
238             }
239         }
240 
241         return;
242     }
243 
244     /**
245      * Get committed exceptions in the group.
246      *
247      * @return  \ArrayObject
248      */
249     public function getExceptions()
250     {
251         return $this->_group->bottom();
252     }
253 
254     /**
255      * Get an iterator over all exceptions (committed or not).
256      *
257      * @return  \ArrayIterator
258      */
259     public function getIterator()
260     {
261         return $this->getExceptions()->getIterator();
262     }
263 
264     /**
265      * Count the number of committed exceptions.
266      *
267      * @return  int
268      */
269     public function count()
270     {
271         return count($this->getExceptions());
272     }
273 
274     /**
275      * Count the stack size, i.e. the number of opened transactions.
276      *
277      * @return  int
278      */
279     public function getStackSize()
280     {
281         return count($this->_group);
282     }
283 }
284