Is there anything equivalent in PHP for jQuery's $.extend()?
I want to pragmatically merge/combine/extend classes with other classes and it's properties (including methods) without using late static binding.
class a {}
class b {}
class c {}
$merged = combine(new a(), new b(), new c());
This is not what I am looking for:
class a {}
class b extends a {}
$merged = new b();
There isn't really anything equivalent in PHP, because their object systems are very different.
JavaScript doesn't have real classes like PHP does, its object system is based on prototypes. Methods can be direct properties of the instance, but if there isn't a direct property the prototype chain will be searched to find it. $.extend() automatically copies properties that are inherited from the prototype to the target object, so the result is kind of like an instance of a subclass of all the original classes.
But PHP doesn't have any equivalent to attaching methods directly to objects, so there's no way that a combine() function could merge all the methods from classes of the arguments. An object is an instance of a particular class, and it gets its methods from the class object (and its superclasses) only.
Like #Barmar said, this isn't really a thing in PHP. Workarounds are ugly but I am going to describe two ugly workarounds for whoever want a big laugh. 🙂
Workaround #1 - Extend classes by the use of magic methods in a class. Pros are that the properties and methods can be accessed from one merged object. But under the surface they are not really merged. So setting properties is discouraged and cross accessing properties is a no go.
class extend {
private $_objects;
private $_properties = [];
public function __construct(...$objects) {
$this->_objects = array_reverse($objects);
}
public function &__isset($name) {
return $this->__get($name);
}
public function __get($name) {
foreach ($this->_objects as $object) {
if (isset($object->$name)) {
return $object->$name;
}
}
if (isset($this->_properties[$name])) {
return $this->_properties[$name];
}
return null;
}
public function __set($name, $value) {
foreach ($this->_objects as $object) {
if (isset($object->$name)) {
$object->$name = $value;
return true;
}
}
$this->_properties[$name] = $value;
return true;
}
public function __call($name, $params) {
foreach ($this->_objects as $object) {
if (method_exists($object, $name)) {
call_user_func_array([$object, $name], $params);
}
}
return null;
}
}
Example Objects:
class a {
var $fruit= 'apple';
function foo() { return 'a'; }
}
class b {
var $fruit = 'banana';
function bar() { return 'b'; }
}
Merge Example:
$merged = new extend(new a(), new b());
echo $merged->foo();
echo $merged->bar();
$merged->this = 'that';
echo $merged->this;
Workaround #2 - Merging objects with anonymous functions instead of methods makes them portable and mergeable. Cons are it may be considered unethic or a future security risk to rely on them.
Anonymous functions in properties can not be defined in the class, but must be set in __construct(). And accessing anonymous functions as properties conflicts the syntax for calling regular methods. So a workaround for the calling syntax is also needed (PHP 7.0+).
Example Objects:
class a {
var $fruit = 'apple';
function __construct(){
$this->foo = function() { return 'a'; };
}
}
class b {
var $fruit = 'banana';
function __construct() {
$this->bar = function() { return 'b'; };
}
}
Merge Example:
$merged = (object)array_merge(
get_object_vars(new a()),
get_object_vars(new b())
);
echo ($merged->foo)(); // $class->var() doesn't work for anonymous functions
echo ($merged->bar)();
$merged->fruit = 'orange';
echo $merged->fruit;
Final conclusions: Surely unconventional, mostly ugly. 😁
You might be better off with late static bindings for extending PHP class objects.
class a {}
class b extends a {}
class c extends b {}
Related
I need to be able to set my object like this:
$obj->foo = 'bar';
then I need to use it as an array like that:
if($obj['foo'] == 'bar'){
//more code here
}
Just add implements ArrayAccess to your class and add the required methods:
public function offsetExists($offset)
public function offsetGet($offset)
public function offsetSet($offset, $value)
public function offsetUnset($offset)
See http://php.net/manual/en/class.arrayaccess.php
Want to improve this post? Provide detailed answers to this question, including citations and an explanation of why your answer is correct. Answers without enough detail may be edited or deleted.
Try extending ArrayObject
You'll also need to implement a __get Magic Method as Valentin Golev mentioned.
Your class will need to looks something like this:
Class myClass extends ArrayObject {
// class property definitions...
public function __construct()
{
//Do Stuff
}
public function __get($n) { return $this[$n]; }
// Other methods
}
ArrayObject implements the ArrayAccess interface (and some more). Using the ARRAY_AS_PROPS flag it provides the functionality you're looking for.
$obj = new ArrayObject(array(), ArrayObject::ARRAY_AS_PROPS);
$obj->foo = 'bar';
echo $obj['foo'];
Alternatively you can implement the ArrayAccess interface in one of your own classes:
class Foo implements ArrayAccess {
public function offsetExists($offset) {
return isset($this->$offset);
}
public function offsetGet($offset) {
return $this->$offset;
}
public function offsetSet($offset , $value) {
$this->$offset = $value;
}
public function offsetUnset($offset) {
unset($this->$offset);
}
}
$obj = new Foo;
$obj->foo = 'bar';
echo $obj['foo'];
You can access PHP object as PHP array, but in different ways. Try this:
$obj->{'foo'}
That is similar with accessing array like this:
$arr['foo']
You can also do this:
$propertyName = 'foo';
$obj->$propertyName; // same like first example
You'll have to implement the ArrayAccess interface to be able to do that -- which only means implementing a few (4 to be exact) simple methods :
ArrayAccess::offsetExists : Whether or not an offset exists.
ArrayAccess::offsetGet : Returns the value at specified offset.
ArrayAccess::offsetSet : Assigns a value to the specified offset.
and ArrayAccess::offsetUnset : Unsets an offset.
There is a full example on the manual's page I pointed to ;-)
You're mixing objects and arrays. You can create and access an object like so:
$obj = new stdClass;
$obj->foo = 'bar';
if($obj->foo == 'bar'){
// true
}
and an array like so:
$obj = new Array();
$obj['foo'] = 'bar';
if($obj['foo'] == 'bar'){
// true
}
You can define a class and add implements ArrayAccess if you want to access your class as both an array and a class.
http://www.php.net/manual/en/language.oop5.php
Your object must implement the ArrayAccess interface, then PHP will allow you to use the square brackets like that.
You could also cast the object as an array:
if((array)$obj['foo'] == 'bar'){
//more code here
}
Enhance Class capability with no functionality drawbacks
You can also use ArrayAccess to access a single array property in your class and leave other properties being accessed in OOP way. Yet still it will work as you requested.
class Foo implements \ArrayAccess
{
/**
* mixed[] now you can access this array using your object
* like a normal array Foo['something'] = 'blablabla'; echo Foo['something']; ... and so on
* other properties will remain accessed as normal: $Foo->getName();
*/
private myArrayOptions = [];
private $name = 'lala';
...
public function offsetExists($offset)
{
return isset($this->myArrayOptions[$offset]);
}
public function offsetGet($offset)
{
if ($this->offsetExists($offset)) {
return $this->myArrayOptions[$offset];
}
return null; // or throw the exception;
}
public function offsetSet($offset, $value)
{
$this->myArrayOptions[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->myArrayOptions[$offset]);
}
public function getName()
{
return $this->name;
}
public function __set($offset, $value){
$this->myArrayOptions[$offset] = $value;
}
...
}
The above will work as you expected.
$obj->foo = 'bar';
if($obj['foo'] == 'bar'){
echo "WoWo";
}
Also note that Foo['name'] !== Foo->getName()
those a two different variables
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;
}
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.
I'd like to be able to use transparent (poor mans) caching of objects by using the constructor and not some factory method.
$a = new aClass(); should check if this objects exists in cache and if it doesn't exist create it and add it to the cache.
Some pseudo-code:
class aClass {
public function __construct($someId) {
if (is_cached($someId) {
$this = get_cached($someId);
} else {
// do stuff here
set_cached($someId, $this);
}
}
}
Unfortunately, this is impossible because you can't redefine $this in php.
Any suggestions?
This will not work because ctors dont return and you cannot redefine $this.
You can use a static factory method instead:
class Foo
{
protected static $instances = array();
public function getCachedOrNew($id)
{
if (!isset(self::$instances[$id])) {
self::$instances[$id] = new self;
}
return self::$instances[$id];
}
}
$foo = Foo::getCachedOrNew(1);
$foo->bar = 1;
$foo = Foo::getCachedOrNew(1);
echo $foo->bar; // 1
Another alternative would be to use a Dependency Injection Container (DIC) that can manage objects instances. Have a look at The Symfony Componenent DIC. for this.
Is it possible to set the parent of the class? I.e. an instance of the parent class gets instantiated during runtime and then a child class instance extending a certain parent instance gets created. For example:
class A {
var $test = 'default';
}
class B extends A {
public function __contsruct(A $a) {
parent = $a; //does not work
}
}
$a = new A();
$a->test = 'changed';
$b = new B($a);
$b->test == 'changed'; //should be true
I know that I could just $b = new B(); $b->test = 'changed', but that's not what I'm asking about.
A simple way to accomplish this is like so:
class Foo
{
function __construct() {
$this->hello = "Hello";
}
public function sayHello() {
echo $this->hello."!";
}
}
class Bar
{
function __construct(&$foo) {
$this->foo = $foo;
}
public function sayHelloAgain() {
echo $this->foo->sayHello()." Again!";
}
}
$foo = new Foo();
echo $foo->sayHello(); //outputs "Hello!"
$bar = new Bar($foo);
echo $bar->sayHelloAgain(); //outputs "Hello! Again!"
What you've asked for is not possible in base PHP. There are a few ways to do similar things.
You could use the highly experimental runkit extension. The runkit_class_emancipate and runkit_class_adopt functions should work. However, they operate on entire classes, not instances of a class. This probably limits their usefulness for your application.
If you're trying to emulate the expandable class features of other languages, like Ruby and Perl, runkit_method_add and related functions might be more suitable. Again, however, it still operates on entire classes.
The normally accepted "PHP way" to do things like this is via __call. In fact, with anonymous functions in 5.3, you can do something like...
class Bar {
public function say($thing) {
echo "Bar::say says: $thing\n";
}
}
class Foo extends Bar {
private $extensions = array();
public function addExtension($func_name, $func) {
$this->extensions[ $func_name ] = $func;
}
public function __call($func_name, $arguments) {
array_unshift($arguments, $this);
if(array_key_exists($func_name, $this->extensions))
call_user_func_array($this->extensions[ $func_name ], $arguments);
}
}
$f = new Foo();
$anon = function($obj, $string){ $obj->say($string); };
$f->addExtension('example', $anon);
$f->example("Hello, world!");
You'll note in __call and that in creating the anonymous function that the first argument becomes the instance. That's because PHP 5.3's implementation of anonymous functions can't reference $this. That also means that they can't reference protected or private members of the class. This can be corrected by cloning the instance and using Reflection to expose the protected and private members. See this comment on the anonymous function manual page for an example implementation.
Because of limitations of PHP, you can't directly assign an anonymous function to a property and call it. This example will not work:
class WillNotWork {
public $fatal_error;
}
$broken = new WillNotWork();
$anon = function($arg) { echo "I'm about to create a {$arg} error!"; };
$broken->fatal_error = $anon;
$broken->fatal_error("fatal");
// Fatal error: Call to undefined method WillNotWork::fatal_error()
No, because $a is a separate instance than $b.