130

Possible Duplicate:
Are Magic Methods Best practice in PHP?

These are simple examples, but imagine you have more properties than two in your class.

What would be best practice?

a) Using __get and __set

class MyClass {
    private $firstField;
    private $secondField;

    public function __get($property) {
            if (property_exists($this, $property)) {
                return $this->$property;
            }
    }

    public function __set($property, $value) {
        if (property_exists($this, $property)) {
            $this->$property = $value;
        }
    }
}

$myClass = new MyClass();

$myClass->firstField = "This is a foo line";
$myClass->secondField = "This is a bar line";

echo $myClass->firstField;
echo $myClass->secondField;

/* Output:
    This is a foo line
    This is a bar line
 */

b) Using traditional setters and getters

class MyClass {

    private $firstField;
    private $secondField;

    public function getFirstField() {
        return $this->firstField;
    }

    public function setFirstField($firstField) {
        $this->firstField = $firstField;
    }

    public function getSecondField() {
        return $this->secondField;
    }

    public function setSecondField($secondField) {
        $this->secondField = $secondField;
    }

}

$myClass = new MyClass();

$myClass->setFirstField("This is a foo line");
$myClass->setSecondField("This is a bar line");

echo $myClass->getFirstField();
echo $myClass->getSecondField();

/* Output:
    This is a foo line
    This is a bar line
 */

In this article: http://blog.webspecies.co.uk/2011-05-23/the-new-era-of-php-frameworks.html

The author claims that using magic methods is not a good idea:

First of all, back then it was very popular to use PHP’s magic functions (__get, __call etc.). There is nothing wrong with them from a first look, but they are actually really dangerous. They make APIs unclear, auto-completion impossible and most importantly they are slow. The use case for them was to hack PHP to do things which it didn’t want to. And it worked. But made bad things happen.

But I would like to hear more opinions about this.

Community
  • 1
  • 1
rfc1484
  • 9,441
  • 16
  • 72
  • 123
  • 2
    *(tip)* [GetterEradicator](http://martinfowler.com/bliki/GetterEradicator.html) – Gordon May 31 '11 at 08:02
  • 1
    *(tip)* [How to remove getters and setters](http://css.dzone.com/articles/how-remove-getters-and-setters) – Gordon Jun 01 '11 at 11:00
  • 3
    I agree that __get is more slow to a custom get function (doing the same things), this is 0.0124455 the time for __get() and this 0.0024445 is for custom get() after 10000 loops. – Melsi Nov 23 '12 at 22:32
  • 5
    this question (and it's answers) is much more valuable than the "possible duplicate". Especially if the duplicate is even closed – shock_gone_wild Nov 29 '15 at 12:27
  • 2
    @Gordon This question is really valuable and it's not a duplicate of 'Are Magic Methods Best practice in PHP?'. You are as Zend Certified Engineer should understand it very well. It's a bad practice to close really valuable questions by miserable reasons. – Roman Podlinov Jan 06 '16 at 11:20
  • @Roman vote to reopen if you disagree. – Gordon Jan 06 '16 at 11:25
  • I ask community to support this question and vote for reopen. Please vote for this comment if you think that this question is valuable and must be reopen. If I see that 10+ people will vote for it I will reopen this question. – Roman Podlinov Jan 06 '16 at 11:35
  • @Roman for what it's worth: question asking for "the best practise" are way too often resulting in opinions (as we can see in the answers below). We do not like questions like this at Stack Overflow. That's why the linked question is closed as "not constructive". The community could have closed this one as "not constructive" for the same reason back then. They chose to pick up a dupe instead, which gives the OP some more info. I think that's good enough. On a side note: I don't know why you single out and attack me specifically. I didn't close it alone. – Gordon Jan 06 '16 at 12:46
  • @Gordon sorry for this :) not intentionally. I saw several times that moderators do not like "Best Practice" questions. – Roman Podlinov Jan 07 '16 at 16:04
  • This is a very useful question containing some very solid answers. The duplicate is neither. – Chuck Le Butt Jun 01 '17 at 15:53
  • I just did some testing. Makes interesting reading: https://pastebin.com/DdWKU5wp – Chuck Le Butt Jun 01 '17 at 15:59

9 Answers9

171

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.

Matthieu Napoli
  • 48,448
  • 45
  • 173
  • 261
  • 9
    I think that magic methods are there for a reason... – Adam Arold May 31 '11 at 13:59
  • 17
    While I agree with your general argument that `__get` and `__set` should not be abused for lazy accessors, it is not true that you cannot get autocompletion for them. See http://stackoverflow.com/questions/3814733/code-completion-for-private-protected-member-variables-when-using-magic-get/3815198#3815198 on how to do that. – Gordon May 31 '11 at 15:00
  • @Gordon : very interesting thanks! But does this work with Eclipse? – Matthieu Napoli Jun 01 '11 at 10:48
  • 2
    @stereofrog : Yes, this is exactly that :p. But another thing I forgot to mention is that: **having getters for properties is more coherent 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 property) and `$user->getToken()` (computed). – Matthieu Napoli Jun 01 '11 at 10:52
  • I updated my answer regarding the comments. – Matthieu Napoli Jun 01 '11 at 10:54
  • 5
    well, having "$user->name" (plain) and "$user->token" (computed via __get) is even more consistent, isn't it. – user187291 Jun 01 '11 at 10:57
  • @Matthieu i'm not 100% sure, but given that it works in Zend Studio and Zend Studio is Eclipse-based, I'd say yes. It works. – Gordon Jun 01 '11 at 10:59
  • @stereofrog "token" was maybe not the best example. If I take `$user->getNewPosition($direction, $speed)` this is more obvious because of the parameters that prevent it from being a property. But I agree that that could also be `$user->speed = 5; $user->move(); $newPosition = $user->position;`... That is just consistent with methods that can't be properties, but that's probably a question of taste. – Matthieu Napoli Jun 01 '11 at 12:00
  • 3
    The only thing I can really agree with here is they are probably slower than getters/setters, but in many cases that does not really matter; what's a millisecond if not used frequently? You can get autocomplete _(at least in PhpStorm)_ by setting PHPDoc's @property which also provides documentation and the last point about consistency is merely opinion and opinions vary _(see user187291's comment.)_ There is a benefit to using `__get()` that has not been mentioned AFAICT and that's properties can be embedded in HEREDOCs but method calls cannot. – MikeSchinkel Feb 24 '13 at 01:07
  • 1
    I disagree with the second bullet. How is maintaining a `@property` documentation more of a pain than writing individual getters and setters? – andrewtweber Mar 13 '15 at 20:51
  • @andrewtweber To be honest, 4 years later, I think the last bullet point + the "it's magic" argument are the most important after all. – Matthieu Napoli Mar 13 '15 at 23:13
  • @MatthieuNapoli the last bullet point is a good point that I hadn't thought of. For me the main issue is when using interfaces, you can enforce having a "getName" and "setName" function but you can't enforce having a "name" property on any class that implements the interface – andrewtweber Mar 14 '15 at 03:02
  • In your oppinion the modern Frameworks like Laravel are totally bad design. There is a lot of magic in there. As a simple user of that frameworks I really like the intuitive way of accessing data, especially in the Models. As a developer with more interest I could in dig in deeper and have a look at the provided "magic". It's not bad in every way... – shock_gone_wild Nov 29 '15 at 12:34
  • You may be right, but your reasons are not great: You'd have to be doing 10,000 actions to see any slow down. That's not a reason not to use them most of the time. Auto-complete can work just fine, too. @vbence's answer is most correct IMO. – Chuck Le Butt Apr 19 '17 at 14:51
  • How do you know `__get` is slower that using a getter function? – Marco Demaio Dec 24 '18 at 04:31
  • @AdamArold, yes magic methods have a good reason to exist. You need it on very dynamic software like magento, so that you just have to run your database update script so your new EAV attributes are added/removed to/from the database without any further programing needed from you, e.g. you don't have to add/remove all the getters and setters after running database scripts. – Black Sep 13 '19 at 08:37
134

You only need to use magic if the object is indeed "magical". If you have a classic object with fixed properties then use setters and getters, they work fine.

If your object have dynamic properties for example it is part of a database abstraction layer, and its parameters are set at runtime then you indeed need the magic methods for convenience.

vbence
  • 20,084
  • 9
  • 69
  • 118
  • 4
    I agree. Best answer so far. Don't know why it doesn't have more up votes. Do it like this: `$user->getFirstName()` and use magic only when really needed. – Jo Smo Jul 03 '14 at 17:20
  • Brilliant.Combine this with user187291's answer above and you are good to go. – Andrew Feb 13 '16 at 16:36
  • Coming from the nice world of compiled, statically typed languages, I would say, if your object is "from the database layer and has dynamic properties", then my vote would be for a ->get($columnName) method: it makes it clear that the thing you're fetching is something dynamic. Magic methods are just another of PHP's many levels of awfulness, seemingly crafted for the sole purpose of luring developers into traps. Traits are another (god how horrible are they? - and yet how appropriate they are for the rest of the horror in PHP) – Daniel Scott Oct 04 '18 at 14:21
92

I use __get (and public properties) as much as possible, because they make code much more readable. Compare:

this code unequivocally says what i'm doing:

echo $user->name;

this code makes me feel stupid, which i don't enjoy:

function getName() { return $this->_name; }
....

echo $user->getName();

The difference between the two is particularly obvious when you access multiple properties at once.

echo "
    Dear $user->firstName $user->lastName!
    Your purchase:
        $product->name  $product->count x $product->price
"

and

echo "
    Dear " . $user->getFirstName() . " " . $user->getLastName() . "
    Your purchase: 
        " . $product->getName() . " " . $product->getCount() . "  x " . $product->getPrice() . " ";

Whether $a->b should really do something or just return a value is the responsibility of the callee. For the caller, $user->name and $user->accountBalance should look the same, although the latter may involve complicated calculations. In my data classes i use the following small method:

 function __get($p) { 
      $m = "get_$p";
      if(method_exists($this, $m)) return $this->$m();
      user_error("undefined property $p");
 }

when someone calls $obj->xxx and the class has get_xxx defined, this method will be implicitly called. So you can define a getter if you need it, while keeping your interface uniform and transparent. As an additional bonus this provides an elegant way to memorize calculations:

  function get_accountBalance() {
      $result = <...complex stuff...>
      // since we cache the result in a public property, the getter will be called only once
      $this->accountBalance = $result;
  }

  ....


   echo $user->accountBalance; // calculate the value
   ....
   echo $user->accountBalance; // use the cached value

Bottom line: php is a dynamic scripting language, use it that way, don't pretend you're doing Java or C#.

James Jones
  • 3,850
  • 5
  • 25
  • 44
user187291
  • 53,363
  • 19
  • 95
  • 127
  • Good answer. The syntax is the best reason to use magic methods. $foo->bar() implies doing something while $foo->bar implies accessing something. – None Sep 10 '12 at 22:50
  • 1
    @J.Money Yes but what if your simple `$foo->bar` now needs to do a simple thing before returning the value (or compute the value). You need to change everywhere it is used to `$foo->getBar()`. Whereas with getters, you always keep the same interface, you just change the implementation. And this case has happened to me many times. Stored values and computed values are not very different actually, and IMO they should be named consistently (with `get...`). – Matthieu Napoli Oct 12 '12 at 09:20
  • 5
    @Matthieu: As noted in the answer, `$foo->bar` would simply call `$this->get_bar()`, which **is** a getter and **can** be changed to do whatever you need it to. – FtDRbwLXw6 Feb 13 '13 at 00:09
  • 3
    do you know -> Dear {$user->getFirstName()} ?? – Mauro Jun 11 '13 at 15:37
  • 14
    Your final line is my philosophy: Let PHP be PHP, let SQL be SQL, let Javascript be Javascript, let HTML be HTML, let Java, C#, or your chosen language be what it is and operate how it's designed to operate. Inherent in that is that it's mainly effective when your team *knows* how to do that, how to use a language to its greatest potential as opposed to shoe-horning it into another language's style, but that's how you're going to get the best work anyway, it won't be by trying to make PHP be Java, etc. – Jason Aug 22 '13 at 13:32
  • 13
    ... In addition, I feel that a lot of "best practice" in PHP is being driven by Java programmers that started using PHP because they needed or wanted a better "web page" language, and found the (at the time) truly abysmal state of good programming hygiene in the PHP world and enforced their worldview just to get *some* structure, and some structure being better than none, it was taken that this is actually the best way to do things in PHP. It may be that that's just where the language is headed, but I don't think it represents ultimate truth, at least not at the moment. – Jason Aug 22 '13 at 13:38
  • @Jason I agree. It's why I have no problem with Magento's magic get and set functions, for example. – Matthew G Nov 08 '13 at 10:14
  • You are emulating properties (known from VB or C#) using a second-class feature of PHP. Code using this logic won't be easily ported to other languages, and not future-proof either. – vbence Jan 20 '14 at 13:22
  • @vbence Why won't it be future-proof? What change are you imagining that could break such code? – Mark Amery Jan 21 '14 at 14:03
  • @MatthieuNapoli The *entire point* of the kind of hack this answer is proposing is that you don't ever have to change your interface when your properties change from not needing custom logic to needing it. The downside of your approach is that you either need to *never* use public fields (and provide a getter and setter method for everything that would otherwise be a public field - horribly verbose) *or* have a mixture of public fields and getters (horribly inconsistent, and you risk having to change your interface when a field you weren't expecting needs a sophisticated getter). – Mark Amery Jan 21 '14 at 14:28
  • 2
    @MarkAmery No `public` fields, yes. That's what I recommend: encapsulation. – Matthieu Napoli Jan 21 '14 at 14:36
  • 1
    @MatthieuNapoli Do correct me if I'm being thick, but there's zero difference I can see in the amount of encapsulation between your approach and user187291's; both provide the ability to read and set the property as part of your object's public interface. I'm not saying your way has no upsides - it clearly does - but by any reasonably definition of 'encapsulation' I can imagine, saying that your approach has more encapsulation is straightforwardly false. – Mark Amery Jan 21 '14 at 14:51
  • 1
    @MarkAmery You are right, *technically*, both are encapsulated since with __get and __set you control the access to the property. But more practically, encapsulation also means you have no idea what the properties of the object are. You are just given a set of public methods you can call on the object. To me it makes a difference, maybe for others it doesn't. – Matthieu Napoli Jan 21 '14 at 15:02
  • I like it, especially the elegant caching, but somehow I still feel dirty using public properties ;) Two small remarks though, your get_accountBalance function is missing a return, but maybe this is obvious to everyone. And the echo example is a bit misleading as you can use the getters in the string immediately too by doing: echo "Dear {$user->get_firstName()} {$user->get_lastName()}"; – Bas Mar 17 '14 at 17:46
  • 3
    -1, I disagree, magic methods are not more readable, your example is flawed, as you could easily use sprintf for such string and keep the code readable with getters --- __get and __set are not one-to-fit-all solution for interacting with objects – ioleo Feb 07 '16 at 14:38
  • great answer but your "bottom line" is either silly or I don't understand it. A better bottom line might be "why not use a powerful feature of a language that can simplify both writing and viewing your code" – Andrew Feb 13 '16 at 16:35
  • There's no reason not to write PHP like you're writing Java or C#. Writing well structured maintainable code is a good idea even if PHP allows you to do otherwise. PHP gives you enough rope to hang yourself, but its your choice whether you put your head in the noose. – Henry Dec 05 '16 at 01:58
2

I vote for a third solution. I use this in my projects and Symfony uses something like this too:

public function __call($val, $x) {
    if(substr($val, 0, 3) == 'get') {
        $varname = strtolower(substr($val, 3));
    }
    else {
        throw new Exception('Bad method.', 500);
    }
    if(property_exists('Yourclass', $varname)) {
        return $this->$varname;
    } else {
        throw new Exception('Property does not exist: '.$varname, 500);
    }
}

This way you have automated getters (you can write setters too), and you only have to write new methods if there is a special case for a member variable.

Adam Arold
  • 29,285
  • 22
  • 112
  • 207
  • 1
    I recommend against it too, I've detailed why in my answer. – Matthieu Napoli May 31 '11 at 08:31
  • 1
    This is the opposite of what you'd want to do. This makes all your private and protected properties become public, with no way to override. You shouldn't add getters/setters just for the sake of having them. – mpen Apr 24 '15 at 17:20
  • @mpen This solution only provides magic getters for all properties, but no setters. It could make sense, if you wanted to provide read-only access to all object properties. But I agree with you: There are way better options to do this. This solution requires a total of 4 function calls to get the value of a property – Philipp Apr 23 '17 at 18:29
2

I do a mix of edem's answer and your second code. This way, I have the benefits of common getter/setters (code completion in your IDE), ease of coding if I want, exceptions due to inexistent properties (great for discovering typos: $foo->naem instead of $foo->name), read only properties and compound properties.

class Foo
{
    private $_bar;
    private $_baz;

    public function getBar()
    {
        return $this->_bar;
    }

    public function setBar($value)
    {
        $this->_bar = $value;
    }

    public function getBaz()
    {
        return $this->_baz;
    }

    public function getBarBaz()
    {
        return $this->_bar . ' ' . $this->_baz;
    }

    public function __get($var)
    {
        $func = 'get'.$var;
        if (method_exists($this, $func))
        {
            return $this->$func();
        } else {
            throw new InexistentPropertyException("Inexistent property: $var");
        }
    }

    public function __set($var, $value)
    {
        $func = 'set'.$var;
        if (method_exists($this, $func))
        {
            $this->$func($value);
        } else {
            if (method_exists($this, 'get'.$var))
            {
                throw new ReadOnlyException("property $var is read-only");
            } else {
                throw new InexistentPropertyException("Inexistent property: $var");
            }
        }
    }
}
Carlos Campderrós
  • 22,354
  • 11
  • 51
  • 57
  • 10
    This is confusing : always avoid providing 2 ways of doing the same thing. – Matthieu Napoli May 31 '11 at 08:26
  • 1
    Well, I always code the same way, using those virtual properties. I usually put the getters and setters private, but for the sake of the example I left them public here because someone complained about IDE autocompletion. – Carlos Campderrós May 31 '11 at 08:28
  • 1
    I think this makes a lot of sense. You can let the magic do the work most of the time, and only implement the custom get/set functions for the magic to call as needed. – colonelclick Jun 17 '13 at 20:14
  • 1
    I vote down. IMO this example makes situation worst. It's not a Clean Code. you make duplication. You example is only for 2 properties for real class it will be much more. For auto completion you can use PhpDoc comments. – Roman Podlinov Jan 06 '16 at 11:05
  • 2
    This way is perfect. I put the get/set magic functions in a trait, that way I can easily use it across multiple classes. – nfplee May 09 '16 at 12:12
  • `> Property names MUST NOT be prefixed with a single underscore to indicate protected or private visibility. That is, an underscore prefix explicitly has no meaning.` Source: https://www.php-fig.org/psr/psr-12/ – Artfaith Jan 27 '22 at 21:10
-2

Second code example is much more proper way to do this because you are taking full control of data which are given to class. There are cases in which the __set and __get are useful but not in this case.

Malitta N
  • 3,384
  • 22
  • 30
kskaradzinski
  • 4,954
  • 10
  • 48
  • 70
-3

You should use stdClass if you want magic members, if you write a class - define what it contains.

Wesley van Opdorp
  • 14,888
  • 4
  • 41
  • 59
  • Using `stdClass` objects doesn't solve the OP's problem; the entire point of this question is that he wants getters and setters that do custom logic, *not* a behaviorless data object. – Mark Amery Jan 21 '14 at 14:19
  • On the bottom of the OP's question he clearly notes that he'd like to hear more opinion's on the subject. So by giving my opinion, I actually do answer his question. He does not ask for actual coding sample or anything.. – Wesley van Opdorp Jan 22 '14 at 12:14
  • I'm not criticising the lack of code samples or the fact that your proposed approach isn't one of the two the question originally suggested - I'm criticising the fact that it simply does not work, full stop, for the purpose that the OP was asking about (which was having properties with custom getting and setting logic). – Mark Amery Jan 22 '14 at 13:37
-3

The best practice would be to use traditionnal getters and setters, because of introspection or reflection. There is a way in PHP (exactly like in Java) to obtain the name of a method or of all methods. Such a thing would return "__get" in the first case and "getFirstField", "getSecondField" in the second (plus setters).

More on that: http://php.net/manual/en/book.reflection.php

SteeveDroz
  • 6,006
  • 6
  • 33
  • 65
  • 1
    Also, magic methods aren't going to work with interfaces and abstract classes/methods. And what about visibility? It's a leak to have a private or protected property modifiable by a magic __set method. Add these to your examples and I agree that there are many good arguments against using magic methods for getter/setters -- and no real arguments in their favor. For quick one-offs, maybe use them for something, but they're not a best practice for reusable code/libraries/APIs. – joelhardi May 31 '11 at 08:47
-4

I am now returning to setters and getters but I am also putting the getters and setters in the magic methos __get and __set. This way I have a default behavior when I do this

$class->var;

This will just call the getter I have set in the __get. Normally I will just use the getter directly but there are still some instances where this is just simpler.

AntonioCS
  • 8,335
  • 18
  • 63
  • 92