Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Psysh Laravel Package

psy/psysh

PsySH is a runtime developer console, interactive debugger, and REPL for PHP. Inspect variables, evaluate expressions, and debug live code from your terminal, with configurable themes, history, and integrations like Laravel Tinker.

View on GitHub
Deep Wiki
Context7

Getting Started

In a Laravel app you almost never install PsySH directly. Laravel ships laravel/tinker (which depends on psy/psysh) in composer.json by default. The day-to-day entry point is:

php artisan tinker

For a non-Laravel project, or to run the raw shell outside a framework:

composer require --dev psy/psysh
vendor/bin/psysh

Where to look first:

  1. config/tinker.php — the Laravel-facing surface for PsySH options (commands, alias, dont_alias). Most behavior you'll want to change lives here rather than in a .psysh.php.
  2. ~/.config/psysh/config.php (or $PSYSH_CONFIG) — global, per-user config: theme, pager, history, readline, semicolonsSuppressReturn, clipboardCommand, trustProject.
  3. Inside the shell: help, help <command>, ls, doc <thing>, and psy\info() (or launch with --info) to see what readline/autocomplete engine is actually active.

First real use case — poke at a model without writing a throwaway route:

$user = App\Models\User::with('posts')->first();
$user->           // press Tab: real property/method list, not a guess
$user->posts->first()->author->   // chains through return types
ls $user                          // grouped listing, magic members in magenta
doc $user->posts                  // PHP manual / docblock-aware help

Note the first-run trust prompt (v0.12.19+). PsySH refuses to load project-local config, local binaries, or Composer autoloads until you approve the directory. Approve it once interactively; the decision is persisted in trusted_projects.json.

Implementation Patterns

Replacing dd() and var_dump() during development. The shell is a REPL, so the useful pattern is iterative narrowing rather than one-shot dumping:

$order = App\Models\Order::find(42);
$order->items->sum('price');           // compute in place
$_->toArray();                          // $_ is the last result
$__->getMessage();                      // $__ is the last exception, if any
wtf                                     // show that exception with context
trace                                   // full backtrace of last error
whereami                                // which file/line am I in (when dropped into a breakpoint)

Non-interactive execution for scripts and CI. This is the pattern most teams underuse. artisan tinker accepts piped stdin and --execute:

php artisan tinker --execute="echo App\Models\User::count();"

# or pipe a whole script
cat scripts/backfill.php | php artisan tinker

Wrap that in a Makefile target or a scheduled Artisan::call-adjacent task and you get throwaway data migrations without a committed command class. Always pass --no-interaction in automated contexts so the trust prompt and readline setup don't block.

Programmatic shell inside your own Artisan command or tests. Psy\Shell is a public API, not just a binary:

use Psy\Shell;
use Psy\Configuration;

$config = new Configuration();
$config->setUpdateCheck(Configuration::UPDATE_CHECK_NEVER);
$config->setStartupMessage('');

$shell = new Shell($config);
$shell->setScopeVariables(['app' => $this->laravel, 'user' => $user]);
$shell->execute('$user->load("posts"); echo $user->posts->count();');

This is how downstream tools (Drush, MediaWiki maintenance scripts, composer-repl) embed PsySH, and it's a reasonable way to give yourself an "open a REPL with this object already in scope" debugging affordance. Use setBoundObject() / setBoundClass() when you want $this and self:: to resolve inside your domain object.

Custom casters for pretty output. Register a caster so your value objects and DTOs print usefully instead of as anonymous blobs:

// ~/.config/psysh/config.php
return [
    'casters' => [
        App\ValueObjects\Money::class => function (App\ValueObjects\Money $m, array $a, Psy\Shell $s) {
            return $s->format($m->amount() . ' ' . $m->currency());
        },
    ],
];

Custom commands for team-wide workflows. Implement Psy\Command\Command and register it in config/tinker.php under 'commands' => [...], or drop it in app/PsySH/ and list it. Commands can define their own argument completion via CommandArgumentCompletionAware (v0.12.22+), which makes a seed --model <Tab> style command genuinely pleasant.

Good candidates: seed (call a specific seeder without db:seed), sql (dump the query builder SQL under the current scope), login (issue a Sanctum token for a user), mail-preview (render a Mailable to $_).

Import discipline inside a session. use App\Models\User; persists for the rest of the session, and as of v0.12.22 aliases are tracked by import type (class/function/constant), so use function resolution works correctly. Prefer explicit imports over fully-qualified names — tab completion on a fully-qualified prefix is noisier.

Runtime config without restarting. As of v0.12.22, config changes settings live:

config pager=false
config theme=light
config useSyntaxHighlighting=false

And copy $_ / copy $order->items pushes to the system clipboard (or OSC 52 over SSH via useOsc52Clipboard), which is the fastest way to get a big result out of the terminal.

Time something before committing it to code:

timeit -n 100 App\Models\Order::with('items')->find(1)

Gotchas and Tips

Writes are real writes. There is no implicit transaction or rollback in Tinker. $user->delete() deletes. If you're exploring destructive paths, open with DB::beginTransaction() and close with DB::rollBack(), or use a dedicated scratch database. This is the single most common way people damage a development dataset.

Trust / Restricted Mode will bite you in containers and CI. Since 0.12.19, a .psysh.php in the CWD is not loaded unless the directory is trusted. Symptoms: your project config silently isn't applied, startup message missing, custom commands absent. Controls:

  • 'trustProject' => 'prompt' | 'always' | 'never' in config
  • --trust-project / --no-trust-project
  • PSYSH_TRUST_PROJECT env var — set this in Docker images and CI rather than fighting the prompt
  • --warm-autoload is also gated by trust (0.12.23)

Non-interactive sessions skip untrusted features with a warning rather than hanging. Also: upgrade past 0.12.19 / 0.11.23 — CVE-2026-25129 is a CWD config-poisoning RCE and it affects Tinker too.

Bare command names now shadow PHP functions. config and copy resolve to PsySH commands before PHP function calls (0.12.22+). If you actually meant copy() from your own namespace, prefix with ; to force PHP evaluation:

;copy($a, $b)

Same issue applies to any future command name collision — ; is the universal escape hatch.

Semicolon return suppression is opt-in and can confuse you. Default behavior returns the value of every expression. If your team enables 'semicolonsSuppressReturn' => true, ending a line with ; prints nothing — which makes $user->posts; look broken to someone expecting output. 'double' mode requires ;;. Document whichever mode you pick, or leave it off.

Multi-line pasting is line-by-line without the new readline. In legacy readline, pasting a foreach block executes the first line immediately and errors. Either paste one statement at a time, or enable:

php artisan tinker --experimental-readline

The experimental engine adds bracketed paste, multi-line editing with auto-dedent, Ctrl+R reverse search, and a built-in pager. It's opt-in and still labeled experimental — expect occasional terminal quirks, but it's substantially better for anything involving closures or heredocs. If readline/libedit extensions are missing entirely, you get a degraded fallback; psy\info() tells you which engine is live.

Long sessions leak memory and stale state. Every model instance you create stays referenced by $_, history, and any variables you've bound. A 30-minute Tinker session against a large dataset will balloon. Restart when things get slow. Also remember $_ changes type after every command — don't rely on it surviving an unrelated expression.

config/tinker.php is not the same file as .psysh.php. Precedence, roughly: CLI flags → project .psysh.php (if trusted) → user config → Laravel's tinker.php → defaults. If a setting "doesn't take," check that you edited the layer that actually wins. --config <path> forces a specific file.

Exception formatting is version-sensitive. 0.12.24 fixed exception output eating the start of messages; earlier versions mangle errors. If wtf output looks truncated or a message is missing its prefix, that's the bug — upgrade. You can also supply an exceptionDetails closure in config to render extra context (e.g. validation error bags) alongside the message.

The pager can appear to hang. Long ls / dump output opens the pager; quitting it early used to emit pipe warnings (fixed in 0.12.22). Disable with config pager=false or --no-pager when scripting.

Debugging the shell itself:

  • psy\info() / --info — readline engine, autocomplete state, version
  • --no-interaction — isolate whether a prompt is blocking you
  • history --session (0.12.24+) — replay just this session's commands, useful for turning exploratory work into a real script
  • edit — opens the current buffer in $EDITOR; Ctrl+C now interrupts it instead of waiting for the editor to close

Extension points worth knowing: Psy\Configuration (programmatic setup), Psy\Shell::setScopeVariables() / setBoundObject() / setBoundClass() (inject context), Psy\Command\Command (custom commands), casters (custom rendering), Psy\Exception\ThrowUpException (control what throw does in the shell — sudo re-runs with elevated access), and the config/copy runtime commands for per-session tweaks.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support