I'm trying to create a __set for an object in PHP that works with multidimensional arrays. Is this even possible?
I would like to be able to something like the following: $post->comments[0]['uid']=3;. However, comments is actually going to be a key in a private cache variable $_cache['comments']=array(). It'd be nice if the __set function could somehow get both the base key (comments) and the index (0) as well as the key/value it is setting (uid/3). However, that's not possible.
I've thought about making $_cache['comments'] and array of ArrayObjects but that wouldn't let me define a custom _get/_set overload. Instead, I think that I might end up having to create a new Comments object and then fill the array with those. However, I really wouldn't like to do this and it'd be sweet if somehow PHP could handle nested arrays in __set overloads.
I'm using Mongo and would like if I could just have one single object for each document. However, arrays objects in Mongo are creating a bit of a problem for me. I would like to just handle them as an array in PHP but that doesn't seem possible. The setter needs to take $post->comments[0]['uid']=3 and update both the cache as well as setting $this->data['comments'][0]['uid']=3.
I know that if comments was an array of objects I could do this:
$post->comments[0]->uid=3;
///Sets $_cache['comments'][0]->uid=3;
And it would work because the getter for comments would return the array of objects and allow it to access the uid property. I could then have a getter/setter within the comments object that would somehow edit the $post->data through a pseudo "friend" function/hack. However, I don't see an easy way of accomplishing this with arrays....
Any advice?
That's more complex than you actually imagine. You can accomplish what you want with a heap of workarounds, but it's seldomly worth the effort.
If ->comments itself is resolved by a getter method, than assigning something to the [0] subarray won't actually end up in the private property. And ->comments[0]= will not even invoke your setter method. Instead this is a read access.
To make this work at all you would have to make your __get method return an reference of & $this->_cache['comments'].
If you want to intercept set accesses in that comments array you would indeed need ArrayObject. The difference is that this requires to override offsetGet and offsetSet instead of __get and __set. But again, since you are accessing a further subarray, the __get method will actually be used and you need to return another reference, or yet again a level of ArrayObject workaround goo.
I jumped through some of these hoops when building my own PHP wrapper class.
https://github.com/gatesvp/MongoModel
It's still in the the works, but it does handle some basic "map this object to DB".
There's virtually nothing worthwhile written in PHP chat rooms or the php documentation that's going to be useful to you, Adam. Most of the suggestions tend along the lines of implementing interface ArrayAccess or extending class ArrayObject, both in the SPL. In fact, there is a surprisingly straightforward solution to your problem: $post->comments[0]['uid']=3 using overloaded setter __set().
Define private $comments = array(); in class post. For convenience, use a text key for the first subscript of $comments: here, integer 0 becomes, say, "zero". You then invoke the setter as follows:
$post->zero = ['uid', 3];
This invokes the magic setter because there is no publicly declared property $zero in class post: "The overloading methods are invoked when interacting with properties or methods that have not been declared or are not visible in the current scope." (PHP 5 man page on Overloading.)
The setter can also be setComments(), a convenience because you won't have to discriminate among incoming properties to identify those intended for array comments, but the calling syntax becomes less natural.
Your overloaded, auto-magical function __set receives two arguments: a property and a value:
public function __set($property, $value) {
very reminiscent of Crockford's JSON protocol. It is helpful to think of it in those terms.
Since property "zero" that you sent in does not exist in classpost, it needs to be trapped, and my preferred method, since the first subscript in property comments will likely have several values, is to define a private array of supported subscript values in post:
private $indices = [
"zero" => 0,
"one" => 1,
"two" => 2,
"three" => 3
];
When the index for comments arrives in __set() as $property, it is verified to exist in $indices. Now you simply iterate through the array supplied in $value, extract
uid and its corresponding value, then assign to $comments as follows:
public function __set($property, $value) {
if (array_key_exists($property, $this->indices) && is_array($value))
foreach ($value as $uid => $uid_value)
$this->comments[$this->indices[property]][$uid] = $uid_value;
else
...
}
with $this->indices[property] being used to extract the integer value 0 to be used to
index the first dimension of comments, and $uid_value extracted with value int 3 to be assigned.
The approach outlined here is not a gimmick, workaround or clever trick. It's a straightforward design technique intended to work with one of SPL's facilities and can, in principle, be extended to arrays of arbitrary dimension. I have the design implemented in a production system so, if you're still having difficulty, post here and I'll help you to debug your application. Best of luck!
I believe the closest you can do for overloading some properties is to use the magic method __set() defined here: http://us.php.net/__set
I am not sure you can handle the [0] before it gets taken by the PHP compiler...
So your other solution would be to transform comments into a method
public function comments($id) {
return $this->obj[$id]; // Obj
}
And the object you return has the __set property
class Obj {
private $id;
public function __set($key, $value) {
if($key === 'uid') {
$_cache = $GLOBALS['_cache'];
$_cache['comments'][$this->id]->uid = $value;
}
}
}
There is a lot of code missing here, but you can figure out how to do it with this __set method()
Create a function instead of trying to hack it on top of something that isn't even meant for that.
public function setCommentUid($commentId, $uid) {
$this->_cache['comments'][$commentId]->uid = $uid;
}
//then...
$post->setCommentUid(0, 3);
This makes it much simpler to use the class and it's much easier to see what it does.
Related
Why UnexpectedValueException is thrown in session_start()?
I have object which has property of SPLObjectstorage. That object is assigned to session like
$_SESSION['foo'] = $barObject;
I suspect that internal session serializing facing problem to decode it. I store the session in database and it looks like it is serializing the objectStorage but can not decode it.
Sample session data
self|O:4:"User":8:{s:5:"�*�id";N;s:7:"�*�nick";N;s:13:"�*�reputation";i:1;s:11:"�*�password";N;s:8:"�*�email";N;s:7:"�*�crud";O:10:"CRUDobject":2:{s:13:"�*�fieldCache";a:0:{}s:13:"�*�dependency";r:1;}s:7:"�*�auth";N;s:11:"�*�roleList";C:11:"RoleStorage":23:{x:i:1;N;,r:13;;m:a:0:{}}}
Rolestorage is extends of SPLObjectstorage
session_decode() on above string also returns false any ideas?
removing the roleList attribute makes it serialize properly.
If I separately do
$sr = serialize($roles); // $roles is RoleStorage object
var_dump($sr);
var_dump(unserialize($sr));
It prints string 'C:11:"RoleStorage":22:{x:i:1;N;,r:3;;m:a:0:{}}' (length=46) and then fails with same message while unserializing. I have no clue why this is happening.
Note: while attaching object to RoleStorage I used the object itself as data. Means it is stored as reference. I don't know how (if) does serialize() handles internally this.
Objects with the name RoleStorage raises a couple of flags for me. Often, this object does contain a resource of sorts, or a reference to a built-in PHP object. Resources can't be serialized, nor can some PHP built-in types be serialized. Consider implementing the magic __sleep and __wakeup methods in those cases.
Say you have a PDO reference somewhere in the RoleStorage object, then these magic properties might look something like this:
public function __sleep()
{
$this->pdo->commit();//commit && close
$this->pdo = array($dsn, $user, $pwd, array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
)
);
return serialize($this);
}
public function __wakeup()
{
$this->pdo = new PDO($this->pdo[0], $this->pdo[1], $this->pdo[2], $this->pdo[3]);
}
But since you say the RoleStorage objects is a child of SPLObjectStorage, you'd be better off overriding the SPLObjectStorage's implementation of the Serializable interface:
It is not possible for __sleep() to return names of private properties in parent classes. Doing this will result in an E_NOTICE level error. Instead you may use the Serializable interface.
I'd suggest declaring iterating over all properties in the child class's serialize method, and store that data into an array. Return that array serialized, and unserialize that string back in the unserialize method, reassigning every property in a loop.
If SPLObjectStorage has private properties, you can get to them like so:
class RoleStorage extends SPLObjectStorage
implements Serializable
{
public function serialize()
{
return serialize((array) $this);
}
public function unserialize($string)
{
$array = unserialize($string);
foreach($array as $property => $value)
{
$property = explode("\0", $property);//private & protected properties
$this->{end($property)} = $value;
}
}
}
For details on the explode("\0",$property);, refer to the manual, or check this question
I have no clue why this is happening
In your PHP version and with your concrete script it is not possible to serialize an object based on SPLObjectStorage unless you take care of the serialization your own.
If you see this part of your serialized string:
C:11:"RoleStorage":23:{x:i:1;N;,r:13;;m:a:0:{}}
This represents the RoleStorage object. The big C at the beginning stands for:
C - Object implementing serializeable Interface
So the object itself is responsible here about the serialization and unserialization. You can normally expect this works, however not all software is without bugs.
In your case it looks like PHP made a mistake. The internal format here is:
x:i:1;N;,r:13;;m:a:0:{}
^^^^^^^
And the problem is at the highlighted position, this requires a serialized object, not NULL. And it's not comma-terminated with a reference (r:13 here), but with null (N) to be working.
So looks like a hick-up triggered by referencing some earlier object (take care that this referencing is not the same as references / variable aliases in userland PHP).
So how to go on?
It's time you start to isolate your problem and create a self-contained, reproduceable example out of it. This is necessary to further look into the issue as you see it. This is important for two reasons:
If this is a bug in PHP, it should be reported, a regression test written and added to PHP Q&A and then the bug fixed (if not yet fixed).
If you're looking for a workaround, reproducing the original problem is necessary to create a workaround quickly and easily.
I did run some tests for a work-around, however so far I'm not able to reproduce your issue so I can not really suggest how to work around the issue as I don't have it here.
There has been a bug closed recently regarding an issue similar to this. Depending on the php version you are running, you may still be affected. The affected version is 5.3.15.
Excerpt:
[2012-07-27 16:08 UTC] j dot henge-ernst at interexa dot de
The problem is that the unserialize of ArrayIterator (and also maybe
ArrayObject or other SPL classes) can not dereference object
references.
If you are affected by this bug, then you may be right that it is related to dereferencing. Perhaps try a newer version of PHP to see if it still happens.
This may be a basic question, but it has kept me wondering for quite some time now.
Should I declare all private/local variables being private? Or is this only necessary for "important" variables?
For instance, I have the (temporary) result of a calculation. Should I pre-declare this variable?
Hope someone can point this out.
Since you're talking about private, protected and public I take it you're talking about properties, instead of variables.
In that case: yes, you should declare them beforehand.
Because of how PHP objects are designed, an array (properties_table) is created on compile time. This array ensures that accessing a given property is as fast as possible. However, if you add properties as you go along, PHP needs to keep track of this, too. For that reason, an object has a simple properties table, too.
Whereas the first (properties_table) is an array of pointers, the latter is a simple key => value table.
So what? Well, because the properties_table contains only pointers (which are of a fixed size), they're stored in a simple array, and the pointers are fetched using their respective offsets. The offsets are stored in yet another HashTable, which is the ce->properties_info pointer.
As bwoebi pointed out to me in the comments: getting the offset (HashTable lookup) is a worst-case linear operation (O(n)) and predefined property lookups are constant-time complex operations (O(1)). Dynamic properties, on the other hand need another HashTable lookup, a worst-case linear operation (O(n)). Which means that, accessing a dynamic property takes in average about twice as long. Authors of the Wikipedia can explain Time-Complexity far better than I can, though.
At first, access modifiers might seem irrelevant. As you go along, you'll soon find that sometimes, you just don't want to take the chance that some property of some object gets modified by some bit of code. That's when you see the value of private.
If an object contains another object, that holds all sorts of settings that your code will rely upon, for example, you'll probably use a getter method to access those settings from the outside, but you'll leave that actual property tucked away nicely using private.
If, further down the line, you're going to add data models and a service layer to your project, there's a good change you'll write an (abstract) parent class, if only for type-hinting.
If those service instances contain something like a config property, you'll probably define that getter in the parent class (to only define it once). private means that only the current class has access to a property, but since you're not going to have an instance of the parent to work with, but an instance of the child, you'll see why protected is invaluable when dealing with larger projects, too.
As far as temporary variables are concerned, be it in methods, functions or anywhere else, you don't have to predeclare them, except for, in certain cases arrays:
public function foo()
{
$temp = $this->getSomeValue();
return $temp ? $temp +1 : null;
}
Is perfectly valid, and wouldn't work any better if you were to write
public function foo()
{
$temp;// or $temp = null;
$temp = $this->getSomeValue();
return $temp ? $temp +1 : null;
}
However, it's not uncommon to see simething like this:
public function bar($length = 1)
{
for ($i=0;$i<$length;$i++)
{
$return[] = rand($i+1, $length*10);
}
return $return;
}
This code relies on PHP being kind enough to create an array, and assign it to $return when the $return[] = rand(); statement is reached. PHP will do so, but setting your ini to E_STRICT | E_ALL will reveal that it doesn't do so without complaining about it. When passing 0 to the method, the array won't be created, and PHP will also complain when it reaches the return $return; statement: undeclared variable. Not only is it messy, it's also slowing you down! You're better off declaring $return as an array at the top of the scope:
public function bar($length = 1)
{
$return = array();//that's it
for ($i=0;$i<$length;$i++)
{
$return[] = rand($i+1, $length*10);
}
return $return;
}
To be on the safe side, I'd also check the argument type:
/**
* construct an array with random values
* #param int $length = 1
* #return array
**/
public function bar($length = 1)
{
$length = (int) ((int) $length > 0 ? $length : 1);//make length > 0
$return = array();
for ($i=0;$i<$length;$i++)
{
$return[] = rand($i+1, $length*10);
}
return $return;
}
In most if not all cases: yes.
If the variables are class properties they absolutely should be declared before use.
If the variable is local to a function, declare it in that function before you use it. Function variables are confined to the function's scope (local variables). They don't have to be declared before use but it's good practice to do so, and it gets rid of a warning message if you do. If they are not used anywhere else, they should not be properties though,
If you are using it in the context of the whole class, then yes, you should define your variable as a member of the class.
However, if you are talking about a local variable within the context of a single function and the variable does not need to be used elsewhere (or is not returned), then no.
Essentially you need to determine the importance and scope of your variable before deciding whether to make it a class property or not.
For example:
<?php
class Test {
private $test; // Private property, for use in the class only
public $public_test; // Public Property, for use both internally and external to the class as a whole
public function testing() {
$local = 5; // Local variable, not needed outside of this function ever
$this->test = rand(1, 5);
$calc = $local * $this->test; // Local variable, not needed outside of this function ever
$this->public_test = $calc / 2; // The only thing that the whole class, or public use cares about, is the result of the calculation divided by 2
}
}
It's generally a good rule of thumb for variables to define and initialize them before use. That includes not only definition and initial value but also validation and filtering of input values so that all pre-conditions a chunk of code is based on are established before the concrete processing of the data those variables contain.
Same naturally applies to object members (properties) as those are the variables of the whole object. So they should be defined in the class already (by default their value is NULL in PHP). Dynamic values / filtering can be done in the constructor and/or setter methods.
The rule for visibility is similar to any rule in code: as little as necessary (the easy rule that is so hard to achieve). So keep things local, then private - depending if it's a function variable or an object property.
And perhaps keep in the back of your mind that in PHP you can access private properties from within the same class - not only the same object. This can be useful to know because it allows you to keep things private a little bit longer.
For instance, I have the (temporary) result of a calculation. Should I pre-declare this variable?
This is normally a local variable in a function or method. It's defined when it receives the return value of the calculation method. So there is no need to pre-declare it (per-se).
...
function hasCalculation() {
$temp = $this->calculate();
return (bool) $temp;
}
...
If the calculation is/was expensive it may make sense to store (cache) the value. That works easily when you encapsulate that, for example within an object. In that case you'll use a private property to store that value once calculated.
Take these rule with a grain of salt, they are for general orientation, you can easily modify from that, so this is open to extend, so a good way to keep things flexible.
I was reading around about the Observer pattern, and found a dated article. Having read through, I noticed an interesting mention in this paragraph:
The key methods to look at here are attach(), detach(), and notify(). attach() and detach() handle adding and removing observers. We use a little trick here. Objects quoted in string context resolve to a unique identifier (even if __toString() is defined). You can use this fact to build keys for an associative array. The notify() method cycles through all attached observers, calling update() on each. The UploadManager class calls notify() whenever it has something important to report on upload and on error, in this case.
Which references this example:
function attach(UploadObserver $obs) {
$this->observers["$obs"] = $obs;
}
Now as mentioned, this article is dated. Casting objects to strings of course no longer works in this manner (I run 5.3.6 on my dev box, and push it for all client projects) but I'd like to achieve similar functionality. I can only think of (something like) this:
function attach(Observer $observer){
$this->_observers[md5(serialize($observer))] = $observer;
}
function detach(Observer $observer){
unset($this->_observers[md5(serialize($observer))]);
}
I'm curious, are there any other efficient ways to achieve this; creating a unique key from the object itself.
Caveat: I don't want to get into defined keys, I use those often enough with other repositories and such, implementing __set($key, $value), etc.
Note: I understand MD5 isn't ideal.
Update: Just found spl_object_hash, and I assume this is likely my best choice, however feel free to share your thoughts.
You're right that does not work that way any longer. You might want to use some other function instead: spl_object_hash()
function attach(Observer $observer){
$this->_observers[spl_object_hash($observer)] = $observer;
}
function detach(Observer $observer){
unset($this->_observers[spl_object_hash($observer)]);
}
The serialization based approach has a design problem btw: I stops working when objects are identical by value or in other words if objects return the same serialized value, e.g. NULL. This is fully controllable by the objects themselves when they implement the Serializable interface.
Have you tried the SPL object hash function?
Alternatively you could use SplObjectStorage directly.
Like:
function __construct(...){
$this->_observers = new SplObjectStorage;
}
function attach(Observer $observer) {
$this->_observers[$observer] = $observer;
}
function detach(Observer $observer){
unset($this->_observers[$observer]);
}
Even though there's some discussions regarding this issue I wanted to check on certain example what would be the best approach.
Instead of using existing solutions I created my own persistence layer (like many do)
So my approach is also in question here.
For every table in db I have model class that has appropriate getters and setters and some mandatory methods. I also created only one generic DAO class that handles all types of model objects.
So, for example to save any model object I instantiate genericDAO class and call save method that I pass model object as attribute.
Problem is that in runtime genericDAO class doesn't know whitch model object it gets and what methods (getters and setters) exist in it, so I need to call mandatory model class method that retrieves list of attributes as multiple string array.
For example for every attribute there's array(table_column_name,attribute_name,is_string).
When I call save function it looks like this:
public function save(&$VO) {
$paramArray = $VO->getParamArray();//get array of attributes
$paramIdArray = $paramArray[0]; //first attribute is always id
/*create and execute getId() and store value into $void to check if it's save or update*/
eval('$voId = $VO->get'.ucfirst($paramIdArray[1]).'();');
...
Currently I'm using eval to execute those methods, but as it is well known eval is very slow.
I'm thinking of changing that into call_user_func method
Something like:
$voId = call_user_func(array($VO, 'get'.ucfirst($paramIdArray[1])));
But also there's other solutions. I can maybe use something like this $method = 'get'.ucfirst($paramIdArray[1]));
$voId = $VO->$method();
or else
$method = 'get'.ucfirst($paramIdArray[1]));
$voId = $VO->{$method}();
What would be the best way?
First of all, there's no need to pass references like you are doing. You should give this a read to try to understand how PHP handles object references.
So public function save(&$VO) { should become public function save($VO) {.
Second, there is no need to use eval (in fact, it's better not to because of speed, debugability, etc). You can't stack-trace an eval call like you can a dynamic one.
Third, call_user_func is all but useless since PHP supports dynamic variable functions. Instead of call_user_func(array($obj, $method), $arg1), just call $obj->$foo($arg1). The call_user_func_array function is still useful since it supports variable length arguments and supports passing references.
So, ultimately, I would suggest this:
$method = 'get' . ucfirst($paramIdArray[1]);
$voId = $VO->$method();
Note that there's no need to call method_exists, since it may be callable and not exist due to __get magic method support...
I normally would use:
$method = 'get'.ucfirst($attribute);
if(method_exists($obj, $method){
$obj->$method();
}
But unless there is a very good reason i would just return a key => value array from getParamArray. And operate on that instead of using the getters...
(This question uses PHP as context but isn't restricted to PHP only. e.g. Any language with built in hash is also relevant)
Let's look at this example (PHP):
function makeAFredUsingAssoc()
{
return array(
'id'=>1337,
'height'=>137,
'name'=>"Green Fred");
}
Versus:
class Fred
{
public $id;
public $height;
public $name;
public function __construct($id, $height, $name)
{
$this->id = $id;
$this->height = $height;
$this->name = $name;
}
}
function makeAFredUsingValueObject()
{
return new Fred(1337, 137, "Green Fred");
}
Method #1 is of course terser, however it may easily lead to error such as
$myFred = makeAFredUsingAssoc();
return $myFred['naem']; // notice teh typo here
Of course, one might argue that $myFred->naem will equally lead to error, which is true. However having a formal class just feels more rigid to me, but I can't really justify it.
What would be the pros/cons to using each approach and when should people use which approach?
Under the surface, the two approaches are equivalent. However, you get most of the standard OO benefits when using a class: encapsulation, inheritance, etc.
Also, look at the following examples:
$arr['naem'] = 'John';
is perfectly valid and could be a difficult bug to find.
On the other hand,
$class->setNaem('John');
will never work.
A simple class like this one:
class PersonalData {
protected $firstname;
protected $lastname;
// Getters/setters here
}
Has few advantages over an array.
There is no possibility to make some typos. $data['firtsname'] = 'Chris'; will work while $data->setFirtsname('Chris'); will throw en error.
Type hinting: PHP arrays can contain everything (including nothing) while well defined class contains only specified data.
public function doSth(array $personalData) {
$this->doSthElse($personalData['firstname']); // What if "firstname" index doesn't exist?
}
public function doSth(PersonalData $personalData) {
// I am guaranteed that following method exists.
// In worst case it will return NULL or some default value
$this->doSthElse($personalData->getFirstname());
}
We can add some extra code before set/get operations, like validation or logging:
public function setFirstname($firstname) {
if (/* doesn't match "firstname" regular expression */) {
throw new InvalidArgumentException('blah blah blah');
}
if (/* in debbug mode */) {
log('Firstname set to: ' . $firstname);
}
$this->firstname = $firstname;
}
We can use all the benefits of OOP like inheritance, polymorphism, type hinting, encapsulation and so on...
As mentioned before all of our "structs" can inherit from some base class that provides implementation for Countable, Serializable or Iterator interfaces, so our structs could use foreach loops etc.
IDE support.
The only disadvantage seems to be speed. Creation of an array and operating on it is faster. However we all know that in many cases CPU time is much cheaper than programmer time. ;)
After thinking about it for some time, here's my own answer.
The main thing about preferring value objects over arrays is clarity.
Consider this function:
// Yes, you can specify parameter types in PHP
function MagicFunction(Fred $fred)
{
// ...
}
versus
function MagicFunction(array $fred)
{
}
The intent is clearer. The function author can enforce his requirement.
More importantly, as the user, I can easily look up what constitutes a valid Fred. I just need to open Fred.php and discover its internals.
There is a contract between the caller and the callee. Using value objects, this contract can be written as syntax-checked code:
class Fred
{
public $name;
// ...
}
If I used an array, I can only hope my user would read the comments or the documentation:
// IMPORTANT! You need to specify 'name' and 'age'
function MagicFunction(array $fred)
{
}
Depending on the UseCase I might use either or. The advantage of the class is that I can use it like a Type and use Type Hints on methods or any introspection methods. If I just want to pass around some random dataset from a query or something, I'd likely use the array. So I guess as long as Fred has special meaning in my model, I'd use a class.
On a sidenote:
ValueObjects are supposed to be immutable. At least if you are refering to Eric Evan's definition in Domain Driven Design. In Fowler's PoEA, ValueObjects do not necessarily have to be immutable (though it is suggested), but they should not have identity, which is clearly the case with Fred.
Let me pose this question to you:
What's so different about making a typo like $myFred['naem'] and making a typo like $myFred->naem? The same issue still exists in both cases and they both error.
I like to use KISS (keep it simple, stupid) when I program.
If you are simply returning a subset of a query from a method, simply return an array.
If you are storing the data as a public/private/static/protected variable in one of your classes, it would be best to store it as a stdClass.
If you are going to later pass this to another class method, you might prefer the strict typing of the Fred class, i.e. public function acceptsClass(Fred $fredObj)
You could have just as easily created a standard class as opposed to an array if it is to be used as a return value. In this case you could care less about strict typing.
$class = new stdClass();
$class->param = 'value';
$class->param2 = 'value2';
return $class;
A pro for the hash: It is able to handle name-value combinations which are unknown at design time.
When the return value represents an entity in your application, you should use an object, as this is the purpose of OOP. If you just want to return a group of unrelated values then it's not so clear cut. If it's part of a public API, though, then a declared class is still the best way to go.
Honestly, I like them both.
Hash arrays are way faster than making objects, and time is money!
But, JSON doesn't like hash arrays (which seems a bit like OOP OCD).
Maybe for projects with multiple people, a well-defined class would be better.
Hash arrays might take more CPU time and memory (an object has a predefined amount), though its hard to be sure for every scenario.
But what really sucks is thinking about which one to use too much. Like I said, JSON doesn't like hashes. Oops, I used an array. I got to change a few thousand lines of code now.
I don't like it, but it seems that classes are the safer way to go.
The benefit of a proper Value Object is that there's no way to actually make an invalid one and no way to change one that exists (integrity and "immutability"). With only getters and type hinting parameters, there's NO WAY to screw it up in compilable code, which you can obviously easily do with malleable arrays.
Alternatively you could validate in a public constructor and throw an exception, but this provides a gentler factory method.
class Color
{
public static function create($name, $rgb) {
// validate both
if ($bothValid) {
return new self($name, $rgb);
} else {
return false;
}
}
public function getName() { return $this->_name; }
public function getRgb() { return $this->_rgb; }
protected function __construct($name, $rgb)
{
$this->_name = $name;
$this->_rgb = $rgb;
}
protected $_name;
protected $_rgb;
}
I have worked with OOP Languages over 10 years.
If you understand the way objects work you will love it.
Inheritance, Polymorphism, Encapsulation, Overloading are the key advantage of OOP.
On the other hand when we talk about PHP we have to consider that PHP isn't a full featured Object Oriented language.
For example we cant use method overloading or constructor overloading (straightforward).
Associative arrays in PHP is a VERY nice feature but i think that harms php enterprise applications.
When you write code you want to get clean and maintainable application.
Another think that you loose with Associative arrays is that you can't use intellisense.
So i think if you want to write cleanner and more maintainable code you have to use the OOP features when it is provided.
I prefer to have hard-coded properties like in your second example. I feel like it more clearly defines the expected class structure (and all possible properties on the class). As opposed to the first example which boils down to just always remembering to use the same key names. With the second you can always go back and look at the class to get an idea of the properties just by looking at the top of the file.
You'll better know you're doing something wrong with the second one -- if you try to echo $this->doesntExist you'll get an error whereas if you try to echo array['doesntExist'] you won't.