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

Sidekick Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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.

Implementation Patterns

Asserting model state in tests instead of raw attributes

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.

Tracking previous values with HasPreviousAttributes

When 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.

Hydrating models without a database round-trip

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.

Version gating in package service providers

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.

Deferred container work with 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.

Package/filesystem plumbing

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.

Testbench-aware behaviour

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.

Prompted installer commands

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
    }
}

Adopting incrementally

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.

Gotchas and Tips

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:

  • Read model_state() before mutating if you want the pristine values.
  • Read model_diff() after the write but before any refresh() / re-query.
  • For post-save history, use 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.

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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator