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(){
}
}
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;
}
...
}
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;
}
....
}
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;
}
}
}
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;
}
}
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();
}
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)