I'm having a little trouble organizing my error messages for two interacting classes. One object has states that are 'error-ish', where something went wrong, or happened unexpectedly, but the situation is still salvageable. I don't want to use exceptions, 1. because they only have a single string for the message, and 2. because I want to access the object after the error. At the very least, I want to use some of its get() methods to construct a useful error message after the exception!
Ultimately I have two messages I want to communicate: one to myself as the coder, that something went wrong. This string would have the technical details of the file, line, function/method, arguments, and results. Obviously, I don't want to show this to the user, so there is another string of an error message that I want to show the user ("That email address was not found" kind of thing ).
So the thought occurs to me to build an error message array, which could use the error code from exceptions, or a status code, as keys for various messages. ( Though if I do this, where do I store the array of messages? ) Another option might be to make an error status object.
Is there anything like "error patterns", similar to design patterns?
Exceptions really are your best option, they do everything you asked. Even multiple messages are possible, since exceptions are just classes that you can extend. You could just pass the object that causes the exception to said exception.
<?php
class ExampleException extends Exception {
private $secondMessage;
private $objectThatCausedIt;
public function __construct( $secondMessage, $objectThatCausedIt ) {
parent::__construct(
"Descriptive message for developer",
// error code for this type of error
1000 );
$this->secondMessage = $secondMessage;
$this->objectThatCausedIt = $objectThatCausedIt;
}
public function getSecondMessage() {
return $this->secondMessage;
}
public function getObjectThatCausedIt() {
return $this->objectThatCausedIt;
}
}
class Example {
public function causeException() {
throw new ExampleException( "Second Message", $this );
}
}
Now you just use the class and wrap the call that might throw the exception in a try-catch block.
<?php
$example = new Example();
try {
$example->causeException();
}
catch ( ExampleException $e ) {
// kind of pointless here, just an illustration
// get object that caused it and do something with it.
dump_and_log_function( $e->getObjectThatCausedIt() );
// or just use $example, which is still "alive"
// and references the same instance
dump_and_log_function( $example );
}
Extending Exception has the benefit that you also get a stack backtrace. The backtrace contains information like in what file, line and function the exception occured. You can also access the error code, the message and more. I suggest you read the PHP documentation on Exceptions (http://php.net/manual/en/class.exception.php) for more information.
Regarding the error messages and logging, I usually use a singleton class for that. A singleton class has only one instance of itself (in case you didn't know.) Here is an extremely simple example:
<?php
class Log {
private $logFile;
private static $instance;
/* Log instances may only be constructed in Log::getInstance */
private function __construct() {
$this->logFile = fopen( "path/to/log/file", "a" );
}
public logMessage( $message ) {
fwrite( $this->logFile, $message );
}
public static getInstance() {
if ( !self::$instance ) self::$instance = new self();
return self::$instance;
}
}
Now going back to the exception handling throw-catch block, you can change it to something like this:
<?php
$example = new Example();
try {
$example->causeException();
}
catch ( ExampleException $e ) {
// log developer message and backtrace
Log::getInstance()->logMessage( $e->getMessage() );
Log::getInstance()->logMessage( $e->getTraceAsString() );
// or more compact by casting to string
Log::getInstance()->logMessage( (string)$e );
// and now print error for users
echo "<p>An Error has occured: {$e->getSecondMessage()}</p>";
}
Instead of echoing the error message right away, you could make the Log class have a property with an array of messages. Then you could list them later in a view script all at once. You could also make the logMessage method store the messages in the session so they could be displayed after a refresh (just don't forget to clear the messages from the session or they will be displayed over and over again ;-).
I don't want to use exceptions, 1.
because they only have a single string
for the message, and 2. because I want
to access the object after the error.
At the very least, I want to use some
of its get() methods to construct a
useful error message after the
exception!
You can create your own exception classes and you can extend PHP's exception classes, adding the support you need. However, possibly you can have two settings, developer and client, where client errors goto the display and developer errors don't instead going to a log file or something.
Which, translates into two custom exception types (though you could have many more, I'm saying two distinct base classes).
Related
I've been searching for an existing question that already asks this, but I wasn't able to find any questions that quite ask what I'm trying to figure out. The most similar question I could find was this: php 5.3 avoid try/catch duplication nested within foreach loop (code sandwich)
Okay so the place I work at has a web application with a PHP back end. We use an MVC type structure. I'm writing a controller that has multiple methods and in each of my methods I'm wrapping my code with identical try / catch code. In the catch, I pass the exception, a reference to the class, and a reference to the function to a method that builds an error message so that the error messages are formatted the same across the application. It looks something this:
class MyController {
public function methodA() {
try {
// code for methodA
} catch(Exception $e) {
$errorMessage = Tasks::buildErrorMessage($e, __CLASS__, __FUNCTION__);
throw new Exception($errorMessage);
}
}
public function methodB() {
try {
// code for methodB
} catch(Exception $e) {
$errorMessage = Tasks::buildErrorMessage($e, __CLASS__, __FUNCTION__);
throw new Exception($errorMessage);
}
}
public function methodC() {
try {
// code for methodC
} catch(Exception $e) {
$errorMessage = Tasks::buildErrorMessage($e, __CLASS__, __FUNCTION__);
throw new Exception($errorMessage);
}
}
}
So the buildErrorMessage function prevents each method from repeating the code that formats the error message, but there is something that really bothers me about have the same code spread through out every method in the class. I know that PHP doesn't support python-like decorator syntax, but just to demonstrate what I'm envisioning conceptually; I want the code to behave something more like this:
class MyController {
#DefaultErrorHandling()
public function methodA() {
// code for methodB
}
#DefaultErrorHandling()
public function methodB() {
// code for methodB
}
#DefaultErrorHandling()
public function methodC() {
// code for methodC
}
}
Where the #DefaultErrorHandling decorator would wrap each method in that standard try / catch. Is there a way I could achieve this behavior so I don't have to have all of these methods that have repeated code? Or am I thinking about error handling incorrectly?
Thanks to anyone who takes the time to answer this.
Have you looked at a writing a custom exception handler and using set_exception_handler?
What you are doing seems a bit like reinventing the wheel. Does the Exception not already have the info you are collecting in the trace? See: Exception::getTrace
Maybe buildErrorMessage does more? Anyway, I assume a custom exception handler is what you are after.
Not sure if there is a better way to solve this or not, but I created a logging class that formatted the log for me. Then just called this in my catch block.
To log the correct Class and Method, I the debug_backtrace() function. See this answer for more information.
Entry point that calls controller methods can wrap those calls with try / catch. That being said, if you are planning to use different type of error handlers on those methods then you can implement something in your base controller (or use trait) that keeps track of which handler should be invoked on each particular method. Something like
<?php
class MyController extends Controller
{
function __construct()
{
$this->setActionErrorHandler('function_name', 'handler');
}
}
Or just call it at the beginning of action method body. Keeping this type of configuration within class itself will help with readability. Not as neat as python example but better than somewhere in configuration files.
More generic error handlers can be implemented in php by using set_exception_handler mentioned by others.
I'm not really getting why there is such a requirement.
I develop a pretty big web application using laravel. Logging to a single file or daily files seems too messy for me.
My purpose is to create error logs which will be categorised and easy for retrieval to show on front-end for tech support. E.g. user entered wrong api-key, email, whatever.
P.S. Users don't always understand meaning of the error showed so they contact tech support.
Example for better understanding:
Suppose I have model relations: User->hasMany->Project and in some
project appears an exception I want it to be written and related to
the project. e.t.c. So I could do something like $some_project_model->logs()->get() and tech support got all logs related to
certain project.
What I have in my mind is separate table for logs which is related to certain Eloquent models. But to do so ExceptionHandler needs to know to which model an Exception is related to.
So how this can be implemented? Maybe I could assign custom ExceptionHandler to certain class?
Thank you for any suggestions.
So I've actually found a proper solution:
First of all it's needed to create Log table with polymorphic relation. Then to catch all the exceptions in certain context __call magic method can be used. __call method allows to wrap every object method call with custom code. Try-catch in this case.
Example:
class Loggable
{
public function __call($method, $arguments)
{
try {
return call_user_func_array([$this->certain_obj, $method], $arguments);
} catch (Exception $e) {
//creating new $log orm
$this->get_orm()->loggable()->save($log);
}
}
protected function do_something() {
//something is going on
throw new \Exception();
}
}
$loggable_obj = new Loggable();
$loggable_obj->do_something();
To make it work, you must make all loggable methods private or protected.
In case if you think that 'encapsulation' is not just another difficult word you can achieve the same result using proxy class.
Example:
class Proxy
{
private $some_obj;
public function __construct($some_obj)
{
$this->some_obj = $some_obj;
}
public function __call($method, $arguments)
{
try {
return call_user_func_array([$this->some_obj, $method], $arguments);
} catch (Exception $e) {
//creating new $log orm
$this->some_obj->get_orm()->loggable()->save($log);
}
}
}
$proxy = new Proxy($some_obj);
$proxy->whatever_method($foo, $bar);
So, in both cases I can get all logs related to some exact orm by calling ->logs()->get() on it.
Not a rocket science at all.
One approach might be to create your own custom exception (sub)class which you could add model and/or project information to at the point of throwing the exception. Then this information would be available in the ExceptionHandler.
You wouldn't have it for built in or third-party exception types though. For those you'd end up having to catch and rethrow where possible (and necessary), wrapping in your custom exception class, which probably isn't ideal.
I was getting an error about an exception thrown in a __toString() method in a class of mine, even though there was a try...catch() in there. I did some tracking down and it turns out an object within my instance was throwing an exception in its __toString(), which meant that it wasn't being caught by the containing class' catch!
I wrote some test code as a demonstration:
class test {
public function __toString() {
try {
$b = new b();
echo (string)$b;
} catch (exception $e) {
return (string)$e;
}
}
}
class b {
public function __toString() {
throw new Exception ("test");
}
}
$test = new test();
echo $test;
I had thought that exceptions always "bubbled up" through the code until they were caught or made it all the way out.
Is there any workaround for this? The instance within my class is from a library; I don't know if I can maintainably modify it to have a catch in its own __toString().
Per PHP's __toString() section in the magic methods docs:
Warning
You cannot throw an exception from within a __toString() method. Doing
so will result in a fatal error.
When executing the code snippet you gave you get a message that says the same thing:
PHP Fatal error: Method b::__toString() must not throw an exception
There's a feature request here to support this, which states that in current state of things it would be hard to implement.
Having said all that, the proper way to fix this is to submit a patch to your upstream library to not throw exceptions from that method.
If that's not possible, the workaround for you is instead of:
echo (string)$b;
write
echo $b->__toString();
In which case you can catch the exception as you expect.
Hi devs
I have a simple PHP OO project. I have an exception who appears 2 times.
Something like :
if (!$x) {
throw new Exception("Don't use this class here.");
}
I want to dev a class in order to edit this code like that :
if (!$x) {
throw new ClassUsageException();
}
How to dev the Excpetion class with default Exception message ?
Thanks
I'd advise creating new exception classes sparsly. It is no fun to check a multitude of exceptions left and right. And if you really feel the need, check what kinds of exceptions are already defined and where in that hierarchy your exception will fit and then extend that class, i.e. give the developers a chance to catch a (meaningful) range of exceptions without having to explicitly write one catch-block after the other.
I'm really not sure what you're trying to achieve here with Don't use this class here. but it could be an InvalidArgumentException (or something derived from that exception, if you really must). There are other mechanisms to prevent an instance of a certain class at a specific place though.
You can extend the Exception class
<?php
Class ClassUsageException extends Exception {
public function __construct($msg = "Don't use this class here.", $code = 0) {
parent::__construct($msg, $code); //Construct the parent thus Exception.
}
}
try {
throw new ClassUsageException();
} catch(ClassUsageException $e) {
echo $e->getMessage(); //Returns Don't use this class here.
}
I have exception class:
class MartbooksException extends Exception
{
public function __construct($msg)
{
parent::__construct($msg, 0, null);
echo $msg;
Kohana::$log->add(Log::ERROR, $msg);
}
}
Kohana::$log->add(Log::ERROR, $msg);
Is this all I should do to write logs in application/logs files?
Is this good solution?
To follow the Kohana style I would recommend that you use:
Class Martbooks_Exception Extends Kohana_Exception
as declaration and place the file with name exception.php in classes/martbooks. This follows the style of Kohana.
Extending Kohana_Exception instead of Exception allows you to use variable substitution along the lines of
throw new Martbooks_Exception ('this is a :v', array (':v' => 'variable', ));
As for the defining a __construct()-method, the echo $msg; part would not be my preferred way of solving error handling, any echoing should be done in the block that catches the exception. The same could be argued for in the case of calling Kohana::$log->add(), but if you want log every Martbooks_Exception, your solution is perfectly valid. In that case I would rewrite your code to:
Class Martbooks_Exception Extends Kohana_Exception
{
public function __construct($message, array $variables = NULL, $code = 0)
{
parent::__construct ($message, $variables, $code);
Kohana::$log->add (Log::ERROR, __ ($message, $variables));
}
}
with a definition of __construct() that conforms to Kohana_Exception's __construct().
The only objection that I would have against logging with Log::ERROR level in the constructor is that it assumes that every exception is an application level error, which might be true of some exception types, but it could also be used to signal other meanings. The exact meaning of an exception should be left to the exception handling block.
To add a log message all you need is a single line indeed; Kohana::$log->add($level, $message[, $values]) see the api
Besides that, I don't think this is a valid solution. You had better create your own exception handler, as you can see on this page of the userguide