开发者

Value objects vs associative arrays in PHP

(This question uses PHP as context but isn't restricted to PHP only. e.g. Any language with bu开发者_如何学Goilt 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.

  1. There is no possibility to make some typos. $data['firtsname'] = 'Chris'; will work while $data->setFirtsname('Chris'); will throw en error.
  2. 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());
    }
    
  3. 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;
    
    }
  4. We can use all the benefits of OOP like inheritance, polymorphism, type hinting, encapsulation and so on...
  5. 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.
  6. 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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜