errorReportingLevel = $errorReportingLevel; $this->retrySeconds = $retrySeconds; } /** * {@inheritdoc} */ public function supportsCall(Call $call) { return true; } /** * {@inheritdoc} */ public function handleCall(Call $call) { $this->startErrorAndOutputBuffering($call); $result = $this->executeCall($call); $this->stopErrorAndOutputBuffering(); return $result; } /** * Used as a custom error handler when step is running. * * @see set_error_handler() * * @param integer $level * @param string $message * @param string $file * @param integer $line * * @return Boolean * * @throws CallErrorException */ public function handleError($level, $message, $file, $line) { if ($this->errorLevelIsNotReportable($level)) { return false; } throw new CallErrorException($level, $message, $file, $line); } /** * Executes single call. * * @param Call $call * * @return CallResult */ private function executeCall(Call $call) { $callable = $call->getBoundCallable(); $arguments = $call->getArguments(); $retry = $this->isCallRetryable($call); $return = $exception = null; try { // Determine whether to call with retries if ($retry) { $return = $this->retryThrowable(function () use ($callable, $arguments) { return call_user_func_array($callable, $arguments ?? []); }, $this->retrySeconds); } else { $return = call_user_func_array($callable, $arguments ?? []); } } catch (Exception $caught) { $exception = $caught; } $stdOut = $this->getBufferedStdOut(); return new CallResult($call, $return, $exception, $stdOut); } /** * Returns buffered stdout. * * @return null|string */ private function getBufferedStdOut() { return ob_get_length() ? ob_get_contents() : null; } /** * Starts error handler and stdout buffering. * * @param Call $call */ private function startErrorAndOutputBuffering(Call $call) { $errorReporting = $call->getErrorReportingLevel() ? : $this->errorReportingLevel; set_error_handler(array($this, 'handleError'), $errorReporting ?? 0); $this->obStarted = ob_start(); } /** * Stops error handler and stdout buffering. */ private function stopErrorAndOutputBuffering() { if ($this->obStarted) { ob_end_clean(); } restore_error_handler(); } /** * Checks if provided error level is not reportable. * * @param integer $level * * @return Boolean */ private function errorLevelIsNotReportable($level) { return !(error_reporting() & $level); } /** * Determine if the call is retryable * * @param Call $call * @return bool */ protected function isCallRetryable(Call $call) { if (!($call instanceof DefinitionCall)) { return false; } $feature = $call->getFeature(); if ($feature->hasTag(self::RETRY_TAG)) { return true; } $scenario = $this->getStepScenario($feature, $call->getStep()); return $scenario && $scenario->hasTag(self::RETRY_TAG); } }