Interactive PHP Debugging with PsySH

Share this article

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 https://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 (https://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.

Frequently Asked Questions (FAQs) about Interactive PHP Debugging with PsySH

What is PsySH and how does it work in PHP debugging?

PsySH is a runtime developer console, interactive debugger, and Read-Eval-Print Loop (REPL) for PHP. It provides an interactive command-line interface where you can execute PHP code and see the output immediately. PsySH is particularly useful for debugging as it allows you to step through your code, inspect variables, and interactively test changes. It’s like having a conversation with your code, which can lead to better understanding and quicker bug resolution.

How do I install PsySH for PHP debugging?

PsySH can be installed globally using Composer, a dependency management tool for PHP. You can install it by running the command composer global require psy/psysh. After installation, you can start PsySH by simply typing psysh in your terminal. Make sure to include the global Composer binaries in your PATH so your system can locate the PsySH executable.

How can I use PsySH to debug my PHP code?

To debug your PHP code using PsySH, you can insert \Psy\sh(); at any point in your code where you want to start an interactive debugging session. When your code execution reaches this point, PsySH will open an interactive shell, allowing you to inspect variables, execute code, and step through your code execution.

Can I use PsySH for unit testing in PHP?

Yes, PsySH can be very useful for unit testing in PHP. You can use PsySH to interactively debug your tests, inspecting variables and state at any point during the test execution. This can be particularly useful for understanding why a test is failing.

How can I customize the PsySH configuration?

PsySH allows you to customize its configuration by creating a .psysh.php file in your home directory. In this file, you can set configuration options such as default includes, error level, and command history size. You can also add custom commands or cleaners.

What are some advanced features of PsySH?

PsySH comes with many advanced features that can help you debug your PHP code more effectively. These include code execution with runtime, automatic semicolon insertion, namespace support, readline support, exception handling, and more. PsySH also supports tab completion for variables, functions, classes, and even PHP built-in keywords.

How does PsySH handle errors and exceptions?

PsySH has a robust error and exception handling mechanism. When an error or exception occurs, PsySH will display a detailed stack trace, helping you to understand exactly where and why the error occurred. You can also use the wtf command to display the last exception stack trace at any time.

Can I use PsySH with my favorite PHP framework?

Yes, PsySH works well with most PHP frameworks, including Laravel, Symfony, and Zend Framework. Some frameworks, like Laravel, even include PsySH out of the box for their tinker command.

How can I contribute to the PsySH project?

PsySH is an open-source project hosted on GitHub. You can contribute to the project by reporting bugs, suggesting features, or submitting pull requests. Before contributing, make sure to read the project’s contributing guidelines.

Where can I find more resources to learn about PsySH?

The official PsySH website and its GitHub repository are the best places to find resources about PsySH. They include detailed documentation, usage examples, and a list of available commands. You can also find tutorials and articles on various PHP and developer blogs.

Miguel Ibarra RomeroMiguel Ibarra Romero
View Author

Web application developer, database administrator, project lead in a variety of enterprise apps and now article author. Interested in database design and optimization. Amazed and involved in distributed systems development. Cryptography and information security fan.

BrunoSdebugdebuggingOOPHPPHPpsyshreplshellunit test
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week