orchestra/sidekick
Orchestra Sidekick is a toolkit of utilities and helpers for Laravel package development. It streamlines common tasks and provides reusable support code for building robust packages, serving as a foundation for the Orchestra Platform ecosystem and its companion packages.
Install. It's a plain Composer library — no service provider, no config file, no migrations, nothing to publish.
# Package development / test suites only
composer require --dev orchestra/sidekick
# If you also call helpers from `src/` (Env, package_path, etc.)
composer require orchestra/sidekick
The split matters: the package exposes globally-autoloaded namespaced functions plus a few classes. If any helper is called from shipping code rather than tests, it must be in require, not require-dev.
Where to look first. Skip the README (it's a badge page) and read the source tree — it's small and grouped by namespace:
| Namespace | What lives there |
|---|---|
Orchestra\Sidekick\Eloquent |
model_diff(), model_state(), model_from(), HasPreviousAttributes trait, internal Watcher |
Orchestra\Sidekick\Filesystem |
filename_from_classname(), join_paths(), is_symlink() |
Orchestra\Sidekick\Console\Concerns |
ConfiguresPrompts trait for artisan commands |
Orchestra\Sidekick\Env |
Environment-value helper class |
| Root functions | package_path(), package_version_compare(), after_resolving(), is_testbench_cli(), working_path(), php_normalize_version() |
First use case — assert Eloquent writes in a test. This is the highest-leverage entry point for most teams:
use function Orchestra\Sidekick\Eloquent\model_diff;
$user = User::factory()->create(['name' => 'Otwell']);
$user->update(['name' => 'Taylor']);
// Only the attributes that actually changed — appends excluded
$this->assertSame(['name' => 'Taylor'], model_diff($user));
Replaces hand-rolled $model->getChanges() / getOriginal() juggling in feature tests, which is where most of the day-to-day value sits.
model_state() gives you the pristine, pre-mutation attribute set with $appends stripped — useful for snapshot-style assertions and for building expected arrays without re-querying:
use function Orchestra\Sidekick\Eloquent\model_diff;
use function Orchestra\Sidekick\Eloquent\model_state;
$post = Post::factory()->create();
$original = model_state($post);
$post->update(['title' => 'New']);
$this->assertSame(['title' => 'New'], model_diff($post));
$this->assertSame($original['title'], model_state($post)['title']);
Prefer this over assertDatabaseHas() when you want to verify what changed, not just the end row — it catches accidental mass-assignment and dirty-attribute leaks that a DB assertion hides.
HasPreviousAttributesWhen getPrevious() isn't available on your Laravel version, add the trait; on newer versions the package polyfills it, so the call site is identical either way:
use Orchestra\Sidekick\Eloquent\Concerns\HasPreviousAttributes;
class Post extends Model
{
use HasPreviousAttributes;
}
$post->update(['title' => 'New']);
$post->getPrevious('title'); // 'Old'
Good fit for audit logging, "changed from X to Y" notifications, and optimistic-concurrency checks — anything where you need the before value after the save already happened.
use function Orchestra\Sidekick\Eloquent\model_from;
$model = model_from(Post::class, [
'title' => 'Draft',
'published_at' => null,
]);
Handy in unit tests, in form objects that hand a populated model to a view, and when you want a model instance to pass through accessors/casts without new Post([...]) boilerplate.
use function Orchestra\Sidekick\package_version_compare;
if (package_version_compare('laravel/framework', '>= 11.0')) {
// register the newer macro/binding
} else {
// fallback path
}
This reads the installed version, including packages satisfied via provide/replace — unlike naive Composer\InstalledVersions checks that miss virtual packages. Use it instead of branching on app()->version() strings.
after_resolving()use function Orchestra\Sidekick\after_resolving;
after_resolving($this->app, 'router', function ($router) {
$router->aliasMiddleware('signed', EnsureSignature::class);
});
Keeps register() free of eager resolution — the callback fires when (and only if) the abstract is actually resolved. Same idiom as $this->callAfterResolving() but callable from plain functions and helpers where you don't have a provider context.
use function Orchestra\Sidekick\Filesystem\filename_from_classname;
use function Orchestra\Sidekick\Filesystem\join_paths;
use function Orchestra\Sidekick\package_path;
// App\Console\Commands\SyncCommand -> SyncCommand.php
$file = filename_from_classname(SyncCommand::class);
// Portable paths regardless of trailing separators
$stub = join_paths(package_path('stubs'), 'config.stub');
package_path() resolves a path inside the current package's root — the reliable way to locate stubs, fixtures, or config templates from inside a package's own code, instead of __DIR__ . '/../../..' guesswork.
use function Orchestra\Sidekick\is_testbench_cli;
use function Orchestra\Sidekick\working_path;
if (is_testbench_cli()) {
// skip interactive prompts, shorten timeouts, use the workbench skeleton
}
is_testbench_cli() also detects Testbench Dusk runs, so one guard covers both PHPUnit-driven and browser-driven suites. working_path() gives you the Testbench skeleton root for fixture paths.
If you ship an install command, borrow the ConfiguresPrompts trait rather than reimplementing Laravel Prompts detection (--no-interaction, CI, non-TTY):
use Orchestra\Sidekick\Console\Concerns\ConfiguresPrompts;
class InstallCommand extends Command
{
use ConfiguresPrompts;
public function handle()
{
$this->configurePrompts();
// $this->prompt(...), $this->confirm(...) now degrade gracefully
}
}
Don't vendor-wide adopt. Pick the two helpers that remove existing glue code — usually model_diff() in tests and package_path() in providers — then grow. Everything is stateless and side-effect-free, so partial adoption costs nothing and leaves no residue to unwind.
You must import the functions. They are namespaced and registered via Composer's files autoload. A bare model_diff($model) in a namespaced class resolves against your namespace and fails:
use function Orchestra\Sidekick\Eloquent\model_diff; // correct
\Orchestra\Sidekick\Eloquent\model_diff($model); // also works
The error is Call to undefined function App\...\model_diff() — a misleading message that sends people hunting for a missing service provider.
model_diff() is not getChanges(). It's built on an internal watcher and normalises create-vs-update state, and it deliberately excludes $appends. If a test asserts an appended attribute shows up in the diff, it won't. Conversely, if you're on an older release and see appended accessors leaking into the diff, you're on a pre-v1.1.17 / pre-v1.2.13 build — upgrade before debugging your model.
Timing matters for previous state. model_diff() and model_state() compare against the model's original snapshot, and save()/update() resync that snapshot. So:
model_state() before mutating if you want the pristine values.model_diff() after the write but before any refresh() / re-query.getPrevious(), not getOriginal() — the latter has already been synced by then.Two active release lines. 1.1.x and 1.2.x receive the same fixes in parallel (see v1.1.13/v1.2.13, v1.1.19/v1.2.15). Pin to whichever matches your Laravel floor, but check both changelogs — a fix listed only under 1.2.x usually has a 1.1.x twin a few entries away.
getPrevious() may come from the framework or the polyfill. Recent Laravel ships it natively; on older versions this package supplies a polyfill, and the trait exists for models that need the explicit use. If you hit a trait/method collision or a "method already defined" style conflict after a Laravel upgrade, drop the HasPreviousAttributes trait — the native implementation takes over with an identical signature. Verify with method_exists() rather than guessing.
Watcher is @internal. It's marked internal as of v1.2.5/v1.1.10. Use the model_diff() / model_state() functions and treat the class as off-limits — it can change shape in a patch release.
No config means no escape hatch. There is nothing to publish, cache, or config:clear. When something behaves unexpectedly the answer is always "read the 10–30 line function source." Budget for that: the helpers are small enough that debugging is reading, and vendor/orchestra/sidekick/src/ is the real documentation.
Env is runtime code, not test code. If you use the Env class to read environment values in a service provider, it must be a production dependency. Leaving the package in require-dev makes it work locally and fatal-error in a production composer install --no-dev. This is the single most likely production incident with this package.
Testbench itself is a dev requirement. If you rely on the integration-test helpers (is_testbench_cli(), working_path()), orchestra/testbench-core needs to be in require-dev — v1.1.11/v1.2.6 onwards assumes it for the package's own test suite, and the CLI-detection helpers are meaningless without it.
php_normalize_version() is infrastructure, not API. It exists so package_version_compare() survives pre-release strings (there was a dedicated fix for PHP 8.6 betas in v1.2.23). Don't call it directly for your own comparisons — pass raw constraint strings to package_version_compare() and let it normalise.
Symlinks are a first-class case. Filesystem\is_symlink() and join_paths() live under the Filesystem namespace as of v1.1.23/v1.2.20 — earlier code had them at the root, so old snippets won't work on current versions. Relevant for monorepo/workbench setups where package paths are routinely symlinked.
Extension points are deliberately absent. No macros, no manager class, no driver system. Composition is the extension story: wrap a helper, or write your own function in your package's namespace that delegates to it. Anything more elaborate means you wanted a package of your own — the helpers are small enough to copy if the dependency becomes awkward.
How can I help you explore Laravel packages today?