Type casting for user defined objects

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

$obj = (MyClass) $another_class_obj;

Answers


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.


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.


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


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!


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.


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.


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.


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');

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


Need Your Help

Angular 2 / 4 / 5 not working in IE11

angular typescript internet-explorer

I am trying to figure out why my angular 2 app is stuck on showing Loading... when running in IE 11.

How to use Python to login to a webpage and retrieve cookies for later usage?

python http authentication cookies

I want to download and parse webpage using python, but to access it I need a couple of cookies set. Therefore I need to login over https to the webpage first. The login moment involves sending two ...