Skip to content

Instantly share code, notes, and snippets.

@renoirb
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save renoirb/9646221 to your computer and use it in GitHub Desktop.
Save renoirb/9646221 to your computer and use it in GitHub Desktop.
Read for `Content-Type` in request, hijacks response to format.

Convert into an API a Symfony2 response

You want to create an alternate way to serve your data from your controllers on a Symfony2 project.

How about adding one event listener to convert to JSON anything that is sent from a controller. To do this, your project has to have your controllers to return arrays of Doctrine2 Entities and also to leverage @Template and @Route.

If you already do, only insert this EventListener and ensure you have a dependency or two met (below). Note that except the Serializer, the other ones are not required, you would need to manually create the services in your project (not covered here).

Use

To use, try cURL to a controller

For example, Imagine you have an endpoint at /api/feed, When you use your web browser, you get your basic HTML version.

When using Content-Type: application/json, you get a JSON representation.

curl -H 'Content-Type: application/json'  http://localhost:8000/app_dev.php/api/feed/

Dependencies

Must

Created with Symfony2 2.4, "symfony/symfony-standard".

"jms/serializer-bundle": "~0.13"

See the Serializer Annotations documentation on how to use on your entities.

Optional

For this example to work by cut-and-paste, at least.

"jms/di-extra-bundle": "~1.4"

I love this extension! It allows to use annotations in your services to define them. See the DiExtra bundle documentation for details.

Files

RequestListener.php

Create a file and adjust paths to suit your project namespaces.

<?php
namespace Renoir\SomeBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use JMS\Serializer\Annotation as Serializer;
use Renoir\SomeBundle\Entity\Message;
/**
* Feed
*
* A feed is a list of messages as a way to separate
* topics. It can be visible to the public or only to
* the contacts.
*
* @Serializer\ExclusionPolicy("none")
*
* @ORM\Table(name="feed")
* @ORM\Entity
*/
class Feed
{
/**
* @var integer
*
* @Serializer\Type("integer")
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @Serializer\Type("string")
*
* @ORM\Column(name="slug", type="string", length=255)
*/
private $slug;
/**
* List of messages attached to this feed
*
* @Serializer\Accessor(getter="getMessages", setter="setMessages")
* @Serializer\Type("ArrayCollection<Renoir\SomeBundle\Entity\Message>")
*
* @ORM\ManyToMany(targetEntity="Message", cascade={"all"})
* @ORM\JoinTable(name="feed_message",
* joinColumns={@ORM\JoinColumn(name="feed_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="message_id", referencedColumnName="id", unique=true)}
* )
*/
private $messages;
public function __construct()
{
$this->messages = new ArrayCollection;
return $this;
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set slug
*
* @param string $slug
* @return Feed
*/
public function setSlug($slug)
{
$this->slug = $slug;
return $this;
}
/**
* Get slug
*
* @return string
*/
public function getSlug()
{
return $this->slug;
}
/**
* Add a Message
*
* @param Message $message
*/
public function addMessage(Message $message)
{
$this->messages->add($message);
return $this;
}
/**
* Get the list of messages categorized
* under this feed.
*
* @param ArrayCollection $list of Message
*/
public function getMessages()
{
return $this->messages;
}
public function setMessages(ArrayCollection $messages)
{
$this->messages = $messages;
return $this;
}
public function createMessage()
{
$message = new Message();
$this->addMessage($message);
return $message;
}
}
<?php
namespace Renoir\SomeBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as Serializer;
/**
* Message
*
* @Serializer\ExclusionPolicy("none")
*
* @ORM\Table(name="message")
* @ORM\Entity
*/
class Message
{
/**
* @var integer
*
* @Serializer\Type("integer")
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @Serializer\Type("string")
*
* @ORM\Column(name="locale", type="string", length=2)
*/
private $locale;
/**
* @var string
*
* @Serializer\Type("string")
*
* @ORM\Column(name="body", type="text", nullable=true)
*/
private $body;
/**
* @var \DateTime
*
* @Serializer\Type("DateTime")
*
* @ORM\Column(name="published", type="datetime", nullable=true)
*/
private $published;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set locale
*
* @param string $locale
*
* @return Message
*/
public function setLocale($locale)
{
$this->locale = $locale;
return $this;
}
/**
* Get locale
*
* @return string
*/
public function getLocale()
{
return $this->locale;
}
/**
* Set body
*
* @param string $body
*
* @return Message
*/
public function setBody($body)
{
$this->body = $body;
return $this;
}
/**
* Get body
*
* @return string
*/
public function getBody()
{
return $this->body;
}
/**
* Set published
*
* @param \DateTime $published
*
* @return Message
*/
public function setPublished(\DateTime $published = null)
{
$this->published = $published;
return $this;
}
/**
* Get published
*
* @return \DateTime
*/
public function getPublished()
{
return $this->published;
}
}
<?php
namespace Renoir\SocialEvent\ApiBundle\EventListener;
// Framework Internals
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelInterface;
// Specific
use JMS\DiExtraBundle\Annotation as DI;
use JMS\Serializer\Serializer;
// Exceptions
use \Exception;
/**
* Read for `Content-Type` in request, hijacks response to format.
*
* @DI\Service
*
* This listener allows you to expose Doctrine2 entities using the
* JMSSerializerBundle into a JSON representation by reading `Content-Type`
* header in the Request it listens to.
*
* That way, you use the same controller code, but expose an API in
* a different format by adjusting the Response; Without using
* templating engine!
*
*
* NOTE
* ====
* This solution is not perfect as it doesn't show how to
* authenticate (yet) but the idea can be applied using
* a similar pattern.
*
* @author Renoir Boulanger <hello@renoirboulanger.com>
*
* See http://symfony.com/doc/current/cookbook/request/mime_type.html
*/
class RequestListener
{
/* @var JMS\Serializer\Serializer */
protected $serializer;
/**
* @DI\InjectParams({
* "serializer" = @DI\Inject("serializer")
* })
**/
public function __construct(Serializer $serializer)
{
$this->serializer = $serializer;
}
protected function serialize($content)
{
try {
// TODO, support also XML
$serialized = $this->serializer->serialize($content, 'json');
} catch(Exception $e){
throw new Exception('Had problems with serializing entity',0,$e);
}
return $serialized;
}
/**
* @DI\Observe("kernel.view")
*
* Renders JSON output if it has been requested
*
* See
* - http://symfony.com/doc/master/components/http_kernel/introduction.html#the-kernel-view-event
* - https://github.com/FriendsOfSymfony/FOSRestBundle/blob/master/EventListener/ViewResponseListener.php
*/
public function onKernelView(GetResponseForControllerResultEvent $event)
{
$request = $event->getRequest();
//var_dump($request->getContentType()); // DEBUG
if($request->getContentType() == 'json' && $event->getRequestType() === HttpKernelInterface::MASTER_REQUEST) {
$cr = $event->getControllerResult();
$serialized = $this->serialize($cr);
$response = new Response($serialized);
$response->setETag(md5($serialized));
$response->setPublic();
$response->headers->set('Content-Type', 'application/json');
$event->setResponse($response);
$event->stopPropagation();
}
}
}
<?php
namespace Renoir\SocialEvent\ApiBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Renoir\SomeBundle\Entity\Message;
use Renoir\SomeBundle\Entity\Feed;
class SomeController extends Controller
{
/**
* @Route("/feed/")
* @Template()
*/
public function feedAction()
{
$em = $this->getDoctrine()->getManager();
$feed = $em->getRepository('RenoirSomeBundle:Feed')->find(1);
return array('feed' => $feed); // Return an array, adjust your templates, and gain free API :)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment