How to serialize?

<?phpusePrewk\Snapper;// Define a recipe defining the fields and their references$r=newSnapper\Recipe;$recipe= ["parents"=>$r->primary("id") // Primary key at field "id"->ingredients(["name"=>$r->value(), // Field "name" is just a value// Field "favorite_child" has a circular dependency to the "children" table"favorite_child"=>$r->circular(// Define the relationship and values considered "no relationship"$r->ref("children")->optional(null),// Fallback to that value until the circular relationship can be resolved$r->raw(null) ), ]),"children"=>$r->primary("id") // Primary key at field "id"->ingredients(["parent_id"=>$r->ref("parents"), // Field "parent_id" is referencing the "parents" table"description"=>$r->value() // Field "description" is just a value ])];// Create a serializer$serializer=newSnapper\Serializer(newSnapper\Sorter,newSnapper\Serializer\SerializationBookKeeper,$recipe);// Feed the serializer with database rows $serializer->add("parents", ["id"=>1,"name"=>"The parent","favorite_child"=>2]);$serializer->add("children", ["id"=>1,"parent_id"=>1,"description"=>"I'm child A"]);$serializer->add("children", ["id"=>2,"parent_id"=>1,"description"=>"I'm child B"]);// Serialize into a snapshot$serialization=$serializer->compile()->getOps();

Raw

Ref

The field is a foreign key that references another table. The optional method takes arguments that, when equal to the encountered field value, is considered non-references and are just passed through like Value.

On string and integer refs inside the JSON

The library is built for the normal scenario of integer keys (AUTO_INCREMENT), but when serializing references they will be converted to UUIDS (v4). They are strings that look like this: a0ff60f5-87fe-4d4e-855b-8993f1c3b065.

This poses a problem in JSON when..

{ "foo_id": 123 }

..gets serialized into..

{ "foo_id": "a0ff60f5-87fe-4d4e-855b-8993f1c3b065" }

..and back into a integer key:

{ "foo_id": "456" }

"456" is not strictly equal to 456. Therefore, the deserialization logic is as follows:

If the id returned from the inserter is numeric then all "UUID" will be replaced with INSERT_ID

All UUID will be replaced with INSERT_ID

Circular

If two of your tables contain circular references to each other, wrap one of the references with a Circular ingredient. Specify a valid fallback as optional value and specify that value as a fallback Raw ingredient.

The resulting serialization will start with an INSERT op containing the fallback value, and end with an UPDATE op with the real reference.

Events

onDeps

<?php$deserializer->onDeps("foos", function(string$dependeeType, $dependeeId, $dependencyId) {// Every time a dependency of type "foos" has been deserialized, this closure will be called});

Returning void is acceptable, but the serialization will fail if later rows depend on the skipped primary keys

To return the primary keys, return an array of the same length as $rows, everything else is invalid

The batch grouping logic considers one of the following conditions as "start a new batch of operations":

A new row type (table) is encountered

The row is dependent on the primary key of another row earlier in the same batch

The exact number of, or names of, fields has changed from one row to the next

Id manifest

The result of a compilation ($serializer->compile()) is a Serialization. It has two methods:

getOps() - Get an array representing the sequence of operations (the actual "serialization" if you will)

getIdManifest() - Get a dictionary mapping the internal uuids in the serialization to your given db ids, grouped by type (table name)

Why?

The library is useful for providing snapshot functionality to multi-tenant services where a user owns one or more complex sets of data with complex internal relationships.

Howtos

I don't want to create table X, it already exists

The deserializer doesn't care about the inserter closure's internal logic, it only requires you to return the primary key:

$somethingId = 123; // Predetermined
$deserializer->setInserter("something", function(array $row) use ($somethingId) {
// Maybe you don't want to do anything to the database here or maybe
// you want to do an UPDATE instead of an INSERT - up to you
return $somethingId;
});

I need row metadata to be serialized

Just use $recipe->value() in the recipe and feed whatever fields you want to the Serializer and it'll end up in the snapshot.

When deserializing, pick and choose which fields to insert into the database in your inserter.

I have composite keys

Unsupported at the moment, a workaround is adding a unique id and pretending that's the primary key.