I am writing a PHP query builder-style library that uses method chaining to add constraints to the query with a final call to get()
at the end to execute the request, similar to Laravel's query builder, but using a public JSON API via Guzzle for the data. For example:
// valid
Foo::queryType()->take(5)->skip(50)->get();
// invalid, missing query type
Foo::take->(5)->get();
Foo
is just a facade for accessing the library. queryType()
is a required initial method (can be one of many, e.g. queryTypeA()
, queryTypeB()
) that sets a protected class variable in Foo
. I want to throw an Exception
if get()
is called without that variable being set first. And I want to be precise in what gets thrown.
PHP's documentation lists a few options:
BadMethodCallException
:
Exception thrown if a callback refers to an undefined method or if some arguments are missing.
Probably doesn't refer to missing prerequisites, and I get the feeling this is intended more for dynamic calls where the method doesn't exist.
BadFunctionCallException
Exception thrown if a callback refers to an undefined function or if some arguments are missing.
Basically same as before but for general functions, and BadMethodCallException
is actually a child class of this.
InvalidArgumentException
Exception thrown if an argument is not of the expected type.
While not technically an argument, an unset protected class variable required to continue execution fits the general idea, so this one makes the most sense so far.
UnexpectedValueException
Exception thrown if a value does not match with a set of values. Typically this happens when a function calls another function and expects the return value to be of a certain type or value not including arithmetic or buffer related errors.
Not really an option; while the name implies that I might want the opposite of this - I expect a value to be set and it's not present - the description seems less of a match than others, and there's no user contributed notes to help refine.
RuntimeException
Exception thrown if an error which can only be found on runtime occurs.
My fallback. Maybe I'm overthinking this? Which PHP exception makes the most sense?
Based on the design of my library, I ended up going with BadMethodCallException
. Since the outer class was simply a facade, I used the __call
magic method to pass through any undefined method calls to the internal class variable.
After passing through the call, the internal class variable is set to null
so as to literally undefine the method call. However so as to distinguish between undefined methods and out of order chaining, I explicitly check the class variable for null and provide the specific error.
protected $request = null;
/**
* Pass through non-explicitly defined method calls to the internal request class
* @param string $function
* @param array $args
* @return mixed
* @throws \BadMethodCallException
*/
public function __call($function, $args)
{
if ($this->request === null) {
throw new \BadMethodCallException('Must specify request type before chaining constraints.');
}
if (method_exists($this->request, $function)) {
$result = $this->request->$function(sizeof($args) ? $args[0] : null);
$this->request = null;
return $result;
}
throw new \BadMethodCallException('Method \'' . $function . '()\' does not exist.');
}