it-swarm.com.de

Können Sie Instanzeigenschaften dynamisch in PHP erstellen?

Gibt es eine Möglichkeit, alle Exemplareigenschaften dynamisch zu erstellen? Zum Beispiel möchte ich in der Lage sein, alle Attribute im Konstruktor zu generieren und trotzdem auf sie zugreifen zu können, nachdem die Klasse wie folgt instanziiert wurde: $object->property. Beachten Sie, dass ich separat auf die Eigenschaften zugreifen möchte und kein Array verwenden möchte. Hier ist ein Beispiel von dem, was ich nicht will:

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;

Um genauer zu sein, wenn ich mit Klassen zu tun habe, die eine große Anzahl von Eigenschaften haben, möchte ich in der Lage sein, alle Spalten in einer Datenbank (die die Eigenschaften darstellen) auszuwählen und Instanzeigenschaften zu erstellen. Jeder Spaltenwert sollte in einer separaten Exemplareigenschaft gespeichert werden.

58
Brayn

Art von. Es gibt magische Methoden, mit denen Sie Ihren eigenen Code verknüpfen können, um das Klassenverhalten zur Laufzeit zu implementieren:

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

Dies ist ein Beispiel für dynamische Getter- und Setter-Methoden. Sie können damit Verhalten ausführen, wenn auf eine Objekteigenschaft zugegriffen wird. Zum Beispiel

print(new foo()->someProperty);

würde in diesem Fall "dynamisch!" Sie können auch einer beliebig benannten Eigenschaft einen Wert zuweisen. In diesem Fall wird die __set () -Methode unbemerkt aufgerufen. Die Methode __call ($ name, $ params) führt das gleiche für Aufrufe von Objektmethoden aus. In besonderen Fällen sehr nützlich. Meistens wirst du aber auskommen mit:

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

... weil Sie meistens nur den Inhalt eines Arrays einmalig oder zumindest an sehr expliziten Stellen im Ausführungspfad in entsprechend benannte Klassenfelder ablegen. Wenn Sie also wirklich kein dynamisches Verhalten benötigen, verwenden Sie das letzte Beispiel, um Ihre Objekte mit Daten zu füllen.

Dies wird als Überladung bezeichnet http://php.net/manual/de/language.oop5.overloading.php

61
Udo

Es hängt davon ab, was Sie wollen. Können Sie die Klasse dynamisch ändern? Nicht wirklich. Können Sie aber object properties dynamisch erstellen, wie in einer bestimmten Instanz dieser Klasse? Ja.

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

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

Ausgänge:

dynamisch

Daher wurde im Konstruktor dynamisch eine Objekteigenschaft namens "bar" erstellt.

23
Chad Birch

Warum ist jedes Beispiel so kompliziert?

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

Sie können eine Instanzvariable als Halter für beliebige Werte verwenden und dann die magische Methode __get verwenden, um sie als reguläre Eigenschaften abzurufen: 

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

Ja, du kannst.

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;

Ausgabe

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

Dieser Code legt dynamische Eigenschaften im Konstruktor fest, auf die dann mit der Spalte $ this-> zugegriffen werden kann. Es empfiehlt sich auch, die magischen Methoden __get und __set zu verwenden, um mit Eigenschaften zu arbeiten, die nicht in der Klasse definiert sind. Weitere Informationen dazu finden Sie hier.

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

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

7

Hier ist eine einfache Funktion zum Auffüllen von Objektmitgliedern, ohne die Klassenmitglieder öffentlich zu machen .. Sie lässt den Konstruktor für Ihre eigene Verwendung frei und erstellt eine neue Instanz des Objekts, ohne den Konstruktor aufzurufen! Ihr Domain-Objekt ist also nicht von der Datenbank abhängig!


/**
 * 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. Das Abrufen von Daten von einem Objekt ist ähnlich, z. verwende $ reflProp-> setValue ($ entity, $ value); P.P.S. Diese Funktion ist stark von Doctrine2 ORM inspiriert, was großartig ist!

3
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

Sie können:

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

Würde das Attribut foo des Objekts, auf das es aufgerufen wird, auf bar setzen.

Sie können auch Funktionen verwenden:

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

Dies würde auch foo (nicht FOO) auf bar setzen.

1
Koraktor

Erweitern Sie stdClass.

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

Ich hoffe das ist was du brauchst.

1
Anthony

Dies ist eine sehr komplizierte Art und Weise, mit dieser Art schneller Entwicklung umzugehen. Ich mag Antworten und magische Methoden, aber meiner Meinung nach ist es besser, Codegeneratoren wie CodeSmith zu verwenden. 

Ich habe eine Vorlage erstellt, die eine Verbindung zur Datenbank herstellt, alle Spalten und ihre Datentypen liest und die gesamte Klasse entsprechend generiert. 

Auf diese Weise habe ich fehlerfreien (kein Tippfehler) lesbaren Code. Und wenn Ihr Datenbankmodell geändert wird, führen Sie den Generator erneut aus ... es funktioniert für mich.

0
zidane

Wenn Sie es wirklich wirklich tun müssen, ist es am besten, ein ArrayObject zu überladen, das die Unterstützung der Iteration (foreach) ermöglicht, die weiterhin alle Ihre Eigenschaften durchläuft.

Ich stelle fest, dass Sie gesagt haben, "ohne ein Array zu verwenden", und ich möchte Ihnen nur versichern, dass zwar technisch ein Array im Hintergrund verwendet wird, Sie es NIEMALS SEHEN. Sie können auf alle Eigenschaften über -> properyname oder foreach zugreifen ($ class in $ name => $ value).

Hier ist ein Beispiel, an dem ich gestern gearbeitet habe. Beachten Sie, dass dies auch STRONGLY TYPED ist. Eigenschaften, die als "Ganzzahl" gekennzeichnet sind, geben einen Fehler aus, wenn Sie versuchen, einen "String" einzugeben.

Sie können das natürlich entfernen.

Es gibt auch eine Member-Funktion AddProperty (), die im Beispiel nicht gezeigt wird. Auf diese Weise können Sie später Eigenschaften hinzufügen.

Verwendungsbeispiel:

    $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

Nach dem Lesen von @Udos s answer . Ich bin mit dem folgenden Muster gekommen, das eine Klasseninstanz nicht mit Elementen aus dem Konstruktorarray aufbläht, aber trotzdem weniger eingeben und der Klasse problemlos neue Eigenschaften hinzufügen kann.

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

Dann können Sie Arrays wie folgt übergeben:

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