l0n3ly/laravel-dynamic-helpers
Laravel package for managing custom helper classes dynamically. Generate helpers with php artisan make:helper, organize them in nested folders, and call them as global functions like moneyHelper() with auto-registration and singleton caching.
Architecture fit
The package is a conventional Laravel package (auto-discovered service provider + one Artisan command) but the mechanism is unconventional for production Laravel. v2.0.0 resolves helpers by scanning app/Helpers at service-provider boot, then:
eval().phpstorm.meta.php and _ide_helper.php to the project root on every boot.gitignore on first bootThat is three boot-time side effects (code generation, filesystem mutation, VCS mutation) sitting on the framework's hottest initialization path. For a TPM the architectural question is not "does it work" — it clearly does in a developer's local checkout — but "does this belong in a request lifecycle that must be deterministic, read-only, and horizontally scalable?"
It solves a real, small problem: Laravel's app/Helpers convention has no autoloading story, so teams either cram functions into a single helpers.php (no namespace, no DI, no testability) or manually register class aliases. Replacing function soup with injectable classes extending Helper is a defensible design position. The delivery mechanism is the liability, not the goal.
Integration feasibility
Installation is a one-liner and make:helper is a nice ergonomic win with sensible name normalization (money-helper / money_helper → MoneyHelper) and nested paths (Store/Product/UpdateHelper → storeProductUpdateHelper()). A single developer can be productive in under an hour.
The problem is verification. Because the API is generated at runtime, the codebase is opaque to the tools we rely on for confidence:
moneyHelper(), MoneyHelper::format(), or helpers()->moneyHelper() — the base class uses __call/__callStatic magic, so there are no real signatures to analyze._ide_helper.php is the exact filename used by barryvdh/laravel-ide-helper. Two packages writing the same artifact to the project root is a concrete, predictable collision.php artisan vendor:publish --tag=dynamic-helpers. For an ops runbook, that ambiguity matters.Technical risk
| Risk | Severity | Notes |
|---|---|---|
eval() in a boot path |
High | Executes generated strings in the global scope on every cold boot. Any escape bug or malicious file dropped into app/Helpers becomes code execution. This is a supply-chain and code-review problem, not a style preference. |
| Boot-time filesystem writes | High | Fails or throws on read-only filesystems (Lambda, distroless/immutable container images, Heroku-style ephemeral FS), on restricted open_basedir, and on CI runners with a dirty-check gate. |
| Global function namespace | High | moneyHelper() and helpers() are unprefixed and live in the root namespace. A second package or a legacy helpers.php defining the same name is a fatal redeclare error at boot. |
| Static analysis blind spot | Medium | No types, no refactor safety, no dead-code detection. Every helper call is effectively an untyped dynamic dispatch. |
| Laravel Octane / Swoole | Medium | Discovery happens at worker boot; helpers created after boot require a worker restart. Singletons cached across requests mean mutable helper state leaks between users. |
| Maintainer/bus factor | Medium | One maintainer, 1 star, 0 dependents, rapid breaking churn (v1.0 → v2.0 within a short window, helpers:ide removed in a major bump). MIT gives us fork rights, but we would own the fork. |
| Version claims | Low | Laravel 13 support asserted with a CI matrix, but 13 is ahead of most enterprise adoption; the constraint floor (Laravel 11) is fine. |
Key questions
local/testing only, or does it run in production?eval() and the stub writers wrapped in function_exists() / is_writable() guards, and do they degrade gracefully to a no-op when the FS is read-only?php artisan optimize-style cache so discovery happens once at deploy rather than per worker?APP_ENV=production to suppress IDE artifact generation?Store/Tenant and StoreTenant both map to storeTenantHelper(); is that a silent overwrite or a hard failure?"files": ["app/Helpers/helpers.php"] in composer.json plus PSR-4 autoloaded classes? The honest answer may be "ergonomics," which is a legitimate but not load-bearing justification.Stack fit
Good fit: Laravel 11–13 monolith or modular monolith, PHP 8.1+, deployed to a long-lived VM or a container with a writable working directory, team already comfortable with service providers and Artisan. Small teams with a single helpers.php and no static-analysis discipline will get real value fast.
Poor fit: serverless (Bref/Lambda), immutable distroless images, Kubernetes with readOnlyRootFilesystem: true, Octane-heavy deployments, or any codebase with a strict PHPStan level ≥ 6 gate.
Migration path
If adopted, migrate in this order:
helpers.php — do not remove it immediately. Guard against fatal redeclares by confirming every legacy function name is unique against the new flattened camelCase names.function money_format($a){ return moneyHelper()->format($a); }) so call sites don't change in the same PR.helpers.php and remove it from composer.json's files array.helpers:ide was removed and the service provider changed shape; treat it as a greenfield install or skip it entirely.Compatibility
barryvdh/laravel-ide-helper (shared _ide_helper.php filename) and any package shipping a global helpers() function.git diff --exit-code gates because the provider mutates .gitignore and writes root-level files.Sequencing
A defensible 4-week evaluation, not a rollout:
APP_ENV=production and with the working directory chmod'd read-only.eval, move discovery to a deploy-time artisan dynamic-helpers:cache, and namespace the generated functions.My recommendation: do not adopt as a dependency; treat it as a design reference. The underlying idea — helpers as first-class, injectable classes with a scaffold command — can be reproduced in ~80 lines of application code (a Helper base class, a make:helper stub, and explicit PSR-4 autoloading) without eval, without boot-time writes, and without global namespace risk. You get 90% of the ergonomics and 0% of the operational surface.
Maintenance
The maintenance burden inverts the usual open-source calculus: the code is small and MIT-licensed, so fixing it is cheap — but the fixes required to make it production-safe (removing eval, adding a discovery cache, prefixing global functions, gating stub generation by environment) amount to a rewrite of its core value proposition. At 1 star and 0 dependents, we are the only meaningful consumer; there is no community to absorb the maintenance. Expect to vendor or fork on first incident. Version churn is a live concern: a breaking major already landed between v1.x and v2.0.0 with a removed Artisan command, which signals the API is still being discovered rather than stabilized.
Support
Effectively self-support. MIT provides no warranty, no SLA, no security response process. An eval-based package with a single maintainer and no dependents is a supply-chain item that belongs on an internal security review, not just an engineering checklist. Escalation path is "read the source and patch it."
Scaling
Boot-time discovery is a per-worker cost. In a fleet of N containers each running M PHP-FPM workers or Octane workers, that is N×M filesystem scans plus N×M stub writes — the writes being the expensive part and the one most likely to hit contention. Under Octane, discovery is once per worker lifetime, which is better, but helpers added at runtime (which is the package's whole premise) are invisible until restart, so the "dynamic" promise degrades to "static with extra steps."
The scaling story is fine at 1–10 helpers and a couple of containers. It does not have a good story for autoscaled ephemeral compute, which is where most teams are heading.
Failure modes
Ranked by likelihood × blast radius:
helpers.php defines helpers(). Blast radius: total outage, and the stack trace points at the framework, not the cause.eval escape / malicious helper file — arbitrary code execution under the app's privileges. Blast radius: full compromise. Low likelihood, unacceptable severity.Store/Tenant and StoreTenant resolve to the same function; one wins non-deterministically by scan order. Blast radius: wrong behavior in production, no error..gitignore mutated, stub files appear, git diff --exit-code fails. Blast radius: pipeline red, developer confusion, slow diagnosis.Ramp-up
For application developers: near zero — make:helper and a global function is the lowest-friction API possible, which is precisely the package's appeal. For platform/SRE/DX: high. Someone must own the read-only-filesystem question, the CI dirty-tree exception, the .gitignore policy, and the security sign-off on eval. That is a disproportionate amount of cross-team coordination for a helper-scaffolding convenience, and it is the single strongest argument against adoption. The asymmetry — five minutes to adopt, two weeks of platform review to operate safely — is the whole assessment in one sentence.
How can I help you explore Laravel packages today?