58

Just like we do with __ToString, is there a way to define a method for casting?

$obj = (MyClass) $another_class_obj;
Charles
  • 50,943
  • 13
  • 104
  • 142
Arsham
  • 1,432
  • 3
  • 19
  • 28
  • 5
    Why would you ever want to cast anything in PHP? You can just call whatever methods you want anyway. – n3rd Jul 18 '09 at 11:14
  • 6
    You can't call those methods if they don't exist in the class -- you may need to typecast one user class to another in order to call the functions directly using the -> operator – Josh Jul 18 '09 at 15:47
  • Josh, in that case, there must be something wrong in the application flow. – Ionuț G. Stan Jul 18 '09 at 19:25
  • I feel your judgement may be hasty; however I would agree that gnarf's solution for creating a new object is better as it allows logic for converting between classes. – Josh Jul 18 '09 at 20:59

12 Answers12

106

There is no need to type cast in php.


Edit: Since this topic seems to cause some confusion, I thought I'd elaborate a little.

In languages such as Java, there are two things that may carry type. The compiler has a notion about type, and the run time has another idea about types. The compilers types are tied to variables, whereas the run time engine tracks the type of values (Which are assigned to variables). The variable types are known at compile time, whereas the value types are only known at run time.

If a piece of input code violates the compilers type system, the compiler will barf and halt compilation. In other words, it's impossible to compile a piece of code that violates the static type system. This catches a certain class of errors. For example, take the following piece of (simplified) Java code:

class Alpha {}

class Beta extends Alpha {
  public void sayHello() {
    System.out.println("Hello");
  }
}

If we now did this:

Alpha a = new Beta();

we would be fine, since Beta is a subclass of Alpha, and therefore a valid value for the variable a of type Alpha. However, if we proceed to do:

a.sayHello();

The compiler would give an error, since the method sayHello isn't a valid method for Alpha - Regardless that we know that a is actually a Beta.

Enter type casting:

((Beta) a).sayHello();

Here we tell the compiler that the variable a should - in this case - be treated as a Beta. This is known as type casting. This loophole is very useful, because it allows polymorphism in the language, but obviously it is also a back door for all sorts of violations of the type system. In order to maintain some type safety, there are therefore some restrictions; You can only cast to types that are related. Eg. up or down a hierarchy. In other words, you wouldn't be able to cast to a completely unrelated class Charlie.

It's important to note that all this happens in the compiler - That is, it happens before the code even runs. Java can still get in to run time type errors. For example, if you did this:

class Alpha {}

class Beta extends Alpha {
  public void sayHello() {
    System.out.println("Hello");
  }
}

class Charlie extends Alpha {}

Alpha a = new Charlie();
((Beta) a).sayHello();

The above code is valid for the compiler, but at run time, you'll get an exception, since the cast from Beta to Charlie is incompatible.

Meanwhile, back at the PHP-farm.

The following is valid to the PHP-compiler - It'll happily turn this into executable byte code, but you'll get a run time error:

class Alpha {}

class Beta extends Alpha {
  function sayHello() {
    print "Hello";
  }
}
$a = new Alpha();
$a->sayHello();

This is because PHP variables don't have type. The compiler has no idea about what run time types are valid for a variable, so it doesn't try to enforce it. You don't specify the type explicitly as in Java either. There are type hints, yes, but these are simply run time contracts. The following is still valid:

// reuse the classes from above
function tellToSayHello(Alpha $a) {
  $a->sayHello();
}
tellToSayHello(new Beta());

Even though PHP variables don't have types, the values still do. A particular interesting aspect of PHP, is that it is possible to change the type of a value. For example:

// The variable $foo holds a value with the type of string
$foo = "42";
echo gettype($foo); // Yields "string"
// Here we change the type from string -> integer
settype($foo, "integer");
echo gettype($foo); // Yields "integer"

This feature some times confused with type casting, but that is a misnomer. The type is still a property of the value, and the type-change happens in runtime - not at compile time.

The ability to change type is also quite limited in PHP. It is only possible to change type between simple types - not objects. Thus, it isn't possible to change the type from one class to another. You can create a new object and copy the state, but changing the type isn't possible. PHP is a bit of an outsider in this respect; Other similar languages treat classes as a much more dynamic concept than PHP does.

Another similar feature of PHP is that you can clone a value as a new type, like this:

// The variable $foo holds a value with the type of string
$foo = "42";
echo gettype($foo); // Yields "string"
// Here we change the type from string -> integer
$bar = (integer) $foo;
echo gettype($bar); // Yields "integer"

Syntactically this looks a lot like how a typecast is written in statically typed languages. It's therefore also often confused with type casting, even though it is still a runtime type-conversion.

To summarise: Type casting is an operation that changes the type of a variable (not the value). Since variables are without type in PHP, it is not only impossible to do, but a nonsensical thing to ask in the first place.

troelskn
  • 115,121
  • 27
  • 131
  • 155
  • There is if you want to go from one user defined class to another. There's a way to do this, I've done it before – Josh Jul 18 '09 at 12:21
  • 4
    Well then, don't keep it to yourself. – VolkerK Jul 18 '09 at 12:24
  • 2
    Variables in PHP are untyped; Only the objects they refer to have types. As such you *can't* typecast a variable in php. – troelskn Jul 18 '09 at 17:35
  • You're wrong troelskn. There is clearly casting ability for primitives. Casting objects is obviously hellish - but it shouldn't be. Telling us that we're wrong, and that the php manual is wrong is just silly. – B T Aug 19 '09 at 01:40
  • I realise that my first answer was a bit short. I've added an explanation that hopefully helps to show why the concept of type casting is alien to php. – troelskn Aug 20 '09 at 09:02
  • 2
    This is an excellent answer. I feel you could improve it by mentioning that in PHP values have types. This is interesting because the value could have its type changed - an Alpha object could be told "become a Beta object". Python and Javascript support this for example (in Python by changing its `__class__` field). However, PHP does not, and has a static class hierarchy. So while it would make sense to allow a PHP value to change its type, it is just not allowed in the language. – Paul Biggar Sep 26 '09 at 13:14
  • Good idea, Paul. I've added a section about that. – troelskn Sep 27 '09 at 10:53
  • 1
    troelskn, I agree, this is an excellent answer and very clearly explains typecasting and the lack of it in PHP. – Josh Sep 29 '09 at 12:12
  • I tried the php code example: $a->sayHello(); and it throws PHP Fatal error: Call to undefined method Alpha::sayHello(). my php version is 5.3.10. So I think that behaivour has changed. – elviejo79 Aug 23 '12 at 03:59
  • 1
    @elviejo No, that's the point. You get a runtime error, but not a compile time one. – troelskn Aug 23 '12 at 09:48
  • Great, thanks, valuable information and well explained. If I could I would vote it up 2. Thought now I have to work at copying my object instead. :P – Stéphan Champagne Mar 12 '13 at 18:44
  • A variable is simply a construct to access data that is stored in memory. If php variables didn't have types how would the interpreter know the difference between a var whose data is stored as a float in memory versus a var whose data is stored as an integer in memory? In php a float is stored in memory as specified by IEEE 754 double-precision (11 bits for the exponent and 52 for the fractional part). In php integers are, more than likely, represented in memory using two's complement. Ergo, variables in php must have some sort of symbol table/tree that keeps track of variable type. – elitechief21 Jan 16 '14 at 20:17
  • You are correct that classes can't be cast, but php most certainly supports type casting for the primitives. And in fact `settype, (integer), etc` are examples of ways to type cast in php, in that they are all methods of explicitly converting one type of data to another. `settype` in particular directly sets the type of the variable to a different type, your example of converting the string `"42"` to an integer is a perfect example of this because it explicitly takes the binary string data stored in memory and converts it to an integer and the new type of the variable becomes an integer. – elitechief21 Jan 16 '14 at 20:28
  • You say there is no need to cast. What if you have the function `sayHello` in your `Alpha` and also `Beta` class (with a different implementation), but you want to specifically call the function from alpha. `$beta = new Beta();` And I want sth like `(Alpha)$beta->sayHello()` to specifiacally invoke the function from the parent. – Asped Sep 16 '15 at 06:01
  • @Asped To the best of my knowledge, you can't do that in PHP. I can't think of any situation where that would be a sane thing to do anyway, so I don't think it's a big loss. – troelskn Sep 17 '15 at 06:21
  • The summary "There is no *need* to type cast in php." is a bit misleading because there's still a need to type cast to get autocompletion right when a method can return multiple types, it's just that there is now way to do it in PHP. – AndreKR Mar 27 '16 at 17:34
  • Because of type hinting there is a need to type cast. Also, since php 7 there is now strict mode which uses strong typing so this answer is out of date. – John Hunt Oct 24 '16 at 13:53
  • 1
    I disagree, though at the time of writing it is true type hinting was not really a thing in PHP. This aside, this forgets the need for classes that do not extend from each other to have type casting rules, which allows for a wide range of extra programming models – That Realty Programmer Guy Jul 21 '18 at 19:38
  • This is at least no longer the case with PHP7 - we have types now, argument and result types. Thus casting becomes a far more important topic as it used to be back in the days, when this answer was written. I downvoted just because it is not valid today ( but it was back then ) – Eugen Mayer Oct 18 '19 at 08:48
9

Although there's no need to type cast in PHP, you might come across a situation where you would like to convert a parent object into a child object.

Simple

//Example of a sub class
class YourObject extends MyObject {
    public function __construct(MyObject $object) {
        foreach($object as $property => $value) {
            $this->$property = $value;
        }
    }
}


$my_object = new MyObject();
$your_object = new YourObject($my_object);

So all you do is pass the parent object down to the child object's constructor, and let the constructor copy over the properties. You can even filter / change them as needed.

Advanced

//Class to return standard objects
class Factory {
    public static function getObject() {
        $object = new MyObject();
        return $object;
    }
}

//Class to return different object depending on the type property
class SubFactory extends Factory {
    public static function getObject() {
        $object = parent::getObject();
        switch($object->type) {
        case 'yours':
            $object = new YourObject($object);
            break;
        case 'ours':
            $object = new OurObject($object);
            break;
        }
        return $object;
    }
}

//Example of a sub class
class YourObject extends MyObject {
    public function __construct(MyObject $object) {
        foreach($object as $property => $value) {
            $this->$property = $value;
        }
    }
}

It's not type casting, but it does what you need.

Jrgns
  • 24,699
  • 18
  • 71
  • 77
7

I reworked the function Josh posted (which will error because of the undefined $new_class variable). Here's what I got:

function changeClass(&$obj, $newClass)
{   $obj = unserialize(preg_replace // change object into type $new_class
    (   "/^O:[0-9]+:\"[^\"]+\":/i", 
        "O:".strlen($newClass).":\"".$newClass."\":", 
        serialize($obj)
    ));
}

function classCast_callMethod(&$obj, $newClass, $methodName, $methodArgs=array())
{   $oldClass = get_class($obj);
    changeClass($obj, $newClass);

    // get result of method call
    $result = call_user_func_array(array($obj, $methodName), $methodArgs);
    changeClass(&$obj, $oldClass);  // change back
    return $result;
}

It works just like you'd expect a class cast to work. You could build something similar for accessing class members - but I don't think I would ever need that, so i'll leave it to someone else.

Boo to all the jerks that say "php doesn't cast" or "you don't need to cast in php". Bullhockey. Casting is an important part of object oriented life, and I wish I could find a better way to do it than ugly serialization hacks.

So thank you Josh!

B T
  • 57,525
  • 34
  • 189
  • 207
  • Just a note, I would add a thrown exception into changeClass if the class casted to is not a parent class. This would include classes that don't exist. But the code i have above is pretty much the simplest you can get for a working cast that I know of. – B T Aug 19 '09 at 01:37
  • The only thing I would use the casting for in php, is to let the IDE know the type of the object.. so the IDE can autocomplete the properties of a class instance.. That's all. – Janov Byrnisson Aug 04 '10 at 08:56
  • Well thats all well and good for you Janov, but if you're building a complex interface that isn't incredibly verbose, you sometimes need a little trickery. I *had* to use class casting for a database abstraction layer I created. Otherwise there would have been no way to adequately handle subclassing. – B T Aug 05 '10 at 00:36
  • 1
    php doesnt cast means php have no native method to cast. Serialization deserialization a method to over come the issue. – nerkn Dec 19 '10 at 14:32
  • Is there a chance this needs to be modified for PHP5.3? – Prasad Jul 10 '17 at 05:36
6

Here's a function to change the class of an object:

/**
 * Change the class of an object
 *
 * @param object $obj
 * @param string $class_type
 * @author toma at smartsemantics dot com
 * @see http://www.php.net/manual/en/language.types.type-juggling.php#50791
 */
function changeClass(&$obj,$new_class)
{
    if(class_exists($class_type,true))
    {
        $obj = unserialize(preg_replace("/^O:[0-9]+:\"[^\"]+\":/i",
            "O:".strlen($class_type).":\"".$new_class."\":", serialize($obj)));
    }
}

In case it's not clear, this is not my function, it was taken from a post by "toma at smartsemantics dot com" on http://www.php.net/manual/en/language.types.type-juggling.php#50791

Josh
  • 10,961
  • 11
  • 65
  • 108
  • 3
    That is not a typecast. That is changing the type of the object - not the variable. In PHP, you can't typecast, since variables don't have types. – troelskn Jul 18 '09 at 17:33
  • 4
    I agree, my function changes the class of the object. WHich is what the question's author intended. And according to the PHP manual, variables *do* have types and typecasting is possible: http://us.php.net/manual/en/language.types.type-juggling.php#language.types.typecasting – Josh Jul 18 '09 at 20:56
  • Fair enough; You can change the type of a primitive. But you can't change the type of an object. Regardless of the wording in the manual, variables do *not* have type in php. They refer to values which have type. – troelskn Jul 18 '09 at 21:59
  • I see your point -- I'll edit my post to make it clear tha it changes the class of an object. – Josh Jul 18 '09 at 22:59
  • Well it doesn't really. It creates a new object of a different class. In a typed language (Such as Java), that is not what a type cast does. There, a type cast simple changes the type of the variable - not the type of the object being referred. There is an (albeit subtle) difference. For example, you can only typecast to types that are valid for the object. You can't typecast to a completely unrelated type. – troelskn Jul 19 '09 at 14:36
  • I understand what you're saying. – Josh Jul 19 '09 at 14:53
  • 3
    Why so many upvotes? The original function by toma has a bug (he missed the `(` after `preg_replace`), but this one can't even work correctly. You used the variable `$class_type` which is never defined. Maybe I'm missing something but in my point of view this looks like making buggy code even worse. – MaBi Aug 29 '15 at 14:10
  • Not to mention, the class in your sample relies on the need to be serializable, without losing data during the process. – Gelmir Nov 26 '18 at 12:57
4

If casting for type hinting is all you're after, this works.

if( is_object($dum_class_u_want) && $dum_class_u_want instanceof ClassYouWant )
{
    // type hints working now
    $dum_class_u_want->is_smart_now();
}

Yep.

alx
  • 2,314
  • 2
  • 18
  • 22
DontBreakIt
  • 109
  • 2
  • 1
    No need to call `is_object()` before `instanceof` -- it works correctly for any input, including non-object scalars and nulls. – alx Oct 12 '18 at 17:53
  • thank you. my syntax type-hint checker was driving me crazy with "Expected type __. Found @TYield" when iterating over a Generator.. this did the trick – fbas Jun 08 '22 at 17:04
2

I do not believe there is a overloading operator in PHP to handle that, however:

<?php

class MyClass {

  protected $_number;

  static public function castFrom($obj) {
    $new = new self();
    if (is_int($obj)) {
      $new->_number = $obj;
    } else if ($obj instanceOf MyNumberClass){
      /// some other type of casting
    }
    return $new;
  }
}

$test = MyClass::castFrom(123123);
var_dump($test);

Is one way to handle it.

gnarf
  • 105,192
  • 25
  • 127
  • 161
1

I think you need to type cast in order to make a better IDE. But php the language itself doesn't need type casting it does however support runtime type changes to the values in the variables. Take a look at autoboxing and unboxing. That's what php inherently does. So sorry no better than already are IDEs.

0

I think you mean Type-Hinting.

As of PHP 7.2, you can type-hint arguments in functions:

function something(Some_Object $argument) {...} # Type-hinting object on function arguments works on PHP 7.2+

But you can't type-hint it like this:

(Some_Object) $variable = get_some_object($id); # This does not work, even in PHP 7.2

The alternative for type-hinting objects while it isn't implemented officialy in PHP, is:

$variable = get_some_object($id); # We expect Some_Object to return
is_a($argument, 'Some_Object') || die('get_some_object() function didn't return Some_Object');
Lucas Bustamante
  • 15,821
  • 7
  • 92
  • 86
0

Even on PHP 7.2 if you try simple type casting, like so

class One
{
    protected $one = 'one';
}

class Two extends One
{
    public function funcOne()
    {
        echo $this->one;
    }
}


    $foo = new One();
    $foo = (Two) $foo;
    $foo->funcOne();

You'll get error like this

PHP Parse error: syntax error, unexpected '$foo' (T_VARIABLE) in xxx.php on line xxx

So you basically cannot do that, but think again, maybe you wanted only a new function on top of other public functionality of the class?

You can do that using Wrapper

class One
{
    protected $one;
    public function __construct(string $one)
    {
        $this->one = $one;
    }
    public function pub(string $par){
        echo (__CLASS__ . ' - ' . __FUNCTION__ . ' - ' . $this->one . '-' .$par);
    }
}

class Wrapper
{
    private $obj;

    public function __construct(One $obj)
    {
        $this->obj = $obj;
    }
    public function newFunction()
    {
        echo (__CLASS__ . ' - ' . __FUNCTION__);
    }
    public function __call($name, $arguments)
    {
        return call_user_func_array([$this->obj, $name], $arguments);
    }
}

    $foo = new One('one');
    $foo->pub('par1');
    $foo = new Wrapper($foo);
    $foo->pub('par2');
    $foo->newFunction();

One - pub - one-par1

One - pub - one-par2

Wrapper - newFunction

What if you want to get a protected property out?

You can do that too

class One
{
    protected $one;

    public function __construct(string $one)
    {
        $this->one = $one;
    }
}


    $foo = new One('one');
    $tmp = (new class ($foo) extends One {
            protected $obj;
            public function __construct(One $obj)
            {
                $this->obj = $obj;
                parent::__construct('two');
            }
            public function getProtectedOut()
            {
                return $this->obj->one;
            }
        } )->getProtectedOut();

    echo ($tmp);

You'll get

one

And you can get_protected_in the same way

Yevgeniy Afanasyev
  • 37,872
  • 26
  • 173
  • 191
0

I mainly need type casting to enable intellisense - so I just create a type cast helper:

function castToTest($val): Test
{
    return $val;
}
$test = castToTest(require("someFile.php"));

Returning from a file is kind of ugly and does not allow for type hinting, so this is a perfect example of how you can achieve intellisense by using a type cast helper.

Jimmy Thomsen
  • 471
  • 3
  • 9
0

You can create two reflection object then copy values to other example can be found at https://github.com/tarikflz/phpCast

    public static function cast($sourceObject)
{
    $destinationObject = new self();
    try {
        $reflectedSource = new ReflectionObject($sourceObject);
        $reflectedDestination = new ReflectionObject($destinationObject);
        $sourceProperties = $reflectedSource->getProperties();
        foreach ($sourceProperties as $sourceProperty) {
            $sourceProperty->setAccessible(true);
            $name = $sourceProperty->getName();
            $value = $sourceProperty->getValue($sourceObject);
            if ($reflectedDestination->hasProperty($name)) {
                $propDest = $reflectedDestination->getProperty($name);
                $propDest->setAccessible(true);
                $propDest->setValue($destinationObject, $value);
            } else {
                $destinationObject->$name = $value;
            }
        }
    } catch (ReflectionException $exception) {
        return $sourceObject;
    }
    return $destinationObject;
}
  • 1
    Please write a more complete answer. A one line sentence and link to outside StackOverflow is not very helpful for people looking for answers. – J. S. Jan 16 '20 at 13:26
0

The answer that has been chosen as correct is actually a wrong answer (or, the question is misinterpreted).

The correct interpretation of the question is related to the basic principle of Object-oriented Design Paradigm according to which a child-class shoul be able to assign to the variable of its parent class, or to the variable of an Interface that is implemented by the class.

As that is the basic of Object-oriented Design, one cant say that it is needed in language-A, but not required in langauge-B.

Correct answer is that PHP, to date, lacks that essential requiremens of OO-Design.

In order for the code to conform to A) Dependency Inversion Principle (DIP), and B) Interface Segregation Principle ("I" in S.O.L.I.D)

... PHP must allow typehinting a variable with the name of the interface that a concrete class implements, so that only interface-specific funtionality of the implementation (serving) class can be consumed.

That is the basic principle of Object-oriented Design and without ability to declare the type of a variable of specific type of interface code can not conform to (and can not enjoy the benefits of) DIP and Interface Segregation Principle.


Plus, that dynamic binding is always done at run time, not at compile time, so PHP should have faced no problem in provisioning of that essential element of OO-paradigm.

Fakhar Anwar
  • 21
  • 1
  • 4