›
›
›
  1. docs
  2. ›
  3. byrcsc/laravel-custom-fields
1.x
Browse documentationOpenClose

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Defining fields
  • Field types and storage
  • Tenant scoping
  • Reading and writing values
  • Validation
  • Filtering records

Operations

  • Changing and deleting fields
  • Queries and eager loading
  • Events and listeners

Reference

  • Configuration
  • Console commands
  • Exceptions
  • Testing
  • Troubleshooting

Getting started

  • Introduction
  • Installation and setup
  • Quick start

Core concepts

  • Defining fields
  • Field types and storage
  • Tenant scoping
  • Reading and writing values
  • Validation
  • Filtering records

Operations

  • Changing and deleting fields
  • Queries and eager loading
  • Events and listeners

Reference

  • Configuration
  • Console commands
  • Exceptions
  • Testing
  • Troubleshooting

byrcsc/laravel-custom-fields · 1.x

Defining fields.

Create field definitions through the CustomFields facade, which checks every attribute before it writes a row.

Definitions are readable through the CustomFieldDefinition model and writable only through the CustomFields facade. The model guards every attribute against mass assignment, so CustomFieldDefinition::create([...]) throws a MassAssignmentException rather than writing a row that skipped the checks below.

That leaves one place where key format, option lists, rule names, and duplicate keys are checked, instead of at each call site that happens to remember.

use ByRcsc\LaravelCustomFields\Enums\FieldType;
use ByRcsc\LaravelCustomFields\Facades\CustomFields;

$definition = CustomFields::define(Customer::class, [
    'key' => 'account_tier',
    'type' => FieldType::Select,
    'options' => ['standard', 'premium'],
    'label' => 'Account tier',
    'description' => 'The service level assigned to this customer.',
    'default' => 'standard',
    'rules' => ['required' => true],
    'section' => 'Account details',
    'sort_order' => 10,
    'tenant' => 'acme',
]);

The first argument is a model class name or a model instance. Either way the definition is stored against the model's morph class, so a morph map alias is respected. A class that is not an Eloquent model throws.

Attributes

Only key and type are required.

AttributeTypeDefaultWhat it does
keystringrequiredHow every later call names the field
typeFieldType or its stringrequiredWhich column the value is stored in and how it is cast
labelstringbuilt from keyThe human name validation messages use
descriptionstringnullStored and returned; the package attaches no behaviour to it
defaultmixednullWhat a record that never set the field reads
optionsarray<string>nullThe allowed values, for select and multi_select
rulesarray<string, mixed>nullPer-field validation: required, min, max, regex, unique
sectionstringnullMetadata for grouping fields on a screen
sort_orderint0Orders the definitions collection, ties broken by key
tenantstring or nullcurrent tenantWhich tenant owns the field

Anything else is rejected rather than ignored:

Unknown custom field definition attribute(s): defualt. Accepted: key, type,
label, description, default, options, rules, section, sort_order, tenant.

A misspelled attribute that silently did nothing would be found much later than one that throws on the line that wrote it. The same reasoning runs through the rest of this page: everything define() cannot honour throws InvalidDefinitionException immediately, naming the field and saying what would have been acceptable.

Keys

A key is lowercase, starts with a letter, and continues with lowercase letters, digits, or underscores, up to 64 characters. account_tier, due_date, and address_line_2 are keys; Account tier, due-date, and 2nd_line are not.

The pattern is narrow on purpose. A key appears in generated validation rule names, in query scope arguments, and as an array key in what the customFields accessor returns, and the excluded characters are the ones that would need quoting or escaping in at least one of those.

Labels

label is what a validation failure calls the field. Leave it out and the package builds one from the key by replacing underscores with spaces and capitalising the first letter, so annual_revenue becomes Annual revenue.

Defaults

A default is what a record reads when it has never set the field. It is stored as JSON on the definition and cast through the same path a stored value uses, so a default and a stored value of the same field come back as the same PHP type.

CustomFields::define(Customer::class, [
    'key' => 'annual_revenue',
    'type' => FieldType::Number,
    'default' => 100000,
]);

Defaults are virtual. They are resolved on read and never written as value rows, which has one consequence worth knowing before you rely on it: changing a definition's default changes what every unset record returns, retroactively. Records that did set the field are untouched. See reading and writing values.

Options

select and multi_select need a non-empty list of strings, and every other type refuses one:

CustomFields::define(Customer::class, [
    'key' => 'service_interests',
    'type' => FieldType::MultiSelect,
    'options' => ['implementation', 'training', 'support'],
]);

Duplicates in the list are dropped, keeping the first occurrence. A list holding anything that is not a non-empty string throws.

Options constrain writes, not reads. Removing an option later does not rewrite the records that already hold it, and they keep reading it back. See changing and deleting fields.

Rules

rules is a map of rule name to setting, and the five names are required, min, max, regex, and unique:

'rules' => ['required' => true, 'min' => 1, 'max' => 120],

Every name and every setting shape is checked here, so a field cannot be defined with a rule that will never run. ['unqiue' => true] throws for the name and ['min' => '3'] throws for the setting, because a field that looks validated and is not is worse than one nobody put rules on. What each rule generates, and what unique does and does not guarantee, is covered in validation.

Section and sort order

section and sort_order are metadata. The package stores them and returns them; grouping fields into panels and laying them out on a screen is your application's job.

sort_order does one thing: it orders the collection CustomFields::definitions() and the customFields accessor return, ascending, with ties broken by key. It takes a whole number, or a numeric string, and refuses anything else rather than coercing it. A sort_order of 'first' quietly becoming 0 would put a field first in a list you meant to put it last in.

Reading definitions back

CustomFields::definitions(Customer::class);

Returns an Illuminate\Support\Collection of CustomFieldDefinition models keyed by field key, holding every field the current tenant can see: its own, plus the global ones it has not overridden. It is a plain collection rather than an Eloquent one, so only() and except() filter by the field keys it is keyed by.

CustomFields::definition(Customer::class, 'account_tier');   // or null

Returns one definition by key, and null when the current tenant has no such field. This is the only read path that answers null instead of throwing, which makes it the way to ask whether a field exists.

Definitions are also queryable like any other model, which is what an admin screen listing them wants:

use ByRcsc\LaravelCustomFields\Models\CustomFieldDefinition;

CustomFieldDefinition::query()
    ->where('model_type', (new Customer)->getMorphClass())
    ->whereNull('tenant')
    ->orderBy('sort_order')
    ->get();

Note that a raw query is not tenant-scoped and does not apply the override rule. CustomFields::definitions() is what answers "what does this tenant see".

Updating a definition

There is no update() on the facade. Change a definition through its model:

$definition = CustomFields::definition(Customer::class, 'account_tier');

$definition->label = 'Customer tier';
$definition->save();

Attributes are guarded, so assign them individually rather than through update() or fill(). Values point at the definition's id rather than its key, so renaming a field keeps every value attached to it.

Two changes carry consequences. A type change is refused outright while values exist. A tenant change moves who can see the field, and any value written under a tenant that can no longer see it stays in the table without being readable through the trait. See changing and deleting fields.

What to read next

  • Field types and storage for the eleven types and what each returns.
  • Validation for what each rule generates.
  • Tenant scoping for tenant and the override rule.
PreviousQuick startNextField types and storage
View source

On this page

  1. Attributes
  2. Keys
  3. Labels
  4. Defaults
  5. Options
  6. Rules
  7. Section and sort order
  8. Reading definitions back
  9. Updating a definition
  10. What to read next