TransWikia.com

When to use Exceptions vs Error Objects vs just plain false/null

WordPress Development Asked by Doug Wollison on January 24, 2021

I’m in the process of writing a plugin and I’m trying to gauge when to use different approaches of handling errors.

There are three methods I’m considering:

  • Throwing an Exception (custom class)
  • Returning an Error Object (extension of WP_Error)
  • Just return null/false

Some situations that I’m considering

  • Trying to get/set an option stored in the Registry that doesn’t exist
  • Passing an invalid value to a method (which should be rare)
  • Calling a method that the class’ overloader cannot resolve to

Suggestions? Since writing a WordPress plugin has some special considerations, I’m not sure whether it would be worth asking this on a general PHP board.

2 Answers

The answer by @gmazzap is quite exhaustive.

Just to add a tiny bit to it: Consider an application with a registration form. Lets say you have a function which you use to validate the username submitted by the users. Now the supplied username may fail validation for a number of reasons, so returning just false isn't going to help the user much in letting them know what went wrong. The better choice, in this case, would be to use WP_Error and to add error codes for each failed validation.

Compare this:

function validate_username( $username ) {
    
    //No empty usernames
    if ( empty( $username ) ) {
        return false;
    }
    
    // Check for spaces
    if ( strpos( $username, ' ' ) !== false ) {
        return false;
    }

    // Check for length
    if ( strlen( $username ) < 8 ) {
        return false;
    }

    // Check for length
    if ( ( strtolower( $username ) == $username ) || strtoupper( $username ) == $username ) ) {
        return false;
    }

    // Everything is fine
    return true;
}

with this:

function validate_username( $username ) {
    
    //No empty usernames
    if ( empty( $username ) ) {
        $error->add( 'empty', 'Username can not be blank' );
    }
    
    // Check for spaces
    if ( strpos( $username, ' ' ) !== false ) {
        $error->add( 'spaces', 'Username can not contain spaces ' );
    }

    // Check for length
    if ( strlen( $username ) < 8 ) {
        $error->add( 'short', 'Username should be at least 8 characters long ' );
    }

    // Check for length
    if ( ( strtolower( $username ) == $username ) || strtoupper( $username ) == $username ) ) {
        $error->add( 'case', 'Username should contain a mix of uppercase and lowercase characters ' );
    }

    // Send the result
    if ( empty( $error->get_error_codes() ) ) {
        return $error;
    }

    // Everything is fine
    return true;
}

The first version gives the user absolutely no clue about what was wrong with the username they chose. The second version adds description to errors, helping the user a lot.

Hope this helps a bit.

Answered by shariqkhan on January 24, 2021

I think it's impossible to give a definitive answer here, because choices like this are personal preference.

Consider that what follows is my approach, and I have no presumption it is the right one.

What I can say for sure is that you should avoid your third option:

Just return null/false

This is bad under different aspect:

  • return type consinstency
  • makes functions harder to unit test
  • force conditional check on return type (if (! is_null($thing))...) making code harder to read

I, more than often, use OOP to code plugins, and my object methods often throw exception when something goes wrong.

Doing that, I:

  • accomplish return type consinstency
  • make the code simple to unit test
  • don't need conditional check on the returned type

However, throwing exceptions in a WordPress plugin, means that nothing will catch them, ending up in a fatal error that is absolutely not desirable, expecially in production.

To avoid this issue, I normally have a "main routine" located in main plugin file, that I wrap in a try / catch block. This gives me the chance to catch the exception in production and prevent the fatal error.

A rough example of a class:

# myplugin/src/Foo.php

namespace MyPlugin;

class Foo {

  /**
   * @return bool
   */
  public function doSomething() {
     if ( ! get_option('my_plugin_everything_ok') ) {
        throw new SomethingWentWrongException('Something went wrong.');
     }

     // stuff here...

     return true;
  }
}

and using it from main plugin file:

# myplugin/main-plugin-file.php

namespace MyPlugin;

function initialize() {

   try {

       $foo = new Foo();
       $foo->doSomething();      

   } catch(SomethingWentWrongException $e) {

       // on debug is better to notice when bad things happen
       if (defined('WP_DEBUG') && WP_DEBUG) {
          throw $e;
       }

       // on production just fire an action, making exception accessible e.g. for logging
       do_action('my_plugin_error_shit_happened', $e);
   }
}

add_action('wp_loaded', 'MyPlugin\initialize');

Of course, in real world you may throw and catch different kinds of exception and behave differently according to the exception, but this should give you a direction.

Another option I often use (and you don't mentioned) is to return objects that contain a flag to verify if no error happen, but keeping the return type consistency.

This is a rough example of an object like that:

namespace MyPlugin;

class Options {

   private $options = [];
   private $ok = false;

   public function __construct($key)
   {
      $options = is_string($key) ? get_option($key) : false;
      if (is_array($options) && $options) {
         $this->options = $options;
         $this->ok = true;
      }
   }

   public function isOk()
   {
     return $this->ok;
   }
}

Now, from any place in your plugin, you can do:

/**
 * @return MyPluginOptions
 */
function my_plugin_get_options() {
  return new MyPluginOptions('my_plugin_options');
}

$options = my_plugin_get_options();
if ($options->isOk()) {
  // do stuff
}

Note how my_plugin_get_options() above always returns an instance of Options class, in this way you can always pass the return value around, and even inject it to other objects that use type-hint with now worries that the type is different.

If the function had returned null / false in case of error, before passing it around you had been forced to check if returned value is valid.

At same time, you have a clearly way to understand is something is wrong with the option instance.

This is a good solution in case the error is something that can be easily recovered, using defaults or whatever fits.

Answered by gmazzap on January 24, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP