PHP
Article

Interactive PHP Debugging with PsySH

By Miguel Ibarra Romero

It’s 1:00 a.m., the deadline for your web application’s delivery is in 8 hours… and it’s not working. As you try to figure out what’s going on, you fill your code with var_dump() and die() everywhere to see where the bug is…

You are annoyed. Every time you want to try out a return value or variable assignment, you have to change your source code, execute your application, and see the results… In the end, you are unsure of whether or not you’ve removed all those var_dumps from the code. Is this situation familiar to you?

PsySH to the rescue

PsySH is a Read-Eval-Print Loop (or REPL). You may have used a REPL before via your browser’s javascript console. If you have, you know that it possesses a lot of power and can be useful while debugging your JS code.

Talking about PHP, you might have used PHP’s interactive console (php -a) before. There, you can write some code and the console will execute it as soon as you press enter:

php -a
Interactive shell

php > $a = 'Hello world!';
php > echo $a;
Hello world!
php >

Unfortunately, the interactive shell is not a REPL since it lacks the “P” (print). I had to execute an echo statement to see the contents of $a. In a true REPL, we would have seen it immediately after assigning the value to it.

You can install PsySH globally either with a composer g require, or downloading the PsySH executable:

Composer

composer g require psy/psysh:~0.1
psysh

Direct download (Linux/Mac)

wget psysh.org/psysh
chmod +x psysh
./psysh

Additionally, you can have it included per project with composer as you’ll see later in this article.

Now let’s play with PsySH a little.

./psysh                                                                                                                                             

Psy Shell v0.1.11 (PHP 5.5.8 — cli) by Justin Hileman                                                                                                                                                                              
>>>

The main help will be your best friend. It’s what will give you all sorts of commands and their explanations:

>>> help

  help      Show a list of commands. Type `help [foo]` for information about [foo].      Aliases: ?
  
  ls        List local, instance or class variables, methods and constants.              Aliases: list, dir
  
  dump      Dump an object or primitive.
  
  doc       Read the documentation for an object, class, constant, method or property.   Aliases: rtfm, man 
  
  show      Show the code for an object, class, constant, method or property.
  
  wtf       Show the backtrace of the most recent exception.                             Aliases: last-exception, wtf?
  
  trace     Show the current call stack.
  
  buffer    Show (or clear) the contents of the code input buffer.                       Aliases: buf
  
  clear     Clear the Psy Shell screen.
  
  history   Show the Psy Shell history.
  
  exit      End the current session and return to caller.                                Aliases: quit, q
>>> help ls

Usage:

ls [--vars] [-c|--constants] [-f|--functions] [-k|--classes] [-I|--interfaces] [-t|--traits] [-p|--properties] [-m|--methods] [-G|--grep="..."] [-i|--insensitive] [-v|--invert] [-g|--globals] [-n|--internal] [-u|--user] [-C|--
category="..."] [-a|--all] [-l|--long] [target]

Aliases: list, dir

Arguments:

 target             A target class or object to list.
 
 
Options:

 --vars             Display variables.
 
 --constants (-c)   Display defined constants.
 
 --functions (-f)   Display defined functions.
 
 --classes (-k)     Display declared classes.
 
 --interfaces (-I)  Display declared interfaces.
 
 --traits (-t)      Display declared traits.
 
 --properties (-p)  Display class or object properties (public properties by default).
 
 --methods (-m)     Display class or object methods (public methods by default).
 
 --grep (-G)        Limit to items matching the given pattern (string or regex).
 
 --insensitive (-i) Case-insensitive search (requires --grep).
 
 --invert (-v)      Inverted search (requires --grep).
 
 --globals (-g)     Include global variables.
 
 --internal (-n)    Limit to internal functions and classes.
 
 --user (-u)        Limit to user-defined constants, functions and classes.
 
 --category (-C)    Limit to constants in a specific category (e.g. "date").
 
 --all (-a)         Include private and protected methods and properties.
 
 --long (-l)        List in long format: includes class names and method signatures.
 
 
 Help:
 
 List variables, constants, classes, interfaces, traits, functions, methods, and properties.
 
 Called without options, this will return a list of variables currently in scope.
 
 If a target object is provided, list properties, constants and methods of that target. If a class, interface or trait name is passed instead, list constants and methods on that class.
 
 e.g. 
 
 >>> ls
 >>> ls $foo
 >>> ls -k --grep mongo -i
 >>> ls -al ReflectionClass
 >>> ls --constants --category date
 >>> ls -l --functions --grep /^array_.*/
 >>>

Basically, what a REPL can do is:

>>> $a = 'hello';
=> "hello"
>>>

Please note that if we compare PsySH against PHP’s interactive console, PsySH prints the $a value as soon as it is assigned.

A more complex example can be as follows:

>>> function say($a) {
...     echo $a;
... }
=> null
>>> say('hello');
hello
=> null
>>>

I defined the function say() and invoked it. Those two null you see are because neither the function definition nor its execution returned a value (the function echoes the value). Additionally, while defining the function, the prompt changed from >>> to ....

Can we define a class and instantiate it?

>>> class Foo
... {
...     protected $a;
...
...     public function setA($a) {
...         $this->a = $a;
...     }
...
...     public function getA() {
...         return $this->a;
...     }
... }
=> null
>>> $foo = new Foo();
=> <Foo #000000001dce50dd000000002dda326e> {}
>>> $foo->setA('hello');
=> null
>>> $foo->getA();
=> "hello"
>>>

When I instantiated Foo, the constructor returned a reference to the object. This is why PsySh printed <Foo #000000001dce50dd000000002dda326e>. Now let’s see what is interesting about PsySH and objects.

>>> ls $foo
Class Methods: getA, setA
>>>

If by any chance you forgot which methods the class Foo has defined, you now have the answer. Have you used a Linux OS or Mac command line interface? Then you might be familiar with the ls command. Remember the -la options?

>>> ls -la $foo
Class Properties:

  $a   "hello" 
  

Class Methods:

  getA   public function getA()
  setA   public function setA($a)

Sweet, isn’t it?

PsySH’s true power shines when integrated with a web application, so let’s build one.

Demo app

I’m going to implement a quick application to showcase the decorator design pattern. The UML class diagram of such a pattern is as follows:
Decorator Design Pattern

Do not worry if you do not know much about UML or design patterns, understanding them is not required for this article.

Also for this project, I created a set of test cases. Those test cases can be run by phpUnit. Again, you do not have to be familiar with Unit Testing to understand this article.

The complete source code for this little application can be found at https://github.com/sitepoint-examples/PsySH

First of all, let’s define our composer.json file in order to declare a dependency on PsySH:

{
    "name": "example/psysh",
    "authors": [
        {
            "name": "John Doe",
            "email": "john@doe.tst"
        }
    ],
    "require": {
        "psy/psysh": "~0.1"
    },
    "autoload": {
        "psr-4": {"Acme\\": "src/"}
    }
}

After a composer install you should be good to go.

Please take a look at the following source code from the file public/decorator.php. It will instantiate the SimpleWindow, DecoratedWindow, and TitledWindow objects to showcase the decorator pattern:

<?php
chdir(dirname(__DIR__));

require_once('vendor/autoload.php');

use Acme\Patterns\Decorator\SimpleWindow;
use Acme\Patterns\Decorator\DecoratedWindow;
use Acme\Patterns\Decorator\TitledWindow;

echo PHP_EOL . 'Simple Window' . PHP_EOL;

$window = new SimpleWindow();

echo $window->render();

echo PHP_EOL . 'Decorated Simple Window' . PHP_EOL;

$decoratedWindow = new DecoratedWindow($window);

echo $decoratedWindow->render();

echo PHP_EOL . 'Titled Simple Window' . PHP_EOL;

$titledWindow = new TitledWindow($window);

echo $titledWindow->render();

We can execute the code via PHP’s CLI (command line interface) or through a webserver if one is configured. We can use PHP’s internal web server too.

Debugging in cli

The execution of the above code through the command line interface will look like this:

php public/decorator.php 

Simple Window
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+

Decorated Simple Window
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+

Titled Simple Window
+-------------+
|Title        |
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+

How can we interact with PsySH? Just add \Psy\Shell::debug(get_defined_vars()); anywhere on the code where you want to debug your application, typically where you would insert a var_dump() statement:

<?php
chdir(dirname(__DIR__));

require_once('vendor/autoload.php');

//... a lot of code here

$titledWindow = new TitledWindow($window);

echo $titledWindow->render();

\Psy\Shell::debug(get_defined_vars()); //we want to debug our application here!

After saving the file, we will get the following output:

php public/decorator.php 

Simple Window
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+

Decorated Simple Window
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+

Titled Simple Window
+-------------+
|Title        |
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+

Psy Shell v0.1.11 (PHP 5.5.8 — cli) by Justin Hileman
>>>

The script’s execution will be suspended, and we now have PsySH’s prompt to play with. I am passing get_defined_vars() as a parameter to Psy\Shell::debug() so I have access to all defined variables inside the shell:

>>> ls
Variables: $_COOKIE, $_FILES, $_GET, $_POST, $_SERVER, $argc, $argv, $decoratedWindow, $titledWindow, $window
>>>

Let’s examine the $window variable:

>>> ls -al $window

Class Methods:
  render   public function render()  
>>>

Something nice about having PsySH inside an application is that we can examine the source code of an instantiated object.

>>> show $window
class Acme\Patterns\Decorator\SimpleWindow implements Acme\Patterns\Decorator\Window
class SimpleWindow implements Window
{
    public function render()
    {
        $returnString = <<<EOL
+-------------+
|             |
|             |
|             |
|             |
|             |
+-------------+
EOL;
        return $returnString . PHP_EOL;
    }
}
>>>

So, $window is an instance of SimpleWindow, which implements the Window interface… I wonder what the source code for the Window interface looks like…

>>> show Acme\Patterns\Decorator\Window
interface Acme\Patterns\Decorator\Window
interface Window
{
    public function render();
}
>>>

Why do SimpleWindow and DecoratedWindow have the same output? Let’s examine the $decoratedWindow object.

>>> ls -al $decoratedWindow

Class Properties:
  $windowReference   <Digitec\Patterns\Decorator\SimpleWindow #000000003643d67700000000731101b7>  

Class Methods:
  __construct          public function __construct(Digitec\Patterns\Decorator\Window $windowReference)         
  getWindowReference   public function getWindowReference()                                                    
  render               public function render()                                                                
  setWindowReference   public function setWindowReference(Digitec\Patterns\Decorator\Window $windowReference)  
>>>

This object is “heavier” than the SimpleWindow one, so the source code might be long… Let’s see the source code for the render() method only:

>>> show $decoratedWindow->render
public function render()
    public function render()
    {
        return $this->getWindowReference()->render();
    }

The getWindowReference() method is invoked, and then it returns the result from the render() method. Let’s check the getWindowReference() source:

>>> show $decoratedWindow->getWindowReference
public function getWindowReference()
    public function getWindowReference()
    {
        return $this->windowReference;
    }
>>>

This method is returning the object’s windowReference property, and as we saw from the ls -al command above, it is an instance of Acme\Patterns\Decorator\SimpleWindow. Of course, we could have just looked at how DecoratedWindow::__construct() works, but this is another way we can check.

Debugging with embedded server

Unfortunately, debugging through a webserver like Apache is not supported. However, we can debug our application using PHP’s embedded server:

$ cd public
$ php -S localhost:8080
PHP 5.5.8 Development Server started at Wed Jul 23 17:40:30 2014
Listening on http://localhost:8080
Document root is /home/action/workspace/lab/PsySH/public
Press Ctrl-C to quit.

The Development Server is now listening for connections on port 8080, so as soon as we request the decorator.php file through this web server (http://localhost:8080/decorator.php), we should see the following:

Psy Shell v0.1.11 (PHP 5.5.8 — cli-server) by Justin Hileman
>>>

We can begin playing with PsySH just as we did with the CLI

>>> ls -al

Variables:
  $_COOKIE           []                                                                              
  $_FILES            []                                                                              
  $_GET              []                                                                              
  $_POST             []                                                                              
  $_SERVER           Array(19)                                                                       
  $decoratedWindow   <Acme\Patterns\Decorator\DecoratedWindow #0000000031ef2e3e000000003c2d3a90>  
  $titledWindow      <Acme\Patterns\Decorator\TitledWindow #0000000031ef2e39000000003c2d3a90>     
  $window            <Acme\Patterns\Decorator\SimpleWindow #0000000031ef2e3f000000003c2d3a90>     
  $_                 null                                                                            
>>> exit
Exit:  Goodbye.

Debugging with unit tests

As a good developer, you should write unit tests for your code as proof that it is working as expected. In the project’s files you will find the tests folder, and if you have phpUnit installed, you can run the tests inside it.

cd tests/
phpunit
PHPUnit 4.0.14 by Sebastian Bergmann.

Configuration read from /home/action/workspace/lab/PsySH/tests/phpunit.xml

...F+-------------+
|Title        |


Time: 66 ms, Memory: 4.50Mb

There was 1 failure:

1) AcmeTest\Patterns\Decorator\TitledWindowTest::testAddTitle
Failed asserting that true is false.

/home/action/workspace/lab/PsySH/tests/Patterns/Decorator/TitledWindowTest.php:46
                                     
FAILURES!                            
Tests: 4, Assertions: 7, Failures: 1.

Even though the code appears to run flawlessly, a test is failing. We can examine further by running just the failing test:

$ phpunit --verbose --debug --filter=TitledWindowTest::testAddTitle
PHPUnit 4.0.14 by Sebastian Bergmann.

Configuration read from /home/action/workspace/lab/PsySH/tests/phpunit.xml


Starting test 'AcmeTest\Patterns\Decorator\TitledWindowTest::testAddTitle'.
F+-------------+
|Title        |


Time: 60 ms, Memory: 4.25Mb

There was 1 failure:

1) AcmeTest\Patterns\Decorator\TitledWindowTest::testAddTitle
Failed asserting that true is false.

/home/action/workspace/lab/PsySH/tests/Patterns/Decorator/TitledWindowTest.php:46
                                     
FAILURES!                            
Tests: 1, Assertions: 1, Failures: 1.

We have the test, file and line where the error is being generated. Let’s take a look at TitledWindowTest.php

<?php
namespace AcmeTest\Patterns\Decorator;

use PHPUnit_Framework_TestCase;
use Acme\Patterns\Decorator\TitledWindow;
use ReflectionMethod;


class TitledWindowTest extends PHPUnit_Framework_TestCase 
{
    public function testRender()
    {
        /* some test code here */
        
    }
    
    public function testAddTitle()
    {
        $renderString = 'bar';
        
        $window = $this->getMock('Acme\Patterns\Decorator\SimpleWindow');
        $window->expects($this->any())->method('render')->will($this->returnValue($renderString));
        
        $titledWindow = new TitledWindow($window);
        
        $reflectionMethod = new ReflectionMethod($titledWindow, 'addTitle');
        $reflectionMethod->setAccessible(true);
        
        $rs = $reflectionMethod->invoke($titledWindow);
        
        $this->assertFalse(empty($rs));
        
    }
}

If you are unfamiliar with phpUnit, do not pay too much attention to the code. In a nutshell, I’m setting up everything to test the TitledWindow::addTitle() method, and expecting to receive a non empty value.

So, how do we use PsySh to check what is going on? Just add the Shell::debug() method as we did previously.

<?php
namespace DigitecTest\Patterns\Decorator;

use PHPUnit_Framework_TestCase;
use Digitec\Patterns\Decorator\TitledWindow;
use ReflectionMethod;

class TitledWindowTest extends PHPUnit_Framework_TestCase 
{
    public function testRender()
    {
        /* Some test code here */
    }
    
    public function testAddTitle()
    {
        $renderString = 'bar';
        
        $window = $this->getMock('Digitec\Patterns\Decorator\SimpleWindow');
        $window->expects($this->any())->method('render')->will($this->returnValue($renderString));
        
        $titledWindow = new TitledWindow($window);
        
        $reflectionMethod = new ReflectionMethod($titledWindow, 'addTitle');
        $reflectionMethod->setAccessible(true);
        
        $rs = $reflectionMethod->invoke($titledWindow);
        
        \Psy\Shell::debug(get_defined_vars());
        
        $this->assertFalse(empty($rs));
        
    }
}

We are ready to rock!

$ phpunit --verbose --debug --filter=TitledWindowTest::testAddTitle
PHPUnit 4.0.14 by Sebastian Bergmann.

Configuration read from /home/action/workspace/lab/PsySH/tests/phpunit.xml


Starting test 'AcmeTest\Patterns\Decorator\TitledWindowTest::testAddTitle'.
Psy Shell v0.1.11 (PHP 5.5.8 — cli) by Justin Hileman
>>>

So in $rs we should have a string; let’s see what we really have.

>>> $rs
=> null

Null value, no wonder the test failed… Let’s check the source code of TitledWindow::addTitle(). If we perform an ls command, we can see that we have that object’s method available through the $titledWindow object.

>>> show $titledWindow->addTitle
protected function addTitle()
    protected function addTitle()
    {
        $returnString = <<<EOL
+-------------+
|Title        |
EOL;
        echo $returnString . PHP_EOL;
    }
>>>

There is the bug. The method is echoing the value instead of returning it. Even though the application seems to work right, through unit testing and PsySH we discovered a defect and we can now fix it.

Conclusion

This article was not meant to be exhaustive in showcasing all the potential PsySH has. There are other cool features (like ‘doc’) that you should try out. PsySH alone may not be very useful, but if combined with other tools and your clever debugging powers, it can prove to be a valuable asset.

  • http://harikt.com/ Hari K T

    Nice :) .

  • Oscar Blank

    Compared to FirePHP, there is way too much effort involved to make debugging with Psysh something I could consider. If you’re really up at 1:00am with a deadline in 8 hours, you probably should have used FirePHP. Instead of presenting your work and feeling like a zombie, you could be getting a pat on the back and a free lunch.

    • Miguel Ibarra

      Thanks for your comments :)

      If it is 1:00am and a deadline in 8 hours, of course you will use the tool you are the most proficient with. For you might be FirePHP, however not everyone might prefer, or even know about FirePHP. For me, actual debugging with xdebug or zend debugger will do the work, however not everyone might be able or be willing to set up those. As usual, it is a matter of preferences and possibilities. PsySH is just another tool out there worth trying out, with its advantages and disadvantages.

  • Taylor Ren

    Seems very much related to CLI debugging. How about WEB + framework debugging?

    IDE may support but to have some more direct debugging support would be nice.

    • Miguel Ibarra

      Indeed. The injection of a web console for debugging in a dev environment would be nice.

    • http://justinhileman.info/ justin hileman

      It works great with web and framework debugging, as long as you run your app from the built-in PHP SAPI CLI server:

      php -S localhost:8000 index.php

      A web-based interface — so that you can use PsySH no matter what server you’re sitting behind — is planned, but hasn’t been implemented yet. Pull requests are accepted though ;)

  • Mark Kasaboski

    I particularly like the “wtf” command. http://psysh.org/

  • http://www.ericlin.me Eric Lin

    Doesn’t look like I can use breakpoints to step through the code, can I? You will still have to place the Shell::debug() call in the right place to find out exactly what happened, might be quite tedious if you don’t know the code very well. Still prefers actual debuggers like xdebug.

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in PHP, once a week, for free.