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

Overload Laravel Package

tightenco/overload

Overload by Tighten adds method overloading to Laravel and PHP, letting you define multiple methods with the same name and dispatch each call to the right implementation based on the arguments passed—keeping controllers and services clean and expressive.

View on GitHub
Deep Wiki
Context7

Getting Started

Understand what you're actually installing. Despite some metadata describing this as "Laravel facades in plain PHP," tightenco/overload is a method-dispatch trait: one public method name, several candidate signatures, resolved at runtime via __call plus reflection. There is no service provider, no config file, no migrations. Its only runtime dependency is illuminate/collections (v0.1.3 was purely a Collect 8 compatibility release). Integration cost is one composer require; the real cost is everything downstream.

Pin the version before you do anything else. The last release was 2020-09-08 and the repo is archived. On Laravel 9+, the illuminate/collections constraint is usually the deciding factor — check it first.

composer require tightenco/overload:0.1.3
composer why-not tightenco/overload 0.1.3   # confirm no illuminate/collections conflict

Where to look first, in this order:

  1. The trait source. It is one small file. Read the resolution loop before you write any code — that loop is the API contract, and it is the only authoritative documentation you'll get. Don't trust the README on a 2020 repo.
  2. The test file. It shows the intended declaration shape more reliably than the prose.
  3. Your own PHP version. Confirm the trait even loads on your runtime. It predates PHP 8 entirely.

First use case — a 20-minute spike, not a feature. Pick a leaf value object or small internal helper with two genuinely different argument shapes, then:

  • Declare two candidate signatures.
  • Call the method with each type; var_dump() inside __call to see exactly what arguments arrive and which candidate wins.
  • Call it with a coercible value ('5' where an int variant exists) and observe which branch fires.
  • Call it with something that matches nothing and note the exception returned.

That four-call spike tells you more than any documentation and tells you whether you want this in your codebase at all. On PHP 8+, the honest answer after the spike is usually "no — find(int|string $key) is one line."


Implementation Patterns

1. One entry point, N documented shapes

The payoff of overloading is a single public name; the cost is that nothing in the signature tells a reader what's accepted. Compensate with an explicit docblock on the public method listing every accepted shape, and treat that docblock as part of the contract:

/**
 * Resolve a record.
 *
 * @param  int|string  $key  Database id (int) or slug (string).
 */
public function find($key) { /* dispatches */ }

This is the only thing standing between your team and "what does this method take?" in every review.

2. Keep it in the leaves

Overloading belongs in leaf objects — value objects, DTO factories, builders, query-parameter parsers. It does not belong on anything a production trace will land on:

  • Eloquent models — __call collision, disqualified.
  • Controllers extending a framework base — same.
  • Facades and anything Macroable — same.
  • Services orchestrating business logic — you've now hidden branch points from traces and coverage.

Write the boundary down (allowlisted namespaces) and enforce it in CI with a grep or a PHPStan rule. Without that, the trait spreads to the first class where it seems convenient.

3. Adapter-first migration (the pattern you'll actually use most)

If the package is already in your codebase, the day-one job is not "use it better," it's "get off it without a big-bang refactor." Wrap it:

// New explicit surface; callers migrate here one at a time.
public function findById(int $id): Model { ... }
public function findBySlug(string $slug): Model { ... }

// Legacy overloaded entry point stays, marked deprecated, delegating down.
public function find($key) { ... }

Migrate call sites per class, delete the trait last. This is a days-scale refactor per domain area and reversible at every step — which is what makes it schedulable as ordinary debt rather than a project.

4. One test per variant is the spec

Because static analysis can't resolve dispatch and the docs are thin, your test suite is the only specification. Enforce, at minimum:

  • one test asserting each candidate branch is actually reached,
  • one test for the coercion/ambiguous case (this is where surprises live),
  • one test for the no-match case and its exception.

If a variant has no dispatch test, you don't know it works.

5. Contain the static-analysis tax

PHPStan and Psalm cannot infer return types through __call. Pick one of two honest options: a baseline scoped to the allowlisted namespaces, or a CI gate that rejects the trait outside them. Do not let suppressions leak file-by-file across the codebase — that's how an unmaintained dependency becomes invisible.

6. Vendor it if you're staying

If you genuinely need it long-term, fork it under an internal namespace, pin it, commit the lockfile, and assign an owner. This isn't pessimism — with an archived repo, "vendored and owned" is the accurate description of your situation anyway. The surface is small enough that patching for a new PHP minor is hours, not weeks.

7. Keep it off hot paths

Reflection-per-call costs microseconds. Irrelevant in request/response; material in tight loops, large queue batches, and serialization-heavy workers. If you find it inside a foreach over 50k rows, that's a bug, not a style question.


Gotchas and Tips

The metadata is unreliable — trust the source. Some listings describe this as a way to use Laravel facades in standalone scripts with a lightweight container. It isn't. It's a method-dispatch trait. Installing it expecting container bootstrapping will burn half a day.

Silent wrong-variant execution is the real risk. A type mismatch doesn't throw — it resolves to some candidate and runs it. You discover it in production, not CI. This is the single strongest argument for per-variant dispatch tests.

strict_types is per-file, and it changes dispatch. Whether the calling file declares strict_types=1 affects how PHP compares arguments against signatures. Two callers can hit different variants with what looks like the same input. If you keep the trait, standardise declare(strict_types=1); across the calling code.

__call collisions are disqualifying, not inconvenient. Eloquent models, controllers, facades, and Macroable classes already own __call. In a Laravel app those are precisely the classes most likely to want overloading, so the trait can't be cleanly applied where you'd most want it. Don't fight this.

Stack traces terminate at __call. Breakpoints inside candidates still work, but the frame is anonymous and exception messages name the magic method, not the variant. When debugging, log the resolved candidate in the wrapper before dispatch — a one-line Log::debug(__METHOD__, [...]) inside the trait's dispatch saves hours.

Autocomplete is gone. @method docblocks on the class claw back a little IDE help. Generated docs and return-type inference don't come back at all — fine for an internal script, unacceptable in anything a customer or another team integrates against.

Coverage reports lie slightly. Distinct behaviours collapse behind one name, so per-branch coverage is harder to read than with explicit methods. If coverage is a gate you report on, expect to explain it.

Extension point: resolution lives in one trait method. Override there if you must tweak matching — but every override is code you own forever. Prefer a thin wrapper class over modifying the trait.

Write down your exit criteria before you start. Set the trigger — "removed by the next PHP minor upgrade," "removed when the findBy* migration completes" — and hold it. The package has 113 stars, zero dependents, and no release since 2020; there is no upstream fix stream, and the language has since absorbed the common cases with union types, named arguments, and match. Borrow the ergonomics idea if you like it; don't inherit the dependency.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package