Download the PHP package ssntpl/data-fields without Composer
On this page you can find all versions of the php package ssntpl/data-fields. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download ssntpl/data-fields
More information about ssntpl/data-fields
Files in ssntpl/data-fields
Package data-fields
Short Description Laravel Data Fields.
License
Homepage https://github.com/ssntpl/data-fields
Informations about the package data-fields
Laravel Data Fields
Typed, dynamic data fields for Eloquent models. Attach admin-defined fields
to any model and read them as typed PHP values — booleans cast to bool,
numbers to float, dates to Carbon, files to your File model, and so on.
Two parallel storage modes:
- Cast mode — one JSON column on the owner model holds a self-describing
DataFielddocument (schema + values together). Ergonomic typed object access, atomic per-column writes, multiple "forms" per model. - Row mode — one row per field in a polymorphic
data_fieldstable. Cross-row queries by key/value, per-field granular updates, indexable.
Pick whichever fits the column you're working with — a single model can use both modes on different attributes.
Table of contents
- Installation
- Choosing a mode
- Cast mode
- Setup
- Defining and writing
- Reading values
- Mutating values
- Adding and removing fields at runtime
- Containers and nesting
- Validation
- Default values
- Conditional visibility (
visible_if) - Factories
- Iteration and array access
- Row mode
- Setup
- Working with rows
- Single-key helpers
- Custom row model
- Field types reference
- File and files types
- Configuration
- API reference
- Common patterns
- Migrating from 0.2.x
- Testing
- Security
- Changelog
- Contributing
- Credits
- License
Installation
Install via Composer:
Publish the config (optional — only needed if you want to override defaults):
Row mode
If you're using row mode (the data_fields table), publish and run the
migration:
If you'd rather skip the publish step, set data-fields.auto_load_migrations
to true in the published config — the service provider will load the
package's migration directly.
Cast mode
Cast mode doesn't ship a table — the consumer adds a JSON column to whichever model they want:
Requirements
- PHP 8.2+
- Laravel 11.0 or 12.0
ssntpl/laravel-files^0.1 (required — used by thefile/filesfield types)
Choosing a mode
Both modes share the same conceptual shape (key, type, value, label, description, validations, meta) and the same casting layer. They differ in where the data lives and which read/write patterns they optimise for.
| Concern | Cast mode | Row mode |
|---|---|---|
| Storage | One JSON column on the owner model | One row per field in data_fields |
| Read one field | Single column read | One DB query (or via eager load) |
| Read all fields | Single column read | One query (eager-loadable) |
| Update one field | Whole column rewrite | One row update |
| Query across rows by field | Hard (DB-specific JSON path queries) | Native SQL |
| Multiple distinct "forms" per record | Natural (one column per form) | Needs a discriminator column |
Containers (step / section / group) |
First-class | Not supported |
| Concurrent writes to different fields | Last-write-wins on the column | Field-independent |
| DB-level constraints (FK, unique) | None inside JSON | Native SQL |
| External (non-PHP) consumers | Must understand JSON shape | Trivial — normalised rows |
| Best fit | Settings, preferences, structured submissions | EAV, searchable attributes, sparse data |
Rule of thumb: start with cast mode. Reach for row mode when you have a real need for cross-row queries by field value, BI/reporting tooling, or field-level DB constraints.
Cast mode
A single JSON column on the owner model holds the entire field document.
The Laravel cast hydrates that JSON into a DataField object you can read,
mutate, and persist with ordinary $model->save() semantics.
Setup
-
Add a
jsoncolumn to your model's table: - Add the cast to your model — write
DataField::classdirectly; the package resolves to its internal cast via Laravel'sCastableinterface:
That's it. Each cast column can hold an entire form's worth of fields. Attach as many as you need:
Defining and writing
A column casts to a single DataField object. The simplest form is a
container holding leaf fields:
You can also assign a plain array — the cast coerces it to a DataField for
you:
A null column casts to null. Assigning null clears the column:
There's no implicit default — initialise the document explicitly via a factory or by assignment. This matches Laravel's nullable-cast contract.
Reading values
Property access returns the matching DataField; chain ->value to read
the typed value:
For nested structures, chain through container children:
Or use the explicit dotted-path lookup:
If a key doesn't exist, property access returns null:
Mutating values
The DataField object is mutable. Mutations persist when you call
$model->save():
Dirty tracking works through Laravel's standard cast-re-serialisation:
isDirty('preferences') returns true after any in-memory change, and
save() writes the new JSON when it differs from the original.
You can also replace an entire field by assignment:
Adding and removing fields at runtime
Containers support addField and removeField:
Adding a duplicate sibling key throws \InvalidArgumentException immediately
— structural validation runs at the point of authorship, not at save.
Containers and nesting
Three container types are available — they're semantically equivalent inside the package; pick whichever your UI vocabulary prefers:
| Type | Typical use |
|---|---|
section |
Logical grouping of related fields |
step |
A wizard/multi-step form pane |
group |
An inline cluster, smaller than a section |
Containers nest arbitrarily:
Access nested leaves via property chains or dotted-path lookup:
Validation
Each leaf can carry inline Laravel validation rules. Run them with
validate():
Notes:
validate()is a guard, not a filter — on success it returns the values unchanged. If you want Laravel's "only validated keys" shape, callValidator::make(...)->validated()directly.- For
select_single/select_multiplewith anoptionslist, anRule::in(...)rule is auto-derived so out-of-options values fail validation without you having to repeat the option keys invalidations. - Hidden fields (resolved via
visible_if) are skipped — their stored values are preserved on read and not deleted on write.
To validate on save, call validate() inside your own saving observer:
Default values
A leaf's default is returned by ->value when no value has been set.
Explicit null overrides the default — callers chose to clear it.
Conditional visibility (visible_if)
Mark a field as visible only when a sibling has a specific value. Currently equality-based; multiple keys mean AND.
Hidden fields skip validation; their stored values are preserved.
Factories
Three container shortcuts plus a generic leaf and a recursive fromArray:
Examples:
Iteration and array access
Containers iterate over their items:
ArrayAccess works by both index and key:
Row mode
Use row mode when you need cross-row queries by field key/value (e.g.
"find all users with plan = 'pro'"), per-field granular updates, BI/
reporting tools that expect normalised data, or DB-level constraints on
individual fields.
Row mode setup
Add the trait to your model:
That's it — the trait wires up the polymorphic fields() relationship
against the package's data_fields table.
Working with rows
Rows store typed values: $row->value returns the cast PHP type
(bool, float, Carbon, File, etc.) based on the row's type.
Single-key helpers
The trait provides two convenience methods for working with a single field by key:
setFieldValue creates the row if absent and updates if present. The third
argument accepts a FieldType enum or a raw string; it defaults to text
on create and preserves the existing type on update.
Custom row model
Subclass DataRow to add custom attributes, accessors, or methods:
Point the config at your subclass:
The fields() relationship will now hydrate as CustomDataRow instances.
Validation rules in row mode
Storing validations rules alongside the field works — but note that row
mode does not auto-run those rules. The rules are persisted as field
metadata; running them is the consuming application's job (typically before
calling create() / update()):
If you want auto-running rules, use cast mode — $df->validate() runs them.
Field types reference
The package supports 12 leaf types and 3 container types. All available as
both literal strings and as cases on the FieldType PHP enum.
Leaves
| Type | Stored as | Read returns |
|---|---|---|
bool |
'1' / '0' (row), native bool (cast) |
bool |
text |
string |
string |
number |
string (row), float (cast) |
float |
select_single |
string |
string |
select_multiple |
JSON array of strings | array<string> |
date |
'YYYY-MM-DD' string |
string |
time |
'HH:MM:SS' string |
string |
datetime |
'YYYY-MM-DD HH:MM:SS' string |
\Carbon\Carbon |
file |
{model_type, model_id} JSON |
\Ssntpl\LaravelFiles\Models\File or null |
files |
array of {model_type, model_id} |
array<File> (always a list) |
json |
JSON | decoded array |
array |
JSON list | array |
Lenient string decoding on read — for json, array, and
select_multiple, the read path will json_decode a stored string if it
encounters one (recovery path for double-encoded or migrated legacy data).
If you want to store an opaque string verbatim, use the text type
instead; json is for structured data and writes always store the native
PHP structure.
Containers (cast mode only)
| Type | Notes |
|---|---|
step |
A step/page in a wizard form |
section |
A logical grouping of related fields |
group |
An inline cluster, smaller than a section |
All three are semantically equivalent inside the package — the choice is a hint to your UI layer.
The FieldType enum
For type safety in your code, use Ssntpl\DataFields\Support\FieldType:
The enum is the in-memory type; JSON storage and the row-mode type column
stay as strings.
File and files types
The file and files types store a reference to a File model from the
ssntpl/laravel-files package.
Pass a File instance, the package handles the rest:
For files (multiple), pass an array — even a single File is wrapped to a
list:
An empty files field round-trips as [], not null.
Configuration
The published config (config/data-fields.php) is small:
API reference
Ssntpl\DataFields\Support\DataField (cast value object)
| Method | Notes |
|---|---|
new DataField($node) / fromArray($node) |
Construct from a node array; throws on malformed input |
static leaf(FieldType\|string $type, $value, array $extra = []) |
Leaf factory |
static section(?string $key, array $items, array $extra = []) |
Section container factory |
static step(?string $key, array $items, array $extra = []) |
Step container factory |
static group(?string $key, array $items, array $extra = []) |
Group container factory |
isLeaf() / isContainer() |
Type-based predicates |
isVisible(?array $siblingValues = null) |
Resolves visible_if |
getValue() / setValue($v) |
Read/write the leaf value (honours default) |
$df->{$childKey} |
Property access — returns child DataField or null |
$df->dataField($dottedPath) |
Explicit path lookup, deep |
$df->addField($node) / $df->removeField($key) |
Container-only mutation |
Iterable, ArrayAccess, Countable |
Walk and index children |
validate() |
Runs Laravel rules; throws ValidationException |
toArray() / jsonSerialize() |
Storage-form serialisation |
Ssntpl\DataFields\Models\DataRow (row-mode Eloquent model)
| Method | Notes |
|---|---|
owner() |
Polymorphic morphTo |
fields() |
Children via self-polymorphism (rare in practice) |
duplicate() / duplicateInto($owner) |
Clone with re-parented children |
delete() |
Transactional cascade to files + children |
Ssntpl\DataFields\Concerns\HasDataFields (row-mode trait)
| Method | Notes |
|---|---|
fields() |
morphMany to DataRow |
getFieldValue($key) |
Cast value or null |
setFieldValue($key, $value, $type = null) |
Upsert by key |
Ssntpl\DataFields\Support\FieldType (enum)
| Method | Notes |
|---|---|
isLeaf() / isContainer() |
Per-case predicates |
static coerce($value) |
Accept enum or string; throws on unknown |
static leaves() / static containers() |
Enumerate by kind |
Common patterns
Per-environment defaults
Use the model's creating event to seed a default document:
Validating on save
Hook into saving:
Schema defined on a parent, values stored per-child
When many child records share one schema (e.g., template + responses), keep the schema on the parent and store only the merged document on the child. The cast handles both shapes identically — the schema lives wherever you choose.
Iterating leaves across containers
dataField('a.b.c') looks up by full path. To walk every leaf:
Migrating from 0.2.x
The 0.4.x release is a breaking redesign. If you were on 0.2.x with the
HasDataFieldsJson trait:
Before (0.2.x):
After (0.4.x):
For row-mode consumers, the rename DataField → DataRow and trait
namespace Traits\ → Concerns\ are the main changes:
Type-string constants are gone — use either the raw string ('bool',
'text', …) or the FieldType enum cases (FieldType::Bool->value, …).
See CHANGELOG.md for the complete list of changes and rationales.
Testing
The test suite runs against SQLite in-memory using Orchestra Testbench.
Security
The file / files types store a reference to a row in
ssntpl/laravel-files's files table as {model_type, model_id} JSON. On
read, the cast resolves model_type through Laravel's morph map
(Illuminate\Database\Eloquent\Relations\Relation::morphMap()) and rejects
any class that is not Ssntpl\LaravelFiles\Models\File or a subclass — so
a tampered value cannot autoload arbitrary classes. If you have subclassed
the File model, ensure your subclass extends
Ssntpl\LaravelFiles\Models\File.
If you discover a security vulnerability, please email
[email protected] instead of opening a public issue.
Changelog
See CHANGELOG.md for a detailed record of changes per release.
Contributing
Issues and pull requests are welcome at github.com/ssntpl/data-fields.
When sending a PR:
- Fork the repo and create a feature branch.
- Add tests covering the change.
- Run
composer testand make sure everything is green. - Update
CHANGELOG.mdunder the[Unreleased]section.
Credits
- Abhishek Sharma — https://ssntpl.com
- All contributors
License
The MIT License (MIT). See LICENSE.md.