すべてのインスタンスプロパティを動的に作成する方法はありますか?たとえば、コンストラクタですべての属性を生成し、クラスが次のようにインスタンス化された後でもそれらにアクセスできるようにしたいと思います:$object->property
。プロパティに個別にアクセスし、配列を使用しないことに注意してください。ここに私がしない欲しいものの例があります:
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;
具体的には、多数のプロパティを持つクラスを扱う場合、データベース内のすべての列(プロパティを表す)を選択し、それらからインスタンスプロパティを作成できるようにします。各列の値は、個別のインスタンスプロパティに格納する必要があります。
並べ替え。独自のコードをフックして、実行時にクラスの動作を実装できる魔法のメソッドがあります。
class foo {
public function __get($name) {
return('dynamic!');
}
public function __set($name, $value) {
$this->internalData[$name] = $value;
}
}
これは動的なgetterおよびsetterメソッドの例であり、オブジェクトプロパティにアクセスするたびに動作を実行できます。例えば
print(new foo()->someProperty);
この場合、「動的!」を印刷します。また、任意の名前のプロパティに値を割り当てることもできます。この場合、__ set()メソッドがサイレントに呼び出されます。 __call($ name、$ params)メソッドは、オブジェクトメソッド呼び出しに対して同じことを行います。特別な場合に非常に便利です。ただし、ほとんどの場合、次の方法で対応できます。
class foo {
public function __construct() {
foreach(getSomeDataArray() as $k => $value)
$this->{$k} = $value;
}
}
...ほとんどの場合、必要なのは、配列の内容を対応する名前のクラスフィールドに1回、または少なくとも実行パスの非常に明示的なポイントにダンプすることだけです。したがって、動的な動作が本当に必要な場合を除き、最後の例を使用してオブジェクトにデータを入力してください。
これはオーバーロードと呼ばれます http://php.net/manual/en/language.oop5.overloading.php
それはまさにあなたが望むものに依存します。 classを動的に変更できますか?あんまり。しかし、そのクラスの特定のインスタンスのように、動的にobjectプロパティを作成できますか?はい。
class Test
{
public function __construct($x)
{
$this->{$x} = "dynamic";
}
}
$a = new Test("bar");
print $a->bar;
出力:
動的
そのため、「bar」という名前のオブジェクトプロパティがコンストラクタで動的に作成されました。
すべての例がそれほど複雑なのはなぜですか?
<?php namespace example;
error_reporting(E_ALL | E_STRICT);
class Foo
{
// class completely empty
}
$testcase = new Foo();
$testcase->example = 'Dynamic property';
echo $testcase->example;
インスタンス変数を使用して任意の値のホルダーとして機能し、__ get magicメソッドを使用してそれらを通常のプロパティとして取得できます。
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;
}
}
はい、できます。
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;
出力
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
このコードは、コンストラクターに動的プロパティを設定し、$ this-> columnでアクセスできます。 __getおよび__setマジックメソッドを使用して、クラス内で定義されていないプロパティを処理することもお勧めします。詳細については、こちらをご覧ください。
クラスメンバをパブリックにせずにオブジェクトメンバを生成する簡単な関数を次に示します。また、コンストラクタを呼び出すことなくオブジェクトの新しいインスタンスを作成し、独自の使用のためにコンストラクタを残します!したがって、ドメインオブジェクトはデータベースに依存しません!
/**
* 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]
//...
追伸オブジェクトからのデータの取得も同様です。 use $ reflProp-> setValue($ entity、$ value); P.P.S.この関数は Doctrine2 ORM に大きく影響を受けています!
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
あなたはできる:
$variable = 'foo';
$this->$variable = 'bar';
呼び出されるオブジェクトの属性foo
をbar
に設定します。
次の関数も使用できます。
$this->{strtolower('FOO')} = 'bar';
これにより、foo
(FOO
ではなく)がbar
に設定されます。
StdClassを拡張します。
class MyClass extends stdClass
{
public function __construct()
{
$this->prop=1;
}
}
これがあなたの必要なものであることを願っています。
これは、この種の急速な開発を処理するための非常に複雑な方法です。私は答えと魔法の方法が好きですが、私の意見では、CodeSmithのようなコードジェネレーターを使用する方が良いと思います。
データベースに接続し、すべての列とそのデータ型を読み取り、それに応じてクラス全体を生成するテンプレートを作成しました。
このように、エラーのない(タイプミスのない)読み取り可能なコードがあります。また、データベースモデルが変更された場合、ジェネレータを再度実行します...
本当にそれを行う必要がある場合、最善の方法はArrayObjectをオーバーロードすることです。これにより、すべてのプロパティをループする反復サポート(foreach)を維持できます。
あなたは「配列を使用せずに」と言ったことに注意してください。技術的に配列がバックグラウンドで使用されている間は、絶対に見ないでください。すべてのプロパティにアクセスするには、-> properynameまたはforeach($ class in $ name => $ value)を使用します。
これが昨日取り組んでいたサンプルです。これも強く型付けされていることに注意してください。そのため、「整数」とマークされたプロパティは、「文字列」を指定しようとするとエラーをスローします。
もちろん削除できます。
例には示されていませんが、AddProperty()メンバー関数もあります。これにより、後でプロパティを追加できます。
サンプル使用法:
$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);
}
}
@Udoの answer を読んだ後。次のパターンを思い付きました。これは、コンストラクター配列引数にあるアイテムでクラスインスタンスを肥大化させませんが、入力を減らしてクラスに新しいプロパティを簡単に追加できます。
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;
}
}
}
}
次に、次のような配列を渡すことができます。
[
'Host' => 'localhost',
'driver' => 'mysql',
'username' => 'myuser',
'password' => '1234',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'db' => 'key not used in receiving class'
]