38

Simple question, how do I convert an associative array to variables in a class? I know there is casting to do an (object) $myarray or whatever it is, but that will create a new stdClass and doesn't help me much. Are there any easy one or two line methods to make each $key => $value pair in my array into a $key = $value variable for my class? I don't find it very logical to use a foreach loop for this, I'd be better off just converting it to a stdClass and storing that in a variable, wouldn't I?

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
    }
}
animuson
  • 53,861
  • 28
  • 137
  • 147
  • I believe this question is answered [here](https://stackoverflow.com/a/9812059/4362965). A loader method inside the class itself doesn't seem to be a smart solution, since you'll need to be extra careful about throwing exceptions and error handling specially inside the constructor. – ttvd94 Aug 16 '21 at 18:20

7 Answers7

86

This simple code should work:

<?php

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

?>

Example usage

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

Alternatively, this might be a better approach

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

?>

Usage is the same

// 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
maček
  • 76,434
  • 37
  • 167
  • 198
  • Don't think you need the { } in the $this->{$key} = $value; statement – AntonioCS Apr 26 '10 at 17:45
  • 15
    @AntonioCS, it's not necessary but it definitely emphasizes the access of a variable-named property. It also demonstrates that `{ }` can be used when the variable property becomes more complex; e.g., `$this->{$this->foo('bar')}->do_something();` – maček Apr 26 '10 at 17:50
  • Really two nice solutions. I was not aware about those magic methods, neat tricks. – John Oct 30 '14 at 14:27
  • 1
    +1. Should also mention that unless you make sure that the __get method returns a reference, using the magic methods with arrays might cause some minor issues (i.e `$this->myArray[] = $value;` will not work) – iLot Apr 30 '15 at 07:29
  • very nice, but doesn't seems to work if they keys of the array are integer, because you can't call $foo->1. Am I missing something? – Kreker Apr 12 '18 at 12:50
  • @Kreker check out [ArrayIterator](https://secure.php.net/manual/en/class.arrayiterator.php) – maček Apr 16 '18 at 21:04
  • 1
    Its a good answer. But it does not work for nested arrays though. – Adam Nov 25 '19 at 20:07
  • is it possibile to change this method to let the constructor assign only defined properties to avoid to invent new properties of the object? – Tobia Apr 15 '20 at 14:52
  • 1
    @Tobia In the first approach, yes: `property_exists` will return true only if the property has been explicitly defined in the class' code. So you just define your properties as usual, then in the constructor's foreach loop, you check `if (property_exists(self::class, $key))` before assigning the property. – scenia Oct 28 '20 at 10:13
  • I don't understand why this answer is accepted. The actual question seems to be looking for a way to recreate a class without calling it's constructor method. – ttvd94 Aug 16 '21 at 17:43
14

Best solution is to have trait with static function fromArray that can be used for data loading:

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

Then you can use this trait like that:

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

Then you can call static fromArray function to get new instance of Example class:

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

I also have much more sophisticated version with nesting and value filtering https://github.com/OzzyCzech/fromArray

OzzyCzech
  • 9,713
  • 3
  • 50
  • 34
  • Instead of using `get_object_vars($obj = new self`, you can just say `get_class_vars(get_class($this))` — no need to create a dummy object. – Meglio May 29 '20 at 03:22
  • 2
    @Meglio actually, it's not a dummy object, it's the instance that will end up being returned after setting the properties. If `get_class($this)` or the alternative `self::class` are used, you'd have to create an instance before the loop. – scenia Oct 28 '20 at 10:21
2

if you (like me) came here looking for a source code generator for array->class, i couldn't really find any, and then i came up with this (a work in progress, not well tested or anything, json_decode returns the array.):

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

output:

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;
}
hanshenrik
  • 19,904
  • 4
  • 43
  • 89
1

Just use the php spread operator

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
Aryeh Beitz
  • 1,974
  • 1
  • 22
  • 23
0

Here's another solution using PDOStatement::fetchObject, though it is a bit of a hack.

$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);
Jonathan Amend
  • 12,715
  • 3
  • 22
  • 29
0

Define a static method to convert get an instance from an array. Best, define an interface for it. This is declarative, does not pollute the constructor, allows you to set private properties and still implement custom logic that would not be possible with Reflection. If you want a generic solution, define a trait and use it in your classes.

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);
}
felixfbecker
  • 2,273
  • 1
  • 19
  • 24
  • 1
    Do not create an Interface if you do not need it for your business logic. It is an overkill. – Meglio May 29 '20 at 03:26
0

If you want to cast nested array to object use this code:

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

Then you can use like this:

$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
M Rostami
  • 4,035
  • 1
  • 35
  • 39