Best Practice: PHP Magic Methods _Set and _Get

Best practice: PHP Magic Methods __set and __get

I have been exactly in your case in the past. And I went for magic methods.

This was a mistake, the last part of your question says it all :

  • this is slower (than getters/setters)
  • there is no auto-completion (and this is a major problem actually), and type management by the IDE for refactoring and code-browsing (under Zend Studio/PhpStorm this can be handled with the @property phpdoc annotation but that requires to maintain them: quite a pain)
  • the documentation (phpdoc) doesn't match how your code is supposed to be used, and looking at your class doesn't bring much answers as well. This is confusing.
  • added after edit: having getters for properties is more consistent with "real" methods where getXXX() is not only returning a private property but doing real logic. You have the same naming. For example you have $user->getName() (returns private property) and $user->getToken($key) (computed). The day your getter gets more than a getter and needs to do some logic, everything is still consistent.

Finally, and this is the biggest problem IMO : this is magic. And magic is very very bad, because you have to know how the magic works to use it properly. That's a problem I've met in a team: everybody has to understand the magic, not just you.

Getters and setters are a pain to write (I hate them) but they are worth it.

Best Practices for __get() and __set()

Lazy Models Getters (using __get())

I don't remember using PHP's magic methods too often in my apps, but I remember one situation where __get() was very useful.

Back in the days I was developing an application in CakePHP framework that had a lot of models and all models that are used in specific controller were initialized even if method make use only of one or two of them (that's how Cake was working). So I decided to change that to lazy models to lazy (loading models when they are used for the first time).

All I did is I added a very simple __get() function that looked for a model with specific name and loaded it. It was like 3-4 lines of code. I defined that in AppController (all CakePHP classes derives from that controller) and suddenly my app gained speed and used lower memory.

I took it further later on and also made lazy components loading in the same way.

Dynamic Model Methods (using __call())

Another good example, also from CakePHP, is how Cake searches on models. Basically you have two methods for that: find() and findAll() in every model, but you can also search using methods findBy<FieldName>() and findAllBy<FieldName>().

In example if you have db table

notes(id, date, title, body)

And create Cake model for that. You can use methods such as findById(), findByTitle() and so on. You need only CamelCase db field and you can do a search on any field much quicker.

Cake does it by using __call() magic method. This method is called if you are trying to execute a method that doesn't exists and then it just runs find() or findAll() with conditions dynamically created from method name and parameters. This is very simple to implement and can give you really a lot of benefits.

PHP __get and __set magic methods

__get, __set, __call and __callStatic are invoked when the method or property is inaccessible. Your $bar is public and therefor not inaccessible.

See the section on Property Overloading in the manual:

  • __set() is run when writing data to inaccessible properties.
  • __get() is utilized for reading data from inaccessible properties.

The magic methods are not substitutes for getters and setters. They just allow you to handle method calls or property access that would otherwise result in an error. As such, there are much more related to error handling. Also note that they are considerably slower than using proper getter and setter or direct method calls.

Are Magic Methods Best practice in PHP?

I don't think magic methods are best or worst practice: depending on what you want to achieve you can use them or not...
What I mean is that you don't have to tweak your code as possible to use them, but if you have to there is no problem at all.

If you have an object with 3 and only 3 attributes you don't need to use magic setters/getters, but in some advanced cases they are a great way to do very complex things (ORM systems etc...)

Maybe some of them are deprecated, I don't know, but most of them are not.

Magic methods (__get, __set) not working in extended class?

This would work:

public function __get($theName)
{
if(property_exists($this, $theName)) {
$reflection = new ReflectionProperty($this, $theName);
$reflection->setAccessible($theName);
return $reflection->getValue($this);
}
}

IMO, you shouldn't use __get and __set as a replacement for getters and setters. Since they are triggered when trying to access a non-accessible property, they are much more related to error-handling. And they are also much slower than a regular getter or setter.

PHP Magic Methods __set and __get

Don't know about best practices, but __get comes extremely useful when you need a property to be lazy loaded, e.g. when getting it involves a complex calcification or a db query. Moreover, php provides an elegant way to cache the response by simply creating an object field with the same name, which prevents the getter to be called again.

class LazyLoader
{
public $pub = 123;

function __get($p) {
$fn = "get_$p";
return method_exists($this, $fn) ?
$this->$fn() :
$this->$p; // simulate an error
}

// this will be called every time
function get_rand() {
return rand();
}

// this will be called once
function get_cached() {
return $this->cached = rand();
}
}

$a = new LazyLoader;
var_dump($a->pub); // getter not called
var_dump($a->rand); // getter called
var_dump($a->rand); // once again
var_dump($a->cached); // getter called
var_dump($a->cached); // getter NOT called, response cached
var_dump($a->notreally); // error!

How do I use PHP's __get() and __set() for defined fields in my class?

This would effectively make them public, and usually this gains you nothing over simply using public properties (but you pay performance penalty and have to write more code, risk bugs).

Use that only if you have existing code that uses public property and you suddenly need getter/setter for it (like your example with age).

If you need to execute some code when property is read/written (e.g. make it read-only or lazily fetch from the database), then you should use normal getters/setters (getAge()/setAge()).



Related Topics



Leave a reply



Submit