nette/robot-loader
RobotLoader automatically loads PHP classes, interfaces, and traits from specified directories by scanning files and caching the results. It spares you from manually requiring or registering each class, making it handy for legacy code or small projects without Composer.
Architecture fit. RobotLoader is a class-discovery engine plus an autoload hook, not a framework component. In a Laravel application the architecture already assumes Composer PSR-4 as the single source of class resolution; composer dump-autoload -o, classmaps, and vendor/composer/* are load-bearing in every deploy. The correct fit is therefore a scoped discovery primitive: point it at directories whose contents are genuinely unknown at build time (plugins, tenant modules, generated code, drop-in extensions) and leave first-party code to Composer. The place it plugs into is narrow and well-defined — service-provider registration, console command discovery, event-subscriber discovery, and manifest generation for admin UIs. Laravel already hand-rolls a version of this per subsystem (command load() loops, event discovery); this package generalizes it with a persistent cache and correct atomic writes. Framing it as an autoloader swap is an architectural regression and should be rejected at the PM level.
Integration feasibility. The public API is small enough to integrate in a day: addDirectory(), setCacheDirectory() (formerly setTempDirectory()), register(), refresh(), getClasses(), reportParseErrors(). No Laravel coupling, no service provider shipped, no config publishing — everything is ours to wire through one bounded singleton. That is a plus for control and a minus for onboarding ergonomics (see ramp-up). The main feasibility constraint is that a writable, absolute cache directory is mandatory, which is fine on traditional hosts and a gate on read-only container platforms.
Technical risk. Ranked:
NOASSERTION. The Nette ecosystem is typically BSD-3-Clause, but distribution in a commercial or SaaS product needs legal sign-off before adoption, not after. This is a checklist item with a hard stop, not a background task.reportParseErrors is left on, the failure can take down boot rather than degrade one feature. That is a blast-radius decision worth making explicitly.Key questions for engineering before I approve the spike. What is the actual inventory of dynamic surface area, with file counts and directory depth? Who owns the cache directory, and is it writable in every target environment including read-only/ephemeral ones? Can we pre-warm in the deploy pipeline so no user request ever triggers a rebuild? What happens on cache miss — do we fail open or fail closed, and what does the product look like in each case? Which classes are currently discovered by Laravel's built-in command/event discovery that we would be replacing, and do we replace or coexist? What is our exit plan if the package is abandoned — is the API surface small enough to vendor a fork?
Stack fit. Bind exactly one configured loader as a singleton in a dedicated service provider, with directories, ignore patterns, accepted file patterns, and cache path all resolved from config. Nothing else in the application should construct a RobotLoader instance. Keep Composer authoritative and confine RobotLoader to an explicitly enumerated dynamic surface (e.g. app/Plugins/**), never app/ wholesale.
Migration path, in four gated phases.
getClasses(). Deliverable is a file count, a scan duration on a cold cache, and a warm-cache read cost. If the directory is small or scanned once per deploy, stop here — a recursive iterator is the cheaper answer.register() for the dynamic directories only, after Composer has had its chance. Add a regression test asserting a stable sorted getClasses() output so a cache or scoping regression fails CI rather than production.Compatibility. Pin ^4.1 and track the 4.x line deliberately; v4.1.3's setCacheDirectory() rename is a soft deprecation with no urgency, so there is no reason to be an early adopter of each patch. Use a per-environment cache key so dev and production indexes never mix, and ensure the cache path is absolute (storage/framework/cache/robotloader is a reasonable default, but verify it survives container rebuilds).
Sequencing constraints. Legal before code. Deploy-pipeline refresh() before any request-path rebuild. Manifest generation before autoloader registration. Autoloader registration before the plugin API. Do not let the plugin API ship in the same release as the first register() call — debugging a new autoloader and a new extension model simultaneously is how incidents get long.
Maintenance. Source churn is low and disciplined: consistent PHP 8.x support tracking, PHPStan clean, phpDoc coverage, clear per-release notes with explicit BC breaks. That is a genuinely cheap dependency to carry. What we carry instead is integration maintenance — the service provider, config surface, deploy hook, and tests are ours forever, and with zero listed dependents there is no community of practice to lean on. Budget a named owner and a small quarterly check that the pinned line still supports our PHP range.
Support. User-facing support load comes almost entirely from three sources: plugin authors hitting parse errors, case-mismatch resolution failures that reproduce on Linux only, and "my plugin doesn't appear" (a cache staleness symptom). All three are diagnosable if we surface the loader's cache generation time and discovered-class count in a health or debug command. Without that, every one of these becomes an hour of engineering triage.
Scaling. Warm-cache lookups are cheap and scale with request volume without concern. Cold rebuilds scale with the size of the scanned tree and are the real cost — O(files) with a tokenizer pass. That is acceptable as a deploy step and unacceptable on a request path, so pre-warming is a structural requirement, not an optimization. On multi-node deployments behind shared storage, confirm that atomic cache writes and the lock behaviour hold on the actual filesystem in use; NFS semantics are worth testing rather than assuming.
Failure modes to instrument.
reportParseErrors(false) in production and route parse failures into diagnostics so one bad plugin cannot take down the application.refresh() are the controls.Ramp-up. A mid-level engineer can be productive on the library itself within a couple of days — the API is a handful of methods and the docs directory carries design rationale. The slower ramp is on the surrounding conventions: which directories are dynamic by policy, why two autoloaders exist, and what to do when discovery misbehaves. Those conventions need to be written down alongside the service provider, or every new hire will re-derive them by breaking production once.
How can I help you explore Laravel packages today?