DEV Community

Cover image for Evolution of PHP Object Instantiation with detailed error diagnostics
Lee Noble
Lee Noble

Posted on

Evolution of PHP Object Instantiation with detailed error diagnostics

I've recently devised a new method (to me) of object instantiation that I think I'm going to use a lot in my future developments. I strongly suspect it's an established design pattern that probably has a name, but I didn't get a CS qualification and I made it up myself.

I say made it up, but mathematicians are fond of stating how they discover new maths, that it already exists in nature and the structure of the universe, and in a similar way I suspect this pattern is a natural evolution that has occurred before, multiple times.

Private constructor with optional return object

Most basic object classes I create have private constructors and offer one or more public instantiation methods:

class token {
    public static function create(string $prop) : ?token {
        $token = null;
        if(is_valid($prop)){
            /* I use static routinely in case I want to extend 
            the class later */
            $token = new static();
            $token->prop = $prop;
        }
        return $token;
    }

    protected function __construct(){

    }
}
Enter fullscreen mode Exit fullscreen mode

Cool, now when we call token::create('Foo') we may get a token object or we may get null. It's a start.

But what if something goes wrong? And we want to know what? NULL doesn't tell us a great deal.

Something I've done in the past is to pass in a generic error handler to the constructor.

class token {
    public static function create(string $prop, errorHandler $errorHandler) : ?token {
        $token = null;
        if(is_valid($prop)){
            /* I use static routinely in case I want to extend the 
            class later */
            $token = new static();
            $token->prop = $prop;
        } else {
            $errorHandler->addError(errorHandler::INVALID_PROPERTY, $prop.' is not a valid string for some reason.');
        }
        return $token;
    }
    ...
}
Enter fullscreen mode Exit fullscreen mode

That's a bit better, we can give our errorHandler class any number of error constants and compile an array of error messages to feed back to the user.

The problem with this is two-fold. If the ::create method has more than a few parameters, devs have to pass defaults for all the intermediate properties before they can append the errorHandler object, unless we put it up front I suppose; and secondly we have to create the errorHandler object prior to creating the object we actually want, every single time. It adds a boilerplate overhead to every object instantiation that I'd rather avoid.

Introducing the requestResult object

The ::create method comes with the implication that it's kind of going to work...that it'll return an object of the correct type. Adding the ? on the typehint because things might go awry is where the problems arise.

What if we could be certain we're going to get back on object of a particular type? We could "request" an object and process the return result.

class token {
    public static function request(string $prop) : tokenRequestResult {
        $result = new tokenRequestResult();
        if(!is_valid($prop)){
            $result->addError(tokenRequestResult::INVALID_PROPERTY_VALUE);
        }
        if(!$result->getErrorMask()){
            $token = new static();
            $token->prop = $prop;
            $result->setObject($token);
        }
        return $result;
    }
    ....
}
Enter fullscreen mode Exit fullscreen mode

I've defined an interface and abstract class to handle most of the functionality of the requestResult object.

interface requestResult {
    public function getErrorMask() : int;
    public function getErrorMessages() : array;
    // TODO: When PHP >= 7.4 add typehints as per commented lines below
    public function getObject();
    public function setObject($object);
//  public function getObject() : Object;
//  public function setObject(Object $object);
    public function addError(int $maskBit, ?string $message);
}

abstract class baseRequestResult implements requestResult {
    protected $errorMask = 0;
    protected $errorMessages = [];
    protected $object;

    public function getErrorMask(): int {
        return $this->errorMask;
    }

    public function getErrorMessages(): array {
        return $this->errorMessages;
    }

    public function buildErrorMessageList(dataWriter $writer) : string {
        $response = '';
        if(count($this->errorMessages)){
            $response .= $writer->beforeResults();

            foreach($this->errorMessages as $errorMessage){
                $response .= $writer->item((object)['message'=>$errorMessage]);
            }

            $response .= $writer->afterResults();
        } else {
            $response .= $writer->noResults();
        }
        return $response;
    }

    // TODO: When PHP >= 7.4 add typehints as per interface
    abstract public function getObject();
    abstract public function setObject($object);

    public function addError(int $maskBit, ?string $message) {
        $this->errorMask |= $maskBit;
        if($message && !in_array($message, $this->errorMessages)){
            $this->errorMessages[] = $message;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

And the tokenRequest class is really very small and easily replicable for different object types:

class tokenRequestResult extends baseRequestResult {
    const INVALID_TYPE = 1<<0;
    const MATCHING_TOKEN_IN_USE = 1<<1;

    // TODO: When PHP >= 7.4 use typehint for \token here instead of instanceof condition
    public function setObject($token) {
        if($token instanceof \token){
            $this->object = $token;
        }
    }

    public function getObject() : \token {
        return $this->object;
    }
}
Enter fullscreen mode Exit fullscreen mode

Externally, the API is then used like this:

$tokenResult = token::request('Foo');
if($tokenResult->getErrorMask()){
    /*
        There are errors that need handling. We can flesh out the 
        tokenRequestResult object to handle the various error mask values 
        and/or error messages, pass in a writer class to output the 
        errors to screen, whatever.
    */

} else {
    /*
        But if reported errors are 0 we can be confident that `->getObject()` 
        will get us our native `token` object and act accordingly
    */
    $token = $tokenResult->getObject();
    $token->callPublicMethodOfTokenObject();
}
Enter fullscreen mode Exit fullscreen mode

I think this technique is going to prove pretty valuable in my work. Like I said, it's probably a documented pattern but given I don't know what it might be called I'm not sure how to begin looking it up.

If you happen to know its name, I'd be thankful, but if it's new to you I hope you find it useful.

Top comments (0)