How to Cast Objects in PHP
Asked Answered
B

13

68

Ive some classes that share some attributes, and i would like to do something like:

$dog = (Dog) $cat;

is it possible or is there any generic work around?

Its not a superclass, or a interface or related in any way. They are just 2 different classes i would like php map the attributes from a cat class to a dog and give me the new object. –

i guess i have to specify a little bit more cause seem like a senseless thing to do.

i've classes that inherits from different parent classes cause i've made an inheritance tree based on the saving method, maybe my bad from the beginning, but the problem is that i have a lot of classes that are practically equal but interacts one with mysql and the other one with xml files. so i have:

class MySql_SomeEntity extends SomeMysqlInteract{}

and

Xml_SomeEntity extends SomeXmlInteract{}

its a little bit deeper tree but the problem its that. i cant make them inherits from the same class cause multiple inheritance is not allowed, and i cant separate current interaction with superclases cause would be a big trouble.

Basically the attributes in each one are practical the same.

since i have a lot of this matching classes i would like to do some generic casting or something like it that can converts (pass the values to each attribute) and but im trying to search the simplest way to everyone of this classes.

Badillo answered 9/2, 2010 at 1:16 Comment(4)
@Ignacio, no I disagree. @Badillo is seeking for cast from inheritenace in PHP5, which I don't think it exist. $dog = (Dog) $four_legged_animal would be a better example.Oligarch
not quite. Its not a superclass, or a interface or related in any way. They are just 2 different clases i would like php map the attributes from a cat class to a dog and give me the new object.Badillo
On a sidenote as comment, if you going to do something like $dog = (Dog) $cat You shouldn't either: call your object Dog (or Cat) in the first place; or casting them across like such.Oligarch
Is there a language out there that will let you cast non-related objects? In Java it would give you ClassCastException... I think when objects are related, in PHP no special action is needed at all and you can just assign.Sadism
A
39

You can use above function for casting not similar class objects (PHP >= 5.3)

/**
 * Class casting
 *
 * @param string|object $destination
 * @param object $sourceObject
 * @return object
 */
function cast($destination, $sourceObject)
{
    if (is_string($destination)) {
        $destination = new $destination();
    }
    $sourceReflection = new ReflectionObject($sourceObject);
    $destinationReflection = new ReflectionObject($destination);
    $sourceProperties = $sourceReflection->getProperties();
    foreach ($sourceProperties as $sourceProperty) {
        $sourceProperty->setAccessible(true);
        $name = $sourceProperty->getName();
        $value = $sourceProperty->getValue($sourceObject);
        if ($destinationReflection->hasProperty($name)) {
            $propDest = $destinationReflection->getProperty($name);
            $propDest->setAccessible(true);
            $propDest->setValue($destination,$value);
        } else {
            $destination->$name = $value;
        }
    }
    return $destination;
}

EXAMPLE:

class A 
{
  private $_x;   
}

class B 
{
  public $_x;   
}

$a = new A();
$b = new B();

$x = cast('A',$b);
$x = cast('B',$a);
Actino answered 21/3, 2012 at 20:5 Comment(2)
Note: This does not handle inheritance at all. Say you wished to cast from one child object to another child object, it will not copy over the parent's properties.Haruspex
I think it's better without else { $destination->$name = $value; }Eglantine
B
29

There is no built-in method for type casting of user defined objects in PHP. That said, here are several possible solutions:

1) Use a function like the one below to deserialize the object, alter the string so that the properties you need are included in the new object once it's deserialized.

function cast($obj, $to_class) {
  if(class_exists($to_class)) {
    $obj_in = serialize($obj);
    $obj_out = 'O:' . strlen($to_class) . ':"' . $to_class . '":' . substr($obj_in, $obj_in[2] + 7);
    return unserialize($obj_out);
  }
  else
    return false;
}

2) Alternatively, you could copy the object's properties using reflection / manually iterating through them all or using get_object_vars().

This article should enlighten you on the "dark corners of PHP" and implementing typecasting on the user level.

Barracks answered 9/2, 2010 at 19:49 Comment(3)
+1 I have used this in the past to convert Cart to SavedCart in a MySQL user session table.Ventriloquism
We use this method and it works, that's for sure. But it is quite slow as serialize and unserialize are not trivial. We also suspect this to be cause of cryptic segfaults and php crashes. Also you should check whether the object isn't instance of desired class already, otherwise you waste quite a lot of resources, gave us lots of headaches as it creepily slowed our site twofold... So just be careful with this.Seminal
worst idea ever. TO ANYONE WHO READ THIS, please disregard this recommendation if you are trying to develop a professional application. This might be interesting to play around an maybe create a fun script to do things FOR YOU, but I see this extremely not recommendable to deliver it on a formal project. please see my post bellow to look into a better possible aproach.Almost
O
5

Without using inheritance (as mentioned by author), it seems like you are looking for a solution that can transform one class to another with preassumption of the developer knows and understand the similarity of 2 classes.

There's no existing solution for transforming between objects. What you can try out are:

Oligarch answered 9/2, 2010 at 1:34 Comment(1)
Is there a method when using inheritance?Plectognath
H
3

You do not need casting. Everything is dynamic.

I have a class Discount.
I have several classes that extends this class:
ProductDiscount
StoreDiscount
ShippingDiscount
...

Somewhere in the code I have:

$pd = new ProductDiscount();
$pd->setDiscount(5, ProductDiscount::PRODUCT_DISCOUNT_PERCENT);
$pd->setProductId(1);

$this->discounts[] = $pd;

.....

$sd = new StoreDiscount();
$sd->setDiscount(5, StoreDiscount::STORE_DISCOUNT_PERCENT);
$sd->setStoreId(1);

$this->discounts[] = $sd;

And somewhere I have:

foreach ($this->discounts as $discount){

    if ($discount->getDiscountType()==Discount::DISCOUNT_TYPE_PRODUCT){

        $productDiscount = $discount; // you do not need casting.
        $amount = $productDiscount->getDiscountAmount($this->getItemTotalPrice());
        ...
    }

}// foreach

Where getDiscountAmount is ProductDiscount specific function, and getDiscountType is Discount specific function.

Hampshire answered 10/9, 2010 at 12:56 Comment(1)
You only use features of "Discount", you cannot use features of "ProductDiscount" in your sample. This is no cast, you only named your "Discount" object "ProductDiscount" which now still is a "Discount". Do a var_dump();Peroxide
A
3

a better aproach:

class Animal
{
    private $_name = null;

    public function __construct($name = null)
    {
        $this->_name = $name;
    }

    /**
     * casts object
     * @param Animal $to
     * @return Animal
     */
    public function cast($to)
    {
        if ($to instanceof Animal) {
            $to->_name = $this->_name;
        } else {
            throw(new Exception('cant cast ' . get_class($this) . ' to ' . get_class($to)));
        return $to;
    }

    public function getName()
    {
        return $this->_name;
    }
}

class Cat extends Animal
{
    private $_preferedKindOfFish = null;

    public function __construct($name = null, $preferedKindOfFish = null)
    {
        parent::__construct($name);
        $this->_preferedKindOfFish = $preferedKindOfFish;
    }

    /**
     * casts object
     * @param Animal $to
     * @return Animal
     */
    public function cast($to)
    {
        parent::cast($to);
        if ($to instanceof Cat) {
            $to->_preferedKindOfFish = $this->_preferedKindOfFish;
        }
        return $to;
    }

    public function getPreferedKindOfFish()
    {
        return $this->_preferedKindOfFish;
    }
}

class Dog extends Animal
{
    private $_preferedKindOfCat = null;

    public function __construct($name = null, $preferedKindOfCat = null)
    {
        parent::__construct($name);
        $this->_preferedKindOfCat = $preferedKindOfCat;
    }

    /**
     * casts object
     * @param Animal $to
     * @return Animal
     */
    public function cast($to)
    {
        parent::cast($to);
        if ($to instanceof Dog) {
            $to->_preferedKindOfCat = $this->_preferedKindOfCat;
        }
        return $to;
    }

    public function getPreferedKindOfCat()
    {
        return $this->_preferedKindOfCat;
    }
}

$dogs = array(
    new Dog('snoopy', 'vegetarian'),
    new Dog('coyote', 'any'),
);

foreach ($dogs as $dog) {
    $cat = $dog->cast(new Cat());
    echo get_class($cat) . ' - ' . $cat->getName() . "\n";
}
Almost answered 7/3, 2013 at 8:33 Comment(2)
Please check my edit. I added several lines that will allow us to dynamically get the values of all properties from source to destination object, instead of doing it manually. But yehp, I prefer your answer, instead of the one on top :)Usually
@AllenLinatoc Your edit would have been rejected because you were substantially changing the original answer. A better approach would have been to post your own answer with your improvements while giving credit to useless for coming up with the original code.Mascarenas
F
2

It sounds like what you really want to do is implement an interface.

Your interface will specify the methods that the object can handle and when you pass an object that implements the interface to a method that wants an object that supports the interface, you just type the argument with the name of the interface.

Fragonard answered 28/5, 2012 at 22:14 Comment(0)
L
0

You may think about factories

class XyFactory {
    public function createXyObject ($other) {
        $new = new XyObject($other->someValue);
        // Do other things, that let $new look like $other (except the used class)
        return $new;
    }
}

Otherwise user250120s solution is the only one, which comes close to class casting.

Laurent answered 29/11, 2010 at 11:2 Comment(0)
S
0
class It {
    public $a = '';

    public function __construct($a) {
        $this->a = $a;
    }
    public function printIt() {
        ;
    }
}

//contains static function to 'convert' instance of parent It to sub-class instance of Thing

class Thing extends it {
    public $b = '';

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }
    public function printThing() {
        echo $this->a . $this->b;
    }
        //static function housed by target class since trying to create an instance of Thing
    static function thingFromIt(It $it, $b) {
        return new Thing($it->a, $b);
    }
}


//create an instance of It
$it = new It('1');

//create an instance of Thing 
$thing = Thing::thingFromIt($it, '2');


echo 'Class for $it: ' . get_class($it);
echo 'Class for $thing: ' . get_class($thing);

Returns:

Class for $it: It
Class for $thing: Thing
Suchta answered 7/5, 2012 at 16:38 Comment(1)
This doesn't really hit the mark - and it's already got a solid answer.Gentilis
C
0

If the object you are trying to cast from or to has properties that are also user-defined classes, and you don't want to go through reflection, you can use this.

<?php
declare(strict_types=1);
namespace Your\Namespace\Here
{
  use Zend\Logger; // or your logging mechanism of choice
  final class OopFunctions
  {
    /**
     * @param object $from
     * @param object $to
     * @param Logger $logger
     *
     * @return object
     */
     static function Cast($from, $to, $logger)
    {
      $logger->debug($from);
      $fromSerialized = serialize($from);
      $fromName = get_class($from);
      $toName = get_class($to);
      $toSerialized = str_replace($fromName, $toName, $fromSerialized);
      $toSerialized = preg_replace("/O:\d*:\"([^\"]*)/", "O:" . strlen($toName) . ":\"$1", $toSerialized);
      $toSerialized = preg_replace_callback(
        "/s:\d*:\"[^\"]*\"/", 
        function ($matches)
        {
          $arr = explode(":", $matches[0]);
          $arr[1] = mb_strlen($arr[2]) - 2;
          return implode(":", $arr);
        }, 
        $toSerialized
      );
      $to = unserialize($toSerialized);
      $logger->debug($to);
      return $to;
    }
  }
}
Carrnan answered 5/1, 2018 at 15:11 Comment(0)
I
0

You can opt for this example below. Hope it will help.

/** @var ClassName $object */

$object->whateverMethod() // any method defined in the class can be accessed by $object

I know this is not a cast but it can be useful sometimes.

Ibsen answered 25/7, 2020 at 9:42 Comment(0)
G
0

I use the following trait

<?php
declare( strict_types = 1 );

// @author Grzegorz Tadeusz Kus <[email protected]>
namespace App\Util\Cast;

trait ObjectCast {

  public static function cast( mixed $data ): static {

    if ( $data instanceof static ) { return $data; }

    throw new ObjectCastException( $data, get_called_class() );
  }
}

With the following class:

<?php
declare( strict_types = 1 );

namespace App\Util\Cast;

use RuntimeException;

class ObjectCastException extends RuntimeException {

  public function __construct( mixed $data, string $expectedClassname ) {

    parent::__construct(
      'Expected instance of ' . self::short( $expectedClassname ) .
      ' but got instance of ' . self::short( $data->className() )
    );
  }

  private static function short( string $name ): string {

    return substr( strrchr( $name, '\\' ), 1 );
  }
}

In each class that I want to have the ability to cast, I put 'use ObjectCast', like this:

class LocationData extends _Data {

  use ObjectCast;

And then I can make a cast (anywhere in application code):

$locData = LocationData::cast( $data );

At this point, the IDE and the runtime know that $locData is of LocationData type. Like

locData = (LocationData) data;

in Java.

Gatekeeper answered 29/4 at 14:26 Comment(0)
M
-1

I think that the best approach is to just create a new instance of a class and than assign the object. Here's what I would do:

public function ($someVO) {

     $someCastVO = new SomeVO();
     $someCastVO = $someVO;
     $someCastVO->SomePropertyInVO = "123";

}

Doing this will give you code hinting in most IDEs and help ensure you are using the correct properties.

Maud answered 23/11, 2012 at 19:36 Comment(2)
Is that magic? No function name, no return value? And second statement does not override first one?Nonpartisan
@Nonpartisan The second statement does indeed overwrite the value of the variable. The first statement is only there so your IDE will know what class the object is to enable code completion and hinting to work. However, it does nothing to ensure that $someVO is of the correct type. It doesn't even have to be a class, although that will cause errors very quickly, e.g. in the third statement. This does not cast the object at all.Mascarenas
L
-5

PHP provides a very simple way of doing this by using:

(object) ['id'=>1,'name'=>'cat']

https://www.php.net/manual/en/language.types.object.php

In your case you try this:

$dog = json_encode($dog);

$cat = (object) json_decode($dog)

More optimize method is:

$dog = (array)$dog;
$dog['newfield'] = 'xyz';
$dog = (object)$dog;
Lipetsk answered 9/12, 2019 at 17:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.