JMS Serializer. Create 2 ways of model's serialization that has "one-to-many" relationship

Victor Lopez picture Victor Lopez · Mar 3, 2014 · Viewed 7.5k times · Source

I'm using JMS Serializer for PHP project and had stumbled upon one problem.

Look at the code

<?php
use JMS\Serializer\Annotation as Serializer;

/**
 * @Serializer\ExclusionPolicy("all")
 */
class Order
{
    /**
     * @var int
     * @Serializer\Type("integer")
     * @Serializer\Expose
     */
    private $id;

    /**
     * @var Product[]
     * @Serializer\Type("array<Product>")
     * @Serializer\Expose
     */
    private $products;

    /**
     * @var float
     * @Serializer\Type("float")
     * @Serializer\Expose
     */
    private $total;

    private $someInternalProperty;

    function __construct($products)
    {
        $this->id = rand(0, 100);
        $this->products = $products;
        $this->total = rand(100, 1000);
        $this->someInternalProperty = 'Flag';
    }
}

/**
 * @Serializer\ExclusionPolicy("all")
 */
class Product
{
    /**
     * @var int
     * @Serializer\Type("integer")
     * @Serializer\Expose
     */
    private $id;

    /**
     * @var string
     * @Serializer\Type("string")
     * @Serializer\Expose
     */
    private $name;

    private $price;

    private $description;

    function __construct($id, $name, $price, $description)
    {
        $this->id = $id;
        $this->name = $name;
        $this->price = $price;
        $this->description = $description;
    }
}

$order = new Order([
    new Product(
        1,
        'Banana',
        10,
        'Yellow'
    ),
    new Product(
        2,
        'Tomato',
        12,
        'Red'
    )
]);

$serializer = \JMS\Serializer\SerializerBuilder::create()
    ->setPropertyNamingStrategy(new \JMS\Serializer\Naming\SerializedNameAnnotationStrategy(new \JMS\Serializer\Naming\IdenticalPropertyNamingStrategy()))
    ->build();

print_r(
    $serializer->serialize(
        $order,
        'json',
        \JMS\Serializer\SerializationContext::create()
            ->setSerializeNull(true)
    )
);

Here I've shown simplified example of my code. I use it to store history of order's changes. Before and after the update I save this serialized model into database. Ok.

And now I want to serialize Product's model with all properties for working on clientside. So first idea I had is to use groups. I need to set for $id and $name properties "Group({'history', 'edit'})" and for all other "Group({'edit'})". Ok it works for product serialization but it corrupts first solution. Now my "order history" stores unnecessary information like $price and $description.

Is there some correct way to specify default group for Product's model that will be used if serialization group not specified implicitly (like in Orders' history example)? Or some other way to have this two types of serialization available without moving groups into Order's model (because in reality there are more than one model that should be refactored in this case).

Answer

Victor Lopez picture Victor Lopez · Mar 4, 2014

Sorry. I found that all works correct if I use a group "Default".

<?php

require_once __DIR__ . '/../../../app/Autoload.php';

use JMS\Serializer\Annotation as Serializer;

/**
 * @Serializer\ExclusionPolicy("all")
 */
class Order
{
    /**
     * @var int
     * @Serializer\Type("integer")
     * @Serializer\Expose
     */
    private $id;

    /**
     * @var Product[]
     * @Serializer\Type("array<Product>")
     * @Serializer\Expose
     */
    private $products;

    /**
     * @var float
     * @Serializer\Type("float")
     * @Serializer\Expose
     */
    private $total;

    private $someInternalProperty;

    function __construct($products)
    {
        $this->id = rand(0, 100);
        $this->products = $products;
        $this->total = rand(100, 1000);
        $this->someInternalProperty = 'Flag';
    }
}

/**
 * @Serializer\ExclusionPolicy("all")
 */
class Product
{
    /**
     * @var int
     * @Serializer\Expose
     * @Serializer\Type("integer")
     * @Serializer\Groups({"Default", "edit"})
     */
    private $id;

    /**
     * @var string
     * @Serializer\Expose
     * @Serializer\Type("string")
     * @Serializer\Groups({"Default", "edit"})
     */
    private $name;

    /**
     * @Serializer\Expose
     * @Serializer\Groups({"edit"})
     */
    private $price;

    /**
     * @Serializer\Expose
     * @Serializer\Groups({"edit"})
     */
    private $description;

    private $hiddenProperty;

    function __construct($id, $name, $price, $description)
    {
        $this->id = $id;
        $this->name = $name;
        $this->price = $price;
        $this->description = $description;
        $this->hiddenProperty = 42;
    }
}

$product1 = new Product(
    1,
    'Banana',
    10,
    'Yellow'
);

$order = new Order([
    $product1,
    new Product(
        2,
        'Tomato',
        12,
        'Red'
    )
]);

$serializer = \JMS\Serializer\SerializerBuilder::create()
    ->setPropertyNamingStrategy(new \JMS\Serializer\Naming\SerializedNameAnnotationStrategy(new \JMS\Serializer\Naming\IdenticalPropertyNamingStrategy()))
    ->build();

print_r([
    $serializer->serialize(
        $order,
        'json',
        \JMS\Serializer\SerializationContext::create()
            ->setSerializeNull(true)
            ->setGroups(['Default'])
    ),
    $serializer->serialize(
        $product1,
        'json',
        \JMS\Serializer\SerializationContext::create()
            ->setSerializeNull(true)
            ->setGroups(['edit'])
    ),

]);

And here is result:

Array
(
    [0] => {"id":86,"products":[{"id":1,"name":"Banana"},{"id":2,"name":"Tomato"}],"total":644} // Here I have short model for history
    [1] => {"id":1,"name":"Banana","price":10,"description":"Yellow"} // And here I have expanded model for other purpose.
)

I love JMS-Serializer :)