1

I am trying to link a one to many entity together using Doctrine in Apigility. The relationship is 1 slide has many bullets points. So I want the return json to show each slide and all its related nested bullets. But as you can see from the output below the bullets always return empty:

{
  "_links": {
    "self": {
      "href": "http://0.0.0.0:8888/slide"
    }
  },
  "_embedded": {
    "slide": [
      {
        "id": "2",
        "title": "Title",
        "previous": "Previous",
        "next": "Next",
        "bullets": {},
        "_links": {
          "self": {
            "href": "http://0.0.0.0:8888/slide/2"
          }
        }
      },
      {
        "id": "3",
        "title": "Title",
        "previous": "Previous",
        "next": "Next",
        "bullets": {},
        "_links": {
          "self": {
            "href": "http://0.0.0.0:8888/slide/3"
          }
        }
      },
      {
        "id": "4",
        "title": "Title",
        "previous": "Previous",
        "next": "Next",
        "bullets": {},
        "_links": {
          "self": {
            "href": "http://0.0.0.0:8888/slide/4"
          }
        }
      },
      {
        "id": "5",
        "title": "Title",
        "previous": "Previous",
        "next": "Next",
        "bullets": {},
        "_links": {
          "self": {
            "href": "http://0.0.0.0:8888/slide/5"
          }
        }
      },
      {
        "id": "6",
        "title": "Title",
        "previous": "Previous",
        "next": "Next",
        "bullets": {},
        "_links": {
          "self": {
            "href": "http://0.0.0.0:8888/slide/6"
          }
        }
      }
    ]
  },
  "total_items": 5
}

The slide entity for both the bullet and the slide

namespace demo\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Sentence
 *
 * @ORM\Table(name="slide")
 * @ORM\Entity(repositoryClass="demo\Repository\SlideRepository")
 */
class Slide
{
   /**
     * @var integer
     *
     * @ORM\Column(name="id", type="bigint", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(type="string", length=45, nullable=true)
     */
    private $title;
    /**
     * @var string
     *
     * @ORM\Column(type="string", length=45, nullable=true)
     */
    private $previous;

    /**
     * @var string
     *
     * @ORM\Column(type="string", length=45, nullable=true)
     */
    private $next;

    /**
     * @ORM\OneToMany(targetEntity="Bullet", mappedBy="slide")
     */
    private $bullets;

    public function __construct()
    {
        $this->bullets = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * @param string $title
     */
    public function setTitle($title)
    {
        $this->title = $title;
    }

    /**
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * @param string $previous
     */
    public function setPrevious($previous)
    {
        $this->previous = $previous;
    }

    /**
     * @return string
     */
    public function getPrevious()
    {
        return $this->previous;
    }

    /**
     * @param int $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }

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

    /**
     * @param string $next
     */
    public function setNext($next)
    {
        $this->next = $next;
    }

    /**
     * @return string
     */
    public function getNext()
    {
        return $this->next;
    }

    /**
     * Add bullets
     *
     * @param Bullet $bullets
     * @return Slide
     */
    public function addBullet(Bullet $bullets)
    {
      $this->bullets[] = $bullets;
      return $this;
    }
    /**
     * Remove bullets
     *
     * @param Bullet $bullets
     */
    public function removeBullet(Bullet $bullets)
    {
      $this->bullets->removeElement($bullets);
    }
    /**
     * Get bullets
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getBullets()
    {
      return $this->bullets;
    }
}

namespace demo\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Sentence
 *
 * @ORM\Table(name="bullets")
 * @ORM\Entity(repositoryClass="demo\Repository\BulletsRepository")
 */
class Bullet
{
   /**
     * @var integer
     *
     * @ORM\Column(name="id", type="bigint", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="bullet", type="string", length=45, nullable=true)
     */
    private $bullet;

   /**
     * @ORM\ManyToOne(targetEntity="Slide", inversedBy="bullets")
     * @ORM\JoinColumn(name="slide_id", referencedColumnName="id")
     */
    protected $slide;

    public function __construct()
    {
        var_dump('TEST!!');
        exit();
    }

    /**
     * @param int $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }

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

    /**
     * @param string $bullet
     */
    public function setBullet($bullet)
    {
        $this->bullet = $bullet;
    }

    /**
     * @return string
     */
    public function getBullet()
    {
        return $this->bullet;
    }

    /**
     * @return string
     */
    public function getSlide()
    {
        return $this->slide;
    }

    /**
     * @param string $slide
     */
    public function setSlide(Slide $slide = null)
    {
        $this->slide = $slide;
        return $this;
    }

}

The database entries

+----+-------+----------+------+
| id | title | previous | next |
+----+-------+----------+------+
|  2 | Title | Previous | Next |
|  3 | Title | Previous | Next |
|  4 | Title | Previous | Next |
|  5 | Title | Previous | Next |
|  6 | Title | Previous | Next |
+----+-------+----------+------+
+----+----------+---------------+
| id | slide_id | bullet        |
+----+----------+---------------+
|  1 |        2 | Test Bullet   |
|  2 |        3 | Test Bullet   |
|  3 |        4 | Array         |
|  4 |        5 | test Bullet 2 |
|  5 |        6 | test Bullet 2 |
+----+----------+---------------+

Any help would be gratefully appreciated.

I've also added the application.config.php:

/**
 * Configuration file generated by ZF Apigility Admin
 *
 * The previous config file has been stored in application.config.old
 */
return array(
    'modules' => array(
        'ZF\\DevelopmentMode',
        'ZF\\Apigility',
        'ZF\\Apigility\\Provider',
        'ZF\\Apigility\\Documentation',
        'AssetManager',
        'ZF\\ApiProblem',
        'ZF\\Configuration',
        'ZF\\MvcAuth',
        'ZF\\OAuth2',
        'ZF\\Hal',
        'ZF\\ContentNegotiation',
        'ZF\\ContentValidation',
        'ZF\\Rest',
        'ZF\\Rpc',
        'ZF\\Versioning',
        'apiexample',
        'DoctrineModule',
        'DoctrineORMModule'
    ),
    'module_listener_options' => array(
        'module_paths' => array(
            './module',
            './vendor'
        ),
        'config_glob_paths' => array(
            'config/autoload/{,*.}{global,local}.php'
        )
    )
);

module/apiexample/config/module.config.php

return array(
    'controllers' => array(
        'factories' => array(
            'apiexample\\V1\\Rpc\\Ping\\Controller' => 'apiexample\\V1\\Rpc\\Ping\\PingControllerFactory',
        ),
    ),
    'router' => array(
        'routes' => array(
            'apiexample.rpc.ping' => array(
                'type' => 'Segment',
                'options' => array(
                    'route' => '/ping',
                    'defaults' => array(
                        'controller' => 'apiexample\\V1\\Rpc\\Ping\\Controller',
                        'action' => 'ping',
                    ),
                ),
            ),
            'apiexample.rest.user' => array(
                'type' => 'Segment',
                'options' => array(
                    'route' => '/user[/:user_id]',
                    'defaults' => array(
                        'controller' => 'apiexample\\V1\\Rest\\User\\Controller',
                    ),
                ),
            ),
        ),
    ),
    'zf-versioning' => array(
        'uri' => array(
            0 => 'apiexample.rpc.ping',
            1 => 'apiexample.rest.user',
        ),
    ),
    'zf-rpc' => array(
        'apiexample\\V1\\Rpc\\Ping\\Controller' => array(
            'service_name' => 'Ping',
            'http_methods' => array(
                0 => 'GET',
            ),
            'route_name' => 'apiexample.rpc.ping',
        ),
    ),
    'zf-content-negotiation' => array(
        'controllers' => array(
            'apiexample\\V1\\Rpc\\Ping\\Controller' => 'Json',
            'apiexample\\V1\\Rest\\User\\Controller' => 'HalJson',
        ),
        'accept_whitelist' => array(
            'apiexample\\V1\\Rpc\\Ping\\Controller' => array(
                0 => 'application/vnd.apiexample.v1+json',
                1 => 'application/json',
                2 => 'application/*+json',
            ),
            'apiexample\\V1\\Rest\\User\\Controller' => array(
                0 => 'application/vnd.apiexample.v1+json',
                1 => 'application/hal+json',
                2 => 'application/json',
            ),
        ),
        'content_type_whitelist' => array(
            'apiexample\\V1\\Rpc\\Ping\\Controller' => array(
                0 => 'application/vnd.apiexample.v1+json',
                1 => 'application/json',
            ),
            'apiexample\\V1\\Rest\\User\\Controller' => array(
                0 => 'application/vnd.apiexample.v1+json',
                1 => 'application/json',
            ),
        ),
    ),
    'zf-content-validation' => array(
        'apiexample\\V1\\Rpc\\Ping\\Controller' => array(
            'input_filter' => 'apiexample\\V1\\Rpc\\Ping\\Validator',
        ),
    ),
    'input_filter_specs' => array(
        'apiexample\\V1\\Rpc\\Ping\\Validator' => array(
            0 => array(
                'name' => 'ack',
                'required' => true,
                'filters' => array(),
                'validators' => array(),
                'description' => 'Acknowledge the request with a timestamp.',
            ),
        ),
    ),
    'service_manager' => array(
        'factories' => array(
            'apiexample\\V1\\Rest\\User\\UserResource' => 'apiexample\\V1\\Rest\\User\\UserResourceFactory',
        ),
    ),
    'zf-rest' => array(
        'apiexample\\V1\\Rest\\User\\Controller' => array(
            'listener' => 'apiexample\\V1\\Rest\\User\\UserResource',
            'route_name' => 'apiexample.rest.user',
            'route_identifier_name' => 'user_id',
            'collection_name' => 'user',
            'entity_http_methods' => array(
                0 => 'GET',
                1 => 'PATCH',
                2 => 'PUT',
                3 => 'DELETE',
                4 => 'POST',
            ),
            'collection_http_methods' => array(
                0 => 'GET',
                1 => 'POST',
            ),
            'collection_query_whitelist' => array(),
            'page_size' => '2',
            'page_size_param' => null,
            'entity_class' => 'apiexample\\Entity\\User',
            'collection_class' => 'apiexample\\V1\\Rest\\User\\UserCollection',
            'service_name' => 'user',
        ),
    ),
    'zf-hal' => array(
        'metadata_map' => array(
            'apiexample\\V1\\Rest\\User\\UserEntity' => array(
                'entity_identifier_name' => 'id',
                'route_name' => 'apiexample.rest.user',
                'route_identifier_name' => 'user_id',
                'hydrator' => 'Zend\\Stdlib\\Hydrator\\ObjectProperty',
            ),
            'apiexample\\V1\\Rest\\User\\UserCollection' => array(
                'entity_identifier_name' => 'id',
                'route_name' => 'apiexample.rest.user',
                'route_identifier_name' => 'user_id',
                'is_collection' => true,
            ),
            'apiexample\\Entity\\User' => array(
                'entity_identifier_name' => 'id',
                'route_name' => 'apiexample.rest.user',
                'route_identifier_name' => 'user_id',
                'hydrator' => 'apiexample\\Hydrator\\UserHydrator',
            ),
        ),
    ),
    'doctrine' => array(
        'driver' => array(
            'lanekeep_driver' => array(
                'class' => 'Doctrine\\ORM\\Mapping\\Driver\\AnnotationDriver',
                'cache' => 'array',
                'paths' => array(
                    0 => __DIR__ . '/../src/apiexample/Entity',
                ),
            ),
            'orm_default' => array(
                'drivers' => array(
                    'apiexample\\Entity' => 'lanekeep_driver',
                ),
            ),
        ),
    ),
    'view_manager' => array(
        'display_not_found_reason' => true,
        'display_exceptions' => true,
        'doctype' => 'HTML5',
        'not_found_template' => 'error/404',
        'exception_template' => 'error/index',
        'template_map' => array(
            'layout/layout' => __DIR__ . '/../view/layout/layout.phtml',
            'error/404' => __DIR__ . '/../view/error/404.phtml',
            'error/index' => __DIR__ . '/../view/error/index.phtml',
        ),
        'template_path_stack' => array(
            0 => __DIR__ . '/../view',
        ),
    ),
);
Wilt
  • 41,477
  • 12
  • 152
  • 203

1 Answers1

0

Your getBullets method returns a ArrayCollection instance and the Hal renderer doesn't know how to render (serialize) this object and that is why you see {} (an object in json format). You should add a custom hydrator/extractor where you return some renderable value for your bullits. You could for example make it to a Hal\Collection instance.

In your hydrator/extractor you could do something like:

<?php

namespace Application\Extractor;

use Zend\Stdlib\Extractor\ExtractionInterface;
use ZF\Hal;

class SlideExtractor implements ExtractionInterface

    public function extract($object){

         $data = [];

         $route = //...your route to bullits
         $routeParams = //...route params if needed
         $bullits = $object->getBullits()->toArray();
         $collection = new Hal\Collection($bullits, $route, $routeParams);

         $data['bullits'] = $collection;

         //...other extractions from your slide ( $object )

         return $data;
    }

}
Wilt
  • 41,477
  • 12
  • 152
  • 203