latevaweb/laravel-custom-properties
composer require latevaweb/laravel-custom-properties
use LaTevaWeb\CustomProperties\HasCustomProperties;
class Customer extends Model
{
use HasCustomProperties;
}
custom_properties JSON column to your database table:
Schema::table('customers', function (Blueprint $table) {
$table->json('custom_properties')->nullable();
});
php artisan migrate
Adding and retrieving a dynamic property:
$customer = new Customer();
$customer->setCustomProperty('preferred_language', 'es')
->setCustomProperty('loyalty_points', 150)
->save();
// Retrieve the property
$language = $customer->getCustomProperty('preferred_language'); // 'es'
// Check if a property exists
$hasPoints = $customer->hasCustomProperty('loyalty_points'); // true
// Remove a property
$customer->forgetCustomProperty('loyalty_points')->save();
Dynamic Model Attributes:
Use setCustomProperty() to add metadata that doesn’t warrant a dedicated database column (e.g., user preferences, temporary flags, or API-specific configurations).
$user->setCustomProperty('api_token_expiry', now()->addDays(30))->save();
Conditional Logic:
Leverage hasCustomProperty() to gate behavior without querying the database.
if ($order->hasCustomProperty('is_rushed')) {
$order->update(['status' => 'processing']);
}
Bulk Property Management:
Use getCustomProperties() to retrieve all dynamic properties as an array.
$preferences = $user->getCustomProperties(); // ['theme' => 'dark', 'notifications' => false]
Serialization/Deserialization: Integrate with API responses or JSON payloads:
$data = $model->toArray(); // Includes custom_properties as a JSON-encoded string
Validation: Validate custom properties before saving:
$this->validate([
'custom_properties' => 'required|array',
'custom_properties.*' => 'string|max:255',
]);
Caching: Cache frequently accessed custom properties:
$cacheKey = "user_{$user->id}_preferences";
$preferences = Cache::remember($cacheKey, now()->addHours(1), function () use ($user) {
return $user->getCustomProperties();
});
API Resources: Expose custom properties in API responses:
public function toArray($request)
{
return array_merge(parent::toArray($request), [
'custom' => $this->getCustomProperties(),
]);
}
Model Events:
Listen for saved events to log or process custom properties:
$model->saved(function ($model) {
if ($model->hasCustomProperty('audit_log')) {
AuditLog::create([
'action' => 'update_custom_properties',
'changes' => $model->getCustomProperties(),
]);
}
});
Query Scoping: Filter models by custom property values (requires raw SQL or a custom scope):
public function scopeWithCustomProperty($query, $key, $value)
{
return $query->whereRaw("JSON_CONTAINS(custom_properties, ?)", [json_encode($value)]);
}
JSON Column Limitations:
JSON column has a 64KB limit for values. Avoid storing large blobs (e.g., serialized arrays or base64-encoded files).Case Sensitivity:
$model->setCustomProperty('UserId', 123); // Key is 'UserId'
$model->getCustomProperty('userId'); // Returns null (case mismatch)
Database Driver Quirks:
JSON columns in older versions. Use text with manual JSON encoding/decoding.->> for text extraction), but the package uses generic JSON handling.public function getCustomProperty($key)
{
if (app()->make('db')->getDriverName() === 'pgsql') {
return json_decode($this->custom_properties)->{$key} ?? null;
}
return parent::getCustomProperty($key);
}
Mass Assignment Risks:
$fillable. Explicitly whitelist or validate:
$this->validate([
'custom_properties' => 'sometimes|array',
'custom_properties.allowed_key' => 'required_if:custom_properties|string',
]);
Migration Conflicts:
custom_properties column already exists, migrations may fail. Use:
Schema::table('table', function (Blueprint $table) {
if (!$table->hasColumn('custom_properties')) {
$table->json('custom_properties')->nullable()->after('updated_at');
}
});
Serialization Issues:
toArray() converts custom_properties to a string. Override if needed:
public function toArray()
{
$array = parent::toArray();
$array['custom_properties'] = json_decode($array['custom_properties'], true);
return $array;
}
Inspect Raw JSON:
Dump the raw custom_properties column to debug encoding issues:
dd($model->getRawOriginal('custom_properties'));
Check for Null Values:
Ensure custom_properties is never null when accessing:
$properties = json_decode($model->custom_properties ?? '{}', true);
Validate JSON Syntax:
Use json_last_error() to catch malformed JSON:
$json = $model->custom_properties;
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception("Invalid JSON in custom_properties: " . json_last_error_msg());
}
Custom Storage Backend: Override the trait’s storage logic to use Redis or a cache layer:
public function getCustomProperty($key)
{
$cacheKey = "model_{$this->id}_{$key}";
return Cache::get($cacheKey) ?? parent::getCustomProperty($key);
}
Encryption: Encrypt sensitive custom properties:
public function setCustomProperty($key, $value)
{
if (in_array($key, ['api_token', 'credit_card'])) {
$value = encrypt($value);
}
$this->custom_properties[$key] = $value;
return $this;
}
Event Triggers: Dispatch events when custom properties change:
public function setCustomProperty($key, $value)
{
$oldValue = $this->getCustomProperty($key);
parent::setCustomProperty($key, $value);
event(new CustomPropertyUpdated($this, $key, $oldValue, $value));
}
Default Values: Add defaults for frequently used properties:
protected static function bootHasCustomProperties()
{
static::created(function ($model) {
$model->setCustomProperty('created_via', 'api')->save();
});
}
Query Builder Extensions: Add scopes for filtering by custom properties:
public function scopeWithPreference($query, $key, $value)
{
return $query->where(function ($q) use ($key, $value) {
$q->whereNotNull('custom_properties')
->whereRaw("JSON_EXTRACT(custom_properties, '$.{$key}') = ?", [$value]);
});
}
How can I help you explore Laravel packages today?