My coworker ask me how to dynamically implement methods in a class. What I come up with was strategy pattern. At the first time, I made regular strategy pattern, and at the end I understood it's not good idea to make property call function. Because the child class is controller class whose methods needs to be called directly.
So, I'm trying to implement Package's method to B class directly. But I'm stuck when calling __call function. The function tried to implement works in class B. However, when it's extended the function I saved in B class doesn't work.
<?php
class A {
public $once = 0;
}
class B extends A {
public $methods = [];
public function __construct()
{
//load package
$package = new Package;
//get method names
$methods = get_class_methods($package);
//loop to assign methods to class B instance
foreach($methods as $method)
{
$this->methods[$method] = $this->setFunc($package, $method);
}
}
//I made this function because [$package, $method] was regarded as
// array instead of function when it is assigned to other variable
private function setFunc($package, $methodName)
{
return function() use($package, $methodName)
{
return call_user_func([$package, $methodName]);
};
}
}
//package class
class Package {
public function hello_world(){
return "hello world";
}
}
class C extends B{
public function __construct()
{
parent::__construct();
//assigning functions to class C
foreach($this->methods as $key => $val)
{
//I did it in child class because dynamically produced properties
// weren't recognized
$this->$key = $val;
}
}
//dynamically assigned functions to dynamic properties must be called by
//__call function
public function __call($name, $arguments)
{
//I made this condition because calling function loops infinitely.
if($this->once >= 1)
{
return;
}
$this->once++;
//not working here. nothing shown
return $this->$name();
}
}
$c = new C;
echo $c->hello_world(); //I want to display hello world!
replace return $this->$name(); with call_user_func($this->$name,[]);
or in php7 this works return ($this->$name)();
Related
I have two classes: Action and MyAction. The latter is declared as:
class MyAction extends Action {/* some methods here */}
All I need is method in the Action class (only in it, because there will be a lot of inherited classes, and I don’t want to implement this method in all of them), which will return classname from a static call. Here is what I’m talking about:
Class Action {
function n(){/* something */}
}
And when I call it:
MyAction::n(); // it should return "MyAction"
But each declaration in the parent class has access only to the parent class __CLASS__ variable, which has the value “Action”.
Is there any possible way to do this?
__CLASS__ always returns the name of the class in which it was used, so it's not much help with a static method. If the method wasn't static you could simply use get_class($this). e.g.
class Action {
public function n(){
echo get_class($this);
}
}
class MyAction extends Action {
}
$foo=new MyAction;
$foo->n(); //displays 'MyAction'
Late static bindings, available in PHP 5.3+
Now that PHP 5.3 is released, you can use late static bindings, which let you resolve the target class for a static method call at runtime rather than when it is defined.
While the feature does not introduce a new magic constant to tell you the classname you were called through, it does provide a new function, get_called_class() which can tell you the name of the class a static method was called in. Here's an example:
Class Action {
public static function n() {
return get_called_class();
}
}
class MyAction extends Action {
}
echo MyAction::n(); //displays MyAction
Since 5.5 you can use class keyword for the class name resolution, which would be a lot faster than making function calls. Also works with interfaces.
// C extends B extends A
static::class // MyNamespace\ClassC when run in A
self::class // MyNamespace\ClassA when run in A
parent::class // MyNamespace\ClassB when run in C
MyClass::class // MyNamespace\MyClass
It's not the ideal solution, but it works on PHP < 5.3.0.
The code was copied from septuro.com
if(!function_exists('get_called_class')) {
class class_tools {
static $i = 0;
static $fl = null;
static function get_called_class() {
$bt = debug_backtrace();
if (self::$fl == $bt[2]['file'].$bt[2]['line']) {
self::$i++;
} else {
self::$i = 0;
self::$fl = $bt[2]['file'].$bt[2]['line'];
}
$lines = file($bt[2]['file']);
preg_match_all('/([a-zA-Z0-9\_]+)::'.$bt[2]['function'].'/',
$lines[$bt[2]['line']-1],
$matches);
return $matches[1][self::$i];
}
}
function get_called_class() {
return class_tools::get_called_class();
}
}
Now (when 5.3 has arrived) it's pretty simple:
http://php.net/manual/en/function.get-called-class.php
class MainSingleton {
private static $instances = array();
private static function get_called_class() {
$t = debug_backtrace();
return $t[count($t)-1]["class"];
}
public static function getInstance() {
$class = self::get_called_class();
if(!isset(self::$instances[$class]) ) {
self::$instances[$class] = new $class;
}
return self::$instances[$class];
}
}
class Singleton extends MainSingleton {
public static function getInstance()
{
return parent::getInstance();
}
protected function __construct() {
echo "A". PHP_EOL;
}
protected function __clone() {}
public function test() {
echo " * test called * ";
}
}
Singleton::getInstance()->test();
Singleton::getInstance()->test();
(PHP 5 >= 5.3.0, PHP 7)
get_called_class — The "Late Static Binding" class name
<?php
class Model
{
public static function find()
{
return get_called_class();
}
}
class User extends Model
{
}
echo User::find();
this link might be helpfull
There is no way, in the available PHP versions, to do what you want. Paul Dixon's solution is the only one. I mean, the code example, as the late static bindings feature he's talking about is available as of PHP 5.3, which is in beta.
I am not sure how to name this, but here it goes. Lets suppose i have the following
class A {
public function aa() {
$this->bb();
}
public function bb() {
}
}
class B extends a {
}
class C {
__construct(B $service) {
$this->service = $service;
}
public function aa() {
$this->service->aa();
}
}
My call in code will be
$C = new C(new B());
$C->aa();
So this will basically execute A:aa() which is what i want. As you can see, in A::aa() AA::bb() is called.
What I need. When AA::bb() is called i want to execute some code defined in class C, but I am not allowed to change the A class. I can only change the B class or the C class.
My idea was to add a listener in the B class and overwrite the bb() function like this
class B extends a {
public $listener;
bb() {
parent::bb();
$this->listener();
}
}
class C {
__construct(B $service) {
$this->service = $service;
}
public function aa() {
$this->service->listener = function() { }
$this->service->aa();
}
}
But I don't like this idea a lot, doesn't look like a good one. What are my options here?
Again, I CANNOT change the A class and i can only call the C class.
PHP version is 5.3
You have two options. Extend or decorate.
First one would be kinda what you have already written, though, I would not use public visibility for the listener:
class Foo extends A {
private $listener;
public function setListener(callable $func) {
$this->listener = $func;
}
public function bb() {
call_user_func($this->listener);
return parent:bb();
}
}
In the example I passed the listener via setter injection, but you can also use constructor injection and pass the $listened in the overloaded __construct() method. When you extend a class, the "interface restriction" does not aply to the constructor's signature.
The other approach is to use a decorator:
class Foo {
private $target;
public function __construct(A $target) {
$this->target = $target;
}
public function bb($callback) {
$callback();
return $this->target->bb();
}
public function __call($method, $arguments) {
return call_user_func_array(
array( $this->target, $method ),
$arguments
);
}
}
The second approach would let you alter the interface.
Which option you pick depend on the exact functionality you actually need to implement. The decorator is a solution for, when you need drastic change in the objects behavior - for example, it is really good for adding access control.
I understand that you want to execute code in C after code in A completes. You cannot change A.
As written, C::aa calls A::aa, which calls A::bb and the stack unwinds. Why not just do the work in C::aa after the service call finishes?
class C {
public function aa() {
$this->service->aa();
// whatever you want to do
}
}
If, on the other hand, you need to call code after A::aa is called but before A::bb is called then the example you posted would suffice with clarity:
class B extends a {
public $listener;
public function bb() {
call_user_func($this->listener);
parent::bb();
}
}
Note the use of call_user_func, which is necessary for PHP 5.3 to call an anonymous function stored in a member variable.
I'm a bit confused on whether or not this is possible. I've checked a couple of posts here on SO and they don't really explain what I'm looking for.
I have 3 classes. One main class and two classes extending that main class. (see code below). Is it possible to run a method in one of the two extended classes from it's sibling (the other extended class)?
If it's not possible, how can I change my code to accomplish what I'm doing in the example below?
DECLARATION
class A {
public function __construct() {
//do stuff
}
}
class B extends A {
private $classb = array();
public function __construct() {
parent::__construct();
//do stuff
}
public function get($i) {
return $this->classb[$i];
}
public function set($i, $v) {
$this->classb[$i] = $v;
}
}
class C extends A {
public function __construct() {
parent::__construct();
//do stuff
}
public function display_stuff($i) {
echo $this->get($i); //doesn't work
echo parent::get($i); //doesn't work
}
}
USAGE
$b = new B();
$c = new C();
$b->set('stuff', 'somestufftodisplay');
$c->display_stuff('stuff'); // <----- Displays nothing.
Your code shows an additional problem apart from the main question so there are really two answers:
No, you cannot run a method from a sibling class in another sibling class. If you need that, the method should be in the parent class. The same applies to properties.
You cannot use the value of a property from one object in another object, even if they are both of the same class. Setting a property value in one object sets its value only there as different objects can have the same properties with completely different values. If you need to share the value of a property between the objects and also be able to modify it, you should use a static property. In this case you would have to define that in the parent class, see my previous point.
So to make it work, you would need something like
class A {
private static $var = array();
public function get($i) {
return self::$var[$i];
}
public function set($i, $v) {
self::$var[$i] = $v;
}
}
class B extends A {
}
class C extends A {
public function display_stuff($i) {
echo $this->get($i); // works!
}
}
$b = new B();
$c = new C();
$b->set('stuff', 'somestufftodisplay');
$c->display_stuff('stuff');
An example.
So I have a class which is designed to "mix" other classes in, via what i call a "bridge" class. So you have your sample classes for example:
class A{
public function __construct(){}
public function hello_a(){ echo "hello A"; }
}
class B{
public function __construct(){}
public function hello_b(){ echo "hello B"; }
}
You might also have a single class called C - which needs to inherit from both A and B, but since PHP doesn't have multiple inheritance we have the following:
class C extends Bridge{
public function __construct(){
parent::__construct();
}
public function hello_C(){
$this->hello_a(); // Freaks out*
}
}
class Bridge extends AisisCore_Loader_Mixins{
public function construct(){
parent::construct();
$this->setup(array(
'A' => array(),
'B' => array()
));
}
}
And now finally we have our mix-in class which allows all of this to work. Note: this code assumes you have a auto loader using the pear naming standards to load classes for you.
class AisisCore_Loader_Mixins {
private $_classes;
private $_class_objects = array();
private $_methods = array();
public function __construct(){
$this->init();
}
public function init(){}
public function setup($class){
if(!is_array($class)){
throw new AisisCore_Loader_LoaderException('Object passed in must be of type $class_name=>$params.');
}
$this->_classes = $class;
$this->get_class_objects();
$this->get_methods();
}
public function get_class_objects(){
foreach($this->_classes as $class_name=>$params){
$object = new ReflectionClass($class_name);
$this->_class_objects[] = $object->newInstanceArgs($params);
}
}
public function get_methods(){
foreach($this->_class_objects as $class_object){
$this->_methods[] = get_class_methods($class_object);
}
return $this->_methods;
}
public function __call($name, $param = null){
foreach($this->_methods as $key=>$methods){
foreach($methods as $method){
if($name === $method){
return $this->isParam($method, $param);
}
}
}
throw new AisisCore_Loader_LoaderException("Method: " .$name.
" does not exist or it's access is not public");
}
private function isParam($method, $param){
if($param != null){
call_user_func($method, $param);
}else{
call_user_func($method);
}
}
}
You can see in class C how the class above is used, we simply just call hello_a. All is well up to this point until it tries call_user_func() and freaks out saying:
Warning: call_user_func() expects parameter 1 to be a valid callback, function 'hello_a' not found or invalid function name
Is there a particular reason it cannot find this? the classes are loaded, the methods are stored in the array, it obviously found the method in the array of methods, the method is public. whats going on?
Your call to call_user_func is passing just the method name, so it's looking for a global function. You must pass your class name or instance:
$a = new A(); // Or however you plan to get your instance of A
call_user_func(array($a, $method));
I'm using WordPress as a CMS, and I want to extend one of its classes without having to inherit from another class; i.e. I simply want to "add" more methods to that class:
class A {
function do_a() {
echo 'a';
}
}
then:
function insert_this_function_into_class_A() {
echo 'b';
}
(some way of inserting the latter into A class)
and:
A::insert_this_function_into_class_A(); # b
Is this even possible in tenacious PHP?
If you only need to access the Public API of the class, you can use a Decorator:
class SomeClassDecorator
{
protected $_instance;
public function myMethod() {
return strtoupper( $this->_instance->someMethod() );
}
public function __construct(SomeClass $instance) {
$this->_instance = $instance;
}
public function __call($method, $args) {
return call_user_func_array(array($this->_instance, $method), $args);
}
public function __get($key) {
return $this->_instance->$key;
}
public function __set($key, $val) {
return $this->_instance->$key = $val;
}
// can implement additional (magic) methods here ...
}
Then wrap the instance of SomeClass:
$decorator = new SomeClassDecorator(new SomeClass);
$decorator->foo = 'bar'; // sets $foo in SomeClass instance
echo $decorator->foo; // returns 'bar'
echo $decorator->someMethod(); // forwards call to SomeClass instance
echo $decorator->myMethod(); // calls my custom methods in Decorator
If you need to have access to the protected API, you have to use inheritance. If you need to access the private API, you have to modify the class files. While the inheritance approach is fine, modifiying the class files might get you into trouble when updating (you will lose any patches made). But both is more feasible than using runkit.
An updated way for 2014 that copes with scope.
public function __call($method, $arguments) {
return call_user_func_array(Closure::bind($this->$method, $this, get_called_class()), $arguments);
}
Eg:
class stdObject {
public function __call($method, $arguments) {
return call_user_func_array(Closure::bind($this->$method, $this, get_called_class()), $arguments);
}
}
$obj = new stdObject();
$obj->test = function() {
echo "<pre>" . print_r($this, true) . "</pre>";
};
$obj->test();
You can use the runkit extension for this, but you should really consider regular inheritance instead.
See runkit_method_add.
No you can't dynamically change a class during runtime in PHP.
You can accomplish this by either extending the class using regular inheritance:
class Fancy extends NotSoFancy
{
public function whatMakesItFancy() //can also be private/protected of course
{
//
}
}
Or you could edit the Wordpress source files.
I'd prefer the inheritance way. It's a lot easier to handle in the long run.