herytz/rcu-bundle
A Laravel/PHP Composer bundle from herytz that packages reusable RCU components into a single dependency, making it easy to add consistent, framework-friendly functionality to your applications with minimal setup and configuration.
This package gives you a file-backed key/value store inside your Laravel app — useful for settings, feature flags, small caches, or any state you'd rather not put in the database. v1.0.1 is a small but important patch: the package now creates the store directory automatically if it doesn't exist, so a fresh install no longer fails on the first write.
Minimal setup:
composer require the package (check the README for the exact constraint; v1.0.1 is a patch, so ^1.0 is safe).php artisan vendor:publish --tag=store-config.storage/app/...; override with the package's env key (typically STORE_PATH) if you want it on a shared volume or per-tenant directory.php artisan config:clear.Where to look first:
put, get, forget, all, etc.).storage/app/<store-dir> after your first write, to confirm the layout and file naming.First use case: persist a handful of application settings (site name, maintenance mode toggle, third-party API keys) that change rarely and are read on nearly every request. Write them once from an admin panel or a seeder, read them from a cached accessor, and you've replaced a settings table with zero migrations.
Wrap it in your own service. Don't sprinkle Store::get() through controllers and Blade templates. Create a small Settings class (or a typed config object) that injects the store and exposes named methods. That gives you one place to add caching, casting, and defaults, and one place to swap the backend later.
Namespace your keys. Use dotted keys (billing.currency, features.new-checkout) rather than flat strings. It keeps all() readable, makes bulk operations like "forget everything under features.*" possible, and avoids collisions between modules.
Seed defaults explicitly. On boot or via an install command, write the default values only if the key is absent (if (! Store::has($key)) Store::put($key, $default)). This makes the store self-healing on a fresh environment and keeps your code from null-checking everywhere.
Cache the read path. A file read per request adds up. Read the whole store once, memoize it in a singleton or the cache, and bust that cache on write. A simple pattern: Cache::rememberForever('store.snapshot', fn () => Store::all()), then Cache::forget('store.snapshot') inside your put/forget wrappers.
Testing. Point the store at a temporary directory in setUp (or via config()->set() plus a container rebind) and clean it in tearDown, so tests never touch your real store. If the package ships a fake/in-memory driver, use it for unit tests and reserve the real filesystem for integration tests.
Multi-tenant and environment separation. If you have per-tenant data, derive the store path from the tenant ID (store_path/tenants/{id}) rather than prefixing keys — it gives you clean deletion, per-tenant backups, and no key-collision risk. For environments, let the path differ by APP_ENV so staging writes never land in production's store.
Deployment. Add the store directory to your deploy script's shared/persistent paths (e.g. storage/app/store in Envoyer/Deployer symlink lists), and ensure it's writable by the PHP-FPM user. Include it in backups if it holds anything you can't regenerate.
Migrations from the database. If you're moving existing settings out of a table, write a one-off artisan command that reads the table and writes through your Settings wrapper, then flip the read path and keep the old table for a release as a rollback.
Directory permissions, not just existence. v1.0.1 fixes the "store dir doesn't exist" crash, but the directory it creates is owned by whoever ran the process first — often your local user after php artisan, then PHP-FPM can't write to it in production. Create it in your deploy script with the correct owner/group and 775, and don't rely on runtime creation for anything but local dev.
Concurrency. File-backed stores generally aren't atomic under concurrent writes. Two requests writing the same key at the same moment can produce a partially written file or a lost update. If writes happen on the request path (rather than in admin actions or queued jobs), wrap them in a Cache::lock() or accept last-write-wins explicitly. Reads during a write can return corrupt JSON — validate on read and fall back to defaults.
Config caching. The store path is read from config, so php artisan config:cache in your deploy pipeline will freeze whatever path was set at build time. Set the path via an env variable and re-run config:cache on the target server, not in CI.
Path traversal and key sanitisation. If any part of a key or tenant identifier comes from user input, validate it against a strict pattern ([A-Za-z0-9_.-]) before it becomes a filename. A key like ../../.env is a very bad day.
Git and permissions drift. Typically the store contents should be gitignored but the directory itself committed with a .gitignore inside it (* then !.gitignore) — that way fresh clones have the folder and v1.0.1's auto-create is a safety net rather than the primary mechanism.
Stale reads after manual edits. If an ops person edits a store file by hand, any in-process or Laravel cache snapshot you built will be stale until it's busted. Document that the store is app-managed, or provide an artisan store:clear-cache command.
Extension points. Before writing your own file-handling code, check whether the package exposes a driver/contract you can implement — a database or Redis driver for the same interface lets you keep your Settings wrapper unchanged when you outgrow the filesystem. Also check for events fired on write; hooking those is cleaner than wrapping every call site if you need audit logging.
Upgrade note for v1.0.1. The change is additive and low-risk: it turns a hard failure into a working first write. The only behavioural difference is that a typo'd store path will now silently create a new directory instead of erroring — so double-check your path config after upgrading rather than assuming success means the right directory was used.
How can I help you explore Laravel packages today?