Loading…

PDODocument
Advanced Usage: Custom Serialization

JSON documents are sent to and received from both PostgreSQL and SQLite as strings; the translation to and from PHP classes uses the built-in json_encode and json_decode functions by default. These do an acceptable job, particularly if you do not care much about how the document is represented. If you want more control, though, there is a library that can help.

Using square/pjson to Control Serialization

The square/pjson library provides an attribute, a trait, and a few interfaces. If these interfaces are implemented on your classes, PDODocument will use them instead of the standard serialization functions. This will not be an exhaustive tutorial of the library, but a few high points:

An example will help here; we will demonstrate all of the above.

use Square\Pjson\{Json, JsonDataSerializable, JsonSerialize};

// A strongly-typed ID for our things; it is stored as a string, but wrapped in this class
class ThingId implements JsonDataSerializable
{
    public string $value = '';
    
    public function __construct(string $value = '')
    {
        $this->value = $value;
    }

    public function toJsonData(): string
    {
        return $this->value;
    }
    // $jd = JSON data
    public static function fromJsonData(mixed $jd, array|string $path = []): static
    {
        return new static($jd);
    }
}

// A thing; note the JsonSerialize trait
class Thing
{
    use JsonSerialize;
    
    #[Json]
    public ThingId $id;
    
    #[Json]
    public string $name = '';
    
    // If the property is null, it will not be part of the JSON document
    #[Json(omit_empty: true)]
    public ?string $notes = null;
    
    public function __construct()
    {
        $this->id = new ThingId();
    }
}

class BoxOfThings
{
    use JsonSerialize;
    
    // Customize the JSON key for this field
    #[Json('box_number')]
    public int $boxNumber = 0;
    
    // Specify the type of this array
    #[Json(type: Thing::class)]
    public array $things = [];
}

With these declarations, the following code…

$thing1 = new Thing();
$thing1->id = new ThingId('one');
$thing1->name = 'The First Thing';

$thing2 = new Thing();
$thing2->id = new ThingId('dos');
$thing2->name = 'Alternate';
$thing2->notes = 'spare';

$box = new BoxOfThings();
$box->boxNumber = 6;
$box->things = [$thing1, $thing2];

echo $box->toJsonString();

...will produce this JSON: (manually pretty-printed below)

{
  "box_number": 6,
  "things": [
    {
      "id": "one",
      "name": "The First Thing"
    },
    {
      "id": "dos",
      "name": "Alternate",
      "notes": "spare"
    }
  ]
}

Deserializing that tree, we get:

$box2 = BoxOfThings::fromJsonString('...');
var_dump($box2);
object(BoxOfThings)#13 (2) {
  ["boxNumber"]=>
  int(6)
  ["things"]=>
  array(2) {
    [0]=>
    object(Thing)#25 (3) {
      ["id"]=>
      object(ThingId)#29 (1) {
        ["value"]=>
        string(3) "one"
      }
      ["name"]=>
      string(15) "The First Thing"
      ["notes"]=>
      NULL
    }
    [1]=>
    object(Thing)#28 (3) {
      ["id"]=>
      object(ThingId)#31 (1) {
        ["value"]=>
        string(3) "dos"
      }
      ["name"]=>
      string(9) "Alternate"
      ["notes"]=>
      string(5) "spare"
    }
  }
}

Our round-trip was successful!

Any object passed as a document will use square/pjson serialization if it is defined. When passing an array as a document, if that array only has one value, and that value implements square/pjson serialization, it will be used; otherwise, arrays will use the standard json_encode/json_decode pairs. In practice, the ability to call Patch::by* with an array of fields to be updated may not give the results you would expect; using Document::update will replace the entire document, but it will use the square/pjson serialization.

Uses for Custom Serialization

« Back to Advanced Usage