Have you ever wondered, how does Laravel Tinker work?
If you take a look at it’s compose.json file, you’ll see that it depends on a package called psy/psysh
. If you do not recognize this package, it is a REPL tool for PHP. It Reads user input code, Evaluates it, Prints the output one after another(On a Loop). The Tinker package adds some Laravel magic to it like auto detecting model/facade classes, but at it’s core, it’s simply a REPL tool. Let’s see if we can build our own REPL tool.
Reading code from User
This step is very easy, we can read input from user using fgets
and STDIN
resource constant.
<?php $code = trim(fgets(STDIN));
That’s it! The trim
function is there to simply get rid of some extra spaces. Nothing too complex…yet. If you are unsure of how to run this code, just write it in a file called repl.php
and call it from your terminal with php repl.php
Let’s move on.
Evaluate the code
Another very easy step. We have eval
on our hand for exactly this purpose.
<?php $code = trim(fgets(STDIN)); eval($code);
You might be thinking, that’s it? Well we can make some improvements here to make the users’ life easier but for now that’s enough. Let’s test this code, shall we? Put the code in a file and let’s run it. You’ll see that it doesn’t output anything, it just waits. That’s actully our first line of code, waiting for your input. If you type echo "Hello World";
(mind the ;
at the end), you will see it outputs Hello World
and exits. We are in business!
Loop it
Reading and executing one line of code is not that much helpful, right? We need to do this again and again. Let’s add a infinite loop here.
<?php while (true) { echo "\n<"; $code = trim(fgets(STDIN)); eval($code); }
If you’ve been paying attention, I’ve also snuck in a echo "\n> ";
inside the infinite loop. Now it looks just like PsySH and Laravel Tinker!
An interaction with the script may look like this,
> $name = "Bob";
> echo "Hello {$name}\n";
Hello Bob
>
Now, we can make a couple of “small” improvements to make it actually useful. First, let’s admin, the ;
thing is annoying. I mean it doesn’t bother me when I am writing actual code in an IDE but when I’m just trying out things, it’s annoying. So let’s make sure our code always has a ;
even if we don’t write one.
<?php while (true) { echo "\n<"; $code = trim(fgets(STDIN)); if(substr($code, -1) !== '}'){ $code .= ';'; } eval($code); }
So what’s happening here? we are using the substr function to get the last character of our code. If it’s a }
that means our code has something like a for loop or a function definition as the last character and we don’t want to put extra ;
in that case. Otherwise, we add the ;
. Now the same interaction looks like this,
> $name = "Bob"
> echo "Hello {$name}\n"
Hello Bob
>
The next big issue here is, one exception and our script exits.
> bar()
PHP Fatal error: Uncaught Error: Call to undefined function bar() in repl.php(10) : eval()'d code:1
Let’s fix it.
<?php while (true) { echo "\n<"; $code = trim(fgets(STDIN)); if(substr($code, -1) !== '}'){ $code .= ';'; } try{ eval($code); }catch(\Throwable $e){ echo $e->getMessage() . "\n"; } }
Now the interaction improves,
> bar()
Error: Call to undefined function ba()
>
We can do a lot more here but this is how tools like PsySH or Laravel Tinker works. We can modify the code just like we added a semicolon after each line, we can detect incomplete code blocks and wait for more input before evaluating, we can autoload classes and many many more!
Leave a Reply