The library I wrote for Laravel uses DomDocument.
I use this library under my Controller, and its namespace is app/Services/Verify/. The library gets initialized and used when I put it some inputs into a form.
When the library fails, Laravel would fail the way it would - returning the following message: Whoops, looks like something went wrong.
I use the following regex to validate the URL on the client-side - 'our_team_link' => 'required|url|regex:/^http:\/\/www\.ugcleague\.com\/team_page\.cfm\?clan_id=\d+$/'
Below is my DomXPath code for the above URL.
// Generate our team's HTML file
$this->ourTeamHTML = new \DomDocument;
$this->ourTeamHTML->loadHTMLFile($this->ourTeamURL);
Most of the time the web app works just fine. However, there are cases where even if they type in a URL that is valid by the regex, there are indeed URLs that don't exist (that still get past the regex), and an error like the following gets returned:
PHP Warning: DOMDocument::loadHTMLFile(http://www.ugcleague.com/team_page.cfm?clan_id=8831111118): failed to open stream: HTTP request failed! HTTP/1.1 500 Internal Server Error
in /Users/loop/Code/laravel/app/Services/Verify/ScrapeUGC.php on line 49
Warning: DOMDocument::loadHTMLFile(http://www.ugcleague.com/team_page.cfm?clan_id=8831111118): failed to open stream: HTTP request failed! HTTP/1.1 500 Internal Server Error
in /Users/loop/Code/laravel/app/Services/Verify/ScrapeUGC.php on line 49
Laravel would be linked to the Whoops, looks like something went wrong page.
This confuses the user. I would like to instead return a more descriptive error, that would say, perhaps, "this team does not exist."
You could handle this in Laravel app/Exceptions/Hnadler.php
NB: I have looked in the option of using DOMException handler which is available in PHP, however the error message you are getting in not really and exception by an I/O Warning.
This what PHP native DomException looks like:
/**
* DOM operations raise exceptions under particular circumstances, i.e.,
* when an operation is impossible to perform for logical reasons.
* #link http://php.net/manual/en/class.domexception.php
*/
class DOMException extends Exception {
/**
* #var
* (PHP 5)<br/>
* An integer indicating the type of error generated
* #link http://php.net/manual/en/class.domexception.php#domexception.props.code
*/
public $code;
}
So I came up with this because we can not use DomException to dictate this error since its not an Exception, you can add this in your app/Exceptions/Handler.php
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that should not be reported.
*
* #var array
*/
protected $dontReport = [
\Symfony\Component\HttpKernel\Exception\HttpException::class,
];
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* #param \Exception $e
* #return void
*/
public function report(Exception $e)
{
return parent::report($e);
}
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
$message = $e->getMessage();
if (str_contains($message, 'DOMDocument::loadHTMLFile(): I/O warning')) {
return redirect($request->fullUrl())->with('error', "This team does not exist");
}
//We could also handle DomException like so, but not Dom warning
if ($e instanceof \DomException){
return redirect($request->fullUrl())->with('error', "Your friendly message here");
}
}
}
NB: Be careful when modifying Handler.php, you might start having blank pages instead of Laravel whoops or errors if you don't know what you are doing. You can make a backup somewhere if you are unsure.
There is a php function you will want to use to disable libxml errors.
libxml_use_internal_errors(true);
Now when you are done processing your script, you will need to get any errors generated by using...
$errors = libxml_get_errors();
That would return an array so you can now check it with...
if(count($errors) > 0) {
echo 'This team does not exist';
} else {
echo 'Successful';
}
// Be sure to clear errors
libxml_clear_errors();
You will most likely need to loop through that array and process each error to be sure it's the specific error you are looking for. When working with DOMDocument, you are bound to get a bunch of notices/warnings every time it finds something it doesn't like about invalid HTML/XML.
Related
I'm developing a (CakePHP) web application, thereby creating own exception classes.
At the moment I try to create a locked exception, that shall return HTTP status code 423 (Locked):
<?php
namespace App\Exceptions;
use Cake\Network\Exception\HttpException;
class MeasuringPointLockedException extends HttpException{
/**
* Constructor
*
* #param string $message If no message is given 'Locked' will be the message
*/
public function __construct($message = 'Locked'){
parent::__construct($message, 422);
}
}
?>
Unfortunately, at some point my code 423 is consumed and replaced by 500 (internal server error). I noticed, that only some codes are replaced, others (like 400, 404, 422) are passed through.
Note: HttpException is an extension of PHP builtin exception.
In between, I noticed, that response code 423 is intended for WebDAV services, but:
Is there any documentation, which codes are passed through? How could I achieve a status = 423 upon throwing (and not catching) such an exception?
You can seen a bunch of http exceptions in here:
[https://book.cakephp.org/3/en/development/errors.html1
Here is also a good example of how to make this implementation:
use Cake\Core\Exception\Exception;
class MissingWidgetException extends Exception
{
// Set your message here
protected $_messageTemplate = 'Your message';
// Set your status code here
protected $_defaultCode = 404;
}
throw new MissingWidgetException(['widget' => 'Pointy']);
In an Extbase extension, the need may arise to inform the user about an error or exception.
In my case, I have to parse some data from a potentially bad source. So the extension has to validate this data. And if the data is invalid, it needs to throw an exception that then can be handled by TYPO3.
However, I can only find information about how the exception and error handlers works, but no information on how to correctly throw an exception from inside an extension.
So what is the intended way to throw an exception from inside an Extbase extension?
Expected result
If I produce a syntax error, TYPO3 displays a message similar to this:
(Taken from the core API reference.)
That is what I would expect a correctly thrown error or exception to look like.
What I tried
Edit: I tried throwing an error like this:
throw new \Exception('Invalid data');
However, all the frontend displays is
Oops, an error occurred! Code: 20160721101726b5339896
Another possible way to produce an error:
$GLOBALS['TSFE']->pageNotFoundAndExit('Invalid data');
However, this shows a Page Not Found error instead of the expected exception.
You implicitly asked 2 questions:
How do I correctly throw an exception in my code?
I think, that was correct, what you did: Just use PHP \Exception or a suitable exception inherited from \Exception:
throw new \UnexpectedValueException('Invalid data');
Once an exception has been thrown, how do I see more information?
This has already been answered quite well: https://stackoverflow.com/a/34067853/2444812
On a development system:
set configuration preset "debug"
Add TypoScript on start page: config.contentObjectExceptionHandler = 0
see Error and ExceptionHandling Example
On a production system:
You usually do not want to see full stack traces in the frontend. That is why config.contentObjectExceptionHandler is usually set to default, which only shows Oops, an error occurred! Code: 20160721101726b5339896 on the rendered page. Using this code, you can look in the logs (if things are logged and what is logged where always depends on the configuration of the logging system):
sys_log : see "Log" in the backend
logfile: var/logs/*.log (see Logging with TYPO3). May be typo3temp/logs on older versions and typo3temp/var/logs/ on non-Composer systems.
namespace VendorName\ExtensionName\Controller;
abstract class ActionController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
/**
* #var string
*/
protected $entityNotFoundMessage = 'The requested entity could not be found.';
/**
* #var string
*/
protected $unknownErrorMessage = 'An unknown error occurred. The wild monkey horde in our basement will try to fix this as soon as possible.';
/**
* #param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request
* #param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response
* #return void
* #throws \Exception
* #override \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
*/
public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response) {
try {
parent::processRequest($request, $response);
}
catch(\Exception $exception) {
// If the property mapper did throw a \TYPO3\CMS\Extbase\Property\Exception, because it was unable to find the requested entity, call the page-not-found handler.
$previousException = $exception->getPrevious();
if (($exception instanceof \TYPO3\CMS\Extbase\Property\Exception) && (($previousException instanceof \TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException) || ($previousException instanceof \TYPO3\CMS\Extbase\Property\Exception\InvalidSourceException))) {
$GLOBALS['TSFE']->pageNotFoundAndExit($this->entityNotFoundMessage);
}
throw $exception;
}
}
/**
* #return void
* #override \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
*/
protected function callActionMethod() {
try {
parent::callActionMethod();
}
catch(\Exception $exception) {
// This enables you to trigger the call of TYPO3s page-not-found handler by throwing \TYPO3\CMS\Core\Error\Http\PageNotFoundException
if ($exception instanceof \TYPO3\CMS\Core\Error\Http\PageNotFoundException) {
$GLOBALS['TSFE']->pageNotFoundAndExit($this->entityNotFoundMessage);
}
// $GLOBALS['TSFE']->pageNotFoundAndExit has not been called, so the exception is of unknown type.
\VendorName\ExtensionName\Logger\ExceptionLogger::log($exception, $this->request->getControllerExtensionKey(), \VendorName\ExtensionName\Logger\ExceptionLogger::SEVERITY_FATAL_ERROR);
// If the plugin is configured to do so, we call the page-unavailable handler.
if (isset($this->settings['usePageUnavailableHandler']) && $this->settings['usePageUnavailableHandler']) {
$GLOBALS['TSFE']->pageUnavailableAndExit($this->unknownErrorMessage, 'HTTP/1.1 500 Internal Server Error');
}
// Else we append the error message to the response. This causes the error message to be displayed inside the normal page layout. WARNING: the plugins output may gets cached.
if ($this->response instanceof \TYPO3\CMS\Extbase\Mvc\Web\Response) {
$this->response->setStatus(500);
}
$this->response->appendContent($this->unknownErrorMessage);
}
}
}
Here is an article that explains that.
However like most of articles about TYPO3 Programming is in German ;-)
http://nerdcenter.de/extbase-fehlerbehandlung/
So I want to cover all posible and unexpected errors (like 401, 404, 500) with just one view. I want the same view to show up on all possible errors. I came up with a solution - to copy/paste the same code and just name the views with different error codes. But that seems stiff and wrong. Is there a better way of achieving this?
In the file app/Exceptions/Handler.php you can change what happens when an exception is thrown. In particular there's a render method in there that you can use to catch all the exceptions in an application.
public function render($request, Exception $e)
{
// Handle your error here. Perhaps you can show a view
// when a condition is met. Anything that isn't caught
// here will be handled by Laravel with the
// parent::render call below
return parent::render($request, $e);
}
The parent::render($request, $e) is where Laravel would normally show it's exception/oops page. So by overriding this method you can catch all application errors, including 404, 401 etc.
A cleaner way to achieve this effect is by modifying Laravel's exception handler.
Modify App\Exceptions\Handler to catch every error and return your shared custom error page.
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
if ($e instanceof NotFoundHttpException) {
return response()->view('errors.custom', [], 404);
}
return parent::render($request, $e);
}
Some customization may be required to fully meet exactly what & how you want data passed to your shared custom view.
Pass the error code to your view on the handler, and display the code on your page, use a switch to handle all the messages depending on the error code.
You can create one unique view (default to 404 error), use try catch in your code to capture other errors and call to this view with parameters so you can change the 404 default error to other error.
I am on Laravel 5.1.
Whenever there is an exception, Laravel usually shows an exception on the page. I'd like to add a custom 404 page with a human readable error report and email an admin with the entire error dump. I would also like to have the email nicely formatted in HTML (like on the error page).
How can I do this?
So, the first part for displaying fancy 404 comes with adding 404.blade.php to resources/views/errors.
Regarding emailing the error_dump, this requires a way to catch the error events. Here's how to handle that. In the file App\Exceptions\Handler.php, add the email code to your report() method
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* #param \Exception $e
* #return void
*/
public function report(Exception $e)
{
if ($e instanceof NotFoundHttpException) {
//Send email here
}
return parent::report($e);
}
We are working on setting up a SOAP server and I am specifically working on creating automated tests for it in Behat. The problem I am running into is that I am trying to test authentication of connections to the SOAP server and specifically trying to write tests that make sure that it fails to connect when no username and password are provided.
However, every time I run the test without HTTP authentication added in (and WSDL caching disabled intentionally) I get a PHP warning that throws up an error trace all over the console screen.
It is a PHP Warning that reads:
PHP Warning: SoapClient::SoapClient(http://cti.local/api/v1/companies/d1/soap?wsdl): failed to open stream: HTTP request failed! HTTP/1.1 401 Unauthorized
I would like to be able to suppress that error and just make sure it returns NULL or negative when declaring the new client. To do this I have tried adding the "#" sign right before the "new" keyword and wrapping the call in a try/catch statement that doesn't throw exceptions. But that doesn't work. I also tried adding the "#" sign at the beginning of the line to no avail.
That was using PHP's built-in SOAP client class. It seemed like an awesome way to go until I ran into this issue. So then, after exhausting all the options I would find, I then tried setting up the Zend SOAP Client and using that. Only to discover that it seems to extend PHP's built-in SoapClient class. So that is not working either.
Unfortunately we are using HTTP auth for all calls to the API, including to the WSDL. Currently I am running a checked out copy of the API locally (since we don't have it setup on an environment yet). I know that when I put in the proper authentication it works fine. But I want to make sure that my automated Behat tests can successfully test and return a test passed message for when I verify that the connection authentication fails.
Sample of connection call:
/**
* SOAP connection initiation.
*
* #param string $soapURL
* #param string $options (optional, no implemented yet)
*
* #Given /^I connect to a soap service at "([^"]*)"$/
*/
public function iConnectToASoapServiceAt($soapURL) {
$this->soapURL = $soapURL;
try {
$this->client = #new Zend\Soap\Client($soapURL, $this->options);
$this->soapFunctions = $this->client->getFunctions();
}
catch (\Exception $e) {
#For testing when it fails we cannot actually throw an error here.
#throw new Exception("Error connecting to SOAP server.");
}
}
/**
* #Given /^soap returns an error$/
*/
public function soapReturnsAnError() {
if ($this->client !== NULL && $this->soapFunctions !== NULL) {
throw new Exception("SOAP connection did not fail as expected.");
}
}
Does anyone have any ideas? I need to be able to test both a successful connection and an unsuccessful one in an automated way that won't error out and kill the PHP call.
# only suppresses errors of a single function call.
Try changing the error reporting level before the failed authentication code is executed, and than bring it back afterwards:
$level = error_reporting(E_ERROR | E_PARSE);
// your code with failed authentication here
error_reporting($level);