PHP like a Boss: The Magic of Metaprogramming & Reflection – Part 1
PHP is a Shitty language.
PHP is garbage.
PHP is f**ked up!
I have been constantly molested verbally by developers of other languages like Python and Ruby that think PHP is just outrightly stupid but as a PHP Ninja that I am, I know the good sides, I know the dark sides, I have also explored the ugly sides and overtime I have known how to wield superpowers in PHP that can do what these other languages can & also better.
Metaprogramming is the ability of a program/code to be able to create another piece of code dynamically at runtime and also manipulate itself if need be. Ikem has given a good explanation about it in his post: Ruby on Steroids: The Magic of MetaProgramming – An Unexpected Journey so you can actually just check that out. So much ado about Metaprogramming but PHP can handle this technique pretty well too.
According to Serge Smerting, Metaprogramming :
- is how programs are self-aware
- is the art of operating metadata
- is black magic processed at build step
- is making the code shorter
Let’s get started with some of the techniques:
1. eval
There is a powerful PHP function that can evaluate a piece of PHP code represented in string format which means that it is possible for one to accept user input or read code off of a file and get it evaluated from within your program.
1 2 |
eval("function getName() { echo 'this is prosper'; } getName();"); #=> this is prosper |
Let’s change getName
to getSomething
, now that’s a function that doesn’t exist so it should throw an error:
1 2 |
eval("function getName() { echo 'this is prosper'; } getSomething();"); #=> Fatal error: Call to undefined function getSomething() in /Users/unicodeveloper/source/php-workspace/reflection/index.php(9) : eval()'d code on line 1 |
A typical real world example is WordPress plugins and online code editors. These editors allow you write PHP code and it processes that code to give you the desired output.
Tip: You can use file_get_contents() to read off source code from a file and pass it onto the eval() function to process.
This eval() function is very dangerous because it allows running of any form of PHP code, so be very careful and properly validate any user input that is passed onto this function.
2. __set and __get magic methods
These methods enable you to dynamically create and retrieve properties in a class that never existed during runtime. __get is used to read the value of an undefined property of a class while __set is used to set the value of a new propery of a class at runtime. Let’s get to the code for better understanding.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class Student { public $information = array(); public $school = 'Babcock University'; public $country = 'Nigeria'; public function __get($item) { return $this->information[$item]; } public function __set($item, $value) { $this->information[$item] = $value; } } $student = new Student; $student->name = 'Prosper Otemuyiwa'; $student->email = 'prosper@goodheads.io'; echo $student->name; #=> Prosper Otemuyiwa echo $student->email; #=> prosper@goodheads.io echo $student->country; #=> Nigeria |
The name
and email
properties don’t exist in the class, We created them on the fly, yet we were able to assign values to them and also retrieve those values. How sweet!
In a Real World, A very good use case for this are ORMs, In a typical ORM you can do something like:
1 2 3 4 5 6 7 8 |
<?php $user = new User; $user->name = 'unicodeveloper'; $user->bestFood = 'Bread and beans'; $user->save(); #=> This saves the information in the name and bestFood columns of the user table |
The underlying code makes use of these magic methods to be able to dynamically set any property for the model and check if the names of those properties correspond to column names in the table,…if they do, it saves successfully, if they don’t it throws an appropriate exception. Same applies for also retrieving data from the database.
3. __invoke
This magic method allows you to call an object as if it were a function or even as a closure. How can something be an object and also a function?. Magic right? A trivial example is:
1 2 3 4 5 6 7 8 9 10 |
class Student { public function __invoke() { echo "This is a student"; } } $student = new Student; $student(); #=> This is a student |
So the object was invoked as a function. This magic method came with PHP 5.3
A practical use case of this is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Student { public function __invoke() { echo "I am a student"; } } Class College { public static function attends(callable $callback) { $callback(); echo " I attend Rutherford's College"; } } $student = new Student(); College::attends($student); #=> I am a student I attend Rutherford's College |
The object was invoked as a callback into attends
method of the College
class.
4. __toString()
This magic method allows us to change the default behaviour of an object when it is been echoed. It returns a string representation. Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Student { private $name; public function __construct($name) { $this->name = $name; } public function __toString() { return "My name is {$this->name}. I am a Student"; } } $student = new Student('Prosper Otemuyiwa'); echo $student; #=> My name is Prosper Otemuyiwa. I am a Student |
Here we are echoing the object directly as a string. It’s a very nice shortcut that can shorten the amount of lines of code in your project.
5. __call and __callStatic
With these methods, you can handle situations when methods that don’t exist/publicly accessible are called on an object. Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Person { public function eat() { return "I can eat"; } public function drive() { return "I can drive"; } public function sleep() { return "I can sleep"; } } $person = new Person(); $person->sleep(); #=> I can sleep; $person->missisipi(); #=> Fatal error: Call to undefined method UnicodeveloperSeasonPerson::missisipi() in /Users/unicodeveloper/source/php-workspace/reflection/index.php on line 12 |
Calling a missisipi method that doesn’t exist in that class throws a FATAL error
Now, let’s take care of methods that don’t exist. Example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class Person { public function eat() { return "I can eat"; } public function drive() { return "I can drive"; } public function sleep() { return "I can sleep"; } public function __call($name, $args) { if (method_exists($this, $name)) { return $this->$name(); } if ($name === 'destroy') { return "You can't call this method on this Class. Too dangerous my friend"; } $message = "Hey, you just called the {$name} method but unfortunately there is nothing like that."; } } $person = new Person; $person->destroy(); #=> You can't call this method on this Class. Too dangerous my friend $person->missisipi(); #=> Hey, you just called the missisipi method but unfortunately there is nothing like that. |
Here, in the __call method, we are explicitly checking with method_exists if the method that is called on the object exists, if it does, go ahead and call the method.
If it was a destroy() method that was called, it should print out You can't call this method on this Class. Too dangerous my friend.
If the method doesn’t exist in that class at all, then report with a custom message that there is no method like that.
__callStatic is used for static functions like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class Person { public static function eat() { return "I can eat"; } public static function drive() { return "I can drive"; } public static function sleep() { return "I can sleep"; } public static function __callStatic($name, $args) { if (method_exists(new static, $name)) { return $this->$name(); } if ($name === 'destroy') { return "You can't call this method on this Class. Too dangerous my friend"; } $message = "Hey, you just called the {$name} method but unfortunately there is nothing like that."; return $message; } } Person::destroy(); #=> You can't call this method on this Class. Too dangerous my friend Person::missisipi(); #=> Hey, you just called the missisipi method but unfortunately there is nothing like that. |
6. call_user_func and call_user_func_array
Sometimes you are not sure what methods would be called, in your case, a method call might be determined by a user’s input..how do you handle that dynamically?
So call_user_func calls functions whose name you don’t know ahead of time.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class Arithmetic { public static function operations(callable $callback) { return call_user_func($callback); } } $number = 7; $multResult = Arithmetic::operations(function() use ($number) { return $number * $number; }); $addResult = Arithmetic::operations(function() use ($number) { return $number + $number; }); echo $multResult; #=> 49 echo $addResult; #=> 14 |
Here we passed callbacks ( anonymous functions), one for addition and another for multiplication and it performed those operations and gave us results because call_user_func was able to call those functions.
call_user_func_array() is very helpful for functions that have complex and variable parameter requirements like so:
1 2 3 4 5 |
function add($num, $num2) { echo $num + $num2; } call_user_func_array("add", array(1,2)); |
Conclusion
You have seen some of the magic you can perform on methods and how you can manipulate your programs during runtime. You have also seen how to use code to create code on the fly. We’ll be talking about the Reflection Class and it’s sister classes in the next part of this series.
You’ll receive more POWER as a PHP developer!
Please let me know if you have any questions or observations in the comments section.
- How to build your own Youtube – Part 10 - August 1, 2016
- How to build your own Youtube – Part 9 - July 25, 2016
- How to build your own Youtube – Part 8 - July 23, 2016
- How to build your own Youtube – Part 6 - July 6, 2016
- Introducing Laravel Password v1.0 - July 3, 2016
- How to build your own Youtube – Part 5 - June 28, 2016
- How to build your own Youtube – Part 4 - June 23, 2016
- How to build your own Youtube – Part 3 - June 15, 2016
- How to build your own Youtube – Part 2 - June 8, 2016
- How to build your own Youtube – Part 1 - June 1, 2016