setConfig($config); } $this->checks = new ArrayObject(); if ($checks !== null) { $this->addChecks($checks); } if ($reporter !== null) { $this->addReporter($reporter); } } /** * Run all Checks and return a Result\Collection for every check. * * @param string|null $checkAlias An alias of Check instance to run, or null to run all checks. * @return ResultsCollection The result of running Checks */ public function run($checkAlias = null) { $results = new ResultsCollection(); $checks = $checkAlias ? new ArrayObject(array($this->getCheck($checkAlias))) : $this->getChecks(); // Trigger START event $this->triggerReporters('onStart', $checks, $this->getConfig()); // Iterate over all Checks foreach ($checks as $alias => $check) { /* @var $check CheckInterface */ // Skip Checking if BEFORE_RUN returned false or has been stopped if (!$this->triggerReporters('onBeforeRun', $check, $alias)) { continue; } // Run the Check! try { $this->startErrorHandler(); $result = $check->check(); $this->stopErrorHandler(); } catch (ErrorException $e) { $result = new Failure( 'PHP ' . static::getSeverityDescription($e->getSeverity()) . ': ' . $e->getMessage(), $e ); } catch (\Exception $e) { $this->stopErrorHandler(); $result = new Failure( 'Uncaught ' . get_class($e) . ': ' . $e->getMessage(), $e ); } // Check if we've received a Result object if (is_object($result)) { if (!$result instanceof ResultInterface) { $result = new Failure( 'Test returned unknown object ' . get_class($result), $result ); } } elseif (is_bool($result)) { // Interpret boolean as a failure or success $result = $result ? new Success() : new Failure(); } elseif (is_scalar($result)) { // Convert scalars to a warning $result = new Warning('Test returned unexpected '.gettype($result), $result); } else { // Otherwise interpret as failure $result = new Failure( 'Test returned unknown result of type ' . gettype($result), $result ); } // Save Check result $results[$check] = $result; // Stop Checking if AFTER_RUN returned false if (!$this->triggerReporters('onAfterRun', $check, $result, $alias)) { $this->triggerReporters('onStop', $results); break; } // Stop Checking on first failure if ($this->breakOnFailure && $result instanceof FailureInterface) { $this->triggerReporters('onStop', $results); break; } } // trigger FINISH event $this->triggerReporters('onFinish', $results); $this->lastResults = $results; return $results; } /** * Set config values from an array. * * @param array|Traversable $config * @throws InvalidArgumentException * @throws BadMethodCallException * @return $this */ public function setConfig($config) { if (!is_array($config) && !$config instanceof Traversable) { throw new InvalidArgumentException('Expected an array or Traversable as config for Runner.'); } foreach ($config as $key => $val) { $methodName = 'set' . implode(array_map(function ($value) { return ucfirst($value); }, explode('_', $key))); if (!is_callable(array($this, $methodName))) { throw new BadMethodCallException('Unknown config parameter ' . $key); } $this->$methodName($val); } return $this; } /** * Get current config. * * @return array */ public function getConfig() { return array( 'break_on_failure' => $this->getBreakOnFailure(), 'catch_error_severity' => $this->getCatchErrorSeverity(), ); } /** * Add diagnostic Check to run. * * @param CheckInterface $check * @param string|null $alias */ public function addCheck(CheckInterface $check, $alias = null) { $alias = is_string($alias) ? $alias : count($this->checks); $this->checks[$alias] = $check; } /** * Add multiple Checks from an array, Traversable or CheckCollectionInterface. * * @param array|Traversable|CheckCollectionInterface $checks * @throws InvalidArgumentException */ public function addChecks($checks) { if ($checks instanceof CheckCollectionInterface) { $checks = $checks->getChecks(); } if (!is_array($checks) && !$checks instanceof Traversable) { $what = is_object($checks) ? 'object of class ' . get_class($checks) : gettype($checks); throw new InvalidArgumentException('Cannot add Checks from ' . $what . ' - expected array or Traversable'); } foreach ($checks as $key => $check) { if (!$check instanceof CheckInterface) { $what = is_object($check) ? 'object of class ' . get_class($check) : gettype($check); throw new InvalidArgumentException( 'Cannot use ' . $what . ' as Check - expected ZendDiagnostics\Check\CheckInterface' ); } $alias = is_string($key) ? $key : null; $this->addCheck($check, $alias); } } /** * Add new reporter. * * @param Reporter $reporter */ public function addReporter(Reporter $reporter) { $this->reporters[] = $reporter; } /** * Remove previously attached reporter. * * @param Reporter $reporter */ public function removeReporter(Reporter $reporter) { $this->reporters = array_filter($this->reporters, function (Reporter $r) use (&$reporter) { return $r !== $reporter; }); } /** * Get a single Check instance by its alias name * * @param string $alias Alias name of the Check instance to retrieve * @throws \RuntimeException * @return CheckInterface */ public function getCheck($alias) { if (empty($this->checks[$alias])) { throw new RuntimeException(sprintf( 'There is no Check instance with an alias of "%s"', $alias )); } return $this->checks[$alias]; } /** * @return ArrayObject */ public function getChecks() { return $this->checks; } /** * @return ResultsCollection */ public function getLastResults() { return $this->lastResults; } /** * Set if checking should abort on first failure. * * @param boolean $breakOnFailure */ public function setBreakOnFailure($breakOnFailure) { $this->breakOnFailure = (bool) $breakOnFailure; } /** * @return boolean */ public function getBreakOnFailure() { return $this->breakOnFailure; } /** * @return array */ public function getReporters() { return $this->reporters; } /** * Set severity of error that will result in a check failing. Defaults to: * E_WARNING|E_PARSE|E_USER_ERROR|E_USER_WARNING|E_RECOVERABLE_ERROR * * @param int $catchErrorSeverity */ public function setCatchErrorSeverity($catchErrorSeverity) { $this->catchErrorSeverity = $catchErrorSeverity; } /** * Get current severity of error that will result in a check failing. * * @return int */ public function getCatchErrorSeverity() { return $this->catchErrorSeverity; } /** * Trigger an event on reporters. * * @param $eventType * @return bool */ protected function triggerReporters($eventType) { $args = func_get_args(); array_shift($args); foreach ($this->reporters as $reporter) { if (call_user_func_array(array($reporter, $eventType), $args) === false) { return false; } } return true; } protected function startErrorHandler() { set_error_handler(array($this, 'errorHandler'), $this->catchErrorSeverity); } public function errorHandler($errno, $errstr = '', $errfile = '', $errline = 0) { throw new ErrorException($errstr, 0, $errno, $errfile, $errline); } protected function stopErrorHandler() { restore_error_handler(); } /** * Convert PHP error severity INT to name. * * @param integer $severity * @return string */ public static function getSeverityDescription($severity) { switch ($severity) { case E_WARNING: // 2 // return 'WARNING'; // @codeCoverageIgnoreStart case E_ERROR: // 1 // return 'ERROR'; case E_PARSE: // 4 // return 'PARSE'; case E_NOTICE: // 8 // return 'NOTICE'; case E_CORE_ERROR: // 16 // return 'CORE_ERROR'; case E_CORE_WARNING: // 32 // return 'CORE_WARNING'; case E_COMPILE_ERROR: // 64 // return 'COMPILE_ERROR'; case E_COMPILE_WARNING: // 128 // return 'COMPILE_WARNING'; case E_USER_ERROR: // 256 // return 'USER_ERROR'; case E_USER_WARNING: // 512 // return 'USER_WARNING'; case E_USER_NOTICE: // 1024 // return 'USER_NOTICE'; case E_STRICT: // 2048 // return 'STRICT'; case E_RECOVERABLE_ERROR: // 4096 // return 'RECOVERABLE_ERROR'; case E_DEPRECATED: // 8192 // return 'DEPRECATED'; case E_USER_DEPRECATED: // 16384 // return 'USER_DEPRECATED'; default: return 'error severity ' . $severity; } // @codeCoverageIgnoreEnd } }