genealabs/bones-macros
genealabs/bones-macros bundles reusable Laravel macros for GeneaLabs Bones projects, extending Blade, collections, and request helpers so common patterns become one-line calls and everyday boilerplate shrinks.
Architecture fit. bones-macros is a single service provider that registers Blade::directive(...) and/or Form::macro(...) calls at boot. That is the entire architecture: a global, unscoped, boot-time mutation of the framework's templating layer. It fits cleanly into exactly one slot in our stack — presentation-layer sugar for internal CRUD and admin screens — and fits nowhere else. It does not touch domain logic, persistence, or request handling, so it cannot corrupt data; it can only emit wrong or unsafe markup. That narrow blast radius is the strongest thing in its favour.
Against modern Laravel (8+/9+), native Blade components and FormRequest already occupy this slot with first-party support, test coverage, and zero supply-chain exposure. So this is not an architecture decision; it is a "do we accept an unmaintained third-party dependency to avoid writing ~200 lines of Blade" decision. Framed that way, the answer is usually no.
Integration feasibility. Mechanically trivial. composer require on Laravel 5.5+ auto-discovers the provider; older versions need a line in config/app.php. Real feasibility is gated on metadata we have not yet read: declared illuminate/* and php constraints, the emitted HTML flavour (Bootstrap 3/4, Tailwind, or framework-agnostic), and whether it drags in laravelcollective/html. That last one is decisive — the Form/Html facades were removed from the framework proper and the Collective package is effectively unmaintained past 5.x, so if it is a dependency, every form built on it breaks at once on the next major bump.
Effort to a defensible answer is small: roughly half a day for the source read and compatibility spike, 3–4 engineer-days to close the evaluation including a port of the macros that earn their keep. This is a cheap decision to make and an expensive one to defer.
Technical risk. Ranked by severity:
| Risk | Severity | Notes |
|---|---|---|
| Unescaped attribute injection | Critical | Hand-rolled sprintf form macros are a classic stored-XSS vector. Payload " onfocus="alert(1) bound to old() is the test. Back-office stored XSS is a hard stop, not a finding to log. |
Missing CSRF / _method |
High | Silent 419 on first submit. Needs verification, not assumption. |
laravelcollective/html dependency |
High | Couples our form rendering path to a deprecated package; upgrade becomes an all-forms-at-once event. |
| Silent macro name collision | Medium | Blade::directive overwrites without warning. Shadows an in-house helper and produces subtly wrong markup that surfaces weeks later as a styling regression. |
| Abandonment | Medium | Zero stars, zero dependents, score 0, no visible release cadence. Assume dormant and plan to own it. |
| Discovery skipped in prod | Low | --no-dev / optimized autoloaders can skip package discovery; works locally, breaks in CI/prod. |
The absence of stars, dependents, and peer review is not vanity-metric noise — it means no production corroboration and no one else's bug reports to borrow. We would be the first serious consumer of code sitting directly on our form-rendering path.
Key questions to close before any template is touched.
php and illuminate/* constraints, and what is the last commit date?laravelcollective/html?Blade::getCustomDirectives() after install is the authoritative inventory.)e()/htmlspecialchars to attribute values? Read every concatenation.@csrf and @method?value and error arguments, or must repopulation be post-processed in a wrapper?Stack fit. Constrained to legacy or version-pinned applications (roughly Laravel 5.x–8.x), or teams with an existing macro convention worth preserving for consistency. On any new build or modern Laravel target, native <x-form.input> components are the first-party default and this package should not enter the conversation. Where it does fit: internal admin panels, settings pages, disposable prototypes, migration-era back-office tooling.
Two migration paths — pick one deliberately, do not drift into one.
composer.json entirely. Same developer-hour savings, zero upgrade liability, no dependency-intake precedent to defend. Given the package's size, this is the default.In practice two or three macros carry all the value; the rest are noise. Treat the macro set as a menu, not a bundle.
Compatibility. Verify via spike, never from the README. Pin the exact version. If Path A is chosen and upstream is dormant — assume it is — vendor via private fork or packages/ path repository at a commit SHA. Do not let a floating Packagist resolution sit on the form-rendering path.
Sequencing.
composer.json, last-commit date.array_keys(Blade::getCustomDirectives()) before and after; this doubles as the in-house-helper shadow check.One sequencing rule that matters: run this after the Laravel upgrade target is fixed, not before. The port-vs-abandon calculus inverts completely once the target version is known, and a pre-upgrade pilot can produce conclusions we then have to unwind.
Maintenance. Assume upstream stays dormant and budget accordingly — this becomes our code the day it ships. Path B removes the question entirely, which is most of its appeal. Path A requires a named internal owner, a pinned version or fork, and an entry in the upgrade checklist tied to the Laravel major version. Given zero dependents and no release cadence, "we'll pick up fixes upstream" is not a plan.
Support. No vendor, no community, no issue-tracker signal to lean on. Support is entirely internal. Bus factor is one until at least two engineers have read the provider — mitigate by making the source read part of the pilot rather than a solo spike.
Scaling. Runtime scaling is a non-issue: directives compile into cached views, no per-request overhead. The real scaling constraint is organizational. Registration is global, boot-time, and permanent — every macro is available everywhere with no opt-in, scoping, or ordering control. That is tolerable for one team; as teams are added, the shared directive namespace becomes a collision surface with no detection mechanism. This is a second, independent argument for owning a single app-level provider instead.
Failure modes to instrument for.
config:clear, view:clear, composer dump-autoload.composer install --no-dev. Diagnose with php artisan package:discover.Form facade removal on major bump — if laravelcollective/html is in the tree, every form breaks simultaneously. This is the failure mode that turns a utility into an incident.Debug by dumping storage/framework/views/*.php — the compiled output shows what a directive actually emitted, which is faster than reasoning about the template.
Ramp-up. Half a day for any Laravel-fluent developer to absorb the macro contract. That is not the cost. The real cost is review discipline: someone must actually inspect the emitted HTML — escaping, error classes, label association, aria-invalid, accessibility — rather than trusting it. Budget that as explicit task effort on the pilot, not as background QA. It is also the line item that most often reveals the package is not worth keeping.
Governance note. With zero stars, zero dependents, and a score of 0, this evaluation is really a test of whether we enforce dependency intake policy. Whatever we decide, treat it as precedent-setting — the answer sets the bar for the next "small utility" request. Recommended position: evaluate cheaply, decide explicitly, default to porting the useful macros into app-owned components, and record the decision so the next request inherits a rule rather than a re-litigation.
How can I help you explore Laravel packages today?