web-dev-qa-db-fra.com

PHP - Modification indirecte d'une propriété surchargée

Je sais que cette question a été posée à plusieurs reprises, mais aucune d’entre elles n’a de véritable solution de rechange. Peut-être qu'il y en a un pour mon cas particulier.

Je construis une classe de mappeur qui utilise la méthode magique __get() pour charger paresseusement d'autres objets. Cela ressemble à quelque chose comme ça:

public function __get ( $index )
{
    if ( isset ($this->vars[$index]) )
    {
        return $this->vars[$index];
    }

    // $index = 'role';
    $obj = $this->createNewObject ( $index );

    return $obj;
}

Dans mon code je fais:

$user = createObject('user');
$user->role->rolename;

Cela fonctionne jusqu'à présent. L'objet User n'a pas de propriété appelée 'rôle', il utilise donc la méthode magique __get() pour créer cet objet et renvoie sa propriété à partir de l'objet 'role'.

Mais quand j'essaie de modifier le 'nom de rôle':

$user = createUser();
$user->role->rolename = 'Test';

Ensuite, cela me donne l'erreur suivante:

Remarque: la modification indirecte de la propriété surchargée est sans effet

Je ne sais pas s'il s'agit toujours d'un bogue dans PHP ou s'il s'agit d'un "comportement attendu", mais dans tous les cas, cela ne fonctionne pas comme je le souhaite. C'est vraiment un spectacle pour moi. .. Comment puis-je changer les propriétés des objets chargés paresseux?


MODIFIER:

Le problème réel ne semble se produire que lorsque je retourne un tableau contenant plusieurs objets.

J'ai ajouté un exemple de code qui reproduit le problème:

http://codepad.org/T1iPZm9t

Vous devriez vraiment exécuter ceci dans votre environnement PHP, voyez vraiment l'erreur). Mais il y a quelque chose de vraiment intéressant qui se passe ici.

J'essaie de changer la propriété d'un objet, ce qui me donne la mention 'ne peut pas changer la propriété surchargée'. Mais si je répète la propriété après cela, je constate qu'elle DID change la valeur ... vraiment bizarre ...

51
w00

Sympa tu m'as donné quelque chose à jouer avec

Courir

class Sample extends Creator {

}

$a = new Sample ();
$a->role->rolename = 'test';
echo  $a->role->rolename , PHP_EOL;
$a->role->rolename->am->love->php = 'w00';
echo  $a->role->rolename  , PHP_EOL;
echo  $a->role->rolename->am->love->php   , PHP_EOL;

Sortie

test
test
w00

Classe utilisée

abstract class Creator {
    public function __get($name) {
        if (! isset ( $this->{$name} )) {
            $this->{$name} = new Value ( $name, null );
        }
        return $this->{$name};
    }

    public function __set($name, $value) {
        $this->{$name} = new Value ( $name, $value );
    }



}

class Value extends Creator {
    private $name;
    private $value;
    function __construct($name, $value) {
        $this->name = $name;
        $this->value = $value;
    }

    function __toString()
    {
        return (string) $this->value ;
    }
}      

Edit: New Array Support à la demande

class Sample extends Creator {

}

$a = new Sample ();
$a->role = array (
        "A",
        "B",
        "C" 
);


$a->role[0]->Nice = "OK" ;

print ($a->role[0]->Nice  . PHP_EOL);

$a->role[1]->Nice->ok = array("foo","bar","die");

print ($a->role[1]->Nice->ok[2]  . PHP_EOL);


$a->role[2]->Nice->raw = new stdClass();
$a->role[2]->Nice->raw->name = "baba" ;

print ($a->role[2]->Nice->raw->name. PHP_EOL);

Sortie

 Ok die baba

Classe modifiée

abstract class Creator {
    public function __get($name) {
        if (! isset ( $this->{$name} )) {
            $this->{$name} = new Value ( $name, null );
        }
        return $this->{$name};
    }

    public function __set($name, $value) {
        if (is_array ( $value )) {
            array_walk ( $value, function (&$item, $key) {
                $item = new Value ( $key, $item );
            } );
        }
        $this->{$name} = $value;

    }

}

class Value {
    private $name ;
    function __construct($name, $value) {
        $this->{$name} = $value;
        $this->name = $value ;
    }

    public function __get($name) {
        if (! isset ( $this->{$name} )) {
            $this->{$name} = new Value ( $name, null );
        }

        if ($name == $this->name) {
            return $this->value;
        }

        return $this->{$name};
    }

    public function __set($name, $value) {
        if (is_array ( $value )) {
            array_walk ( $value, function (&$item, $key) {
                $item = new Value ( $key, $item );
            } );
        }
        $this->{$name} = $value;
    }

    public function __toString() {
        return (string) $this->name ;
    }   
}
15
Baba

Tout ce que vous avez à faire est d’ajouter "&" devant votre fonction __get pour le transmettre comme référence:

public function &__get ( $index )

Lutté avec celui-ci pendant un moment.

89
VinnyD

J'ai eu la même erreur, sans tout votre code, il est difficile de déterminer exactement comment le réparer, mais cela est dû au fait que vous n'avez pas de fonction __set.

La façon dont je l’ai contourné par le passé est que j’ai fait des choses comme celle-ci:

$user = createUser();
$role = $user->role;
$role->rolename = 'Test';

maintenant si vous faites ceci:

echo $user->role->rolename;

vous devriez voir 'Test'

9
Phil W

Je recevais cet avis pour cela:

$var = reset($myClass->my_magic_property);

Cela l'a corrigé:

$tmp = $myClass->my_magic_property;
$var = reset($tmp);
3
Andrew

Bien que je sois très en retard dans cette discussion, j'ai pensé que cela pourrait être utile pour quelqu'un à l'avenir.

J'avais fait face à une situation similaire. La solution de contournement la plus simple pour ceux qui ne craignent pas de réinitialiser et de réinitialiser la variable consiste à le faire. Je suis à peu près sûr que la raison pour laquelle cela ne fonctionne pas est clairement indiquée dans les autres réponses et dans le manuel php.net. La solution la plus simple a fonctionné pour moi est

Supposition:

  1. $object est l’objet surchargé __get et __set de la classe de base, que je ne suis pas libre de modifier.
  2. shippingData est le tableau dont je veux modifier un champ, par exemple. :- numéro de téléphone

// First store the array in a local variable.
$tempShippingData = $object->shippingData;

unset($object->shippingData);

$tempShippingData['phone_number'] = '888-666-0000' // what ever the value you want to set

$object->shippingData = $tempShippingData; // this will again call the __set and set the array variable

unset($tempShippingData);

Remarque: cette solution est l’une des solutions rapides permettant de résoudre le problème et de copier la variable. Si le tableau est trop énorme, il peut être utile de forcer la réécriture du __get méthode pour renvoyer une référence assez coûteuse à la copie de grands tableaux.

3
Althaf

J'ai rencontré le même problème que w00, mais je n'avais pas la liberté de réécrire les fonctionnalités de base du composant dans lequel ce problème (E_NOTICE) se produisait. J'ai pu résoudre le problème en utilisant un ArrayObject au lieu du type de base array () . Cela retournera un objet, qui sera renvoyé par défaut.

1
Klaas van der Weij

Je suis d’accord avec VinnyD que ce que vous devez faire, c’est d’ajouter "&" devant votre fonction __get, pour que le résultat voulu soit renvoyé comme référence:

public function &__get ( $propertyname )

Mais soyez conscient de deux choses:

1) Vous devriez aussi faire

return &$something;

ou vous retournez peut-être encore une valeur et non une référence ...

2) N'oubliez pas que dans tous les cas où __get renvoie une référence, cela signifie également que le __set correspondant ne sera JAMAIS appelé; c'est parce que php résout ce problème en utilisant la référence renvoyée par __get, appelée à la place!

Alors:

$var = $object->NonExistentArrayProperty; 

signifie que __get est appelé et, puisque __get a & __ get and return & $ quelque chose, $ var est maintenant, comme prévu, une référence à la propriété surchargée ...

$object->NonExistentArrayProperty = array(); 

fonctionne comme prévu et __set est appelé comme prévu ...

Mais:

$object->NonExistentArrayProperty[] = $value;

ou

$object->NonExistentArrayProperty["index"] = $value;

fonctionne comme prévu dans le sens où l'élément sera correctement ajouté ou modifié dans la propriété de tableau surchargé, MAIS __set NE SERA PAS APPELÉ: __get sera appelé à la place!

Ces deux appels NE fonctionneraient PAS s'ils n'utilisaient pas & __ obtenir et retourner quelque chose, mais ils fonctionnent de cette façon, mais ils n'appellent JAMAIS __set, mais ils appellent toujours __get.

C'est pourquoi j'ai décidé de renvoyer une référence

return &$something;

lorsque $ quelque chose est un tableau () ou lorsque la propriété surchargée n'a pas de méthode de définition spéciale et renvoie à la place une valeur

return $something;

quand $ quelque chose n'est PAS un tableau ou a une fonction spéciale de définition.

En tout cas, c'était assez difficile à comprendre pour moi! :)

1
Shores

Ceci est dû à la façon dont PHP traite les propriétés surchargées en ce sens qu'elles ne sont pas modifiables ni transmises par référence.

Voir le manuel pour plus d'informations sur la surcharge.

Pour contourner ce problème, vous pouvez soit utiliser un __set fonction ou créer une méthode createObject.

Ci-dessous, un __get et __set qui fournit une solution de contournement à une situation similaire à la vôtre, vous pouvez simplement modifier le __set pour répondre à vos besoins.

Noter la __get ne renvoie jamais réellement une variable. et plutôt une fois que vous avez défini une variable dans votre objet, elle n'est plus surchargée.

/**
 * Get a variable in the event.
 *
 * @param  mixed  $key  Variable name.
 *
 * @return  mixed|null
 */
public function __get($key)
{
    throw new \LogicException(sprintf(
        "Call to undefined event property %s",
        $key
    ));
}

/**
 * Set a variable in the event.
 *
 * @param  string  $key  Name of variable
 *
 * @param  mixed  $value  Value to variable
 *
 * @return  boolean  True
 */
public function __set($key, $value)
{
    if (stripos($key, '_') === 0 && isset($this->$key)) {
        throw new \LogicException(sprintf(
            "%s is a read-only event property", 
            $key
        ));
    }
    $this->$key = $value;
    return true;
}

Ce qui permettra:

$object = new obj();
$object->a = array();
$object->a[] = "b";
$object->v = new obj();
$object->v->a = "b";
1
Nick