teamdotblue/psysh-bundle
Brings the PsySH interactive PHP REPL into your framework's console, giving you a tinker-style shell to inspect and experiment with application code, services, and configuration from the command line. Ideal for quick debugging and prototyping.
Install it as a dev-only dependency:
composer require --dev teamdotblue/psysh-bundle
This pulls in psy/psysh. Register the bundle:
// Symfony 4+/5+: config/bundles.php
TeamDotBlue\PsyshBundle\PsyshBundle::class => ['dev' => true, 'test' => true],
// Symfony 3: AppKernel::registerBundles()
if (in_array($this->getEnvironment(), ['dev', 'test'])) {
$bundles[] = new TeamDotBlue\PsyshBundle\PsyshBundle();
}
Then boot the REPL against your kernel:
php bin/console psysh
Where to look first
bin/console list — confirm the command name and that it only appears in dev/test.bin/console help psysh — see which options the command forwards to PsySH.vendor/teamdotblue/psysh-bundle/Command/ — the command class is the real documentation for what it injects into the shell scope (usually $container/$kernel) and how config is wired.psysh: config keys accepted by your app.First use case: poke at services and data without writing a throwaway controller.
>>> $container->get('doctrine')->getManager()
>>> $em = $container->get('doctrine')->getManager()
>>> $em->getRepository(App\Entity\User::class)->find(1)
>>> $em->getRepository(App\Entity\User::class)->findBy(['active' => true])
If $container is not in scope, read the command class before assuming — that variable is the bundle's core value proposition and is exactly what you want to verify on day one.
Treat the REPL as a scratchpad, not a code path. The productive loop is: explore a service/repository interactively → copy the working expression into real code → restart the shell after editing classes.
Bootstrap a working set with a require file. PsySH executes require in the shell scope, so variables become available for the session:
<?php // var/psysh_bootstrap.php
$em = $container->get('doctrine')->getManager();
$users = $em->getRepository(App\Entity\User::class);
$router = $container->get('router');
>>> require 'var/psysh_bootstrap.php';
>>> $users->find(42)
Configure sensibly per project:
# config/packages/dev/psysh.yaml
psysh:
historyFile: '%kernel.project_dir%/var/psysh_history'
updateCheck: false
startupMessage: 'Project REPL — use require "var/psysh_bootstrap.php"'
usePcntl: true
useTabCompletion: true
Lean on PsySH commands, which are where most day-to-day value sits:
>>> ls $container # method/property listing
>>> show App\Service\Foo # class reflection
>>> doc $em # inline documentation
>>> trace # last exception backtrace
Doctrine discipline. For experiments you don't intend to persist, wrap in a transaction and roll back, or $em->clear() between runs so the identity map doesn't give you stale objects.
Pipe code for one-liners in scripts/CI-ish contexts (works when stdin is not a TTY):
echo '$container->get("router")->generate("homepage")' | bin/console psysh
Integration tips
symfony/var-dumper; PsySH picks it up and dump() output becomes far more useful on entities and service graphs.casters key) to render domain objects as something readable instead of a wall of properties.dev/test only; never let it leak into a production container.test environment when debugging a failing functional test — the kernel and container are already booted, so you can reproduce setup steps interactively.Adoption is essentially zero (5 stars, 0 dependents) and the bundle targets older Symfony. Run composer require and read the constraints before committing; on Symfony 5/6+ it likely will not install. Fall back to theofidry/psysh-bundle or plain vendor/bin/psysh with a hand-written bootstrap that boots your kernel.
Private services are unreachable. On Symfony 3.4+, compiled containers remove private services; $container->get('app.some_service') throws ServiceNotFoundException. Options: use the test.service_container in test env, alias the service publicly in dev config, or inspect via bin/console debug:container.
The container is a boot-time snapshot. Change a service definition, a config value, or a class body and the shell won't see it — PHP has already loaded the class. Restart the shell. This is the single most common source of "why is my fix not working" confusion.
Long sessions leak. Doctrine's identity map and your own references grow in memory. $em->clear() regularly, and restart the process when things slow down.
Xdebug ruins it. Startup and eval become slow, and deep dumps hit xdebug.max_nesting_level. Run with XDEBUG_MODE=off bin/console psysh.
PCNTL/tab completion flakiness. On Windows or unusual PHP builds, PCNTL isn't available; set usePcntl: false. Terminal-specific tab completion bugs are usually solved with useTabCompletion: false.
Docker/CI without a TTY. The REPL can hang or exit immediately. Always use docker compose exec -it app bin/console psysh (or docker run -it).
History file permissions. A historyFile inside the project can be written by one user (host) and not another (container), producing silent failures. Point it at var/ and keep that directory writable.
Namespacing. The shell starts in the global namespace; use fully-qualified class names or a use statement per session. If a class "doesn't exist," check the autoloader has been dumped after adding files.
Missing scope variables. What's injected ($container, $kernel, $parameters) is defined entirely by the bundle's command class, not by PsySH. If something you expect isn't there, the extension point is either a custom startup file, or a small command of your own that calls Shell::setScopeVariables(). The bundle is small enough to fork if you need more.
Error handling. App exceptions surface in the shell; if you want them in the app log, check the errorLoggingLevel config key. Don't assume errors are silently swallowed — verify once with a deliberate throw.
How can I help you explore Laravel packages today?