Magic __call method in abstract superclass - php

This question is related to this other question: PHP's magic method __call on subclasses, but I'm not satisfied with the accepted answer.
What I'm trying to do is implement a generic way to create method aliases, without having to define a named function for every alias, using the magic __call method.
This system would use an associative array as a lookup table in the form of "alias" => "actualMethod.
abstract class Super {
private $aliases;
protected function __construct(array $aliases) {
$this->aliases = $aliases;
}
public function __call($name, $arguments) {
/* if $name is an alias, replace it */
if (isset($this->aliases[$name])) {
$name = $this->aliases[$name];
}
/* throw an exception if the method is undefined */
if (!method_exists($this, $name)) {
throw new Exception("The specified method or method alias is undefined in the current context");
}
/* finally, call the method by its actual name */
return $this->$name($arguments);
}
}
The problem seems to be that either me or the PHP guys don't understand polymorphism.
class Sub extends Super {
public function __construct() {
parent::__construct(array(
"alias" => "actualMethod"
));
}
private function actualMethod() {
echo "Inside the actual method";
}
}
When I define the __call method on an abstract class, then define the actualMethod on a subclass, PHP enters an infinite recursion loop inside __call when I try to invoke the actualMethod by its alias.
try {
$object = new Sub();
$object->alias(); /* causes infinite __call recursion inside Super */
} catch (Exception $exc) {
echo $exc->getTraceAsString();
}
This is funny, because the call to method_exists inside __call returns TRUE.
Surely I can't be the first person to notice this behavior, right? What's the deal here?
EDIT
So basically, normal inheritance rules don't apply for magic methods? It seems that I can't call private methods further down the inheritance tree from inside __call() (*). However I can still call private methods if they are defined in the same class.
(*): even though __call is public, and the object is an instance of the subclass where the private method is defined.
How does that work exactly?

Yes, this is weird - I don't have an answer to why, but a workaround for your problem could be:
/* finally, call the method by its actual name */
return call_user_func_array(array($this, $name), $arguments);

Looks like I found a way to do it. I'm not sure if it is the way to do it or just a dirty hack. Anyway:
class Sub extends Super {
public function __construct() {
parent::__construct(array(
"alias" => "actualMethod"
));
}
public function __call($name, $arguments) {
if (!method_exists($this, $name)) {
return parent::__call($name, $arguments);
}
return $this->$name($arguments);
}
private function actualMethod() {
echo "Inside the actual method";
}
}
This works by only calling the __call method inside Sub if the specified method does not exist already in either Sub or Super. When it doesn't, the Sub::__call() is invoked, which in turn invokes Super::__call. The result is that either an exception is thrown, or control is handed back to Sub::__call which then invokes the actualMethod().
I hope that made sense.
EDIT
I completely forgot to add return keywords in my examples. Obviously these are crucial if you're trying to return anything other than void.

Related

php - check in constructor if class function is called by class instance

Is it possible to do something like this one:
class conditional
{
function __construct()
{
if (launch() is called) {
//return True
//function is called by class instance
}else{
//return False
//launch() is not called
}
}
public function launch(){
echo "function is launcher";
}
}
I am trying to use a condition in class constructor to know if the class function is called by class instance or object as $class_instance->launch(). If the function is not called by class instance then it should run return else condition.
I don't think you can do what you want to do (nor I understand why would want to).
The constructor will run when you create the instance, before you are able to call any of the instance methods. Unless you have a way to delve into the future and know at instantiation time if any of the instance methods will be called later on, no dice :)
But you can do something that more or less looks like that using the __call magic method, even if probably doesn't make much sense in most scenarios.
E.g., a very simplified implementation:
class Magical {
public function __call($method, $args) {
switch($method) {
case "launch":
echo "magical launch() was called\n";
$this->magicLaunch();
break;
default:
throw new \Exception("Method not implemented");
}
}
private function magicLaunch() {
echo "magic!";
}
}
// this is the only point where the constructor gets called
$magic = new Magical();
// since the launch method doesn't exist, the magic method __call is invoked
$magic->launch();
Outputs:
magical launch() was called
magic!

php - any way to invoke a method for ALL classes magically?

I have reviewed PHP magic methods but don't see any way at least from these to accomplish this.
I would like to have a method "magically" called when any class is instantiated, something like __onClassCall(), which is not specifically written in the class - this would be for debugging and I would like to turn it on or off. Similarly, I would like to fire another method when any method in a class is called, which again could be written somewhere else and turned on or off. Is this possible?
You may want to implement those on your own. The __invoke() Magic Method might not be what you need as you still have to explicitly trigger it, anyways. Consider the following code below where you have a MagicHelper Class wherein you declare your Magic Methods and then use them within your Class (Here: SomeClassOne). This might not be a built-in PHP Function but that is what OOP is all about, right?
<?php
class MagicHelper{
public static function __onClassCall(){
// LOGIC FOR SOME SPECIAL INITIALIZATION ON THE INSTANTIATION OF A CLASS OBJECT GOES HERE...
// THIS METHOD IS FIRED EACH TIME AN OBJECT IS INSTANTIATED
}
public static function __onMethodCall(){
// LOGIC FOR DEBUGGING GOES HERE...
// THIS METHOD IS FIRED EACH TIME A METHOD IS CALLED
}
}
class SomeClassOne{
private $prop;
public static $CLASS_PREP_ENABLED = true; //<== COULD BE TURNED OFF OR ON
public static $METHOD_DEBUG_ENABLED = true; //<== COULD BE TURNED OFF OR ON
public function __construct($prop = null) {
$this->prop = $prop;
if(self::$CLASS_PREP_ENABLED){
// CALL THE STATIC MAGIC __onClassCall METHOD DECLARED IN THE MagicHelper CLASS
// EACH TIME A NEW OBJECT IS INSTANTIATED
MagicHelper::__onClassCall();
}
}
function __invoke() {
// YOU COULD AS WELL USE THIS BUT I DOUBT THIS FITS YOUR DESCRIPTION.
}
public function getProp() {
return $this->prop;
}
public function setProp($prop) {
$this->prop = $prop;
return $this;
}
public function methodOne($paramOne){
// THE LOGIC FOR THIS METHOD GOES HERE
//...
// CALL THE STATIC MAGIC __onMethodCall METHOD DECLARED IN THE MagicHelper CLASS
// EACH TIME A NEW OBJECT IS INSTANTIATED
if(self::$METHOD_DEBUG_ENABLED) {
MagicHelper::__onMethodCall();
}
}
public function methodTwo($paramTwo){
// THE LOGIC FOR THIS METHOD GOES HERE
//...
// CALL THE STATIC MAGIC __onMethodCall METHOD DECLARED IN THE MagicHelper CLASS
// EACH TIME A NEW OBJECT IS INSTANTIATED
if(self::$METHOD_DEBUG_ENABLED) {
MagicHelper::__onMethodCall();
}
}
}

How does Laravel MessageBag work?

I encountered something magical about laravel (4.2), that i really want explaind how it could be possible.
When i up a MessageBag Class in Class A
And Pass that variable to Class B, somehow Class B overrides the Class A MessageBag without me declaring it.
class ClassA {
public function test()
{
$msgBag = new \Illuminate\Support\MessageBag;
new ClassB($msgBag);
if($msgBag->any()) {
#This will trigger and spit out "Message from Class B"
dd($msgBag);
}else{
print('nope no messages');
}
}
}
class ClassB {
protected $msgBag;
function __construct(\Illuminate\Support\MessageBag $msgBag)
{
$this->msgBag = $msgBag;
$this->setMessage();
}
public function setMessage()
{
$this->msgBag->add('message', 'Message from Class B');
}
}
I tested the same thing with a normal object but that behaved like i expected it to.
class ClassA {
public function test()
{
$object = (object) ['class'=>'A'];
new ClassB($object);
dd($object->class); # Will return A
}
}
class ClassB {
protected $object;
function __construct($object)
{
$this->object = $object;
$this->setMessage();
}
public function setMessage()
{
$this->object = (object) ['class'=>'B'];
}
}
So obviously Laravel is doing something behind the seances to make this possible, but I haven't found it yet.
Does anyone know how to replicate this ?
There is no Laravel magic here. In PHP, objects behave as though they are passed by reference (although they technically are not, but that's not relevant here).
This means that in your first example, the MessageBag object you created is the same object as the as the one assigned to $this->msgBag in ClassB. Therefore, any modifications made to the object inside ClassB are going to be seen when you inspect the $msgBag object in the test() method in ClassA.
This is not the case in your second example, because in your setMessage() method, you override the first object with an entirely new one.
Basically everything is behaving as you would expect with normal PHP.

Call __get() on protected properties with -> operator?

My team is using lazyloading techniques to load sub-objects from our database. To do this we're using the magic __get() method and making a database call. All of our properties are protected so the __get method gets called from outside of the object but our issue is that it doesn't get called from within the object without using $this->__get($name);
So my question is: Is it possible to force __get() to be called with the normal chaining operator even from within the object?
If I want to chain object access I currently have to do:
$this->__get('subObject')->__get('subObject')->__get('subObject')
Is it possible to write the following, but have it still call __get()?
$this->subObject->subObject->subObject
Thanks,
Jordan
Jordan,
PHP won't call the __get() method unless the property is inaccessible—either because it doesn't exist, or because of visibility. When the property is hidden from the calling scope the __get() method is called because the property is inaccessible. When the property is referenced and is available to the calling scope, __get() will never fire. (It's not meant to fire for an existing/accessible property.)
To work around this you have to rename your internal properties. Either prefix them with a name, underscore, or store them in a common array parameter with a special name. The following is somewhat contrived, but it ought to demonstrate how you can deal with this situation.
class MyObject {
protected $lazyloads = array();
protected function lazyload($relation) {
// ...
}
public function __get($key) {
if (isset($this->lazyloads[$key]) {
return $this->lazyloads[$key];
} else {
return $this->lazyload($key);
}
}
}
See: http://www.php.net/manual/language.oop5.overloading.php#object.get
#Robert K Or (for example):
class A
{
protected $objects = array();
public function __get($name)
{
if(isset($this->objects[$name])) {
return $this->objects[$name];
}
return null; // or throw \Exception
}
}
class B
{
protected $objects = array();
public function __get($name)
{
// for example: $this->objects[$name] is type of A
if(isset($this->objects[$name])) {
return $this->objects[$name];
}
return null; // or throw \Exception
}
}
each returned object must have a magic method __get that returns an object.

Making a method in PHP automatically run some code after being called. Possible?

I'm not really sure if what I am looking for has a name, so it has been a bit difficult for me to search for, so I apologise if what I am asking has already been answered here before.
The situation is that I have an abstract class which has, of course, many other classes which extend it.
The abstract method in the abstract class is called run() and all of the extending classes define this method.
My problem is that I want to call some common code after the run() method has been called, but I want to know if there is a better way to do this.
I could of course just paste my common code into each and every extending class's run() method, but if I do that then it would be a lot of work for me to make simple changes from that point onward.
I could also put my common code into a method in the parent class and then call if from the extending class's run() method with $this.
But my question is, is there a better way to do this, or do I have to either use the $this keyword or paste in the code into each class's?
Here is a small example of what I want to do with my current idea:
abstract class Parent_Class {
public abstract function run();
protected function common_code() {
// Common code here
}
}
class Child_Class {
public function run() {
// Code here
// I want some common code to run after the run method has been called
$this->common_code(); // Is this the best way to do it?
}
}
Or is it possible to somehow tell the class that when the run() method has been called to automatically run the common_code() method?
A far simpler way to do this would to simply have a third method which calls run() and then calls common_code(). Subclasses can then override run() all they want.
abstract class Parent_Class {
public abstract function run();
protected function common_code() {
// Common code here
}
protected function start() {
$this->run();
$this->common_code();
}
}
class Child_Class {
public function run() {
// Code here
}
}
Based on Bradley Forster answer, you can define the method as protected, so when it's called from outside the class, you can intercept the event with php magic __call metod because
__call() is triggered when invoking inaccessible methods in an object context.
and then you can execute that method from the __call function
class A {
public function __call($method, $args){
if(!method_exists($this, $method))
throw new Exception("Call to undefined method ".__CLASS__."::$method()");
echo "doing something BEFORE function 'run' execution\n";
$retval = call_user_func_array(array($this, $method), $args);
echo "doing something AFTER function 'run' execution\n";
return $retval;
}
protected function run() {
echo "function 'run' executed\n" ;
}
}
$a = new A;
$a->run();
The answer given by Amber is nice and simple. But requires you to put your data in run() but call start(). Here is an alternative that allows you to have your code $a->run() in all your scripts, and $a->run() with your common code, but encapsulated in a namespace.
File hooks.php
<?php
// File: hooks.php
namespace hook;
class processHooks {
public function __construct() { /* Fatal Error without constructor */ }
protected function processHooks($_c, $method, $args) {
/* Swap the next two callbacks to run your custom code after */
call_user_func_array("\hook\\{$_c}::{$method}", $args);
return call_user_func_array(array($_c,$method), $args);
}
}
class A {
public function foo() {
echo 'Doing code stuff BEFORE calling child function....<br>';
}
}
File regular_file.php
<?php
// File: regular_file.php
include "hooks.php";
class A extends \hook\processHooks {
/* All that is required is this function
* and the function with common code to be protected
*/
public function __call($method, $args) {
self::processHooks(__CLASS__, $method, $args);
}
protected function foo() {
echo 'Method is called....<br>';
}
}
$a = new A();
$a->foo();
This works beacause method foo of class A in regular_file.php is protected, so it's not callable outside the class, so calling it triggers PHP magic method __call
__call() is triggered when invoking inaccessible methods in an object context.

Categories