web-dev-qa-db-fra.com

Pouvez-vous créer des propriétés d'instance dynamiquement en PHP?

Est-il possible de créer toutes les propriétés d'instance de manière dynamique? Par exemple, j'aimerais pouvoir générer tous les attributs du constructeur et pouvoir y accéder une fois la classe instanciée, comme ceci: $object->property. Notez que je veux accéder aux propriétés séparément, sans utiliser de tableau; voici un exemple de ce que je ne pas veux:

class Thing {
    public $properties;
    function __construct(array $props=array()) {
        $this->properties = $props;
    }
}
$foo = new Thing(array('bar' => 'baz');
# I don't want to have to do this:
$foo->properties['bar'];
# I want to do this:
//$foo->bar;

Pour être plus précis, lorsque je traite avec des classes ayant un grand nombre de propriétés, j'aimerais pouvoir sélectionner toutes les colonnes d'une base de données (qui représentent les propriétés) et en créer des propriétés d'instance. Chaque valeur de colonne doit être stockée dans une propriété d'instance distincte.

58
Brayn

Sorte de. Il existe des méthodes magiques qui vous permettent de connecter votre propre code pour implémenter le comportement de classe au moment de l'exécution:

class foo {
  public function __get($name) {
    return('dynamic!');
  }
  public function __set($name, $value) {
    $this->internalData[$name] = $value;
  }
}

C'est un exemple pour les méthodes de lecture et d'acquisition dynamiques, il vous permet d'exécuter le comportement à chaque accès à une propriété d'objet. Par exemple

print(new foo()->someProperty);

serait imprimer, dans ce cas, "dynamique!" et vous pouvez également affecter une valeur à une propriété nommée de manière arbitraire, auquel cas la méthode __set () est appelée en mode silencieux. La méthode __call ($ name, $ params) fait la même chose pour les appels de méthodes d'objet. Très utile dans des cas particuliers. Mais la plupart du temps, vous vous débrouillerez avec:

class foo {
  public function __construct() {
    foreach(getSomeDataArray() as $k => $value)
      $this->{$k} = $value;
  }
}

... parce que la plupart du temps, tout ce dont vous avez besoin est de vider le contenu d'un tableau dans des champs de classe nommés de manière correspondante une fois, ou au moins à des points très explicites du chemin d'exécution. Donc, sauf si vous avez vraiment besoin d'un comportement dynamique, utilisez ce dernier exemple pour remplir vos objets avec des données.

C'est ce qu'on appelle la surcharge http://php.net/manual/en/language.oop5.overloading.php

61
Udo

Cela dépend exactement de ce que vous voulez. Pouvez-vous modifier le class dynamiquement? Pas vraiment. Mais pouvez-vous créer des propriétés object de manière dynamique, comme dans une instance particulière de cette classe? Oui.

class Test
{
    public function __construct($x)
    {
        $this->{$x} = "dynamic";
    }
}

$a = new Test("bar");
print $a->bar;

Les sorties:

dynamique

Ainsi, une propriété d'objet nommée "bar" a été créée dynamiquement dans le constructeur.

23
Chad Birch

Pourquoi chaque exemple est-il si compliqué?

<?php namespace example;

error_reporting(E_ALL | E_STRICT); 

class Foo
{
    // class completely empty
}

$testcase = new Foo();
$testcase->example = 'Dynamic property';
echo $testcase->example;
7
srcspider

Vous pouvez utiliser une variable d'instance pour agir en tant que détenteur de valeurs arbitraires, puis utiliser la méthode magique __get pour les récupérer en tant que propriétés normales: 

class My_Class
{
    private $_properties = array();

    public function __construct(Array $hash)
    {
         $this->_properties = $hash;
    }

    public function __get($name)
    {
         if (array_key_exists($name, $this->_properties)) {
             return $this->_properties[$name];
         }
         return null;
    }
}
7
Carlton Gibson

Oui, vous pouvez.

class test
{
    public function __construct()
    {
        $arr = array
        (
            'column1',
            'column2',
            'column3'
        );

        foreach ($arr as $key => $value)
        {
            $this->$value = '';
        }   
    }

    public function __set($key, $value)
    {
        $this->$key = $value;
    }

    public function __get($value)
    {
        return 'This is __get magic '.$value;
    }
}

$test = new test;

// Results from our constructor test.
var_dump($test);

// Using __set
$test->new = 'variable';
var_dump($test);

// Using __get
print $test->hello;

Sortie

object(test)#1 (3) {
  ["column1"]=>
  string(0) ""
  ["column2"]=>
  string(0) ""
  ["column3"]=>
  string(0) ""
}
object(test)#1 (4) {
  ["column1"]=>
  string(0) ""
  ["column2"]=>
  string(0) ""
  ["column3"]=>
  string(0) ""
  ["new"]=>
  string(8) "variable"
}
This is __get magic hello

Ce code définira des propriétés dynamiques dans le constructeur auxquelles on pourra accéder via $ this-> column. Il est également judicieux d'utiliser les méthodes magiques __get et __set pour traiter les propriétés qui ne sont pas définies dans la classe. Plus d'informations les peuvent être trouvés ici.

http://www.tuxradar.com/practicalphp/6/14/2

http://www.tuxradar.com/practicalphp/6/14/3

7

Voici une fonction simple permettant de renseigner des membres d'objet sans rendre public les membres de classe . Il laisse également le constructeur pour votre propre usage, créant une nouvelle instance d'objet sans invoquer de constructeur! Donc, votre objet de domaine ne dépend pas de la base de données!


/**
 * Create new instance of a specified class and populate it with given data.
 *
 * @param string $className
 * @param array $data  e.g. array(columnName => value, ..)
 * @param array $mappings  Map column name to class field name, e.g. array(columnName => fieldName)
 * @return object  Populated instance of $className
 */
function createEntity($className, array $data, $mappings = array())
{
    $reflClass = new ReflectionClass($className);
    // Creates a new instance of a given class, without invoking the constructor.
    $entity = unserialize(sprintf('O:%d:"%s":0:{}', strlen($className), $className));
    foreach ($data as $column => $value)
    {
        // translate column name to an entity field name
        $field = isset($mappings[$column]) ? $mappings[$column] : $column;
        if ($reflClass->hasProperty($field))
        {
            $reflProp = $reflClass->getProperty($field);
            $reflProp->setAccessible(true);
            $reflProp->setValue($entity, $value);
        }
    }
    return $entity;
}

/******** And here is example ********/

/**
 * Your domain class without any database specific code!
 */
class Employee
{
    // Class members are not accessible for outside world
    protected $id;
    protected $name;
    protected $email;

    // Constructor will not be called by createEntity, it yours!
    public function  __construct($name, $email)
    {
        $this->name = $name;
        $this->emai = $email;
    }

    public function getId()
    {
        return $this->id;
    }

    public function getName()
    {
        return $this->name;
    }

    public function getEmail()
    {
        return $this->email;
    }
}


$row = array('employee_id' => '1', 'name' => 'John Galt', 'email' => '[email protected]');
$mappings = array('employee_id' => 'id'); // Employee has id field, so we add translation for it
$john = createEntity('Employee', $row, $mappings);

print $john->getName(); // John Galt
print $john->getEmail(); // [email protected]
//...

P.S. La récupération de données depuis un objet est similaire, par ex. use $ reflProp-> setValue ($ entity, $ value); P.P.S. Cette fonction est fortement inspirée de Doctrine2 ORM qui est génial!

3
Sergiy Sokolenko
class DataStore // Automatically extends stdClass
{
  public function __construct($Data) // $Data can be array or stdClass
  {
    foreach($Data AS $key => $value)  
    {
        $this->$key = $value;    
    }  
  }
}

$arr = array('year_start' => 1995, 'year_end' => 2003);
$ds = new DataStore($arr);

$gap = $ds->year_end - $ds->year_start;
echo "Year gap = " . $gap; // Outputs 8
2
Anthony

Vous pouvez:

$variable = 'foo';
$this->$variable = 'bar';

Définirait l'attribut foo de l'objet sur lequel il a été appelé sur bar.

Vous pouvez également utiliser des fonctions:

$this->{strtolower('FOO')} = 'bar';

Cela définirait également foo (pas FOO) à bar.

1
Koraktor

Étendre stdClass.

class MyClass extends stdClass
{
    public function __construct()
    {
        $this->prop=1;
    }
}

J'espère que c'est ce dont vous avez besoin.

1
Anthony

C'est une manière très compliquée de gérer ce genre de développement rapide. J'aime les réponses et les méthodes magiques, mais à mon avis, il est préférable d'utiliser des générateurs de code comme CodeSmith. 

J'ai créé un modèle qui se connecte à la base de données, lit toutes les colonnes et leurs types de données et génère la classe entière en conséquence. 

De cette façon, j'ai du code lisible sans erreur (pas de fautes de frappe). Et si vos modifications de modèle de base de données exécutent à nouveau le générateur ... cela fonctionne pour moi.

0
zidane

Si vous devez vraiment le faire, le meilleur moyen est de surcharger un ArrayObject, ce qui permet de maintenir un support d'itération (foreach) qui restera en boucle dans toutes vos propriétés.

Je remarque que vous avez dit "sans utiliser de tableau" et je tiens simplement à vous assurer que même si techniquement un tableau est utilisé en arrière-plan, vous n’AVEZ JAMAIS À LE VOIR. Vous accédez à toutes les propriétés via -> properyname ou foreach ($ class dans $ name => $ value).

Voici un exemple sur lequel je travaillais hier, notez que ceci est aussi FORTEMENT TYPÉ. Ainsi, les propriétés marquées "entier" généreront une erreur si vous essayez de fournir une "chaîne".

Vous pouvez supprimer cela bien sûr.

Il existe également une fonction membre AddProperty (), bien qu'elle ne soit pas illustrée dans l'exemple. Cela vous permettra d'ajouter des propriétés plus tard.

Exemple d'utilisation:

    $Action = new StronglyTypedDynamicObject("Action",
            new StrongProperty("Player", "ActionPlayer"),   // ActionPlayer
            new StrongProperty("pos", "integer"),
            new StrongProperty("type", "integer"),
            new StrongProperty("amount", "double"),
            new StrongProperty("toCall", "double"));

    $ActionPlayer = new StronglyTypedDynamicObject("ActionPlayer",
            new StrongProperty("Seat", "integer"),
            new StrongProperty("BankRoll", "double"),
            new StrongProperty("Name", "string"));

    $ActionPlayer->Seat = 1;
    $ActionPlayer->Name = "Doctor Phil";

    $Action->pos = 2;
    $Action->type = 1;
    $Action->amount = 7.0;
    $Action->Player = $ActionPlayer;

    $newAction = $Action->factory();
    $newAction->pos = 4;

    print_r($Action);
    print_r($newAction);


    class StrongProperty {
            var $value;
            var $type;
            function __construct($name, $type) {
                    $this->name = $name;
                    $this->type = $type;
            }

    }

    class StronglyTypedDynamicObject extends ModifiedStrictArrayObject {

            static $basic_types = array(
                    "boolean",
                    "integer",
                    "double",
                    "string",
                    "array",
                    "object",
                    "resource",
            );

            var $properties = array(
                    "__objectName" => "string"
            );

            function __construct($objectName /*, [ new StrongProperty("name", "string"), [ new StrongProperty("name", "string"), [ ... ]]] */) {
                    $this->__objectName = $objectName;
                    $args = func_get_args();
                    array_shift($args);
                    foreach ($args as $arg) {
                            if ($arg instanceof StrongProperty) {
                                    $this->AddProperty($arg->name, $arg->type);
                            } else {
                                    throw new Exception("Invalid Argument");
                            }
                    }
            }

            function factory() {
                    $new = clone $this;
                    foreach ($new as $key => $value) {
                            if ($key != "__objectName") {
                                    unset($new[$key]);
                            }
                    }

                    // $new->__objectName = $this->__objectName;
                    return $new;
            }

            function AddProperty($name, $type) {
                    $this->properties[$name] = $type;
                    return;

                    if (in_array($short_type, self::$basic_types)) {
                            $this->properties[$name] = $type;
                    } else {
                            throw new Exception("Invalid Type: $type");
                    }
            }

            public function __set($name, $value) {
                    self::sdprintf("%s(%s)\n", __FUNCTION__, $name);
                    $this->check($name, $value);
                    $this->offsetSet($name, $value);
            }

            public function __get($name) {
                    self::sdprintf("%s(%s)\n", __FUNCTION__, $name);
                    $this->check($name);
                    return $this->offsetGet($name);
            }

            protected function check($name, $value = "r4nd0m") {
                    if (!array_key_exists($name, $this->properties)) {
                            throw new Exception("Attempt to access non-existent property '$name'");
                    }

                    $value__objectName = "";
                    if ($value != "r4nd0m") {
                            if ($value instanceof StronglyTypedDynamicObject) {
                                    $value__objectName = $value->__objectName;
                            }
                            if (gettype($value) != $this->properties[$name] && $value__objectName != $this->properties[$name]) { 
                                    throw new Exception("Attempt to set {$name} ({$this->properties[$name]}) with type " . gettype($value) . ".$value__objectName");
                            }
                    }
            }
    }

    class ModifiedStrictArrayObject extends ArrayObject {
            static $debugLevel = 0;

            /* Some example properties */

            static public function StaticDebug($message) {
                    if (static::$debugLevel > 1) {
                            fprintf(STDERR, "%s\n", trim($message));
                    }
            }

            static public function sdprintf() {
                    $args = func_get_args();
                    $string = call_user_func_array("sprintf", $args);
                    self::StaticDebug("D            " . trim($string));
            }

            protected function check($name) {
                    if (!array_key_exists($name, $this->properties)) {
                            throw new Exception("Attempt to access non-existent property '$name'");
                    }
            }

            //static public function sget($name, $default = NULL) {
            /******/ public function get ($name, $default = NULL) {
                    self::sdprintf("%s(%s)\n", __FUNCTION__, $name);
                    $this->check($name);
                    if (array_key_exists($name, $this->storage)) {
                            return $this->storage[$name];
                    }
                    return $default;
            }

            public function offsetGet($name) { 
                    self::sdprintf("%s(%s)\n", __FUNCTION__, implode(",", func_get_args()));
                    $this->check($name);
                    return call_user_func_array(array(parent, __FUNCTION__), func_get_args());
            }
            public function offsetSet($name, $value) { 
                    self::sdprintf("%s(%s)\n", __FUNCTION__, implode(",", func_get_args()));
                    $this->check($name);
                    return call_user_func_array(array(parent, __FUNCTION__), func_get_args());
            }
            public function offsetExists($name) { 
                    self::sdprintf("%s(%s)\n", __FUNCTION__, implode(",", func_get_args()));
                    $this->check($name);
                    return call_user_func_array(array(parent, __FUNCTION__), func_get_args());
            }
            public function offsetUnset($name) { 
                    self::sdprintf("%s(%s)\n", __FUNCTION__, implode(",", func_get_args()));
                    $this->check($name);
                    return call_user_func_array(array(parent, __FUNCTION__), func_get_args());
            }

            public function __toString() {
                    self::sdprintf("%s(%s)\n", __FUNCTION__, $name);
                    foreach ($this as $key => $value) {
                            $output .= "$key: $value\n";
                    }
                    return $output;
            }

            function __construct($array = false, $flags = 0, $iterator_class = "ArrayIterator") { 
                    self::sdprintf("%s(%s)\n", __FUNCTION__, implode(",", func_get_args()));
                    parent::setFlags(parent::ARRAY_AS_PROPS);
            }
    }
0
Orwellophile

Après avoir lu @Udo's answer . J'ai mis au point le modèle suivant, qui ne bloque pas une instance de classe avec tous les éléments qui figurent dans l'argument de votre tableau de constructeur, mais vous permet néanmoins de taper moins et d'ajouter facilement de nouvelles propriétés à la classe.

class DBModelConfig
{
    public $Host;
    public $username;
    public $password;
    public $db;
    public $port = '3306';
    public $charset = 'utf8';
    public $collation = 'utf8_unicode_ci';

    public function __construct($config)
    {
        foreach ($config as $key => $value) {
            if (property_exists($this, $key)) {
                $this->{$key} = $value;
            }
        }
    }
}

Ensuite, vous pouvez passer des tableaux comme:

[
    'Host'      => 'localhost',
    'driver'    => 'mysql',
    'username'  => 'myuser',
    'password'  => '1234',
    'charset'   => 'utf8',
    'collation' => 'utf8_unicode_ci',
    'db'        => 'key not used in receiving class'
]
0
dotnetCarpenter