Can you do this in PHP? I've heard conflicting opinions:
Something like:
Class bar {
function a_function () { echo "hi!"; }
}
Class foo {
public $bar;
function __construct() {
$this->bar = new bar();
}
}
$x = new foo();
$x->bar->a_function();
Will this echo "hi!" or not?
Will this echo "hi!" or not?
No
Change this line:
$bar = new bar();
to:
$this->bar = new bar();
to output:
hi!
It's perfectly fine, and I'm not sure why anyone would tell you that you shouldn't be doing it and/or that it can't be done.
Your example won't work because you're assigning new Bar() to a variable and not a property, though.
$this->bar = new Bar();
In a class, you need to prefix all member variables with $this->. So your foo class's constructor should be:
function __construct() {
$this->bar = new bar();
}
Then it should work quite fine...
Yes, you can. The only requirement is that (since you're calling it outside both classes), in
$x->bar->a_function();
both bar is a public property and a_function is a public function. a_function does not have a public modifier, but it's implicit since you specified no access modifier.
edit: (you have had a bug, though, see the other answers)
Related
Can i bind method of class Foo to class Bar? And why the code below throws a warning "Cannot bind method Foo::say() to object of class Bar"? With function instead of method code works fine.
P.S. I know about extending) it is not practical question, just want to know is it real to bind non-static method to another class
class Foo {
public $text = 'Hello World!';
public function say() {
echo $this->text;
}
}
class Bar {
public $text = 'Bye World!';
public function __call($name, $arguments) {
$test = Closure::fromCallable(array(new Foo, 'say'));
$res = Closure::bind($test, $this);
return $res();
}
}
$bar = new Bar();
$bar->say();
Code below works fine
function say(){
echo $this->text;
}
class Bar {
public $text = 'Bye World!';
public function __call($name, $arguments) {
$test = Closure::fromCallable('say');
$res = Closure::bind($test, $this);
return $res();
}
}
$bar = new Bar();
$bar->say();
This is currently not supported. If you want to bind a closure to a new object, it must not be a fake closure, or the new object must be compatible with the old one (source).
So, what is a fake closure: A fake closure is a closure created from Closure::fromCallable.
This means, you have two options to fix your problem:
Bar must be compatible with the type of Foo - so just make Bar
extend from Foo, if possible.
Use unbound functions, like annonymous, static or functions outside of classes.
It is not supported by PHP. However in PHP 7.0 it was kinda possible. Consider this example:
class Foo {
private $baz = 1;
public function baz() {
var_dump('Foo');
var_dump($this->baz);
}
}
class Bar {
public $baz = 2;
public function baz() {
var_dump('Bar');
var_dump($this->baz);
}
}
$fooClass = new ReflectionClass('Foo');
$method = $fooClass->getMethod('baz');
$foo = new Foo;
$bar = new Bar;
$closure = $method->getClosure($foo);
$closure2 = $closure->bindTo($bar);
$closure2();
The output of foregoing code is:
string(3) "Foo"
int(2)
And this means method Foo::baz was called on object $bar and has accessed its $baz property rather than Foo::$baz.
Also, please note that Bar::$baz is public property. If it was private, php would throw Fatal error cannot access private property. This could've be fixed up by changing the scope of closure $closure->bindTo($bar, $bar);, however doing this was already prohibited in php7.0 and would lead to a following warning: Cannot rebind scope of closure created by ReflectionFunctionAbstract::getClosure().
There's workaround however, which will work for latest versions of php. Laravel created a splendid package called laravel/serializable-closure. What it does is simple - it reads source code of closure in order to serialize it and be able to unserialize it later with all necessary context.
So basically the functionality of unserialized closure remains the same, however it is different from PHP's perspective. PHP doesn't know it was created from class method and therefore allows to bind any $this.
Final variant will look like this:
$callback = [$foo, 'baz'];
$closure = unserialize(
serialize(new SerializableClosure(Closure::fromCallable($callback)))
)->getClosure();
$closure->call($bar);
Please, note that serialization and unserialization are expensive operations, so do not use provided solution unless there's no other solution.
class Foo {
protected static $a = 1;
protected $b = 2;
public function func() { return 'foo' . static::$a . $this->b; }
}
class Bar extends Foo {
protected static $a = 3;
protected $b = 4;
public function func() { return 'bar' . static::$a . $this->b; }
}
$obj = new Bar();
$obj->func(); // returns of course 'bar34'
Is there any option in PHP to call func() from Foo class?
In C++ I would cast $obj to Foo and simply call func()
Bar* obj = new Bar();
Foo* obj2 = (Bar*) obj;
obj2->func(); // returns 'foo14';
If you want to get down and dirty with Reflection then it's possible, but I'd strongly argue that this shouldn't be used anywhere near any production code. If you've got an instance of a child class, then you've got it for a reason, and if it's overridden a parent method then that has also happened for a reason.
Assuming you already know all this, then with that disclaimer out of the way, this should work in any remotely recent version of PHP:
class Foo { public function func() { echo 'I am the parent'; } }
class Bar extends Foo { public function func() { echo 'I am the child'; } }
// Create instance of child class
$bar = new Bar;
// Create reflection class
$reflected = new ReflectionClass(get_class($bar));
// Get parent method
$method = $reflected->getParentClass()->getMethod('func');
// Invoke method on child object
$method->invokeArgs($bar, []);
// I am the parent
See https://3v4l.org/NP6j8
This to me looks like a design issue more than anything else.
However if I were to handle this in a way that were easily readable and without rethinking my design I would do:
<?php
class Foo {
public function func() { return 'foo'; }
}
class Bar extends Foo {
public function func() { return 'bar'; }
public function parentFunc() { return parent::func(); }
}
$obj = new Bar();
$obj->parentFunc(); // returns of course 'foo'
Loek's answer also works, but doesn't call the method on the objects parent. It just calls the method on the classes parent. It all depends on the functionality you are looking for.
You could also do something like:
<?php
class Foo {
public function func() { return 'foo'; }
}
class Bar extends Foo {
public function func($parent = false) {
if ($parent) {
return parent::func();
}
return 'bar';
}
}
$obj = new Bar();
$obj->func(true); // returns of course 'foo'
Which is similar but without the need for the extra method.
Personally though I feel this issue likely requires a rethink in code design more than a coding solution.
-- edit --
To elaborate on 'a rethink in code design', I would ask myself "Why do I need an object that has two methods with the same name, but different functionalities? Is this not a job for two different objects? Trace the issue backwards until you find the design issue. Or the point at which the decision needs to be made as to which object your framework requires.
This isn't exactly what I'd call pretty, but it works and is relatively similar to what you described for C++; It works by calling get_parent_class() and then abusing PHP's ability to create objects from strings.
<?php
class Foo {
public function func() { echo 'foo'; }
}
class Bar extends Foo {
public function func() { echo 'bar'; }
}
$obj = new Bar();
$obj->func(); // Prints 'bar'
$parentClassString = get_parent_class($obj);
$newObj = new $parentClassString; // Gotta love PHP for magic like this
$newObj->func(); // Prints 'foo'
See this snippet to see it in action.
EDIT
It's a lot of work, but you could use so called Late Static Binding, perhaps more clearly explained in Jokerius's answer here. This requires you to write a crapload of custom code though, which I don't think is preferential. Overall the short answer seems to be: it isn't really possible.
I don't know should it help you but try to add this function in Bar class
public function callParent($function){
return parent::$function();
}
and call
echo $obj->callParent("func");
[UPDATED]
Also you can write cast function yourself
something like this
public function castAs($newClass) {
$obj = new $newClass;
foreach (get_object_vars($this) as $key => $name) {
$obj->$key = $name;
}
return $obj;
}
Can i bind method of class Foo to class Bar? And why the code below throws a warning "Cannot bind method Foo::say() to object of class Bar"? With function instead of method code works fine.
P.S. I know about extending) it is not practical question, just want to know is it real to bind non-static method to another class
class Foo {
public $text = 'Hello World!';
public function say() {
echo $this->text;
}
}
class Bar {
public $text = 'Bye World!';
public function __call($name, $arguments) {
$test = Closure::fromCallable(array(new Foo, 'say'));
$res = Closure::bind($test, $this);
return $res();
}
}
$bar = new Bar();
$bar->say();
Code below works fine
function say(){
echo $this->text;
}
class Bar {
public $text = 'Bye World!';
public function __call($name, $arguments) {
$test = Closure::fromCallable('say');
$res = Closure::bind($test, $this);
return $res();
}
}
$bar = new Bar();
$bar->say();
This is currently not supported. If you want to bind a closure to a new object, it must not be a fake closure, or the new object must be compatible with the old one (source).
So, what is a fake closure: A fake closure is a closure created from Closure::fromCallable.
This means, you have two options to fix your problem:
Bar must be compatible with the type of Foo - so just make Bar
extend from Foo, if possible.
Use unbound functions, like annonymous, static or functions outside of classes.
It is not supported by PHP. However in PHP 7.0 it was kinda possible. Consider this example:
class Foo {
private $baz = 1;
public function baz() {
var_dump('Foo');
var_dump($this->baz);
}
}
class Bar {
public $baz = 2;
public function baz() {
var_dump('Bar');
var_dump($this->baz);
}
}
$fooClass = new ReflectionClass('Foo');
$method = $fooClass->getMethod('baz');
$foo = new Foo;
$bar = new Bar;
$closure = $method->getClosure($foo);
$closure2 = $closure->bindTo($bar);
$closure2();
The output of foregoing code is:
string(3) "Foo"
int(2)
And this means method Foo::baz was called on object $bar and has accessed its $baz property rather than Foo::$baz.
Also, please note that Bar::$baz is public property. If it was private, php would throw Fatal error cannot access private property. This could've be fixed up by changing the scope of closure $closure->bindTo($bar, $bar);, however doing this was already prohibited in php7.0 and would lead to a following warning: Cannot rebind scope of closure created by ReflectionFunctionAbstract::getClosure().
There's workaround however, which will work for latest versions of php. Laravel created a splendid package called laravel/serializable-closure. What it does is simple - it reads source code of closure in order to serialize it and be able to unserialize it later with all necessary context.
So basically the functionality of unserialized closure remains the same, however it is different from PHP's perspective. PHP doesn't know it was created from class method and therefore allows to bind any $this.
Final variant will look like this:
$callback = [$foo, 'baz'];
$closure = unserialize(
serialize(new SerializableClosure(Closure::fromCallable($callback)))
)->getClosure();
$closure->call($bar);
Please, note that serialization and unserialization are expensive operations, so do not use provided solution unless there's no other solution.
In PHP, is it possible to access the static properties of an object that is itself a property of another object, using similar syntax below:
<?php
class Foo
{
public $bar;
function __construct()
{
$this->bar = new Bar();
}
}
class Bar
{
const TEST = 123;
function __construct() {}
}
$bar = new Bar();
$foo = new Foo();
echo Bar::TEST; // ok
echo $bar::TEST; // ok
echo $foo->bar::TEST; // error
?>
Assign the property to a variable.
$foo = new Foo();
$bar = $foo->bar;
echo $bar::TEST; // that's good.
more tricky, but you can use ReflectionClass
echo (new ReflectionClass(get_class($foo->bar)))->getconstant("TEST");
You might have better luck using Late Static Binding instead of inheritance on that property. So it would be something like this (changed to an example from the above PHP manual page):
<?php
class A
{
public static function who()
{
echo __CLASS__;
}
public static function test()
{
static ::who();
// Here comes Late Static Bindings
}
}
class B extends A
{
public static function who()
{
echo __CLASS__;
}
}
B::test();
?>
Here's another thread that might be relevant or helpful: PHP Inheritance and Static Methods and Properties
Am I missing something or do closures simply not work as class methods? Take this for instance:
$foo = new stdClass();
$foo->bar = function() {
echo '###';
};
$foo->bar();
Seems to give me an error of "Fatal error: Call to undefined method stdClass::bar() in /blah/blah.php on line X"
Shouldn't this instead invoke the closure that was placed in the "bar" property?
Yes, that is indeed correct.
The only way to call bar is:
$bar = $foo->bar;
$bar();
Sad, but true.
Also worth noting, because of this same effect, there is no $this inside $bar call (unless you pass it as function argument named as $this).
Edit: As nikic pointed out, the value of $this inside the closure is the same value of the scope of when the closure was created.
This may mean that $this might be undefined on two occasions: when the scope was the global PHP scope or when the scope was from a static context. This, however, means that you can in theory feed the correct instance:
class Foo {
public $prop = 'hi';
function test(){
$this->bar = function(){
echo $this->prop;
}
$bar = $this->bar;
$bar();
}
}
$foo = new Foo();
$foo->test();
Also, it seems that with some class magic, you can achieve $this->bar() as well:
class Foo {
// ... other stuff from above ...
public function __call($name, $args){
$closure = $this->$name;
call_user_func_array( $closure, $args ); // *
}
}
[*] Beware that call_user_func_array is very slow.
Oh, and this is strictly for PHP 5.4 only. Before that, there's no $this in closures :)
Also, you can see it in action here.
Methods and fields are completely separate; in fact, you can even have a method and field of the same name:
<?php
class foo{
function bar() { echo "hello\n"; }
}
$object = new foo;
$object->bar = 1;
$object->bar(); // echoes "hello"
?>
This explains why your syntax could not have created a "real" method.