PHP provides a decent model of class member visibility, with public, private, and protected members to help you define tight APIs for your objects and show other developers how your object is supposed to be used. But used naively, PHP’s ‘magic methods’ can easily and subtly subvert this system, making everything public.
If you’re still new to object oriented programming in PHP5, think of “public” as roughly analogous to “my function’s arguments” and “private” as “local variables inside the function”. You wouldn’t want someone calling your function and messing with the local vars, and you wouldn’t want someone using your object messing with its private members.
Magic methods provide functionality like catching references to methods and properties which are not visible to us, and doing special things with them. Magic methods have always struck me as a bit weird, and whenever you bring them up in discussions online, there’s always a few people with reservations about them – efficiency, clarity, use-cases and so on.
I’m still in two minds; they can be useful in some circumstances, but here’s one reason why they could be considered harmful: Used carelessly, they can easily enable an OOP antipattern where all class members become public, even those declared as private or protected in the class definition.
This is really very bad indeed. with just a few lines of seemingly simple __call magic, you can invalidate your object’s internal scope and expose parts of your object which are not intended to be exposed. This also makes it easy for other people to unintentionally abuse your object later on, and paves the way for all kinds of headaches down the road. It’s also easy to do by accident when playing with magic methods.
Here’s the offending code. I call it ‘the most exquisitely hideous bit of PHP I’ve ever written’:
Looks pretty simple. Kinda redundant maybe, but not really harmful, right? It’s about the worst, most dangerous piece of code I’ve ever written. Let’s take a look at how it destroys the PHP object model.
The __call method is executed whenever you try to call a method which is not visible or is undefined. It can be useful for a few obscure cases, like providing default getters and setters, or doing magic things like java-esque overloading in PHP.
Inside that method, we check if the method we were trying to call exists in the class. If it does, we execute it.
Now you should be saying: Hang on a second, __call is executed if the method doesn’t exist in the class. And then we check to see if the method exists in the class… Surely the method won’t exist, because if it did, __call wouldn’t have been called, right?
Wrong. The problem is one of scope. You already know about scope; remember those local variables inside a function? They’re not accessible to the outside world because they fall outside the scope of the rest of the script.
When we define a member as ‘public’, we’re saying “let this member be visible to the global scope”. When we define a member as ‘private’ we’re saying “let this member be visible only to ourselves”, and when we say ‘protected’ we mean “let this member be visible only to ourselves and classes which extend us”.
Let’s augment that Foo class with a private method, which shouldn’t be visible to the outside world:
Now from outside that class, if we try to say $foo->bar(); it should fail, yeah? Because it’s private, not visible to the external scope. And without that __call magic, that’s exactly what would happen.
But remember what __call does – it gets fired when we try to access something which is not visible to us. bar is not visible, so __call gets fired.
Then the code inside __call checks to see if bar exists within Foo. Because __call is a member of Foo, it has access to the internal scope, so bar *is* visible from __call, and method_exists returns true. Then we execute it using call_user_func_array, and that’s how we poke holes in privacy in PHP. We declared a method as private, added some __call magic, and now it’s public. The same goes for private and protected properties with __get and __set too.
You might think that this is a somewhat academic problem – that no-one would write code like that in the real world, at least not intentionally. But on a google search for “__call call_user_func_array” there are 4 examples on the first page alone which unwittingly fall into this trap. Even more hits on koders.com. It’s really, really easy to do, because combining __call and call_user_func_array like this is a really obvious ‘clever’ way to use them. It’s just subtly dangerous too. Be warned!
Oh, and here’s an added bonus.When another object extends the one with the clever __call trick, the same magic function will mean that the child class is exposed in the same way, all its protected methods become public. Ouch.
Yay for openness. Finally:
Again, all protected methods from AnotherClass are now public. Yep, that little __call method can totally wreck the OO model in PHP if not used with extreme caution.
There was some great discusson when I submitted this to reddit a few months back – check out the comment thread. I decided it was worth a blog post of its own when I was writing my post on method polymorphism in PHP and nearly made the same mistake!