2

I have a problem using Apigility and Doctrine at their latest versions.

I have 2 entities: Advert and Application on a one to many bidirectional relationship. Many applications can be associated with an Advert and an Advert has many applications. My tables are created as such (foreign key advert_id on applications table).

I cannot manage to call an advert route with its applications, the applications field is always empty.

After some research ive come to the conclusion that apigility just doesnt know how to render the nested collection. Ive read about hydrators and a package called api-skeletons/zf-doctrine-hydrator that would provide a helper for these doctrine nested relationships, but sadly its not compatible with version 4 of phpro/zf-doctrine-hydration-module and I cannot downgrade because of other packages and dependencies.

<?php

namespace Fil\V1\Entity;

use Doctrine\ORM\Mapping as ORM;

class Applications
{
    /**
     * @var \Fil\V1\Entity\Adverts
     *
     * @ORM\ManyToOne(targetEntity="Fil\V1\Entity\Adverts",inversedBy="applications")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="author_id", referencedColumnName="id", nullable=true)
     * })
     */
    private $advert;

    /**
     * Set advert.
     *
     * @param \Fil\V1\Entity\Adverts|null $advert
     *
     * @return Applications
     */
    public function setAdvert(\Fil\V1\Entity\Adverts $advert = null)
    {
        $this->advert = $advert;

        return $this;
    }

    /**
     * Get advert.
     *
     * @return \Fil\V1\Entity\Adverts|null
     */
    public function getAdvert()
    {
        return $this->advert;
    }
}
class Adverts
{
   ...
    /**
     * @var \Doctrine\Common\Collections\Collection
     *
     * @ORM\OneToMany(targetEntity="Fil\V1\Entity\Applications", mappedBy="advert")
     */
    private $applications;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->applications = new \Doctrine\Common\Collections\ArrayCollection;
    }

    /**
     * Add application.
     *
     * @param \Fil\V1\Entity\Applications $application
     *
     * @return Adverts
     */
    public function addApplication(\Fil\V1\Entity\Applications $application)
    {
        $this->applications[] = $application;

        return $this;
    }

    /**
     * Remove application.
     *
     * @param \Fil\V1\Entity\Applications $application
     *
     * @return boolean TRUE if this collection contained the specified element, FALSE otherwise.
     */
    public function removeApplication(\Fil\V1\Entity\Applications $application)
    {
        return $this->applications->removeElement($application);
    }

    /**
     * Get applications.
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getApplications()
    {
        return ($this->applications);
    }
}

When I call the route for Applications (example: applications/1) it works perfectly, I get my json objet with the Advert relationship correct, with all the information. So the relationship is well conceived. {

id: 2,
name: "application1",
_embedded: 
    {
      advert: 
           {
            id: 1,
            name: "Advert1",
            applications: { },
            _links: 
              {
               self: 
                  {
                   href: "http://localhost:8080/adverts/1"
                   }
              }
    }
 }, 
 _links: 
    {
      self: 
         {
         href: "http://localhost:8080/applications/2"
         }
    }
}

The problem is when I call and Advert route (example: advert/2). I get the Json with the correct advert information, but the applications field is empty, just {}. There are multiple applications linked to the advert but it just doesnt work.

{
    id: 2,
    name: "Advert1,
    applications: { },
    _links: 
       {
          self: 
             {
                href: "http://localhost:8080/adverts/2"
              }
        }
}

Normally the applications field should be filled with different objets representing each of the applications.

Is there anyway to overcome this issue? thank you!

Diego PAd
  • 21
  • 1

1 Answers1

0

The ApiSkeletons vendor package has this by design. I'm with you, would prefer it return collections, but the fix is easy: create a new strategy!

  • Create a strategy class and extend the AllowRemoveByValue strategy of Doctrine.
  • Overwrite the extract function to return a Collection, either filled or empty

That's it.

Full class:

namespace Application\Strategy;

use DoctrineModule\Stdlib\Hydrator\Strategy\AllowRemoveByValue;
use ZF\Hal\Collection;

class UniDirectionalToManyStrategy extends AllowRemoveByValue
{
    public function extract($value)
    {
        return new Collection($value ?: []);
    }
}

Apply this strategy where you need it. E.g. your Advert as many Applications, so the config should be modified like so:

'doctrine-hydrator' => [
    'Fil\\V1\\Entity\\ApplicationHydrator' => [
        // other config
        'strategies' => [
            'adverts' => \Application\Strategy\UniDirectionalToManyStrategy::class,
        ],
    ],
],

Now collections should be returned.


Quick note: this will only work as a strategy for the *ToMany side.

rkeet
  • 3,406
  • 2
  • 23
  • 49