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 37namespace 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 */ 47class 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