If you really need to do this, the best way is to overload ArrayObject, which allows you to support foreach support, which will still cover all your properties.
I note that you said “without using an array”, and I just want to assure you that when using the array in technical mode in the background, you NEVER SHOULD SEE THIS. You get access to all properties through → properyname or foreach ($ class in $ name => $ value).
Here is a sample that I worked on yesterday, please note that this is also STRONGLY TYPE. Thus, properties marked as "integer" will throw an error if you try to put a "string".
You can remove it, of course.
There is also a member function AddProperty (), although this is not shown in the example. This will allow you to add properties later.
Sample Usage:
$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); } }