Skip to content

Settings Casting #129

@glorand

Description

@glorand

Feature Idea: Settings Casting

This document outlines the idea and implementation plan for a new "Settings Casting" feature.

1. Feature Summary

This feature would introduce a $settingsCasts property on models, similar to Eloquent's native $casts. It would allow developers to define expected data types for their settings, and the package would automatically handle the casting of values when they are retrieved or stored. This ensures data integrity and convenience (e.g., always getting a Carbon object for a date, or a boolean instead of a string '1').

2. User-Facing API

The developer would define a $settingsCasts property on their model.

// In a model like App\Models\User.php
use Glorand\Model\Settings\Traits\HasSettings;

class User extends Model
{
    use HasSettings;

    protected $settingsCasts = [
        'notifications_enabled' => 'boolean',
        'login_attempts' => 'integer',
        'last_seen' => 'datetime',
        'preferences' => 'array',
        'api_token' => 'encrypted',
    ];
}

3. Implementation Plan

The implementation would primarily involve modifying the HasSettings trait and the AbstractSettingsManager.

Step 3.1: Add getSettingsCasts() to HasSettings Trait

To safely access the $settingsCasts property from the manager, a helper method will be added to the src/Traits/HasSettings.php file.

trait HasSettings
{
    // ... existing methods

    public function getSettingsCasts(): array
    {
        if (property_exists($this, 'settingsCasts') && is_array($this->settingsCasts)) {
            return $this->settingsCasts;
        }
        return [];
    }
}

Step 3.2: Modify AbstractSettingsManager

The core logic will reside in src/Managers/AbstractSettingsManager.php.

A. Update the get() method:
The get() method will be modified to pass the retrieved value through a new casting method before returning it.

public function get(string $path = null, $default = null)
{
    if (is_null($path)) {
        return $this->all();
    }

    $value = Arr::get($this->all(), $path, $default);

    return $this->castAttribute($path, $value); // New casting step
}

B. Update the set() method:
The set() method will be updated to handle two-way casting, such as encryption, before the value is stored.

public function set(string $path, $value): SettingsManagerContract
{
    $settings = $this->all();

    // Apply cast before setting the value in the array
    $value = $this->castAttributeForSet($path, $value); // New step

    Arr::set($settings, $path, $value);

    return $this->apply($settings);
}

C. Add the Caster Methods:
Two new private methods will be added to handle the actual casting logic.

/**
 * Cast an attribute to a native PHP type on get.
 */
private function castAttribute(string $key, $value)
{
    $casts = $this->model->getSettingsCasts();
    if (!isset($casts[$key])) {
        return $value;
    }

    return match ($casts[$key]) {
        'int', 'integer' => (int) $value,
        'real', 'float', 'double' => (float) $value,
        'string' => (string) $value,
        'bool', 'boolean' => (bool) $value,
        'array', 'json' => is_string($value) ? json_decode($value, true) : $value,
        'object' => is_string($value) ? json_decode($value, false) : $value,
        'date' => \Illuminate\Support\Carbon::parse($value),
        'datetime' => \Illuminate\Support\Carbon::parse($value),
        'encrypted' => \Illuminate\Support\Facades\Crypt::decryptString($value),
        default => $value,
    };
}

/**
 * Cast an attribute for storage.
 */
private function castAttributeForSet(string $key, $value)
{
    $casts = $this->model->getSettingsCasts();
    if (!isset($casts[$key])) {
        return $value;
    }

    return match ($casts[$key]) {
        'encrypted' => \Illuminate\Support\Facades\Crypt::encryptString($value),
        // Other set-side casts (e.g., date to string) could be added here.
        default => $value,
    };
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions