Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
ErrorProcessor.php
Go to the documentation of this file.
1 <?php
7 
18 use Magento\Framework\Webapi\Exception as WebapiException;
19 
28 {
29  const DEFAULT_SHUTDOWN_FUNCTION = 'apiShutdownFunction';
30 
32 
33  const DEFAULT_RESPONSE_CHARSET = 'UTF-8';
34 
35  const INTERNAL_SERVER_ERROR_MSG = 'Internal Error. Details are available in Magento log file. Report ID: %s';
36 
40  const DATA_FORMAT_JSON = 'json';
41 
42  const DATA_FORMAT_XML = 'xml';
43 
47  protected $encoder;
48 
52  protected $_appState;
53 
57  protected $_logger;
58 
64  protected $_filesystem;
65 
69  protected $directoryWrite;
70 
76  private $serializer;
77 
85  public function __construct(
86  \Magento\Framework\Json\Encoder $encoder,
87  \Magento\Framework\App\State $appState,
88  \Psr\Log\LoggerInterface $logger,
89  \Magento\Framework\Filesystem $filesystem,
90  Json $serializer = null
91  ) {
92  $this->encoder = $encoder;
93  $this->_appState = $appState;
94  $this->_logger = $logger;
95  $this->_filesystem = $filesystem;
96  $this->directoryWrite = $this->_filesystem->getDirectoryWrite(DirectoryList::VAR_DIR);
97  $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class);
98  $this->registerShutdownFunction();
99  }
100 
110  public function maskException(\Exception $exception)
111  {
112  $isDevMode = $this->_appState->getMode() === State::MODE_DEVELOPER;
113  $stackTrace = $isDevMode ? $exception->getTraceAsString() : null;
114 
115  if ($exception instanceof WebapiException) {
116  $maskedException = $exception;
117  } elseif ($exception instanceof LocalizedException) {
118  // Map HTTP codes for LocalizedExceptions according to exception type
119  if ($exception instanceof NoSuchEntityException) {
120  $httpCode = WebapiException::HTTP_NOT_FOUND;
121  } elseif (($exception instanceof AuthorizationException)
122  || ($exception instanceof AuthenticationException)
123  ) {
124  $httpCode = WebapiException::HTTP_UNAUTHORIZED;
125  } else {
126  // Input, Expired, InvalidState exceptions will fall to here
127  $httpCode = WebapiException::HTTP_BAD_REQUEST;
128  }
129 
130  if ($exception instanceof AggregateExceptionInterface) {
131  $errors = $exception->getErrors();
132  } else {
133  $errors = null;
134  }
135 
136  $maskedException = new WebapiException(
137  new Phrase($exception->getRawMessage()),
138  $exception->getCode(),
139  $httpCode,
140  $exception->getParameters(),
141  get_class($exception),
142  $errors,
143  $stackTrace
144  );
145  } else {
146  $message = $exception->getMessage();
147  $code = $exception->getCode();
148  //if not in Dev mode, make sure the message and code is masked for unanticipated exceptions
149  if (!$isDevMode) {
151  $reportId = $this->_critical($exception);
152  $message = sprintf(self::INTERNAL_SERVER_ERROR_MSG, $reportId);
153  $code = 0;
154  }
155  $maskedException = new WebapiException(
156  new Phrase($message),
157  $code,
158  WebapiException::HTTP_INTERNAL_ERROR,
159  [],
160  '',
161  null,
162  $stackTrace
163  );
164  }
165  return $maskedException;
166  }
167 
178  public function renderException(\Exception $exception, $httpCode = self::DEFAULT_ERROR_HTTP_CODE)
179  {
180  if ($this->_appState->getMode() == State::MODE_DEVELOPER ||
181  $exception instanceof \Magento\Framework\Webapi\Exception
182  ) {
183  $this->renderErrorMessage($exception->getMessage(), $exception->getTraceAsString(), $httpCode);
184  } else {
185  $reportId = $this->_critical($exception);
186  $this->renderErrorMessage(
187  new Phrase('Internal Error. Details are available in Magento log file. Report ID: %1', $reportId),
188  'Trace is not available.',
189  $httpCode
190  );
191  }
192  exit;
193  }
194 
201  protected function _critical(\Exception $exception)
202  {
203  $reportId = uniqid("webapi-");
204  $message = "Report ID: {$reportId}; Message: {$exception->getMessage()}";
205  $code = $exception->getCode();
206  $exception = new \Exception($message, $code, $exception);
207  $this->_logger->critical($exception);
208  return $reportId;
209  }
210 
219  public function renderErrorMessage(
220  $errorMessage,
221  $trace = 'Trace is not available.',
222  $httpCode = self::DEFAULT_ERROR_HTTP_CODE
223  ) {
224  if (isset($_SERVER['HTTP_ACCEPT']) && strstr($_SERVER['HTTP_ACCEPT'], 'xml')) {
225  $output = $this->_formatError($errorMessage, $trace, $httpCode, self::DATA_FORMAT_XML);
226  $mimeType = 'application/xml';
227  } else {
229  $output = $this->_formatError($errorMessage, $trace, $httpCode, self::DATA_FORMAT_JSON);
230  $mimeType = 'application/json';
231  }
232  if (!headers_sent()) {
233  header('HTTP/1.1 ' . ($httpCode ? $httpCode : self::DEFAULT_ERROR_HTTP_CODE));
234  header('Content-Type: ' . $mimeType . '; charset=' . self::DEFAULT_RESPONSE_CHARSET);
235  }
236  echo $output;
237  }
238 
248  protected function _formatError($errorMessage, $trace, $httpCode, $format)
249  {
250  $errorData = [];
251  $message = ['code' => $httpCode, 'message' => $errorMessage];
252  $isDeveloperMode = $this->_appState->getMode() == State::MODE_DEVELOPER;
253  if ($isDeveloperMode) {
254  $message['trace'] = $trace;
255  }
256  $errorData['messages']['error'][] = $message;
257  switch ($format) {
259  $errorData = $this->encoder->encode($errorData);
260  break;
262  $errorData = '<?xml version="1.0"?>'
263  . '<error>'
264  . '<messages>'
265  . '<error>'
266  . '<data_item>'
267  . '<code>' . $httpCode . '</code>'
268  . '<message><![CDATA[' . $errorMessage . ']]></message>'
269  . ($isDeveloperMode ? '<trace><![CDATA[' . $trace . ']]></trace>' : '')
270  . '</data_item>'
271  . '</error>'
272  . '</messages>'
273  . '</error>';
274  break;
275  }
276  return $errorData;
277  }
278 
284  public function registerShutdownFunction()
285  {
286  register_shutdown_function([$this, self::DEFAULT_SHUTDOWN_FUNCTION]);
287  return $this;
288  }
289 
295  public function apiShutdownFunction()
296  {
297  $fatalErrorFlag = E_ERROR | E_USER_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_RECOVERABLE_ERROR;
298  $error = error_get_last();
299  if ($error && $error['type'] & $fatalErrorFlag) {
300  $errorMessage = "Fatal Error: '{$error['message']}' in '{$error['file']}' on line {$error['line']}";
301  $reportId = $this->_saveFatalErrorReport($errorMessage);
302  if ($this->_appState->getMode() == State::MODE_DEVELOPER) {
303  $this->renderErrorMessage($errorMessage);
304  } else {
305  $this->renderErrorMessage(
306  new Phrase('Server internal error. See details in report api/%1', [$reportId])
307  );
308  }
309  }
310  }
311 
318  protected function _saveFatalErrorReport($reportData)
319  {
320  $this->directoryWrite->create('report/api');
321  $reportId = abs(intval(microtime(true) * random_int(100, 1000)));
322  $this->directoryWrite->writeFile('report/api/' . $reportId, $this->serializer->serialize($reportData));
323  return $reportId;
324  }
325 }
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
renderException(\Exception $exception, $httpCode=self::DEFAULT_ERROR_HTTP_CODE)
_formatError($errorMessage, $trace, $httpCode, $format)
renderErrorMessage( $errorMessage, $trace='Trace is not available.', $httpCode=self::DEFAULT_ERROR_HTTP_CODE)
$message
$logger
$format
Definition: list.phtml:12
exit
Definition: redirect.phtml:12
__construct(\Magento\Framework\Json\Encoder $encoder, \Magento\Framework\App\State $appState, \Psr\Log\LoggerInterface $logger, \Magento\Framework\Filesystem $filesystem, Json $serializer=null)
$filesystem
$errors
Definition: overview.phtml:9
$code
Definition: info.phtml:12