4

I have been trying out Symfony 2.2, the FOSRest Bundle (using JMS Serializer), and Doctrine ODM using MongoDB.

After many hours of trying to figure out how to correctly setup the FOSRest Bundle I'm still having some trouble: I have a very simple route that returns a list of products and prices. Whenever I request for the HTML format I get the correct response, but if I request any other format (JSON, XML) I get an error:

[{"message": "Resources are not supported in serialized data. Path: Monolog\\Handler\\StreamHandler -> Symfony\\Bridge\\Monolog\\Logger -> Doctrine\\Bundle\\MongoDBBundle\\Logger\\Logger -> Doctrine\\Bundle\\MongoDBBundle\\Logger\\AggregateLogger -> Doctrine\\ODM\\MongoDB\\Configuration -> Doctrine\\MongoDB\\Connection -> Doctrine\\ODM\\MongoDB\\LoggableCursor",
    "class": "JMS\\Serializer\\Exception\\RuntimeException",...

you can see the full error message here

My current setup is very simple: I have created a single route to a controller that returns a list of products and the price (I followed this example to create the product document).

This is the route:

rest_product:
    type: rest
    resource: Onema\RestApiBundle\Controller\ProductController

This is the controller:

<?php
namespace Onema\RestApiBundle\Controller;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Routing\ClassResourceInterface;
use FOS\Rest\Util\Codes;
use JMS\Serializer\SerializationContext;
use Onema\RestApiBundle\Document\Product;

class ProductController extends FOSRestController implements ClassResourceInterface
{
    public function getAction()
    {
        $dm = $this->get('doctrine_mongodb')->getManager();
        $products = $dm->getRepository('RestApiBundle:Product')->findAll();

        if(!$products)
        {
            throw $this->createNotFoundException('No product found.');
        }

        $data = array('documents' => $products);         
        $view = $this->view($data, 200);
        $view->setTemplate("RestApiBundle:Product:get.html.twig");
        return $this->handleView($view);
    }
}

This is the view called from the controller Resources/Product/get.html.twig:

<ul>
{% for document in documents %}
<li>
    {{ document.name }}<br />
    {{ document.price }}
</li>
{% endfor %}
</ul>

Any ideas why this would work correctly for one format but not the others? Anything additional I'm supposed to setup?

UPDATE: This is the config values I have been using. At the end of app/config/config.yml I had this:

sensio_framework_extra:
    view:    { annotations: false }
    router:  { annotations: true }

fos_rest:
    param_fetcher_listener: true
    body_listener: true
    format_listener: true
    view:
        formats:
            json: true
        failed_validation: HTTP_BAD_REQUEST
        default_engine: twig
        view_response_listener: 'force'

WORKAROUND:

Doing a bit more research I ran into another error which lead me to this questions and answer:

https://stackoverflow.com/a/14030646/155248

Once I got rid of the Doctrine\ODM\MongoDB\LoggableCursor by adding every result to an array like this:

$productsQ = $dm->getRepository('RestApiBundle:Product')->findAll();

foreach ($productsQ as $product) {
    $products[] = $product;
}

return $products;

I started getting the results in the correct format. This is kind of a lame solution and still hope to find a better answer to this issue.

Community
  • 1
  • 1
Onema
  • 7,331
  • 12
  • 66
  • 102
  • 2
    No need for looping. Doctrine ODM `find*` methods return a `Cursor`. You can call `toArray` on a `Cursor`. – Entea Jun 27 '13 at 05:29

2 Answers2

6

If you want to get a colection of RestApiBundle:Product documents, you MUST call the method "toArray" after calling find method from repository or getQuery method from query builder

/**
 * @Route("/products.{_format}", defaults={"_format" = "json"})
 * @REST\View()
 */
public function getProductsAction($_format){
    $products = $this->get('doctrine_mongodb')->getManager()
        ->getRepository('RestApiBundle:Product')
        ->findAll()->toArray();

    return $products;
}

also you can call array_values($products) for correct serialization of exclude strategy

BattleBit
  • 754
  • 7
  • 9
2

Most likely the error lies somewhere in your config files or perhaps lack of? Add your configs and if I can I will update my answer.

For now I'll walk you through a simple implementation.

First lets start with the configs:

Note: I'm going to be using annotations for some of the settings see SensioFrameworkExtraBundle.

#app/config/config.yml
sensio_framework_extra:
    view:
        annotations: false

fos_rest:
    param_fetcher_listener: true
    body_listener: true
    format_listener: true
    view:
        view_response_listener: 'force'

First we setup sensio extra bundle. The default config enables annotations are set to true. I disabled annotations for the view(I won't be using them here).

For fos_rest we are setting up the Listeners, we're keeping it simple so we're going to use the example from their docs.

We will create the entity:

<?php

namespace Demo\DataBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints;
use JMS\Serializer\Annotation\ExclusionPolicy;  //Ver 0.11+ the namespace has changed from JMS\SerializerBundle\* to JMS\Serializer\*
use JMS\Serializer\Annotation\Expose;  //Ver 0.11+ the namespace has changed from JMS\SerializerBundle\* to JMS\Serializer\*

/**
 * Demo\DataBundle\Entity\Attributes
 *
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="Demo\DataBundle\Entity\AttributesRepository")
 * 
 * @ExclusionPolicy("all")
 */
class Attributes
{
    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * 
     * @Expose
     */
    private $id;

    /**
     * @var string $attributeName
     *
     * @ORM\Column(name="attribute_name", type="string", length=255)
     * 
     * @Expose
     */
    private $attributeName;


    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set attributeName
     *
     * @param string $attributeName
     * @return Attributes
     */
    public function setAttributeName($attributeName)
    {
        $this->attributeName = $attributeName;

        return $this;
    }

    /**
     * Get attributeName
     *
     * @return string 
     */
    public function getAttributeName()
    {
        return $this->attributeName;
    }
}

You will notice a couple of annotation settings. First we set @ExclusionPolicy("all") then we manually set which object we want to @Expose to the API. You can find out more about this here and a list of JMS Serializer annotations

Now lets move on to a simple controller:

<?php

namespace Demo\DataBundle\Controller;

use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Controller\Annotations as Rest;   //Lets use annotations for our FOSRest config
use FOS\RestBundle\Routing\ClassResourceInterface;   
use FOS\Rest\Util\Codes;
use Symfony\Component\HttpFoundation\Request;
use Demo\DataBundle\Entity\Attributes;


class AttributesController extends FOSRestController implements ClassResourceInterface
{
    /**
     * Collection get action
     * @var Request $request
     * @return array
     *
     * @Rest\View()
     */
    public function cgetAction(Request $request)
    {
        $em = $this->getDoctrine()->getManager();

        $entities = $em->getRepository('DemoDataBundle:Attributes')->findAll();

        return array(
                'entities' => $entities,
        );
    }
}

This is a simple controller that will return everything.

Hopefully this helped. I think your error is coming from a bad setting related to the Serializer. Make sure your exposing some data.

Kirill Fuchs
  • 13,446
  • 4
  • 42
  • 72
  • Than you so much for your detailed answer, I had a very similar setup than the one you described here. Although the one difference between yours and mine is that I was using Doctrine ODM with MongoDB. I tried doing the same with a Doctrine ORM entity and it worked right away. Could this be a bug in the serializer? One funny thing I found out with your setup and the ODM is that the format HTML stops working, I get 'Unable to find template "". ' WTH??? For json and xml I still get the same error messages I did before. – Onema Apr 28 '13 at 16:05
  • Actually I take the comment of the HTML format not working... I missed the annotation @Rest\View() in the controller :P everything else remains the same. – Onema Apr 28 '13 at 16:31