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.

Nik
  • 2,885
  • 2
  • 25
  • 25
user267599
  • 789
  • 1
  • 5
  • 5
  • @Ignacio, no I disagree. @user267599 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. – Trav L Feb 09 '10 at 01:23
  • 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. – user267599 Feb 09 '10 at 01:27
  • 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. – Trav L Feb 09 '10 at 01:37
  • 1
    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. – Stijn de Witt Apr 04 '14 at 07:12

12 Answers12

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);
Adam Puza
  • 1,504
  • 13
  • 9
  • 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. – martixy Jul 13 '17 at 16:25
  • I think it's better without `else { $destination->$name = $value; }` – es cologne Dec 01 '20 at 14:38
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.

nico
  • 50,859
  • 17
  • 87
  • 112
user250120
  • 901
  • 1
  • 6
  • 8
  • +1 I have used this in the past to convert Cart to SavedCart in a MySQL user session table. – Theodore R. Smith Sep 10 '10 at 17:39
  • 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. – Josef Sábl Aug 25 '11 at 16:18
  • 35
    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. – useless Mar 07 '13 at 08:18
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:

Community
  • 1
  • 1
Trav L
  • 14,732
  • 6
  • 30
  • 39
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";
}
useless
  • 1,876
  • 17
  • 18
  • 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 :) – Allen Linatoc Sep 05 '15 at 07:13
  • 2
    @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. – CJ Dennis Oct 28 '16 at 23:39
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.

darpet
  • 3,073
  • 3
  • 33
  • 40
  • 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(); – Daniel Jun 16 '17 at 16:25
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.

kba
  • 19,333
  • 5
  • 62
  • 89
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
Flexo
  • 87,323
  • 22
  • 191
  • 272
0

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.

Peter O.
  • 32,158
  • 14
  • 82
  • 96
  • Is that magic? No function name, no return value? And second statement does not override first one? – robsch Jan 19 '15 at 13:16
  • 1
    @robsch 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. – CJ Dennis Oct 28 '16 at 23:36
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.

KingCrunch
  • 128,817
  • 21
  • 151
  • 173
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;
    }
  }
}
Justin
  • 541
  • 7
  • 15
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.

faye.babacar78
  • 774
  • 7
  • 12
-4

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;
justnajm
  • 4,422
  • 6
  • 36
  • 56