I'm having a debate with a colleague over the correct usage (if any) of trigger_error
in the context of magic methods. Firstly, I think that trigger_error
should be avoided except for this one case.
Say we have a class with one method foo()
class A {
public function foo() {
echo 'bar';
}
}
Now say we want to provide the exact same interface but use a magic method to catch all method calls
class B {
public function __call($method, $args) {
switch (strtolower($method)) {
case 'foo':
echo 'bar';
break;
}
}
}
$a = new A;
$b = new B;
$a->foo(); //bar
$b->foo(); //bar
Both classes are the same in the way they respond to foo()
but differ when calling an invalid method.
$a->doesntexist(); //Error
$b->doesntexist(); //Does nothing
My argument is that magic methods should call trigger_error
when a unknown method is caught
class B {
public function __call($method, $args) {
switch (strtolower($method)) {
case 'foo':
echo 'bar';
break;
default:
$class = get_class($this);
$trace = debug_backtrace();
$file = $trace[0]['file'];
$line = $trace[0]['line'];
trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);
break;
}
}
}
So that both classes behave (almost) identically
$a->badMethod(); //Call to undefined method A::badMethod() in [..] on line 28
$b->badMethod(); //Call to undefined method B::badMethod() in [..] on line 32
My use-case is an ActiveRecord implementation. I use __call
to catch and handle methods that essentially do the same thing but have modifiers such as Distinct
or Ignore
, e.g.
selectDistinct()
selectDistinctColumn($column, ..)
selectAll()
selectOne()
select()
or
insert()
replace()
insertIgnore()
replaceIgnore()
Methods like where()
, from()
, groupBy()
, etc. are hard-coded.
My argument is highlighted when you accidentally call insret()
. If my active record implementation hardcoded all of the methods then it would be an error.
As with any good abstraction, the user should be unaware of the implementation details and rely solely on the interface. Why should the implementation that uses magic methods behave any differently? Both should be an error.
Best Answer
Take two implementations of the same ActiveRecord interface (
select()
,where()
, etc.)If you call an invalid method on the first class, e.g.
ActiveRecord1::insret()
, the default PHP behaviour is to trigger an error. An invalid function/method call is not a condition that a reasonable application would want to catch and handle. Sure, you can catch it in languages like Ruby or Python where an error is an exception, but others (JavaScript / any static language / more?) will fail.Back to PHP - if both classes implement the same interface, why shouldn't they exhibit the same behaviour?
If
__call
or__callStatic
detect an invalid method, they should trigger an error to mimic the default behaviour of the languageI'm not arguing whether errors should be used over exceptions (they 100% shouldn't), however I believe that PHP's magic methods are an exception - pun intended :) - to this rule in the context of the language