beyondcode/tinkerwell-helper
Beyond Code's companion package for Tinkerwell, the desktop code runner for PHP and Laravel. It adds convenient Laravel helpers and integrations so you can execute and debug code in your application's context with less setup.
What you actually need before anything works
This package is glue, not a REPL. It does nothing on its own. You need:
Install
composer require --dev beyondcode/tinkerwell-helper
There is no config to publish, no migration, no env var, no service provider to register manually. Laravel's package auto-discovery picks it up from composer.json. Commit both composer.json and composer.lock.
Find and run the command
The package registers a single Artisan command that builds a tinkerwell:// deep link for the current project root and hands it to your OS. Confirm the exact signature yourself rather than trusting a README:
php artisan list | grep -i tinker
Then run it from your project root. Success looks like: Tinkerwell comes to the foreground, already bound to this project, with Eloquent models, facades, the container, config(), and your services all live — because the package bootstraps your actual Laravel application rather than running bare PHP.
Your first use case
Don't start with anything clever. Open the project in Tinkerwell and run something that would be painful in php artisan tinker:
// Is this relationship actually loading the way I think it is?
$order = Order::with('items.product')->find(1);
$order->items->map(fn ($i) => [$i->id, $i->product->name]);
// What does this query compile to before I commit it to a controller?
User::whereHas('subscriptions', fn ($q) => $q->where('status', 'active'))->toSql();
// Does this event listener actually fire in the real container?
event(new OrderShipped($order));
That loop — edit, re-run, inspect formatted output, keep the result in tabs — is the entire day-to-day value. It collapses "cd into project, remember the tinker invocation, boot the app, lose your history" into one command from the project root.
First-day checklist for a new engineer
composer install (picks up the --dev dependency).php artisan tinker if it doesn't — see Gotchas.The daily inner loop
The pattern that makes this stick is always launch from the project root, never by opening Tinkerwell and browsing to a folder. The helper resolves the current working directory and passes it to the app, so:
cd ~/code/acme-api
php artisan tinkerwell # (or whatever `artisan list` shows)
Tinkerwell then keeps separate connection contexts per project, so multi-tab work across three repos stays unambiguous. This is the reason to prefer the command over the app's file-picker: the picker is manual and error-prone when you have similarly named projects (acme-api vs acme-api-v2).
Codify it as a Composer script (locally only)
If your team likes short verbs, add a convenience alias to composer.json:
"scripts": {
"repl": "@php artisan <the-tinkerwell-command>"
}
Then composer repl. Do not wire this into composer test, composer ci, or any script that a pipeline invokes — see Gotchas.
Snippet patterns worth standardizing in a team gist or tinker notes file
Tinkerwell snippets are the thing you'll actually share. The ones that pay for themselves:
toSql() and ->getBindings(), then paste the finished version into the controller. No more dd()-and-refresh cycles.app(SomeInterface::class) after a provider change, without booting a request cycle.Bus::dispatchSync(new RebuildIndexes) or Artisan::call('...') with formatted output — useful for verifying behavior, not performance.Event::fake() isn't available in a normal snippet, but event(new Thing()) is, and observing real listeners fire against real config catches wiring bugs fast.Model::first()->toArray() or json_encode(...) to confirm casts, accessors, and $hidden behave as your API consumer will see them.Integration with the rest of the tooling layer
Treat this exactly like Pint, Larastan, and PHPUnit: require-dev, pinned, documented in the local-setup guide, zero production surface.
composer install --no-dev so this never lands in a deploy artifact. Don't assume — grep the pipeline.php artisan tinker as the documented baseline. Tinkerwell is the GUI upgrade for people who want history, tabs, and formatted output; it is not a replacement, and container/remote-only developers must not be made to feel they're on a second-class path.The container/remote fallback (document it, don't improvise it)
If a developer works in Docker, a Devcontainer, WSL2, Codespaces, or over SSH, the deep link is structurally meaningless — there's no host OS handler on the other end. Prescribe the workaround in the setup doc:
php artisan tinker.tinkerwell . in the project directory, bypassing the Artisan command entirely.Naming the fallback up front is the difference between "the command is broken" and "the command isn't supported here."
The silent no-op is the #1 failure mode. The command's only job is to ask the OS to open a URL scheme. In a container, WSL2, remote workspace, or CI runner, no handler is registered — so nothing happens, and typically no instructive error is raised. Debugging procedure: copy the tinkerwell:// URL the command generates and paste it into a browser address bar or open / start on the host. If that does nothing, the problem is the environment, not the package. Don't spend an afternoon on the package's source for this.
It opened the app but the right project isn't there. Usually a path-resolution issue: a volume mount the host sees differently, a symlinked working directory, or a path with spaces. Run from the real project root. If your dev environment maps /var/www/html on the container to ~/code/acme on the host, the app can't read the container path — use the app's open-project flow against the host path instead.
Never a production dependency, and never an automation primitive. Three separate rules, all easy to violate by accident:
--no-dev in the deploy build. A stray composer install in a build stage silently ships a dev tool.Don't assume framework-version support. Last release was 2024-05-12. Verify it against your PHP/Laravel matrix — explicitly Laravel 11+ and PHP 8.3 — before rolling it out. This is not a criticism of the wrapper; it's a two-minute test that saves a "why doesn't discovery work on the new app" thread.
Pin it and assume no upstream responsiveness. 59 stars, zero dependents, single maintainer, single-vendor ecosystem. The code risk is near-zero, but plan for the package going quiet. composer.lock is your friend; a future Laravel major stranding provider discovery is the realistic breakage, and the fix is trivial.
Understand the vendor coupling you're accepting. Beyond Code controls both the wrapper and the closed-source, commercially licensed desktop app. URL-scheme stability, OS support, pricing, and roadmap are all outside your control. That's fine — but make it a deliberate decision against artisan tinker, Telescope, Ray, and PhpStorm + Xdebug, not an accident of a Composer one-liner. If the team moves to containerized or remote-only dev environments, drop it without ceremony.
Removal is genuinely one line, no residue. No schema, no config, no published files, no cache to clear:
composer remove --dev beyondcode/tinkerwell-helper
Then delete the Composer script alias and the onboarding line. An hour of work, at most, if you'd rather own it.
Extension points: fork, don't lobby. This is a thin wrapper around one Artisan command. If you want it to also open a specific file, pass a snippet, or integrate with a different launcher, write your own command rather than waiting on upstream — the whole behavior is roughly:
// Illustrative — mirror the actual URL format the package emits
Artisan::command('repl', function () {
$url = 'tinkerwell://'.base_path();
// hand $url to the OS opener for the current platform
});
Five lines in app/Console/Commands and you've replaced the dependency. That escape hatch is precisely why the package is safe to adopt: the blast radius is "the shortcut stops working," never "the app stops working."
Set a review trigger. If the command isn't being invoked unprompted after a quarter, the thing to re-evaluate is the license line item, not the package. Remove it, reclaim the seats, and move on.
How can I help you explore Laravel packages today?