views:

79

answers:

2

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?

A: 

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).

Mr-sk
But if I use an exception, doesn't that make the object unavailable after the exception is thrown? I'm thinking I might still want to use it after that point, but perhaps I can talk myself out of it.
No, you can "Catch" the exception, log it, whatever, decide if you want to proceed or exit. You have all that flexibility.
Mr-sk
I've been doing some testing, and it seems that exceptions do destroy objects: <? class exceptor { public function __construct() { throw new Exception("I except!"); } public function test() { return "test"; } } try { $objExceptor = } catch (Exception $e) { echo 'Caught exception: ', $e->getMessage(), "<BR>\n"; } echo $objExceptor->test() . "<BR>\n"; ?> And got this result: Caught exception: I except!<BR> Fatal error: Call to a member function test() on a non-object on line 20.
+2  A: 

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 ;-).

fresch
What a great first answer. Welcome to SO!
Pekka