Skip to content
EK edited this page Mar 15, 2016 · 8 revisions

Package Architecture

Entry point of the application is Encoder which has methods for encoding data, meta, errors. Encoder is a relatively thin layer that uses the following components

  • Parser is a relatively small class but it's a core of the application. Its aim is iterate through all required levels of input data and return found result as a list of attributes. To make things clear it also returns Stack which indicates where those attributes were found (e.g. stack (type: sites, id: 1) -(post)-> (type: posts, id: 5) -(author)-> (type: people, id: 3)). When Parser finishes parsing objects it also sends a message about it. Thus parser receiver has all required information about input data tree (attributes of each leaf and information when leafs start and finish). As parsing could be constrained by input filters it uses ParserManager to decide whether it should parse particular branch of input data tree.
  • ReplyInterpreter interprets results from Parser and has knowledge about JSON-API document structure. Depending on the found attributes and its depth level it decides where those data should be put (main data or included sections). It sends commands to Document which builds resulting JSON-API document.
  • Document is used by Encoder directly for such trivial tasks as set top level links and meta.

It's also should be mentioned that Parser uses data schemas to get attributes and relationships from particular input data objects. Schemas extend/inherit SchemaProvider class.

Apart from core JSON-API encoding functionality this package has additional classes for

  • Parsing input parameters (field sets, filters, sort, etc) and HTTP headers (a set of classes in Neomerx\JsonApi\Http\Parameters namespace).
  • Auxiliary class RenderContainer for storing exception renders that help implement error reporting.
  • CodecMatcher matches registered encoders and decoders against input HTTP headers (Content-Type and Accept)

Package Extension

This package is designed to be highly modular. Package parts are connected to each other with interfaces so you can easily replace those parts with your implementations.

Let's illustrate the approach by example. Suppose your schema returns an object with a subclass of the expected class.

class PostSchema extends SchemaProvider
{
    ...

    public function getRelationships($post)
    {
        return [
            'author' => [
                // will not return an Author object but
                // AuthorProxy which extends Author
                self::DATA => $post->getAuthor() 
            ]
        ];
    }
    
    ...
}

For this case there is a quick fix (below) however let's see how Container class could be extended

// quick fix
$encoder = Encoder::instance([
    Author::class       => AuthorSchema::class,
    AuthorProxy::class  => AuthorSchema::class,
]);

Main steps

  • Inherit Container and override required method (getResourceType in our case)
  • Inherit Factory and override createContainer method
  • Inherit Encoder and override getFactory and return the new schema factory instance

Implementation

use \Doctrine\Common\Util\ClassUtils;
use \Neomerx\JsonApi\Encoder\Encoder;
use \Neomerx\JsonApi\Schema\Container;
use \Neomerx\JsonApi\Factories\Factory;

class YourContainer extends Container
{
    protected function getResourceType($resource)
    {
        return ClassUtils::getRealClass(get_class($resource));
    }
}

class YourFactory extends Factory
{
    public function createContainer(array $providers = [])
    {
        return new YourContainer($this, $providers);
    }
}

class YourEncoder extends Encoder
{
    protected static function getFactory()
    {
        return new YourFactory();
    }
}

This topic is based on #40 and refactored in #44.

Clone this wiki locally