Download the PHP package matteoc99/laravel-preference without Composer
On this page you can find all versions of the php package matteoc99/laravel-preference. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download matteoc99/laravel-preference
More information about matteoc99/laravel-preference
Files in matteoc99/laravel-preference
Package laravel-preference
Short Description Laravel package that aims to store and manage user settings/preferences in a simple and scalable manner
License MIT
Informations about the package laravel-preference
Laravel User Preferences
This Laravel package aims to store and manage user settings/preferences in a simple and scalable manner.
Table of Contents
- Features
- Roadmap
- Installation
- Usage
- Concepts
- Define your preferences
- Create a Preference
- Preference Building
- Working with preferences
- Examples
- Casting
- Available Casts
- Custom Caster
- Rules
- Available Rules
- Custom Rules
- Policies
- Routing
- Anantomy
- Example
- Actions
- Middlewares
- Security
- Upgrade from v1
- Test
- Contributing
- Security Vulnerabilities
- Credits
- License
- Support target
Features
- Type safe Casting
- Validation & Authorization
- Extensible (Create your own Validation Rules and Casts)
- Enum support
- Custom Api routes
- work with preferences from a GUI or in addition to backend functionalities
Roadmap
- Event System -> #13
- Api Response customization -> #14
- QoL Helpers functions
- Caching
-
Blade Directives
- Additional suggestions are welcome. (check out Contributing)
Installation
You can install the package via composer:
[!IMPORTANT] consider installing also
graham-campbell/security-core:^4.0
to take advantage of xss cleaning. see Security for more information
Configuration
You can publish the config file with:
[!NOTE] Consider changing the base table names before running the migrations, if needed
Run the migrations with:
Usage
Concepts
Each preference has at least a name and a caster. Names are stored in one or more enums and are the unique identifier for that preference
For additional validation you can add you custom Rule object.
For additional security you can add Policies
Define your preferences
Organize them in one or more string backed enum.
[!NOTE] while it does not need to be string backed, its way more developer friendly. Especially when interacting over the APi
Each enum gets scoped and does not conflict with other enums with the same case
e.g.
Create a Preference
single mode
Bulk mode
Preference Building
Check all methods available to build a Preference
### Available Methods This table includes a complete list of all features available, when building a preference. | Single-Mode | Bulk-Mode (array-keys) | Constrains | Description | |-------------------------------------|---------------------------------------------|---------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| | init(>name<,>cast<) | | \>name< = instanceof PreferenceGroup | Unique identifier for the preference | | init(>name<,>cast<) | | \>cast< = instanceof CastableEnum | Caster to translate the value between all different scenarios. Currently: Api-calls as well as saving to and retrieving fron the DB | | nullable(>nullable<) | | \>nullable< = bool | Whether the default value can be null and if the preference can be set to null | | withDefaultValue(>default_value<) | | \>default_value< = mixed, but must comply with the cast & validationRule | Initial value for this preference | | withDescription(>description<) | | \>description< = string | Legacy code from v1.x has no actual use as of now | | withPolicy(>policy<) | | \>policy< = instanceof PreferencePolicy | Authorize actions such as update/delete etc. on certain preferences. | | withRule(>rule<) | | \>rule< = instanceof ValidationRule | Additional validation Rule, to validate values before setting them | | setAllowedClasses(>allowed_values<) | | \>allowed_values< = array of string classes. For non Primitive Casts only | Current use-cases:- restrict classes of enum or object that can be set to this preference
- reconstruct the original class when sending data via api. | ### Available helper functions Optionally, pass the default value as a second parameter
Working with preferences
Two things are needed:
HasPreferences
trait to access the helper functionsPreferenceableModel
Interface to have access to the implementation- in particular to
isUserAuthorized
- in particular to
isUserAuthorized
Guard function to validate if the currently logged in (if any) user has access to this model Signature:
- $user the logged in user
- PolicyAction enum: the action the user wants to perform index/get/update/delete
[!NOTE] this is just the bare minimum regarding Authorization.
For more fine-grained authorization checks refer to Policies
Example implementation:
Examples
Casting
Set the cast when creating a Preference
[!NOTE] a cast has 3 main jobs
- Basic validation
- Casting from and to the database
- Preparing Api Responses
Example:
Available Casts
Cast | Explanation |
---|---|
INT | Converts and Validates a value to be an integer. |
FLOAT | Converts and Validates a value to be a floating-point number. |
STRING | Converts and Validates a value to be a string. |
BOOL | Converts and Validates a value to be a boolean (regards non-empty as true ). |
ARRAY | Converts and Validates a value to be an array. |
BACKED_ENUM | Ensures the value is a BackedEnum type. Useful for enums with underlying values. |
ENUM | Ensures the value is a UnitEnum type. Useful for enums without underlying values. |
OBJECT | Ensures that the value is an object. |
NONE | No casting is performed. Returns the value as-is. |
Date-Casts | Explanation |
---|---|
Converts a value using Carbon::parse, and always return a Carbon instance. Validation is Cast-Specific |
|
DATE | sets the time to be 00:00 . |
TIME | Always uses the current date, setting only the time |
DATETIME | with both date and time(optionally). |
TIMESTAMP | allows a string/int timestamp or a carbon instance |
Custom Caster
Implement CastableEnum
[!IMPORTANT] The custom caster needs to be a string backed enum
Example:
Rules
Additional validation, which can be way more complex than provided by the Cast
Adding Rules
``
Available Rules
Rule | Example | Description |
---|---|---|
AndRule | new AndRule(new BetweenRule(2.4, 5.5), new LowerThanRule(5)) |
Expects n ValidationRule, ensures all pass |
OrRule | new OrRule(new BetweenRule(2.4, 5.5), new LowerThanRule(5)) |
Expects n ValidationRule, ensures at least one passes |
LaravelRule | new LaravelRule("required\|numeric") |
Expects a string, containing a Laravel Validation Rule |
BetweenRule | new BetweenRule(2.4, 5.5) |
For INT and FLOAT, check that the value is between min and max |
InRule | new InRule("it","en","de") |
Expects the value to be validated to be in that equal to one of the n params |
InstanceOfRule | new InstanceOfRule(Theme::class) |
For non primitive casts, checks the instance of the value's class to validate. Tip: goes along well with the OrRule |
IsRule | new IsRule(Type::ITERABLE) |
Expects a Matteoc99\LaravelPreference\Enums\Type Enum. Checks e.g. if the value is iterable |
LowerThanRule | new LowerThanRule(5) |
For INT and FLOAT, check that the value to be validated is less than the one passed in the constructor |
Custom Rules
Implement Laravel's ValidationRule
Example:
Policies
Each preference can have a Policy, should isUserAuthorized not be enough for your usecase
Creating policies
Implement PreferencePolicy
and the 4 methods defined by the contract
parameter | description |
---|---|
Authenticatable $user | the currently logged in user, if any |
PreferenceableModel $model | the model on which you are trying to modify the preference |
PreferenceGroup $preference | the preference enum in question |
Adding policies
``
Routing
Off by default, enable it in the config
[!WARNING] (Current) limitation: it's not possible to set object casts via API
Anantomy:
'Scope': the PreferenceableModel
Model
'Group': the PreferenceGroup
enum
Routes then get transformed to:
Action | URI | Description |
---|---|---|
GET | /{prefix}/{scope}/{scope_id}/{group} | Retrieves all preferences for a given scope and group. |
GET | /{prefix}/{scope}/{scope_id}/{group}/{preference} | Retrieves a specific preference within the scope and group. |
PUT/PATCH | /{prefix}/{scope}/{scope_id}/{group}/{preference} | Updates a specific preference within the scope and group. |
DELETE | /{prefix}/{scope}/{scope_id}/{group}/{preference} | Deletes a specific preference within the scope and group. |
which can all be accessed via the route name: {prefix}.{scope}.{group}.{index/get/update/delete}
URI Parameters
scope_id
: The unique identifier of the scope (e.g., a user's ID).
preference
: The value of the specific preference enum (e.g., General::LANGUAGE->value).
group
: A mapping of group names to their corresponding Enum classes. See config below
scope
: A mapping of scope names to their corresponding Eloquent model. See config below
Config Example:
will result in the following route names:
- custom_prefix.user.general.index
- custom_prefix.user.general.get
- custom_prefix.user.general.update
- custom_prefix.user.general.delete
- custom_prefix.user.video.index
- custom_prefix.user.video.get
- custom_prefix.user.video.update
- custom_prefix.user.video.delete
Actions
[!NOTE] Examples are with scope
user
and groupgeneral
INDEX
- Route Name: custom_prefix.user.general.index
- Url params:
scope_id
- Equivalent to:
$user->getPreferences(General::class)
- Http method: GET
- Endpoint: 'https://your.domain/custom_prefix/user/{scope_id}/general'
GET
- Route Name: custom_prefix.user.general.get
- Url params:
scope_id
,preference
- Equivalent to:
$user->getPreference(General::{preference})
- Http method: GET
- Endpoint: https://your.domain/custom_prefix/user/{scope_id}/general/{preference}
UPDATE
- Route Name: custom_prefix.user.general.update
- Url params:
scope_id
,preference
- Equivalent to:
$user->setPreference(General::{preference}, >value<)
- Http method: PATCH/PUT
- Endpoint: https://your.domain/custom_prefix/user/{scope_id}/general/{preference}
- Payload:
{ "value": >value< }
Enum Patching
When creating your enum preference, add setAllowedClasses
containing the possible enums to reconstruct the value
[!CAUTION] if multiple cases are shared between enums, the first match is taken
then, when sending the value it varies:
- BackedEnum: send the value or the case
- UnitEnum: send the case
Example:
DELETE
- Route Name: (custom_prefix.user.general.delete)
- Url params:
scope_id
,preference
- Equivalent to:
$user->removePreference(General::{preference})
- Http method: DELETE
- Endpoint: https://your.domain/custom_prefix/user/{scope_id}/general/{preference}
Middlewares
set global or context specific middlewares in the config file
[!CAUTION] known Issues: without the web middleware, you won't have access to the user via the Auth facade since it's set by the middleware. Looking into an alternative
Security
XSS cleaning is only performed on user facing api calls.
this can be disabled, if not required, with the config: user_preference.xss_cleaning
When setting preferences directly via setPreference
this cleaning step is assumed to have already been performed, if necessary.
Consider installing Security-Core to make use of this feature
Upgrade from v1
- implement
PreferenceGroup
in your Preference enums - implement
PreferenceableModel
in you all Models that want to use preferences - Switch from
HasValidation
toValidationRule
- Signature changes on the trait: group got removed and name now requires a
PreferenceGroup
- Builder: setting group got removed and name now expects a
PreferenceGroup
enum DataRule
has been removed, add a constructor to get you own, tailored, params- database serialization incompatibilities will require you to rerun your Preference migrations
- single mode: make sure to use
updateOrCreate
, e.gPreferenceBuilder::init(VideoPreferences::QUALITY)->updateOrCreate();
- bulk mode: initBulk as usual, as it works with upsert
- single mode: make sure to use
Test
composer test
composer coverage
Test the pipeline locally
check out act install it via gh
then run: composer pipeline
Contributing
See Contributing for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
- matteoc99
- Joel Brown for this awesome starting point and initial inspiration
License
The MIT License (MIT). Please check the License File for more information.
Support target
Package Version | Laravel Version | Maintained |
---|---|---|
1.x | 10 | ❌ |
2.x | 10 & 11 | ✅ |
All versions of laravel-preference with dependencies
laravel/framework Version ^10 | ^11
ext-pdo Version *
doctrine/dbal Version ^3.8