Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Kaguya Laravel Package

panjinamjaelf/kaguya

Kaguya is a lightweight Laravel package that provides a simple, configurable way to add its functionality to PHP applications. It follows Laravel conventions, keeps installation minimal, and offers a straightforward API for common development tasks.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal setup

composer require panjinamjaelf/kaguya
php artisan vendor:publish --provider="Panjinamjaelf\Kaguya\KaguyaServiceProvider"
php artisan migrate

Steps to verify, in order:

  1. Confirm auto-discovery. Check composer.json of the package for a extra.laravel.providers block. If it is absent, register the service provider manually in bootstrap/providers.php (Laravel 11+) or config/app.php (Laravel 10 and below). This is the single most common reason the package "does nothing" on install.
  2. Read the published config file first. Settings packages almost always expose a table name, cache store, cache TTL, and a defaults / groups array. The defaults array is your source of truth — treat it as configuration-as-code and commit it.
  3. Inspect src/ before trusting the README. At 10 stars the docs are likely thin and the public API may have shifted. Look for the facade class, the repository/manager class, and the migration stub. Five minutes here saves an hour of guessing method names (get/set vs. getValue/put, forget vs. remove).
  4. Run the migration and confirm the schema. Typical shape is key (string, unique), value (text or json), optional group, optional type. If value is text, you own serialization semantics — see Gotchas.

First use case

The highest-value first use is runtime-tunable, non-secret application settings that ops or admins should change without a deploy:

  • Site name, tagline, logo path, support email
  • Feature toggles (features.checkout_v2)
  • Pagination sizes, upload limits, currency/locale defaults

Do not start with credentials, API keys, or anything per-user. Settings packages store plaintext in a database table that ends up in dumps, backups, and .sql fixtures. Secrets belong in config/ + .env (or a vault), with the DB row storing only a reference.

A good first commit: define defaults in the published config, seed them, expose a read helper in a Blade layout, and confirm a change round-trips.

Implementation Patterns

Config-as-defaults, DB-as-override

The pattern that scales is a single accessor that layers DB over config over a hard fallback:

function setting(string $key, mixed $default = null): mixed
{
    return Cache::rememberForever("setting.{$key}", function () use ($key, $default) {
        return Kaguya::get($key) ?? config("settings.{$key}") ?? $default;
    });
}

Then always call setting('site.name') in views and services, never the package facade directly. This gives you one place to swap the backend, add logging, or add tenant scoping later. It also means the rest of your codebase doesn't depend on a 10-star dependency's API surface.

Admin write path: FormRequest + typed cast + cache bust

Put writes behind a controller with validation, then normalize before saving:

public function update(UpdateSettingsRequest $request)
{
    foreach ($request->validated('settings') as $key => $value) {
        Kaguya::set($key, $value);
        Cache::forget("setting.{$key}");
    }

    return back()->with('status', 'Settings saved.');
}

Wrap the loop in DB::transaction() if the package writes one row per key — a mid-loop failure otherwise leaves a half-applied settings page, which is confusing to debug. If the package ships a setMany() or update(array), prefer it and let it own the transaction.

Grouped settings for larger apps

Once you pass ~30 keys, organize by domain prefix (mail.from_name, mail.from_address, billing.currency) rather than a separate group column. Prefixes survive a change of settings backend; a group column does not. Load a whole group once if the package supports it, or eager-load into a cached array:

$mail = Cache::rememberForever('settings.mail', fn () =>
    collect(config('settings.mail'))->mapWithKeys(fn ($d, $k) => [$k => setting("mail.{$k}", $d)])
);

Blade integration

Share settings that every page needs from a base controller or a view composer, not from inside Blade templates. Calling the settings helper 40 times in a layout is a cache hit each time, but the coupling hurts when you add tenant overrides.

View::composer('layouts.app', fn ($view) => $view->with([
    'siteName' => setting('site.name'),
    'features' => setting('features', []),
]));

Feature toggles

For toggles, read through a bool-casting accessor so string/int ambiguity never leaks into @if:

function feature(string $name): bool
{
    return filter_var(setting("features.{$name}", false), FILTER_VALIDATE_BOOL);
}

Testing

Add RefreshDatabase, then seed the settings your tests depend on in setUp() against the in-memory cache. Flush the package's cache key namespace between tests (Cache::flush() in a test environment is acceptable; in CI with a shared Redis it is not — use a tagged or prefixed store).

Gotchas and Tips

Caching is the whole ballgame. Nearly every "I changed the setting and nothing happened" bug is a stale cache. Verify: (a) the TTL from config, (b) whether the package invalidates on set at all, and (c) whether your Cache::rememberForever wrapper is bypassing the package's invalidation. If you wrap, you must forget your key on every write — the package has no idea your key exists.

Multi-server / queue workers. With CACHE_STORE=file, server B and every queue worker keep serving the old value indefinitely. For anything running more than one process, CACHE_STORE=redis (or database) is effectively mandatory. Also remember long-running workers (queue:work, Octane) hold in-process state — restart them after changing settings if the package memoizes per-request.

Serialization is on you. If the value column is TEXT, then false, 0, "false", and null are all distinct and easily conflated. Booleans come back as "0"/"1" or "false" depending on driver. Always cast at the read site (filter_var, (int), json_decode) and never compare with === true against a raw stored value.

Arrays and JSON. Storing arrays works only if the package json_encodes on write and decodes on read. Confirm with a round-trip test before you build a whole feature-flag matrix on it; a package that stores Array via PHP's string cast will silently destroy your data.

config:cache and env drift. If the package's config file reads env() at the top level (many do), then after php artisan config:cache any later env() change is invisible until you re-cache. Standard Laravel rule, but settings packages are where people forget it, because they expect runtime tunability.

Migration column length. A key column of VARCHAR(255) with a unique index can exceed MySQL's index limit under utf8mb4 when combined with other indexed columns. If the migration fails on MySQL 5.7, shorten the key or drop the compound index — check the published stub before editing by hand so re-publishing stays clean.

Extension points worth knowing. Look for a contract/interface the manager is bound to in the service provider. Rebinding it ($this->app->bind(SettingsInterface::class, MySettings::class)) is the clean way to add tenant scoping, encryption, or an audit log without forking. If no interface exists, wrap the facade — don't subclass the concrete class.

Audit trail. Settings changes are production incidents waiting to happen. Log user_id, key, old value, new value in the write path. The package almost certainly won't do this for you, and "who turned off checkout?" is a question you will be asked.

Don't let it become a config dumping ground. If a value never changes without a code review, it belongs in config/. Settings rows drift, get copy-pasted between environments, and silently diverge from the code that expects them. Keep the table small, keep defaults committed in the config file, and write a test asserting that every key the app reads has a default.

Exit ramp. At this maturity level, budget for the package being abandoned. Because every read goes through your own setting() helper, migrating to spatie/laravel-settings or plain config() + a cache is a single-file change. Preserve that property deliberately.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core