将PHP数组转换为类变量

39
简单的问题,如何将一个关联数组转换为类中的变量?我知道可以使用强制类型转换(object) $myarray或其他方法,但那会创建一个新的stdClass并没有帮助我太多。是否有任何简单的一两行代码的方法,将我的数组中每个$key => $value对转换为$key = $value变量,以便在我的类中使用?我不认为使用foreach循环很合适,最好的办法是将其转换为stdClass并将其存储在变量中,不是吗?
class MyClass {
    var $myvar; // I want variables like this, so they can be references as $this->myvar
    function __construct($myarray) {
        // a function to put my array into variables
    }
}

我相信这个问题已经在这里得到了解答。类内部的加载器方法似乎不是一个明智的解决方案,因为你需要特别小心地处理异常和错误处理,尤其是在构造函数内部。 - ttvd94
7个回答

87
这段简单的代码应该可以工作:
<?php

  class MyClass {
    public function __construct(Array $properties=array()){
      foreach($properties as $key => $value){
        $this->{$key} = $value;
      }
    }
  }

?>

示例用法

$foo = new MyClass(array("hello" => "world"));
$foo->hello // => "world"

或者,这可能是一种更好的方法

<?php

  class MyClass {

    private $_data;

    public function __construct(Array $properties=array()){
      $this->_data = $properties;
    }

    // magic methods!
    public function __set($property, $value){
      return $this->_data[$property] = $value;
    }

    public function __get($property){
      return array_key_exists($property, $this->_data)
        ? $this->_data[$property]
        : null
      ;
    }
  }

?>

使用方法相同

// init
$foo = new MyClass(array("hello" => "world"));
$foo->hello;          // => "world"

// set: this calls __set()
$foo->invader = "zim";

// get: this calls __get()
$foo->invader;       // => "zim"

// attempt to get a data[key] that isn't set
$foo->invalid;       // => null

15
@AntonioCS,这不是必需的,但绝对强调了变量命名属性的访问。它还展示了当变量属性变得更加复杂时可以使用 {} 符号;例如, $this->{$this->foo('bar')}->do_something(); - maček
1
请注意,除非您确保__get方法返回一个引用,否则在使用数组的魔术方法时可能会导致一些小问题(例如$this->myArray[] = $value;将无法正常工作)。 - iLot
1
这是一个很好的答案。但是对于嵌套数组它并不适用。 - Adam
有没有可能修改这个方法,让构造函数只分配已定义的属性,以避免发明对象的新属性? - Tobia
1
@Tobia 在第一种方法中,是的:property_exists 只有在该属性在类代码中被显式定义时才会返回 true。因此,您只需像往常一样定义您的属性,然后在构造函数的 foreach 循环中,在分配属性之前检查 if (property_exists(self::class, $key)) - scenia
显示剩余5条评论

14

最佳解决方案是使用具有静态函数 fromArraytrait,可用于数据加载:

trait FromArray {
 public static function fromArray(array $data = []) {
   foreach (get_object_vars($obj = new self) as $property => $default) {
     if (!array_key_exists($property, $data)) continue;
     $obj->{$property} = $data[$property]; // assign value to object
   }
   return $obj;
  }
}

然后您可以像这样使用此特性:

class Example {
  use FromArray;
  public $data;
  public $prop;
}

接下来您可以调用静态的fromArray函数来获取Example类的新实例:

$obj = Example::fromArray(['data' => 123, 'prop' => false]);
var_dump($obj);

我还有一个更复杂的版本,带有嵌套和值过滤功能 https://github.com/OzzyCzech/fromArray


不必创建虚拟对象,您可以使用get_class_vars(get_class($this))而不是使用get_object_vars($obj = new self) - Meglio
2
@Meglio 实际上,这不是一个虚拟对象,而是在设置属性后最终返回的实例。如果使用 get_class($this) 或替代的 self::class,则必须在循环之前创建一个实例。 - scenia

2

如果你(像我一样)在这里寻找一个用于数组->类的源代码生成器,我并没有真正找到任何东西,然后我想出了这个(正在进行中,没有经过充分测试或其他任何事情,json_decode返回数组。):

<?php
declare(strict_types = 1);

$json = <<<'JSON'
{"object_kind":"push","event_name":"push","before":"657dbca6668a99012952c58e8c8072d338b48d20","after":"5ac3eda70dbb44bfdf98a3db87515864036db0f9","ref":"refs/heads/master","checkout_sha":"5ac3eda70dbb44bfdf98a3db87515864036db0f9","message":null,"user_id":805411,"user_name":"hanshenrik","user_email":"divinity76@gmail.com","user_avatar":"https://secure.gravatar.com/avatar/e3af2bd4b5604b0b661b5e6646544eba?s=80\u0026d=identicon","project_id":3498684,"project":{"name":"gitlab_integration_tests","description":"","web_url":"https://gitlab.com/divinity76/gitlab_integration_tests","avatar_url":null,"git_ssh_url":"git@gitlab.com:divinity76/gitlab_integration_tests.git","git_http_url":"https://gitlab.com/divinity76/gitlab_integration_tests.git","namespace":"divinity76","visibility_level":0,"path_with_namespace":"divinity76/gitlab_integration_tests","default_branch":"master","homepage":"https://gitlab.com/divinity76/gitlab_integration_tests","url":"git@gitlab.com:divinity76/gitlab_integration_tests.git","ssh_url":"git@gitlab.com:divinity76/gitlab_integration_tests.git","http_url":"https://gitlab.com/divinity76/gitlab_integration_tests.git"},"commits":[{"id":"5ac3eda70dbb44bfdf98a3db87515864036db0f9","message":"dsf\n","timestamp":"2017-06-14T02:21:50+02:00","url":"https://gitlab.com/divinity76/gitlab_integration_tests/commit/5ac3eda70dbb44bfdf98a3db87515864036db0f9","author":{"name":"hanshenrik","email":"divinity76@gmail.com"},"added":[],"modified":["gitlab_callback_page.php"],"removed":[]}],"total_commits_count":1,"repository":{"name":"gitlab_integration_tests","url":"git@gitlab.com:divinity76/gitlab_integration_tests.git","description":"","homepage":"https://gitlab.com/divinity76/gitlab_integration_tests","git_http_url":"https://gitlab.com/divinity76/gitlab_integration_tests.git","git_ssh_url":"git@gitlab.com:divinity76/gitlab_integration_tests.git","visibility_level":0}}        
JSON;
$arr = json_decode ( $json, true );

var_dump ( array_to_class ( $arr ) );

/**
 *
 * @param array $arr            
 * @param string $top_class_name            
 */
function array_to_class(array $arr, string $top_class_name = "TopClass"): string {
    $top_class_name = ucfirst ( $top_class_name );
    $classes = array (); // deduplicated 'definition'=>true,array_keys();
    $internal = function (array $arr, string $top_class_name) use (&$classes, &$internal) {
        $curr = 'Class ' . $top_class_name . ' {' . "\n";
        foreach ( $arr as $key => $val ) {
            $type = gettype ( $val );
            if (is_array ( $val )) {
                $type = ucfirst ( ( string ) $key );
                $classes [$internal ( $val, ( string ) $key )] = true;
            }
            $curr .= <<<FOO
    /**
     * @property $type \$$key
    */
FOO;
            $curr .= "\n    public $" . $key . ";\n";
        }
        $curr .= '}';
        $classes [$curr] = true;
    };
    $internal ( $arr, $top_class_name );
    return implode ( "\n", array_keys ( $classes ) );
}

输出:

Class project {
    /**
     * @property string $name
    */
    public $name;
    /**
     * @property string $description
    */
    public $description;
    /**
     * @property string $web_url
    */
    public $web_url;
    /**
     * @property NULL $avatar_url
    */
    public $avatar_url;
    /**
     * @property string $git_ssh_url
    */
    public $git_ssh_url;
    /**
     * @property string $git_http_url
    */
    public $git_http_url;
    /**
     * @property string $namespace
    */
    public $namespace;
    /**
     * @property integer $visibility_level
    */
    public $visibility_level;
    /**
     * @property string $path_with_namespace
    */
    public $path_with_namespace;
    /**
     * @property string $default_branch
    */
    public $default_branch;
    /**
     * @property string $homepage
    */
    public $homepage;
    /**
     * @property string $url
    */
    public $url;
    /**
     * @property string $ssh_url
    */
    public $ssh_url;
    /**
     * @property string $http_url
    */
    public $http_url;
}

Class author {
    /**
     * @property string $name
    */
    public $name;
    /**
     * @property string $email
    */
    public $email;
}
Class added {
}
Class modified {
    /**
     * @property string $0
    */
    public $0;
}
Class removed {
}
Class 0 {
    /**
     * @property string $id
    */
    public $id;
    /**
     * @property string $message
    */
    public $message;
    /**
     * @property string $timestamp
    */
    public $timestamp;
    /**
     * @property string $url
    */
    public $url;
    /**
     * @property Author $author
    */
    public $author;
    /**
     * @property Added $added
    */
    public $added;
    /**
     * @property Modified $modified
    */
    public $modified;
    /**
     * @property Removed $removed
    */
    public $removed;
}
Class commits {
    /**
     * @property 0 $0
    */
    public $0;
}
Class repository {
    /**
     * @property string $name
    */
    public $name;
    /**
     * @property string $url
    */
    public $url;
    /**
     * @property string $description
    */
    public $description;
    /**
     * @property string $homepage
    */
    public $homepage;
    /**
     * @property string $git_http_url
    */
    public $git_http_url;
    /**
     * @property string $git_ssh_url
    */
    public $git_ssh_url;
    /**
     * @property integer $visibility_level
    */
    public $visibility_level;
}
Class TopClass {
    /**
     * @property string $object_kind
    */
    public $object_kind;
    /**
     * @property string $event_name
    */
    public $event_name;
    /**
     * @property string $before
    */
    public $before;
    /**
     * @property string $after
    */
    public $after;
    /**
     * @property string $ref
    */
    public $ref;
    /**
     * @property string $checkout_sha
    */
    public $checkout_sha;
    /**
     * @property NULL $message
    */
    public $message;
    /**
     * @property integer $user_id
    */
    public $user_id;
    /**
     * @property string $user_name
    */
    public $user_name;
    /**
     * @property string $user_email
    */
    public $user_email;
    /**
     * @property string $user_avatar
    */
    public $user_avatar;
    /**
     * @property integer $project_id
    */
    public $project_id;
    /**
     * @property Project $project
    */
    public $project;
    /**
     * @property Commits $commits
    */
    public $commits;
    /**
     * @property integer $total_commits_count
    */
    public $total_commits_count;
    /**
     * @property Repository $repository
    */
    public $repository;
}

1

只需使用 PHP 扩展运算符

class Whatever
{
    public function __construct(public int $something) {}

    public static function fromArray(array $data)
    {
        return new self(...$data);
    }
}

$data = ['something' => 1];
$instance = Whatever::fromArray($data);
$instance->something; // equals 1

0
如果您想将嵌套数组转换为对象,请使用以下代码:

class ToObject
{
    private $_data;

    public function __construct(array $data)
    {
        $this->setData($data);
    }

    /**
     * @return array
     */
    public function getData()
    {
        return $this->_data;
    }

    /**
     * @param array $data
     */
    public function setData(array $data)
    {
        $this->_data = $data;
        return $this;
    }

    public function __call($property, $args)
    {
        // NOTE: change lcfirst if you need (ucfirst/...) or put all together
        $property = lcfirst(str_replace('get', '', $property));
        if (array_key_exists($property, $this->_data)) {
            if (is_array($this->_data[$property])) {
                return new self($this->_data[$property]);
            }
            return $this->_data[$property];
        }
        return null;
    }
}

然后你可以像这样使用:

$array = [
    'first' => '1.1',
    'second' => [
        'first' => '2.1',
        'second' => '2.2',
        'third' => [
            'first' => '2.3.1'
        ]
    ]
];
$object = new ToObject($array);
$object->getFirst(); // returns 1.1
$object->getSecond()->getFirst(); // returns 2.1
$object->getSecond()->getData(); // returns second array
$object->getSecond()->getThird()->getFirst(); // returns 2.3.1

0

定义一个静态方法,从数组中获取实例。最好为其定义一个接口。这是声明式的,不会污染构造函数,允许您设置私有属性并仍然实现自定义逻辑,Reflection无法实现。如果您想要一个通用解决方案,请定义一个trait并在您的类中使用它。

class Test implements ContructableFromArray {
   private $property;
   public static function fromArray(array $array) {
       $instance = new self();
       $instance->property = $array['property'];
       return $instance;
   }
}

interface ConstructableFromArray {
   public static function fromArray(array $array);
}

1
如果您的业务逻辑不需要它,就不要创建接口。这是一种过度设计。 - Meglio

0
这是另一种使用PDOStatement::fetchObject的解决方案,尽管它有点像黑客技巧。
$array = array('property1' => 'value1', 'property2' => 'value2');
$className = 'MyClass';

$pdo = new PDO('sqlite::memory:'); // we don't actually need sqlite; any PDO connection will do
$select = 'SELECT ? AS property1, ? AS property2'; // this could also be built from the array keys
$statement = $pdo->prepare($select);

// this last part can also be re-used in a loop
$statement->execute(array_values($array));
$myObject = $statement->fetchObject($className);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接