marshmallow/helpers
Marshmallow Helpers provides a set of practical Laravel/PHP helper functions designed to reduce boilerplate and simplify everyday development tasks. Lightweight, easy to install via Composer, and ready to use throughout your application.
1. Install and pin
composer require marshmallow/helpers
The package is now at v2.22.0 — note that this is a 2.x line with 22 minor releases behind it, so the earlier assumption of a fresh 1.0.0 tag is wrong. Pin to the exact minor you've tested rather than floating on ^2.0:
"marshmallow/helpers": "2.22.0"
With 2 stars and 0 dependents, you are still effectively the only reviewer of this code. A deliberate bump cadence matters more than the constraint operator.
2. Find out how it loads. Open vendor/marshmallow/helpers/composer.json first. The important keys for a "helpers" package are:
autoload.files — global functions loaded on every request (no namespace, no import needed)autoload.psr-4 — namespaced classes you use normallyextra.laravel.providers / aliases — whether a service provider or facade is auto-discoveredcat vendor/marshmallow/helpers/composer.json
ls -R vendor/marshmallow/helpers/src
Also read the illuminate/support (and illuminate/database) constraints. v2.22.0 ships Laravel 13 work, so if you're on an older major, check whether this release still supports your version before upgrading — a minor bump that follows a new framework major is the classic place for a dropped floor or a behaviour change.
3. Refresh autoloaders. If the package was added before you generated autoloads, or you add your own file, run:
composer dump-autoload
Global functions are not discoverable by your IDE until autoload is regenerated.
4. Publish config (if one exists).
php artisan vendor:publish --provider="Marshmallow\Helpers\HelpersServiceProvider" --tag=helpers
If there is no config/ directory in the package, skip this — don't invent a config file.
5. First use case. Replace a sprawling app/helpers.php / app/Support/helpers.php that every project accumulates. Pick two or three helpers you already have written inline (string formatting, money/currency display, date formatting, array access) and swap them for the package equivalents in one feature branch. Run the test suite. If nothing breaks, continue; if autoloading or signatures fight you, you've lost one afternoon instead of a sprint.
6. Second use case — observer registration (new in 2.22.0). This release adds a helper for registering a model observer without booting the model. That is a genuinely useful addition on large apps where an observer registration in booted() drags model instantiation, global scopes, and boot-time side effects into contexts that don't need them (console commands, queue workers, tests). Read the PR-backed implementation in src/ and try it on one noisy model before adopting broadly.
7. Confirm the API surface before committing. With no dependents and a low star count, the README is the only contract you have. Cross-check it against src/ — any helper documented but missing, or present but undocumented, is a signal to read the implementation rather than trust the docs. Note that new helpers in a minor release are frequently undocumented for a cycle or two; the observer helper from PR #61 is exactly that case.
Wrap global functions behind your own boundary. This is the single most valuable pattern for a low-adoption dependency. Don't call global helpers from 40 places; call them from one place you control.
// app/Support/Format.php
namespace App\Support;
final class Format
{
public static function money(float $amount, ?string $currency = null): string
{
// single call site for the third-party helper
return \marshmallow_money($amount, $currency ?? config('shop.currency'));
}
}
Now a breaking change upstream, or a decision to drop the package, is a one-file edit.
Prefer the namespaced/facade API over globals when both exist. Namespaced calls are greppable, mockable, and visible to static analysis. Globals are none of those. Only reach for the global form inside Blade templates or where a string-returning helper reads substantially better inline.
Observer registration without booting. Where the package offers the no-boot observer registration, prefer it in service providers over Model::observe() inside booted():
// AppServiceProvider::boot()
$this->registerObserverWithoutBooting(Order::class, OrderObserver::class);
// instead of, in Order::booted():
// static::observe(OrderObserver::class);
The win is that observer wiring no longer forces the model class through its boot lifecycle during provider boot — cheaper on console/queue startup and less surprising in tests. The trade-off is that anything your observer silently depended on arriving via booted() (custom casts, dynamic relations, global scope registration) is no longer guaranteed to have run first. Move that setup into the observer itself or into a booted-only path you actually need.
Keep booted()-based observers where the registration is conditional. If your booted() call was doing work — checking a config flag, a feature toggle, a tenancy context — don't blindly swap it for an unconditional provider registration. Extract the condition, then register.
Blade and view usage. Global helpers shine in views, but keep them out of anything with logic:
{{ marshmallow_money($order->total) }}
If the helper throws on null, guard it in a view composer or accessor, not in the template.
Progressive migration / feature-flag adoption. Introduce the package alongside your existing helpers rather than deleting them on day one:
if (! function_exists('marshmallow_money')) {
// keep local fallback until migration is complete
}
Wrap the migration in a single PR per domain (money, dates, strings) so a revert is surgical.
Integration with Eloquent. Use helpers in accessors and casts rather than scattered in controllers — that's where the call count is highest and where a signature change will surface fastest in tests:
protected function totalFormatted(): Attribute
{
return Attribute::get(fn () => Format::money($this->total));
}
Make it testable. Global functions cannot be mocked cleanly. If a code path's behaviour depends on a helper's output, inject your wrapper class instead:
public function __construct(private readonly Formatter $formatter) {}
then bind Formatter in a service provider. This also gives you a seam to replace the package later.
Static analysis baseline. If you run Larastan/PHPStan, add a stub file (or phpstan-baseline.neon entry) for the global functions — otherwise every helper call is an "undefined function" error and your team starts ignoring the analyser.
Version discipline in a monorepo of projects. Because this is an internal-style "shared helpers" package, treat it like an internal library: pin one version across all your apps, bump deliberately, and read the diff of src/ between tags before upgrading. There is no curated CHANGELOG beyond release notes; the compare URL in the notes is your changelog.
A minor version can carry a framework-major change. v2.22.0 is a minor bump but explicitly targets Laravel 13. Read the release compare diff before bumping on shared apps: minor versions here are not guaranteed to be behaviour-compatible with your framework major, and the semver signal on a 2-star repo is a convention, not a contract.
Observer registration without booting changes ordering. If you adopt the new helper, verify that observers still fire in the order your tests assume and that any state set up in the model's booted() / boot() is present when the observer runs. Run the model's existing test suite plus any withoutEvents / Event::fake() tests — these are where ordering regressions hide.
Function redeclaration is a fatal error, not a warning. If you define marshmallow_money() yourself and the package also defines it, PHP dies at boot with Cannot redeclare. Guard your own definitions with function_exists(), or better, pick a prefix the package doesn't use. Audit existing helpers before installing — and re-audit after upgrades, since new minor releases add new global function names you may already own.
autoload.files runs on every request. Global functions load on every HTTP request, queue worker boot, and artisan command — even when unused. If the package's helper file does anything non-trivial at include time (reads config, hits a container binding, registers filters), you pay for it everywhere. Check the top-level statements in the files listed under autoload.files.
Composer cache won't pick up new files. Adding a helper file that isn't registered in autoload.files produces a "function not found" that looks like a package bug. Re-run composer dump-autoload and, in Docker, rebuild the image — a cached vendor/autoload_files.php from the image layer will silently omit new entries.
No IDE autocompletion for globals. Use barryvdh/laravel-ide-helper (php artisan ide-helper:generate), or maintain a small _ide_helper.php stub listing the package's functions with correct signatures. Without it, every call is a red squiggle and developers will avoid the package. New helpers in a minor release will be missing from any stub you generated previously — regenerate after bumping.
Name collisions with Laravel's own helpers. Laravel ships str(), value(), retry(), once(), etc. A helpers package from a different era may shadow or conflict with those, or expect a Laravel version where a given helper didn't exist. This is more likely, not less, as the package tracks new framework majors — 2.22.0 explicitly targets Laravel 13. Test on your exact framework version.
Zero dependents is a risk signal, not a disqualifier. The 2.22.0 release with a PR from a named maintainer is evidence the package is maintained — that's a reason to relax the "unstable contract" framing slightly, not to drop the wrapper boundary. Keep the wrapper, keep reading src/ for anything you depend on, and note that MIT licensing means forking is legal; it doesn't guarantee the package will be maintained forever.
Release cadence vs. review effort. With 22 minor releases on the 2.x line, the volume of change is the risk, not its absence. Don't auto-merge dependabot bumps for this package; read each compare diff. Most will be one-liners like PR #61, but "one-liner in a helpers package" is exactly the shape of a subtle autoload or observer-ordering change.
Debugging a helper that misbehaves. Global functions are invisible in the stack trace's use statements, so dd() inside the vendor file is often faster than reasoning about it. Copy the function into app/Support/ locally, add the debug output, confirm behaviour, then go back to the package version and compare. Never edit vendor/ in place — it will be blown away on the next composer install.
Extension points are probably absent. Small helper packages rarely expose contracts, config, or events. Assume you extend by wrapping, not by subclassing. If there is a config file after vendor:publish, that's your only supported knob — check it before patching vendor code. The new observer helper is a utility, not an extension point: don't build subclass hierarchies on top of it.
Composer --no-dev --optimize-autoloader is safe for files autoloads. Don't let anyone tell you production needs dev dependencies to load global helpers; autoload.files is emitted into the optimized classmap too. The failure mode in production is a stale build artefact, not the optimize flag — verify the deployed vendor/ actually contains 2.22.0 before debugging anything else.
How can I help you explore Laravel packages today?