haijin/object-builder

One direction serializers for complex objects using a simple DSL.


Keywords
json, serializer, converter
License
MIT

Documentation

Haijin Object_Builder

One direction serializers of complex objects using a simple DSL.

Latest Stable Version Latest Unstable Version Build Status License

Version 1.1.1

If you like it a lot you may contribute by financing its development.

Table of contents

  1. Installation
  2. Object_Builders
    1. Json_Builder
      1. Standalone example
      2. Integrating it to a class
    2. Building objects
    3. Reusing builders
  3. Running the tests

Installation

Include this library in your project composer.json file:

{
    ...

    "require": {
        ...
        "haijin/object-builder": "^1.1",
        ...
    },

    ...
}

Object_Builders

An Object_Builder is an object that serializes complex nested objects using simple DSL instead of configurations files or classes.

The serialization is one way only because it usually involves loss of information. Consider the case where an application returns objects modeled in a database to a json API response. Not all the fields in the model will be included in the response. Internal id fields will be skipped. Other fields may depend on the role of the user making the request. Some value types may be converted, etc.

On the other hand, some fields will be added to the response that may not be part of the model, such as the API version number, pagination information or timestamps related to the request, not the model.

Json_Builder

An Object_Builder subclass that builds a JSON.

Standalone example

$json_object = \Haijin\Object_Builder\Json_Builder::build_json( function($json) use($user) {
    $json->name = $user->get_name();

    $json->last_name = $user->get_last_name();

    $address = $user->get_address();
    $json->address = $this->build( function($json) use($address) {

        $json->street = $address->get_street_name();
        $json->number = $this->convert( $address->get_street_name() ) ->to_int();
    });
});

Example remarks.

Sets a value to a field:

$json->name = $user->get_name();
// or
$json["name"] = $user->get_name();

Assigns the user address to a variable to be able to pass it along to the closure below it:

$address = $user->get_address();

Uses a closure to create a new json object and assigns it to the address field:

$json->address = $this->build( function($json) use($address) {
    // ...
});

This line is an expressive way of converting a value to an int:

$json->number = $this->convert( $address->get_street_name() ) ->to_int();

It's optional, a simple:

$json->number = (int) $address->get_street_name();

produces the same result, but with more complex custom convertions that form gains in expressiveness.

Integrating it to a class

Given the model classes

class AddressSample
{
    public function get_street()
    {
        return "Evergreen";
    }

    public function get_number()
    {
        return "742";
    }
}

class SampleUser
{
    public function get_name()
    {
        return "Lisa";
    }

    public function get_last_name()
    {
        return "Simpson";
    }

    public function get_addresses()
    {
        return [ new AddressSample() ];
    }
}

And an Action class that serializes a SampleUser object to produce the response

[
    "response" => [
        "api_version" => "1.0.0",
        "success" => true,
        "data" => [
            "user" => [
                "name" => "Lisa",
                "last_name" => "Simpson",
                "addresses" => [
                    [
                        "street" => 'Evergreen',
                        "number" => 742
                    ]
                ]
            ]
        ]
    ]
]

integrate the Json_Builder and factorize its building block like this:

use Haijin\Object_Builder\Json_Builder;

class Action
{
    public function get_json()
    {
        // Get a sample model object.
        $user = new SampleUser();

        // Build the JSON response from the model object.
        return $this->build_json_from($user);
    }

    protected function build_json_from($user)
    {
        return Json_Builder::build_json( function($json) use($user) {

            $json->response = $this->success_response_to_json( $json, $user );

        }, $this);
    }

    protected function success_response_to_json($json, $user)
    {
        return $json->build( function($json) use ($user) {

            $json->api_version = "1.0.0";

            $json->success = true;

            $json->data = [
                "user" => $this->user_to_json( $json, $user )
            ];
        }) ;
    }

    protected function user_to_json($json, $user)
    {
        return $json->build( function($json) use($user) {

            $json->name = $user->get_name();

            $json->last_name = $user->get_last_name();

            $json->addresses = array_map(
                function($each_address) use($json) { return $this->address_to_json( $json, $each_address ); },
                $user->get_addresses()
            );
        });
    }

    protected function address_to_json($json, $address)
    {
        return $json->build( function($json) use($address) {

            $json->street = $json->convert( $address->get_street() ) ->to_string();

            $json->number = $json->convert( $address->get_number() ) ->to_int();
        });
    }
}

Remarks

In this method call, note the last parameter to the build_json call.

$this is passed as the last parameter to bind it to the Action object in all the closure evaluations.

If this last parameter was not passed, inside the closures $this would point to the Object_Builder object and not to the Action object, and calling other methods in the Action class would produce an error.

protected function build_json_from($user)
{
    return Json_Builder::build_json( function($json) use($user) {

        $json->response = $this->success_response_to_json( $json, $user );

    }, $this);
}

Building objects

Build objects of any class by defining the appropiate target and using its own protocol:

$user = Object_Builder::build_object( function($obj) {
    $obj->target = new User();

    $obj->set_name( "Lisa" );
    $obj->set_last_name( "Simpson" );

    $obj->set_address(
        $this->build( function($obj) {
            $obj->target = new Address();
            
            $obj->set_street( "Evergreen" );
            $obj->set_number( 742 );
        }) 
);

Remarks

This first line of each building closure creates the array or object which is later populated:

$obj->target = new User();

Reusing builders

Reuse common builders into Object_Builder subclasses:

class Address_Builder extends Object_Builder
{
    public function evaluate($address)
    {
        $this->target = [];

        $this->street = $address->get_street_name() . " " . $address->get_street_number();
    }
}

and use them:

$object = Object_Builder::build_object( function($obj) use($user) {
    $obj->target = [];

    $obj->name = $user->get_name();
    $obj->last_name = $user->get_last_name();

    $obj->address = $this->build_with( new Address_Builder(), $user->get_address() );
});

or

$object = Object_Builder::build_object( function($obj) use($user) {
    $obj->target = [];

    $obj->name = $user->get_name();
    $obj->last_name = $user->get_last_name();

    $obj->address = $this->build_with( "Address_Builder", $user->get_address() );
});

Running the tests

composer specs