Saving Data
The standard Table::save() / saveMany() / saveOrFail() flow applies unchanged. What differs is that the JsonField.JsonField behavior (attached to your Table) hooks Model.beforeSave and routes JSON-embed changes down to the storage column. Two strategies apply, chosen per entity state:
- Whole column write — a new entity, or one whose JSON storage column is itself dirty, is written as a whole column. Each embed is exported, per-path
toDatabasecasters are applied, and unrelated JSON keys are preserved. - Atomic path update — an existing entity with only dirty nested-embed fields is persisted with engine-native
set()expressions scoped to the changed path, leaving the rest of the column untouched.
Everything below describes only these JSON-specific behaviors; for marshalling, validation, associated options, beforeMarshal/afterMarshal, mass assignment, and saveOrFail/saveMany/findOrCreate, see the standard CakePHP Saving Data cookbook.
Saving Embedded Entities
Embedded entities are part of the parent's JSON column, so you never save them directly — you modify the embed property (or a JSON path) on the root entity and save the root:
$article = $articles->get(1);
$article->profile->title = 'Updated';
$articles->save($article); // atomic set on data->profile->titlePath-string access gives the same result:
$article->set('data->profile->title', 'Updated');
$articles->save($article);A new entity (or one whose data column is dirty) writes the whole column instead.
Adding and Removing Embedded Children
For a JsonFieldHasMany embed, append to the list property and save:
$article->chapters[] = new ArticleChapter(['name' => 'Intro']);
$articles->save($article);Remove a child through its delete() method — it uses the embeddedParent back-pointer to drop the element from the parent's list and mark the embed property dirty:
$article->chapters[0]->delete();
$articles->save($article);You can also drop a whole embed with the path API:
$article->unset('data->profile');
$articles->save($article);Embedded validators
A JsonFieldEmbed association can carry an embeddedValidator callable, invoked for each child before persistence. Configure it on the association object built by JsonFieldAssociationBuilder (see Associations).
List save strategy
JsonFieldHasMany (and #[JsonEmbed] with type: 'many') honors a saveStrategy: replace (default) rewrites the whole list; append keeps persisted rows and merges the in-memory entities in by idField.
Saving JSON-FK References
A JsonFieldBelongsTo / JsonFieldBelongsToMany reference stores a foreign key inside JSON. Assign the target entity (or entities) — or a raw key / key list — to the reference property and save the root; the behavior writes the key(s) into the declared JSON path:
$article->author = $users->get(3); // or $article->set('data->author_id', 3);
$articles->save($article); // writes data->author_id = 3
$article->tags = [$tags->get(1), $tags->get(3)];
$articles->save($article); // writes data->tag_ids = [1, 3]To unlink, clear the foreign key and save:
$article->set('data->author_id', null);
$articles->save($article);There is no link() / unlink() for JSON references — the foreign key is the link.
Bulk Updates
updateAll() and the lower-level updateQuery() bypass the behavior, so they do not apply per-path casting or atomic JSON writes. To issue an atomic path update in bulk, build the expression with the resolved engine adapter:
use Crustum\JsonField\Path\JsonPathParser;
$path = JsonPathParser::parse('data->profile->title');
$expr = $articles->jsonFieldEngine()->set($path, 'Updated');
$articles->updateQuery()
->set(['data' => $expr])
->where(['id' => 1])
->execute();For most cases, load the entities and use save() so the behavior handles casting and atomic writes.