API transformer¶
Context¶
To decouple the data exposed through the API and the data stored in the model, Open Orchestra uses the Facade design pattern.
If you want to add a new entity to your project and need to expose it through the API, an easy way would be to directly serialize the object to render it.
This solution is not ideal for a main reason: the response is going to move each time you will modify your model.
Open Orchestra solution¶
Open Orchestra gives you the opportunity to decouple the data exposition from the data storage.
To simplify this documentation, let’s say that we want to expose a Foo
document.
class Foo
{
/**
* @var string
*/
public $bar;
}
Facade¶
The facade is the object that will be exposed through the API, let’s also say you
need to put the value of Foo::bar
inside a baz
property.
Create the FooFacade
with the baz
property:
namespace FooBundle\Facade;
use JMS\Serializer\Annotation as Serializer;
use OpenOrchestra\BaseApi\Facade\FacadeInterface;
class FooFacade implements FacadeInterface
{
/**
* @Serializer\Type("string")
*/
public $baz;
}
The FooFacade
class should implement the FacadeInterface
to be recognized as
a facade in the rest of the application.
Transformer¶
As the transformation for the bar
to baz
property is not intuitive, you will
need to create a Transformer to perform the modification.
Create the FooTransformer
:
- It must implement the
OpenOrchestra\BaseApi\Transformer\TransformerInterface
- Be registered as a service with the tag
open_orchestra_api.transformer.strategy
namespace FooBundle\Transformer;
use OpenOrchestra\BaseApi\Facade\FacadeInterface;
use OpenOrchestra\BaseApi\Transformer\AbstractTransformer;
use FooBundle\Facade\FooFacade;
class FooTransformer extends AbstractTransformer
{
public function getName()
{
return 'foo';
}
public function transform($foo)
{
$facade = new FooFacade();
$facade->baz = $foo->bar;
return $facade;
}
}
And the declaration :
foo.transformer.foo:
class: FooBundle\Transformer\FooTransformer
tags:
- { name: open_orchestra_api.transformer.strategy }
To limit the dependency, Open Orchestra provides a TransformerManager
knowing
all the transformers. This way, you are able to directly call an other
transformer with the method $this->getTransformer('foo')
.
You also have access to the Router
and to the GroupContext
(see group context page)
Usage¶
In the controller, you should access your transformer via
TransformerManager
.
class FooController extends Controller
{
public function listAction()
{
$foo = new Foo(); //Create foo object
return $this
->get('open_orchestra_api.transformer_manager')
->get('foo')
->transformer($foo);
}
}
Entity modification¶
Once your entity has been serialized as a Facade to expose the data, you might need to modify it.
To help you do it, the TransformerInterface
provides you a reverseTransform
method. This method take as argument the facade send in the request and
the entity to modify.
First, add a method to the FooController
:
// Set a route on the 'POST' method and a paramConverter
public function editAction(Request $request, Foo $foo)
{
// Deserialize the content of the request in the FooFacade
$facade = $this
->get('jms_serializer')
->deserialize(
$request->getContent(),
'FooBundle\Facade\FooFacade',
$request->get('_format', 'json')
);
// Perform the reverse transform operation
$this
->get('open_orchestra_api.transformer_manager')
->get('foo')
->reverseTransform($facade, $foo);
// Check if the entity is valid
if ($this->isValid($foo)) {
//Save the entity
return new Response('', 200);
}
// If the entity is not valid, return the violations
return new Response(
$this
->get('jms_serializer')
->serialize(
$this->getViolations(),
$request->get('_format', 'json')
),
400
);
}
Then complete the reverseTransform
method from the FooTransformer
, I will
keep the same parameter.
public function reverseTansform(FacadeInterface $fooFacade, $fooEntity = null)
{
if (isset($fooFacade->baz)) {
$fooEntity->bar = $facade->baz;
}
return $fooEntity;
}