Buy
Buy

Magic Methods: __toString() __get, __set()

If I give you an object, could you print it? What I mean is, in battle.php, after we determine the winners, we echo $ship1->getName(), which is of course a string:

112 lines battle.php
... lines 1 - 39
<html>
... lines 41 - 59
<body>
<div class="container">
... lines 62 - 64
<div>
<h2 class="text-center">The Matchup:</h2>
<p class="text-center">
<br>
<?php echo $ship1Quantity; ?> <?php echo $ship1->getName(); ?><?php echo $ship1Quantity > 1 ? 's': ''; ?>
VS.
<?php echo $ship2Quantity; ?> <?php echo $ship2->getName(); ?><?php echo $ship2Quantity > 1 ? 's': ''; ?>
</p>
</div>
... lines 74 - 108
</div>
</body>
</html>

But could we just print $ship1 and $ship2?

112 lines battle.php
... lines 1 - 39
<html>
... lines 41 - 59
<body>
<div class="container">
... lines 62 - 64
<div>
<h2 class="text-center">The Matchup:</h2>
<p class="text-center">
<br>
<?php echo $ship1Quantity; ?> <?php echo $ship1; ?><?php echo $ship1Quantity > 1 ? 's': ''; ?>
VS.
<?php echo $ship2Quantity; ?> <?php echo $ship2; ?><?php echo $ship2Quantity > 1 ? 's': ''; ?>
</p>
</div>
... lines 74 - 108
</div>
</body>
</html>

Does it make sense to print an object? The answer is... no. Try to battle, you get a very clear error that says:

Object of class Model\RebelShip could not be converted to string in battle.php.

Remember this error: you'll eventually try to print an object on accident and see this!

But you CAN Print an Object

Why am I telling you this seemingly small and obvious fact? Because I'm lying! You can print objects! You just have to do a little bit more work.

Here's the big picture: there are ways to give a class super-powers - like the ability to be printed or - as we'll see next - the ability to pretend like it's an array.

Open up AbstractShip. To make objects of this class printable, go to the bottom and create a new public function __toString(). Inside, return $this->getName():

... lines 1 - 4
abstract class AbstractShip
{
... lines 7 - 124
public function __toString()
{
return $this->getName();
}
}

Go back, refresh, and now it works just fine.

By adding the __toString() method - we gave PHP the ability to convert our object into a string. The __toString() must be called exactly like this, and there are other methods that take on special meaning. They all start with __, and we've already seen one: __construct():

... lines 1 - 4
abstract class AbstractShip
{
... lines 7 - 29
public function __construct($name)
{
$this->name = $name;
}
... lines 34 - 128
}

These are collectively called Magic Methods.

The Magic __get()

There are actually just a few magic methods: let's look at another common one. In battle.php, scroll down a little bit to where it shows the ship health. Change this: instead of $ship1->getStrength(), say $ship1->strength:

112 lines battle.php
... lines 1 - 39
<html>
... lines 41 - 59
<body>
<div class="container">
... lines 62 - 73
<div class="result-box center-block">
... lines 75 - 95
<dl class="dl-horizontal">
... line 97
<dd><?php echo $ship1->strength; ?></dd>
... lines 99 - 100
</dl>
</div>
... lines 103 - 108
</div>
</body>
</html>

This should not work, and PHPStorm tells us why: the member - meaning property - has private access. We can't access a private property from outside the class.

But once again - via a magic method - you can bend the rules. This time, add a public function __get() with a single argument: $propertyName. For now, just dump that:

... lines 1 - 4
abstract class AbstractShip
{
... lines 7 - 129
public function __get($propertyName)
{
var_dump($propertyName);die;
}
}

Refresh to see what happens. Interesting! It dumps the string strength. Here's the magic: if you reference a property on your object that is not accessible - either because it doesn't exist or is private or protected - and you have an __get() method, then PHP will call that and pass you the property name.

Then - if you want - you can return its value. Add return $this->$propertyName:

... lines 1 - 4
abstract class AbstractShip
{
... lines 7 - 129
public function __get($propertyName)
{
return $this->$propertyName;
}
}

This looks weird: PHP will see $propertyName, evaluate that to strength, and then return $this->strength.

Refresh again. It works!

Not surprisingly, there's also a method called __set(), which allows you to assign a value to a non-existent property, like $ship->strength = 100.

Don't be Too Clever

Now, just because you have all this new power doesn't mean you should use it. As soon as you add things like __get(), it starts to break your object oriented rules. All of a sudden, even though it looks like strength is private, I actually can get it... so it's not really private.

You also won't get reliable auto completions from your editor - it has a hard time figuring out what you're doing in these magic methods.

So my recommendation is: avoid using magic methods, except for __toString() and __construct().

But, you do need to know these exist: even if you don't use them, other libraries will, which might be confusing if you're not watching for it.

But beyond magic methods, there are other super powers you can give your objects that I do love. Let's look at those.

Leave a comment!

  • 2018-12-17 Victor Bocharsky

    Hey Emin,

    Yeah, sure! As always, you just need more practice. Glad it helped you

    Cheers!

  • 2018-12-14 Emin

    Hey Victor Bocharsky,

    Thank you for the fast reply. Now It makes more sense on how it works and with experience I will understand it allot better.

    Cheers!

  • 2018-12-14 Victor Bocharsky

    Hey Emin,

    About magic __get() method: if your class has __get() method - every time you when you call a property that does not exist or does not public - before throwing an error, PHP interpreter will call that __get() method and pass a property name to it that you're trying to access. So, if you have this code in your class:


    public function __get($propertyName)
    {
    return $this->$propertyName;
    }

    Every time you call any property, for example, "$object->test" - the __get() method will be called and "test" string (the property name you're trying to call) will be passed as an argument, i.e. $propertyName variable will be equal to "test". And then, in that method, we call "return $this->$propertyName;" but since $propertyName = "test" it means you just call "return $this->test;" - i.e. you call property name directly.

    So, basically, you need to understand the difference between "$this->propertyName;" and "$this->$propertyName;" - that's very important and I bet you confused because of it. So, you always call properties like "$this->propertyName;" inside your class, but "$this->$propertyName;" is also a legitimate syntax that means PHP will interpret the $propertyName variable first and then call a property that equal to that variable's value, for example:


    $propertyName = 'foo';
    $this->$propertyName; // is the same as calling "$this->foo", because $propertyName is equal to "foo" sting above.

    I hope it clear for you now.

    Cheers!

  • 2018-12-14 Emin

    Hey!

    I get confused with magic methods. I don't get the logic behind it, for example how does $propertyName equels strength I get the __construct() but the rest is confusing.