onramplab/laravel-custom-fields
Installation:
composer require onramplab/laravel-custom-fields
php artisan vendor:publish --provider="OnrampLab\CustomFields\CustomFieldsServiceProvider" --tag="migrations"
php artisan migrate
Run migrations to set up the custom_fields and custom_field_values tables.
Define a Model:
Use the HasCustomFields trait in your Eloquent model:
use OnrampLab\CustomFields\Traits\HasCustomFields;
class Product extends Model
{
use HasCustomFields;
}
First Use Case:
Add a custom field dynamically to a Product instance:
$product = Product::first();
$product->addCustomField('color', 'text', 'Red');
$product->save();
Retrieve the value:
$color = $product->getCustomField('color');
database/migrations/ for the schema structure.HasCustomFields and CustomFieldable in the package source.CustomFieldsServiceProvider for bootstrapping logic.CustomField and CustomFieldValue classes for core functionality.Dynamic Field Management:
$model->addCustomField('field_key', 'field_type', $value);
Types: text, integer, float, datetime, select, boolean, rich_text.$model->addCustomFields([
'price' => ['type' => 'float', 'value' => 19.99],
'is_featured' => ['type' => 'boolean', 'value' => true],
]);
Polymorphic Associations:
HasCustomFields:
$user = User::find(1);
$user->addCustomField('preference', 'text', 'Dark Mode');
$fields = CustomField::where('field_key', 'preference')->get();
Validation & Defaults:
config/custom-fields.php:
'defaults' => [
'field_types' => ['text', 'integer', 'select'],
],
$model->validateCustomFields([
'field_key' => 'required|string',
]);
Querying Custom Fields:
$products = Product::whereHasCustomField('color', 'Red')->get();
$fields = $model->getCustomFields();
return $model->append(['custom_fields']);
CustomFieldCreated, CustomFieldUpdated, etc., via:
event(new CustomFieldCreated($model, $field));
Cache::remember("model_{$model->id}_fields", now()->addHours(1), fn() => $model->getCustomFields());
Polymorphic Conflicts:
custom_fields table has a unique field_key per model type (e.g., products_color vs. users_preference). Overlapping keys may cause silent failures.model_type, field_key) or prefix keys with the model class name.Field Type Mismatches:
string in an integer field may corrupt data. Validate types strictly:
if (!$model->validateCustomFieldType('age', 'integer')) {
throw new \InvalidArgumentException("Field 'age' must be an integer.");
}
Mass Assignment Risks:
$fillable. Explicitly whitelist safe fields:
protected $customFieldWhitelist = ['metadata.*', 'settings.*'];
Performance with Large Datasets:
getCustomFields() in loops. Use eager loading:
$models = Model::withCustomFields()->get();
Missing Fields:
custom_fields table has entries for your model. Run:
php artisan tinker
>>> \OnrampLab\CustomFields\CustomField::where('field_key', 'missing_field')->get();
model_type column matches your model’s class name (e.g., App\Models\Product).Serialization Issues:
json type or encode manually:
$model->addCustomField('tags', 'json', json_encode(['tag1', 'tag2']));
$tags = json_decode($model->getCustomField('tags'), true);
Custom Field Types:
OnrampLab\CustomFields\FieldTypes\FieldType to add support for new types (e.g., image, file):
class ImageFieldType extends FieldType
{
public function validate($value) { /* ... */ }
public function serialize($value) { /* ... */ }
}
config/custom-fields.php:
'field_types' => [
'image' => \App\FieldTypes\ImageFieldType::class,
],
Field Value Accessors:
getCustomFieldAttribute in your model:
public function getCustomFieldAttribute($key)
{
$value = parent::getCustomFieldAttribute($key);
return $key === 'price' ? '$' . $value : $value;
}
Scopes:
CustomField:
class CustomFieldScope
{
public function scopeActive($query)
{
return $query->where('is_active', true);
}
}
CustomFieldsServiceProvider.Migration Hooks:
CustomFieldCreating events to enforce naming conventions:
CustomField::creating(function ($field) {
$field->field_key = strtolower($field->field_key);
});
config/custom-fields.php:
'defaults' => [
'field_types' => ['text', 'integer', 'select'],
'validation_rules' => [
'text' => 'nullable|string|max:255',
'select' => 'required|in:option1,option2',
],
],
'polymorphic_key' => 'customizable_key', // Default: 'model_type'
updateCustomField for atomic updates:
$model->updateCustomField('price', 29.99);
description column or use a separate custom_field_metadata table.use SoftDeletes;
class CustomField extends Model { use SoftDeletes; }
Update migrations accordingly.How can I help you explore Laravel packages today?