PHP - __call all the time
No, I dont think so. What you could do though is write a proxy:
class MayAccessProxy { private $_obj; public function __construct($obj) { $this->_obj = $obj; } public function __call($methodName, $args) { if($mayAccess) call_user_func_array(array($this->_obj, $methodName), $args); }}
This means you have to instantiate a proxy for every object you want to check:
$obj = new MayAccessProxy(new Object());$obj->someMethod();
Ofcourse you'd also want the proxy to behave exactly like the object itself. So you also have to define the other magic methods.
To make it a bit easier for the developers you could do something like this:
class Object { /** * Not directly instanciable. */ private __construct() {} /** * @return self */ public static function createInstance() { $obj = new MayAccessProxy(new self()); return $obj; }}$obj = Object::createInstance();
So what if you made all your methods protected or private? (I know this is old and "answered" question)
The __call magic method intercepts all non-existing and non-public methods so having all your methods not public will allow you to intercepts all of them.
public function __call( $func, $args ){ if ( !method_exists( $this, $func ) ) throw new Error("This method does not exist in this class."); Handle::eachMethodAction(); // action which will be fired each time a method will be called return $this->$func( ...$args );}
Thanks to that you will not need to do anything to your code (expect adding __call and doing quick replace all
) and if your classes have common parent then you can just add it to parent and not care anymore.
BUT
This solution creates two major problems:
- The protected/private methods automatically will be available to public
- The errors will be pointing to __call not the proper file
What can we do?
Custom private/protected
You can add a list of all protected/private methods and check before the call if the method can be return to public:
public function __call( $func, $args ){ $private = [ "PrivateMethod" => null ]; if ( !method_exists( $this, $func ) ) throw new Error("This method does not exist in this class."); if ( isset( $private[$func] ) ) throw new Error("This method is private and cannot be called"); Handle::eachMethodAction(); // action which will be fired each time a method will be called return $this->$func( ...$args );}
For many this might be deal breaker, but I personally use this approach only in classes with only public methods (which I set to protected). So if you can, you might separate methods into publicClass
and privateClass
and eliminate this problem.
Custom Errors and Stack
For better errors I have created this method:
/** * Get parent function/method details * * @param int counter [OPT] The counter allows to move further back or forth in search of methods detalis * * @return array trace It contains those elements : * - function - name of the function * - file - in which file exception happend * - line - on which line * - class - in which class * - type - how it was called * - args - arguments passed to function/method */protected function getParentMethod( int $counter = 0 ) { $excep = new \Exception(); $trace = $excep->getTrace(); $offset = 1; if ( sizeof( $trace ) < 2 ) $offset = sizeof( $trace ) - 1; return $trace[$offset - $counter];}
Which will return details about the previous method/function which called protected method.
public function __call( $func, $args ){ $private = [ "PrivateMethod" => null ]; if ( !method_exists( $this, $func ) ) { $details = (object) $this->getParentMethod(); throw new Error("Method $func does not exist on line " . $details->line . ", file: " . $details->file . " invoked by " . get_class($this) . $details->type . $func . " () "); } if ( isset($private[$func]) ) { $details = (object) $this->getParentMethod(); throw new Error("Method $func is private and cannot be called on line " . $details->line . ", file: " . $details->file . " invoked by " . get_class($this) . $details->type . $func . " () "); } return $this->$func( ...$args );}
This is not much of a problem but might lead to some confusion while debugging.
Conclusion
This solution allows you to have control over any call of private/protected methods FROM OUTSIDE OF CLASS. Any this->Method
will omit __call
and will normally call private/protected method.
class Test { public function __call( $func, $args ) { echo "__call! "; if ( !method_exists( $this, $func ) ) throw new Error("This method does not exist in this class."); return $this->$func( ...$args ); } protected function Public() { return "Public"; } protected function CallPublic() { return "Call->" . $this->Public(); }}$_Test = new Test();echo $_Test->CallPublic(); // result: __call! Call->Public - it uses two methods but __call is fired only once
If you want to add a similar thing to static methods use __callStatic
magic method.