web-dev-qa-db-fra.com

Type casting pour les objets définis par l'utilisateur

Comme avec __ToString, existe-t-il un moyen de définir une méthode de transtypage?

$obj = (MyClass) $another_class_obj;
50
Arsham

Il n'est pas nécessaire de taper cast dans php.


Edit: Étant donné que ce sujet semble semer la confusion, je pensais élaborer un peu.

Dans des langages tels que Java, deux choses peuvent être typées. Le compilateur a une notion sur le type et le temps d'exécution a une autre idée sur les types. Les types de compilateurs sont liés à des variables, tandis que le moteur d’exécution suit le type de valeurs (affectées à des variables). Les types de variable sont connus au moment de la compilation, tandis que les types de valeur ne sont connus qu'au moment de l'exécution.

Si une partie du code d'entrée viole le système de types du compilateur, le compilateur bloquera et arrêtera la compilation. En d'autres termes, il est impossible de compiler un morceau de code qui enfreint le système de types statique. Cela intercepte une certaine classe d’erreurs. Par exemple, prenons le code Java (simplifié) suivant:

class Alpha {}

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

Si nous faisions maintenant ceci:

Alpha a = new Beta();

ça irait, puisque Beta est une sous-classe de Alpha, et donc une valeur valide pour la variable a de type Alpha. Cependant, si nous procédons à faire:

a.sayHello();

Le compilateur donnerait une erreur, car la méthode sayHello n'est pas une méthode valide pour Alpha - Même si nous savons que a est en fait une Beta.

Entrez le type casting:

((Beta) a).sayHello();

Ici, nous disons au compilateur que la variable a devrait - dans ce cas-ci - être traitée comme une Beta. Ceci est connu sous le nom de type casting. Cette lacune est très utile, car elle permet un polymorphisme dans le langage, mais c’est évidemment aussi une porte dérobée pour toutes sortes de violations du système de types. Afin de maintenir un type de sécurité, il y a donc des restrictions; Vous ne pouvez attribuer que des types liés. Par exemple. monter ou descendre une hiérarchie. En d'autres termes, vous ne pourriez pas transtyper vers une classe complètement indépendante Charlie.

Il est important de noter que tout cela se passe dans le compilateur - c’est-à-dire que cela se produit avant même que le code ne soit exécuté. Java peut toujours entrer pour exécuter des erreurs de type temps. Par exemple, si vous avez fait ceci:

class Alpha {}

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

class Charlie extends Alpha {}

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

Le code ci-dessus est valide pour le compilateur, mais au moment de l'exécution, vous obtiendrez une exception, car la conversion de Beta à Charlie est incompatible.

Pendant ce temps, retour à la ferme PHP.

Ce qui suit est valable pour le compilateur PHP - Cela transformera volontiers ceci en code d'octet exécutable, mais vous obtiendrez une erreur d'exécution:

class Alpha {}

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

En effet, les variables PHP n'ont pas de type. Le compilateur n'a aucune idée des types d'exécution valides pour une variable. Il n'essaie donc pas de l'appliquer. Vous ne spécifiez pas le type explicitement comme en Java non plus. Oui, il y a des indications de type, mais il s'agit simplement de contrats à durée déterminée. Ce qui suit est toujours valable:

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

Même si PHP variables n'ont pas de types, les values ​​ le sont toujours. Un aspect intéressant de PHP est qu'il est possible de changer le type d'une valeur. Par exemple:

// 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"

Cette fonctionnalité a parfois été confondue avec le transtypage, mais c'est un terme impropre. Le type est toujours une propriété de la valeur et la modification du type a lieu au moment de l'exécution, pas au moment de la compilation.

La possibilité de changer de type est également assez limitée en PHP. Il est uniquement possible de changer le type entre des types simples - pas des objets. Ainsi, il n'est pas possible de changer le type d'une classe à une autre. Vous pouvez créer un nouvel objet et copier l'état, mais modifier le type n'est pas possible. PHP est un peu un outsider à cet égard; D'autres langages similaires traitent les classes comme un concept beaucoup plus dynamique que PHP.

Une autre caractéristique similaire de PHP est que vous pouvez cloner une valeur sous un nouveau type, comme ceci:

// 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"

En termes de syntaxe, cela ressemble beaucoup à la façon dont un transtypage est écrit dans des langages statiquement typés. Il est donc aussi souvent confondu avec le transtypage, même s’il s’agit toujours d’une conversion de type à l’exécution.

Pour résumer: La transtypage est une opération qui modifie le type d'une variable (pas la valeur). Puisque les variables ne sont pas typées en PHP, il est non seulement impossible de le faire, mais aussi absurde de demander.

99
troelskn

Voici une fonction pour changer la classe d'un objet:

/**
 * 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)));
    }
}

Au cas où ce n’est pas clair, ce n’est pas ma fonction, il est tiré d’un article de "toma at smartsemantics dot com" sur http://www.php.net/manual/fr/language.types.type-juggling .php # 50791

7
Josh

Bien qu'il ne soit pas nécessaire de taper cast en PHP, il est possible que vous souhaitiez convertir un objet parent en objet enfant.

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

Donc, tout ce que vous faites est de transmettre l'objet parent au constructeur de l'objet enfant et de laisser le constructeur copier les propriétés. Vous pouvez même les filtrer/modifier au besoin.

Avancée

//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;
        }
    }
}

Ce n'est pas du typage, mais il fait ce dont vous avez besoin.

7
Jrgns

J'ai retravaillé la fonction publiée par Josh (ce qui entraînera une erreur en raison de la variable indéfinie $ new_class). Voici ce que j'ai eu:

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;
}

Cela fonctionne comme vous le souhaiteriez pour un casting de classe. Vous pouvez créer quelque chose de similaire pour accéder aux membres de la classe - mais je ne pense pas que j'en aurais besoin, alors je laisserai cela à quelqu'un d'autre.

Boo à tous les saccadés qui disent "php ne lance pas" ou "vous n'avez pas besoin de lancer en php". Bullhockey. Le casting est une partie importante de la vie orientée objet, et je souhaiterais pouvoir trouver un meilleur moyen de le faire que les hacks de sérialisation laids.

Alors merci Josh!

7
B T

Si vous recherchez un indice de type, c'est ce que vous faites.

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

Oui.

1
DontBreakIt

Je ne crois pas qu'il y ait un opérateur surchargé dans PHP pour gérer cela, cependant:

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

Est un moyen de le gérer.

1
gnarf

Je pense que vous avez besoin de dactylographier le cast afin de créer un meilleur IDE. Mais php le langage lui-même n'a pas besoin de transtypage, il supporte toutefois les modifications de type à l'exécution des valeurs dans les variables. Jetez un coup d'œil à l'autoboxing et au unboxing. C'est ce que php fait intrinsèquement. Donc, désolé, pas mieux que ne le sont déjà les IDE.

1
Syed Zeeshan Shah

Je pense que vous voulez dire Type-Hinting .

A partir de PHP 7.2, vous pouvez taper des arguments de type-indice dans les fonctions:

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

Mais vous ne pouvez pas le taper comme ceci:

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

L’alternative pour les objets de type-index alors qu’elle n’est pas implémentée officiellement en PHP est:

$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');
0
Lucas Bustamante

Même sur PHP 7.2 si vous essayez de transtypage simple, comme si

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

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


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

Vous aurez une erreur comme ça

Erreur d'analyse PHP: erreur de syntaxe, '$ foo' (T_VARIABLE) inattendu dans xxx.php à la ligne xxx

En gros, vous ne pouvez pas faire cela, mais détrompez-vous, vous souhaitiez peut-être uniquement une nouvelle fonction par-dessus d'autres fonctionnalités publiques de la classe?

Vous pouvez le faire en utilisant 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

Que faire si vous souhaitez obtenir une propriété protégée?

Vous pouvez faire cela aussi

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

Tu auras

un

Et vous pouvez obtenir_protégé de la même manière

0
Yevgeniy Afanasyev