hosmelq / laravel-model-preferences
Per-model preferences for Laravel with defaults and validation.
Installs: 164
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/hosmelq/laravel-model-preferences
Requires
- php: ^8.2
- illuminate/contracts: ^11.0 || ^12.0
- illuminate/database: ^11.0 || ^12.0
- illuminate/support: ^11.0 || ^12.0
- spatie/laravel-package-tools: ^1.92
- thecodingmachine/safe: ^3.3
Requires (Dev)
- ergebnis/composer-normalize: ^2.49
- larastan/larastan: ^3.9
- laravel/pint: ^1.27
- orchestra/pest-plugin-testbench: ^3.2
- orchestra/testbench: ^9.0 || ^10.0
- pestphp/pest: ^3.8
- pestphp/pest-plugin-laravel: ^3.2
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- rector/rector: ^2.3
- rector/type-perfect: ^2.1
- shipmonk/composer-dependency-analyser: ^1.8
- spaze/phpstan-disallowed-calls: ^4.7
- thecodingmachine/phpstan-safe-rule: ^1.4
- ticketswap/phpstan-error-formatter: ^1.2
- tomasvotruba/type-coverage: ^2.1
README
Laravel Model Preferences provides a simple, fluent API for storing per-model preferences. You may store preferences in a JSON column or database tables and define defaults and validation rules per model.
Table of Contents
- Introduction
- Installation
- Configuration
- Defining Preferences
- Using Preferences
- Enum Keys
- Preference Stores
- Adding Custom Preference Drivers
- Model Cleanup
- Testing
- Changelog
- Contributing
- License
Introduction
Model preferences are small settings that live alongside a model, such as themes, notification options, or feature toggles. This package provides a clean API to read and write preferences with sensible defaults and validation.
Installation
First, install the package via Composer:
composer require hosmelq/laravel-model-preferences
Next, publish the configuration and migration files:
php artisan model-preferences:install
If you prefer, you may publish the assets manually:
php artisan vendor:publish --tag="model-preferences-config" php artisan vendor:publish --tag="model-preferences-migrations"
Finally, run your migrations if you are using the shared or table stores:
php artisan migrate
Configuration
After publishing the package assets, the configuration file will be located at
config/model-preferences.php. This configuration file allows you to specify the default store
and configure each store.
Default Store
You may specify the default preference store using the model-preferences.default configuration
value or the MODEL_PREFERENCES_DRIVER environment variable. Supported drivers are column,
shared, and table.
Store Configuration
Each store has its own configuration. For example, you may configure the JSON column name for the
column driver or the database connection and table name for the shared and table drivers.
Environment Variables
The following environment variables are supported:
MODEL_PREFERENCES_DRIVER(default:shared)MODEL_PREFERENCES_COLUMN_NAME(default:preferences)MODEL_PREFERENCES_TABLE(default:preferences)MODEL_PREFERENCES_DB_CONNECTION(default:DB_CONNECTION)
Defining Preferences
The InteractsWithPreferences Trait
To define preferences on a model, add the InteractsWithPreferences trait and implement the
HasPreferences contract:
use HosmelQ\ModelPreferences\Contracts\HasPreferences; use HosmelQ\ModelPreferences\Models\Concerns\InteractsWithPreferences; use HosmelQ\ModelPreferences\Support\PreferencesConfig; use Illuminate\Database\Eloquent\Model; class User extends Model implements HasPreferences { use InteractsWithPreferences; public function preferencesConfig(): PreferencesConfig { return PreferencesConfig::configure(); } }
The preferencesConfig Method
The preferencesConfig method allows you to define defaults, validation rules, and the driver
for a given model:
use HosmelQ\ModelPreferences\Support\PreferencesConfig as ModelPreferencesConfig; use Illuminate\Validation\Rule; public function preferencesConfig(): ModelPreferencesConfig { return ModelPreferencesConfig::configure() ->withDefaults([ 'notifications' => true, 'theme' => 'system', ]) ->withRules([ 'notifications' => ['boolean'], 'theme' => [Rule::in(['dark', 'light', 'system'])], ]); }
Defaults
Defaults may be defined per model using withDefaults. These values will be returned when the
preference key is not present in the store:
use HosmelQ\ModelPreferences\Support\PreferencesConfig; return PreferencesConfig::configure() ->withDefaults([ 'theme' => 'system', ]);
Validation Rules
You may validate preferences using Laravel's validation rules. Rules are evaluated when calling
set, and a PreferenceValidationException will be thrown if validation fails:
use App\Models\User; use HosmelQ\ModelPreferences\Exceptions\PreferenceValidationException; $user = User::query()->firstOrFail(); try { $user->preferences()->set('theme', 'invalid'); } catch (PreferenceValidationException $e) { $errors = $e->errors(); }
Using Preferences
Obtaining a Preferences Instance
Call the preferences method on your model to obtain a scoped preferences instance:
use App\Models\User; $user = User::query()->firstOrFail(); $preferences = $user->preferences();
Retrieving Preferences
Use get to retrieve a single preference value. You may pass a default value or a closure that
will be evaluated lazily. To retrieve multiple preferences at once, use getMultiple:
use App\Models\User; $user = User::query()->firstOrFail(); $theme = $user->preferences()->get('theme'); $timezone = $user->preferences()->get('timezone', 'UTC'); $locale = $user->preferences()->get('locale', fn () => app()->getLocale()); $values = $user->preferences()->getMultiple(['theme', 'timezone']);
Storing Preferences
Use set to store a preference value or setMultiple to store multiple values:
use App\Models\User; $user = User::query()->firstOrFail(); $user->preferences()->set('theme', 'dark'); $user->preferences()->setMultiple([ 'theme' => 'dark', 'locale' => 'en', ]);
Checking for Presence
Use has to determine if a preference key exists. You may also use missing to check the
opposite:
use App\Models\User; $user = User::query()->firstOrFail(); if ($user->preferences()->has('theme')) { // ... } if ($user->preferences()->missing('timezone')) { // ... }
Retrieving All Preferences
Use all to retrieve all stored preferences:
use App\Models\User; $user = User::query()->firstOrFail(); $all = $user->preferences()->all();
Defaults & Missing Values
Preference lookup follows these rules:
- If the preference exists (even if its value is
null),getreturns that value. - If the preference does not exist and a model default is configured, the default is returned.
- If the preference does not exist and a default is passed to
get, that default is returned. - Otherwise,
nullis returned.
Deleting Preferences
Use delete to remove a single preference, deleteMultiple for batches, and clear to remove all
preferences:
use App\Models\User; $user = User::query()->firstOrFail(); $user->preferences()->delete('theme'); $user->preferences()->deleteMultiple(['theme', 'locale']); $user->preferences()->clear();
Enum Keys
Preference keys may be strings, backed enums, or unit enums. Backed enums use their backed value, while unit enums use their name:
use App\Models\User; enum UserPreference: string { case Theme = 'theme'; } $user = User::query()->firstOrFail(); $user->preferences()->set(UserPreference::Theme, 'dark');
Preference Stores
You may select a store globally via configuration or per model using withDriver.
Column Driver (JSON Column)
The column driver stores all preferences in a single JSON column on the model's table. You may
configure the column name via withColumn or the model-preferences.stores.column.name setting.
When using this driver, the InteractsWithPreferences trait automatically adds a JSON cast for
the configured column.
Example migration column:
use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; Schema::table('users', function (Blueprint $table): void { $table->json('preferences')->nullable(); });
Shared Table Driver (Polymorphic)
The shared driver stores preferences in a single table using a polymorphic relation. The
published migration creates a preferences table with preferable_type, preferable_id, key,
and value columns.
Per-Model Table Driver
The table driver stores preferences in a dedicated table per model. When using this driver, you
must configure the table name in preferencesConfig:
use HosmelQ\ModelPreferences\Support\PreferencesConfig; return PreferencesConfig::configure() ->withDriver('table') ->withTable('users_preferences');
Custom Tables & the Artisan Generator
You may generate a preferences table migration using the Artisan command:
php artisan model-preferences:table users_preferences
This stub creates a table with model_id, key, and value columns and a unique index on
model_id and key.
Adding Custom Preference Drivers
Implementing a Driver
To add your own driver, implement the PreferenceDriver contract. If you need to distinguish
between missing values and null, implement PresenceAwarePreferenceDriver and return a
PreferenceRead instance from getWithPresence.
use HosmelQ\ModelPreferences\Contracts\HasPreferences; use HosmelQ\ModelPreferences\Contracts\PreferenceDriver; class CustomDriver implements PreferenceDriver { public function all(HasPreferences $model): array { return []; } public function clear(HasPreferences $model): void { // ... } public function delete(HasPreferences $model, string $key): void { // ... } public function deleteMultiple(HasPreferences $model, array $keys): void { // ... } public function get(HasPreferences $model, string $key): mixed { return null; } public function getMultiple(HasPreferences $model, array $keys): array { return []; } public function has(HasPreferences $model, string $key): bool { return false; } public function missing(HasPreferences $model, string $key): bool { return true; } public function set(HasPreferences $model, string $key, mixed $value): void { // ... } public function setMultiple(HasPreferences $model, array $values): void { // ... } }
Registering a Driver
Register your driver via the preferences manager, then configure your model to use it:
use App\Preferences\CustomDriver; use HosmelQ\ModelPreferences\PreferencesManager; use HosmelQ\ModelPreferences\PreferencesStore; app(PreferencesManager::class)->extend('custom', function () { return new PreferencesStore('custom', new CustomDriver()); });
use HosmelQ\ModelPreferences\Support\PreferencesConfig; return PreferencesConfig::configure() ->withDriver('custom');
Model Cleanup
When using the shared or table drivers, the InteractsWithPreferences trait automatically
removes stored preferences when the model is deleted.
Testing
composer test
Changelog
See CHANGELOG.md for a list of changes.
Contributing
Pull requests are welcome. Please run the test suite before submitting changes.
License
The MIT License (MIT). Please see License File for more information.