StatusValue.php 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. <?php
  2. /**
  3. * This program is free software; you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation; either version 2 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License along
  14. * with this program; if not, write to the Free Software Foundation, Inc.,
  15. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. * http://www.gnu.org/copyleft/gpl.html
  17. *
  18. * @file
  19. */
  20. /**
  21. * Generic operation result class
  22. * Has warning/error list, boolean status and arbitrary value
  23. *
  24. * "Good" means the operation was completed with no warnings or errors.
  25. *
  26. * "OK" means the operation was partially or wholly completed.
  27. *
  28. * An operation which is not OK should have errors so that the user can be
  29. * informed as to what went wrong. Calling the fatal() function sets an error
  30. * message and simultaneously switches off the OK flag.
  31. *
  32. * The recommended pattern for Status objects is to return a StatusValue
  33. * unconditionally, i.e. both on success and on failure -- so that the
  34. * developer of the calling code is reminded that the function can fail, and
  35. * so that a lack of error-handling will be explicit.
  36. *
  37. * The use of Message objects should be avoided when serializability is needed.
  38. *
  39. * @since 1.25
  40. */
  41. class StatusValue {
  42. /** @var bool */
  43. protected $ok = true;
  44. /** @var array[] */
  45. protected $errors = [];
  46. /** @var mixed */
  47. public $value;
  48. /** @var bool[] Map of (key => bool) to indicate success of each part of batch operations */
  49. public $success = [];
  50. /** @var int Counter for batch operations */
  51. public $successCount = 0;
  52. /** @var int Counter for batch operations */
  53. public $failCount = 0;
  54. /**
  55. * Factory function for fatal errors
  56. *
  57. * @param string|MessageSpecifier $message Message key or object
  58. * @param mixed ...$parameters
  59. * @return static
  60. */
  61. public static function newFatal( $message, ...$parameters ) {
  62. $result = new static();
  63. $result->fatal( $message, ...$parameters );
  64. return $result;
  65. }
  66. /**
  67. * Factory function for good results
  68. *
  69. * @param mixed|null $value
  70. * @return static
  71. */
  72. public static function newGood( $value = null ) {
  73. $result = new static();
  74. $result->value = $value;
  75. return $result;
  76. }
  77. /**
  78. * Splits this StatusValue object into two new StatusValue objects, one which contains only
  79. * the error messages, and one that contains the warnings, only. The returned array is
  80. * defined as:
  81. * [
  82. * 0 => object(StatusValue) # the StatusValue with error messages, only
  83. * 1 => object(StatusValue) # The StatusValue with warning messages, only
  84. * ]
  85. *
  86. * @return static[]
  87. */
  88. public function splitByErrorType() {
  89. $errorsOnlyStatusValue = clone $this;
  90. $warningsOnlyStatusValue = clone $this;
  91. $warningsOnlyStatusValue->ok = true;
  92. $errorsOnlyStatusValue->errors = $warningsOnlyStatusValue->errors = [];
  93. foreach ( $this->errors as $item ) {
  94. if ( $item['type'] === 'warning' ) {
  95. $warningsOnlyStatusValue->errors[] = $item;
  96. } else {
  97. $errorsOnlyStatusValue->errors[] = $item;
  98. }
  99. }
  100. return [ $errorsOnlyStatusValue, $warningsOnlyStatusValue ];
  101. }
  102. /**
  103. * Returns whether the operation completed and didn't have any error or
  104. * warnings
  105. *
  106. * @return bool
  107. */
  108. public function isGood() {
  109. return $this->ok && !$this->errors;
  110. }
  111. /**
  112. * Returns whether the operation completed
  113. *
  114. * @return bool
  115. */
  116. public function isOK() {
  117. return $this->ok;
  118. }
  119. /**
  120. * @return mixed
  121. */
  122. public function getValue() {
  123. return $this->value;
  124. }
  125. /**
  126. * Get the list of errors
  127. *
  128. * Each error is a (message:string or MessageSpecifier,params:array) map
  129. *
  130. * @return array[]
  131. */
  132. public function getErrors() {
  133. return $this->errors;
  134. }
  135. /**
  136. * Change operation status
  137. *
  138. * @param bool $ok
  139. */
  140. public function setOK( $ok ) {
  141. $this->ok = $ok;
  142. }
  143. /**
  144. * Change operation result
  145. *
  146. * @param bool $ok Whether the operation completed
  147. * @param mixed|null $value
  148. */
  149. public function setResult( $ok, $value = null ) {
  150. $this->ok = (bool)$ok;
  151. $this->value = $value;
  152. }
  153. /**
  154. * Add a new warning
  155. *
  156. * @param string|MessageSpecifier $message Message key or object
  157. * @param mixed ...$parameters
  158. */
  159. public function warning( $message, ...$parameters ) {
  160. $this->errors[] = [
  161. 'type' => 'warning',
  162. 'message' => $message,
  163. 'params' => $parameters
  164. ];
  165. }
  166. /**
  167. * Add an error, do not set fatal flag
  168. * This can be used for non-fatal errors
  169. *
  170. * @param string|MessageSpecifier $message Message key or object
  171. * @param mixed ...$parameters
  172. */
  173. public function error( $message, ...$parameters ) {
  174. $this->errors[] = [
  175. 'type' => 'error',
  176. 'message' => $message,
  177. 'params' => $parameters
  178. ];
  179. }
  180. /**
  181. * Add an error and set OK to false, indicating that the operation
  182. * as a whole was fatal
  183. *
  184. * @param string|MessageSpecifier $message Message key or object
  185. * @param mixed ...$parameters
  186. */
  187. public function fatal( $message, ...$parameters ) {
  188. $this->errors[] = [
  189. 'type' => 'error',
  190. 'message' => $message,
  191. 'params' => $parameters
  192. ];
  193. $this->ok = false;
  194. }
  195. /**
  196. * Merge another status object into this one
  197. *
  198. * @param StatusValue $other
  199. * @param bool $overwriteValue Whether to override the "value" member
  200. */
  201. public function merge( $other, $overwriteValue = false ) {
  202. $this->errors = array_merge( $this->errors, $other->errors );
  203. $this->ok = $this->ok && $other->ok;
  204. if ( $overwriteValue ) {
  205. $this->value = $other->value;
  206. }
  207. $this->successCount += $other->successCount;
  208. $this->failCount += $other->failCount;
  209. }
  210. /**
  211. * Returns a list of status messages of the given type
  212. *
  213. * Each entry is a map of:
  214. * - message: string message key or MessageSpecifier
  215. * - params: array list of parameters
  216. *
  217. * @param string $type
  218. * @return array[]
  219. */
  220. public function getErrorsByType( $type ) {
  221. $result = [];
  222. foreach ( $this->errors as $error ) {
  223. if ( $error['type'] === $type ) {
  224. $result[] = $error;
  225. }
  226. }
  227. return $result;
  228. }
  229. /**
  230. * Returns true if the specified message is present as a warning or error
  231. *
  232. * @param string|MessageSpecifier $message Message key or object to search for
  233. *
  234. * @return bool
  235. */
  236. public function hasMessage( $message ) {
  237. if ( $message instanceof MessageSpecifier ) {
  238. $message = $message->getKey();
  239. }
  240. foreach ( $this->errors as $error ) {
  241. if ( $error['message'] instanceof MessageSpecifier
  242. && $error['message']->getKey() === $message
  243. ) {
  244. return true;
  245. } elseif ( $error['message'] === $message ) {
  246. return true;
  247. }
  248. }
  249. return false;
  250. }
  251. /**
  252. * If the specified source message exists, replace it with the specified
  253. * destination message, but keep the same parameters as in the original error.
  254. *
  255. * Note, due to the lack of tools for comparing IStatusMessage objects, this
  256. * function will not work when using such an object as the search parameter.
  257. *
  258. * @param MessageSpecifier|string $source Message key or object to search for
  259. * @param MessageSpecifier|string $dest Replacement message key or object
  260. * @return bool Return true if the replacement was done, false otherwise.
  261. */
  262. public function replaceMessage( $source, $dest ) {
  263. $replaced = false;
  264. foreach ( $this->errors as $index => $error ) {
  265. if ( $error['message'] === $source ) {
  266. $this->errors[$index]['message'] = $dest;
  267. $replaced = true;
  268. }
  269. }
  270. return $replaced;
  271. }
  272. /**
  273. * @return string
  274. */
  275. public function __toString() {
  276. $status = $this->isOK() ? "OK" : "Error";
  277. if ( count( $this->errors ) ) {
  278. $errorcount = "collected " . ( count( $this->errors ) ) . " error(s) on the way";
  279. } else {
  280. $errorcount = "no errors detected";
  281. }
  282. if ( isset( $this->value ) ) {
  283. $valstr = gettype( $this->value ) . " value set";
  284. if ( is_object( $this->value ) ) {
  285. $valstr .= "\"" . get_class( $this->value ) . "\" instance";
  286. }
  287. } else {
  288. $valstr = "no value set";
  289. }
  290. $out = sprintf( "<%s, %s, %s>",
  291. $status,
  292. $errorcount,
  293. $valstr
  294. );
  295. if ( count( $this->errors ) > 0 ) {
  296. $hdr = sprintf( "+-%'-4s-+-%'-25s-+-%'-40s-+\n", "", "", "" );
  297. $i = 1;
  298. $out .= "\n";
  299. $out .= $hdr;
  300. foreach ( $this->errors as $error ) {
  301. if ( $error['message'] instanceof MessageSpecifier ) {
  302. $key = $error['message']->getKey();
  303. $params = $error['message']->getParams();
  304. } elseif ( $error['params'] ) {
  305. $key = $error['message'];
  306. $params = $error['params'];
  307. } else {
  308. $key = $error['message'];
  309. $params = [];
  310. }
  311. $out .= sprintf( "| %4d | %-25.25s | %-40.40s |\n",
  312. $i,
  313. $key,
  314. implode( " ", $params )
  315. );
  316. $i += 1;
  317. }
  318. $out .= $hdr;
  319. }
  320. return $out;
  321. }
  322. }