Development Tip

PHP에서 동적으로 인스턴스 속성을 만들 수 있습니까?

yourdevel 2020. 12. 11. 20:22
반응형

PHP에서 동적으로 인스턴스 속성을 만들 수 있습니까?


모든 인스턴스 속성을 동적으로 만드는 방법이 있습니까? 예를 들어 생성자에서 모든 속성을 생성하고 클래스가 다음과 같이 인스턴스화 된 후에도 여전히 액세스 할 수 있기를 원합니다 $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);

이 경우에는 "dynamic!"이 인쇄됩니다. 또한 임의로 이름이 지정된 속성에 값을 할당 할 수도 있습니다.이 경우 __set () 메서드가 자동으로 호출됩니다. __call ($ name, $ params) 메서드는 개체 메서드 호출에 대해 동일한 작업을 수행합니다. 특별한 경우에 매우 유용합니다. 그러나 대부분의 경우 다음과 같이 처리됩니다.

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

... 대부분 필요한 것은 배열의 내용을 해당 이름이 지정된 클래스 필드에 한 번 또는 적어도 실행 경로의 매우 명시적인 지점에 덤프하는 것입니다. 따라서 실제로 동적 동작이 필요하지 않은 경우 마지막 예제를 사용하여 개체를 데이터로 채우십시오.

이것을 오버로딩 http://php.net/manual/en/language.oop5.overloading.php 라고합니다 .


그것은 정확히 당신이 원하는 것에 달려 있습니다. 클래스를 동적으로 수정할 수 있습니까 ? 별로. 하지만 해당 클래스의 특정 인스턴스 에서처럼 동적으로 개체 속성 을 만들 수 있습니까? 예.

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

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

출력 :

동적

따라서 "bar"라는 개체 속성이 생성자에서 동적으로 생성되었습니다.


그래 넌 할수있어.

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 매직 메서드를 사용하여 클래스 내에 정의되지 않은 속성을 처리하는 것도 좋은 방법입니다. 더 많은 정보는 여기에서 찾을 수 있습니다.

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

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


인스턴스 변수를 사용하여 임의 값의 홀더 역할을 한 다음 __get 매직 메서드를 사용하여이를 일반 속성으로 검색 할 수 있습니다.

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

모든 예가 왜 그렇게 복잡한가요?

<?php namespace example;

error_reporting(E_ALL | E_STRICT); 

class Foo
{
    // class completely empty
}

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

다음은 클래스 멤버를 공개하지 않고 개체 멤버를 채우는 간단한 함수입니다. 또한 생성자를 호출하지 않고 객체의 새 인스턴스를 생성하여 자신의 사용을 위해 생성자를 남겨 둡니다! 따라서 도메인 개체는 데이터베이스에 의존하지 않습니다!


/**
 * 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' => 'john.galt@whoisjohngalt.com');
$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(); // john.galt@whoisjohngalt.com
//...

PS 개체에서 데이터를 검색하는 것도 비슷합니다. 예를 들어 $ reflProp-> setValue ($ entity, $ value); PPS이 기능은 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 ($ name의 $ class => $ value)를 통해 모든 속성에 액세스합니다.

다음은 어제 작업 한 샘플입니다.이 또한 STRONGLY TYPED라는 점에 유의하세요. 따라서 "정수"로 표시된 속성은 "문자열"을 제공하려고하면 오류가 발생합니다.

물론 제거 할 수 있습니다.

예제에서는 설명하지 않지만 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);
            }
    }

After reading @Udo 's answer. I've come up with the following pattern, that doesn't bloat a class instance with what-ever items that is in your constructor array argument but still let you type less and easily add new properties to the class.

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

Then you can pass arrays like:

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

참고URL : https://stackoverflow.com/questions/829823/can-you-create-instance-properties-dynamically-in-php

반응형