Skip to main content
PsySH’s most powerful feature is the ability to drop into an interactive debugging session from anywhere in your code using \Psy\debug().

Quick Start

Add a single line to your code to start debugging:
<?php

function calculateTotal($items) {
    $total = 0;
    foreach ($items as $item) {
        \Psy\debug();  // Drop into PsySH here
        $total += $item['price'] * $item['quantity'];
    }
    return $total;
}
When execution reaches \Psy\debug(), you’ll get an interactive prompt with access to all local variables.

Basic Usage

Simple Debugging

The simplest form requires no arguments:
<?php

$user = User::find(1);
$orders = $user->orders;

\Psy\debug();  // Inspect $user and $orders interactively
Inside the debugger:
>>> $user->name
=> "Alice Johnson"
>>> count($orders)
=> 5
>>> $orders[0]->total
=> 129.99

Passing Variables Explicitly

Pass specific variables using get_defined_vars():
<?php

function processOrder($orderId, $userId) {
    $order = Order::find($orderId);
    $user = User::find($userId);
    $total = calculateTotal($order);
    
    \Psy\debug(get_defined_vars());
}
Using get_defined_vars() gives you access to all local variables in the current scope.

Object Context Debugging

Binding to $this

Debug within an object method with access to private properties:
<?php

class ShoppingCart {
    private $items = [];
    private $discountRate = 0.1;
    
    public function addItem($item) {
        $this->items[] = $item;
        
        \Psy\debug(get_defined_vars(), $this);
    }
}
Inside the debugger:
>>> $this->items
=> [
     [
       "name" => "Widget",
       "price" => 29.99,
     ],
   ]
>>> $this->discountRate  // Access private property!
=> 0.1
>>> $this->calculateTotal()  // Call private methods!
When you bind to $this, you get access to private and protected properties and methods, making it perfect for deep debugging.

Binding to Static Context

For static methods, bind to the class name:
<?php

class PaymentProcessor {
    private static $apiKey = 'secret-key';
    
    public static function process($amount) {
        \Psy\debug(get_defined_vars(), __CLASS__);
        
        return self::charge($amount);
    }
    
    private static function charge($amount) {
        // ...
    }
}
Inside the debugger:
>>> self::$apiKey  // Access private static property
=> "secret-key"
>>> self::charge(100)  // Call private static method

The extract() Pattern

Two-Way Variable Sync

Use extract() to modify variables in your code:
<?php

function processData($data) {
    $filtered = array_filter($data);
    $mapped = array_map('strtoupper', $filtered);
    
    // Debug and potentially modify variables
    extract(\Psy\debug(get_defined_vars()));
    
    // Variables modified in PsySH are now available here!
    return $mapped;
}
Inside the debugger:
>>> $mapped = array_reverse($mapped)  // Modify the variable
>>> exit  // Return to code execution
After exiting, $mapped contains the modified value in your running code.
The extract() pattern works in functions and methods, but not in the global scope. It requires PHP 8.0+ for static contexts.

Complete extract() Example

<?php

class DataProcessor {
    public function transform(array $input) {
        $step1 = $this->normalize($input);
        $step2 = $this->validate($step1);
        $step3 = $this->enrich($step2);
        
        // Debug here and modify variables
        extract(\Psy\debug(get_defined_vars(), $this));
        
        return $step3;
    }
}
You can now inspect and modify $step1, $step2, or $step3:
>>> count($step2)
=> 50
>>> $step3 = array_slice($step3, 0, 10)  // Take only first 10
=> [...]
>>> exit  // Continue with modified $step3

Debugging Loops

Drop into the debugger during iteration:
<?php

foreach ($users as $user) {
    if ($user->needsReview()) {
        extract(\Psy\debug(get_defined_vars()));
        // Inspect or modify $user
    }
    $user->process();
}
You can exit the debugger with exit to continue to the next iteration, or modify variables to affect subsequent iterations.

Conditional Debugging

Only debug when certain conditions are met:
<?php

function processPayment($payment) {
    $amount = $payment->amount;
    
    // Only debug large payments
    if ($amount > 10000) {
        \Psy\debug(get_defined_vars());
    }
    
    // Process payment...
}

Debug on Exception

Debug when catching exceptions:
<?php

try {
    $result = riskyOperation();
} catch (\Exception $e) {
    \Psy\debug(get_defined_vars());
    throw $e;
}

Debugging Tips

1

Examine Context

Start by looking at what’s available:
>>> ls
Variables: $user, $orders, $total
>>> ls -v
# Shows variables with values and types
2

Inspect Objects

Use show to see object details:
>>> show $user
# Shows class definition, properties, methods
3

Test Fixes

Try potential fixes interactively:
>>> $user->balance -= 10
>>> $user->save()
# Test if this fixes the issue
4

Continue Execution

Exit when done:
>>> exit
# Or press Ctrl+D

Advanced Debugging

Multiple Debug Points

Add multiple breakpoints:
<?php

function complexOperation($data) {
    $step1 = preprocess($data);
    \Psy\debug(get_defined_vars());  // Checkpoint 1
    
    $step2 = transform($step1);
    \Psy\debug(get_defined_vars());  // Checkpoint 2
    
    $step3 = finalize($step2);
    \Psy\debug(get_defined_vars());  // Checkpoint 3
    
    return $step3;
}

Whereami Command

See where you are in the code:
>>> whereami
From /app/src/Payment/Processor.php:42:

    37:     public function process($payment) {
    38:         $amount = $payment->amount;
    39:         $fee = $this->calculateFee($amount);
    40:         
    41:         // Debug checkpoint
  > 42:         \Psy\debug(get_defined_vars(), $this);
    43:         
    44:         $total = $amount + $fee;
    45:         return $this->charge($total);
    46:     }
The whereami command is automatically executed when you enter a debug session, showing you the surrounding code context.

Working with Frameworks

Laravel

<?php

Route::get('/debug/{id}', function ($id) {
    $user = User::find($id);
    \Psy\debug(get_defined_vars());
    return view('user.show', compact('user'));
});

Symfony

<?php

class UserController extends AbstractController {
    public function show($id) {
        $user = $this->getDoctrine()
            ->getRepository(User::class)
            ->find($id);
        
        extract(\Psy\debug(get_defined_vars(), $this));
        
        return $this->render('user/show.html.twig', [
            'user' => $user,
        ]);
    }
}

WordPress

<?php

add_action('init', function() {
    if (isset($_GET['debug'])) {
        global $wpdb, $post;
        \Psy\debug(get_defined_vars());
    }
});

Eval Alternative: sh()

Use \Psy\sh() for a quick eval-based debugger:
<?php

$user = User::find(1);
$orders = $user->orders;

eval(\Psy\sh());  // Opens debugger with current scope
This works in PHP 8.0+:
<?php

if (isset($this)) {
    extract(\Psy\debug(get_defined_vars(), $this));
} else {
    try {
        static::class;
        extract(\Psy\debug(get_defined_vars(), static::class));
    } catch (\Error $e) {
        extract(\Psy\debug(get_defined_vars()));
    }
}
The eval(\Psy\sh()) pattern generates code that properly handles the current context, including $this in object contexts and static::class in static contexts. This is the most reliable way to maintain proper scope.

Return Value Handling

\Psy\debug() returns an array of variables from the debugging session:
<?php

$x = 10;
$result = \Psy\debug(get_defined_vars());
// Returns ['x' => 10, ...other vars...]

extract($result);
// Now any variables modified in the debugger are available

Source Code Reference

The debug function is defined in:
  • Main function: src/functions.php:55-122
  • Function documentation: src/functions.php:56-97
  • Context binding: src/functions.php:112-116
  • Scope handling: src/functions.php:103
  • Return values: src/functions.php:120

Comparison with Other Debuggers

Feature\Psy\debug()Xdebugdump/dd
Interactive
No setup required
Modify variables
Access private members
Call methods
Lightweight
Step debugging
Use \Psy\debug() for quick, interactive debugging without IDE setup. Use Xdebug for complex step-through debugging.

Build docs developers (and LLMs) love