I have a setup with Country and Currency objects. A Country may have 0 or more Currency objects and vice versa there's the Currency that may be used by 0 or more Country objects. The problem is the data is not returned to the front-end (api requests).
In Country
Entity
/**
* @var Collection|ArrayCollection|Currency[]
* @ORM\ManyToMany(targetEntity="Currency", inversedBy="countries", fetch="EAGER")
* @ORM\JoinTable(
* name="country_country_currencies",
* joinColumns={@ORM\JoinColumn(name="country_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="currency_id", referencedColumnName="id")}
* )
*/
protected $currencies;
In Currency
Entity
/**
* @var Collection|ArrayCollection|Currency[]
* @ORM\ManyToMany(targetEntity="Country", mappedBy="currencies", fetch="EAGER")
*/
protected $countries;
Both sides have a __construct()
function setting the initial values to new ArrayCollection()
. Both have their own get*()
, add*()
and remove*()
functions.
When debugging the DoctrineResource::fetch()
function on a single object (e.g. /currencies/45
) the $entity
object does contain the data when executing return $entity
(last line of fethc()
), so I know the data is there.
However, when it finally returns to the "front-end", I'm missing the data of the ManyToMany relation:
As you can see above: countries
is empty.
When requesting from the other side (from Country
), the currencies
is empty.
Other similar questions had suggestions in comments or answers I'll put here straight away as I already checked those.
Doctrine caching - I ran the following commands:
./vendor/bin/doctrine-module orm:clear-cache:query
./vendor/bin/doctrine-module orm:clear-cache:result
./vendor/bin/doctrine-module orm:clear-cache:metadata
I've made sure that Zend Framework caching is disabled (also, development mode is enabled via composer development-enable
).
The Entity proxies generated have also been deleted multiple times to make sure they aren't the issue. I've tried also without fetch="EAGER"
(without is the original actually), but that yields the same result.
Using wrong hydrator for Entities: both are configured to use the Doctrine EntityManager (same for both):
'zf-hal' => [
'metadata_map' => [
\Country::class => [
'route_identifier_name' => 'id',
'entity_identifier_name' => 'id',
'route_name' => 'country.rest.doctrine.country',
'hydrator' => 'Country\\V1\\Rest\\Country\\CountryHydrator',
'max_depth' => 3,
],
\Country\V1\Rest\Country\CountryCollection::class => [
'entity_identifier_name' => 'id',
'route_name' => 'country.rest.doctrine.country',
'is_collection' => true,
],
// other entity
],
],
// some more config
'zf-apigility' => [
'doctrine-connected' => [
\Country\V1\Rest\Country\CountryResource::class => [
'object_manager' => 'doctrine.entitymanager.orm_default',
'hydrator' => 'Country\\V1\\Rest\\Country\\CountryHydrator',
],
// other entity
],
],
'doctrine-hydrator' => [
'Country\\V1\\Rest\\Country\\CountryHydrator' => [
'entity_class' => \Salesupply\Core\Country\Entity\Country::class,
'object_manager' => 'doctrine.entitymanager.orm_default',
'by_value' => true,
'strategies' => [],
'use_generated_hydrator' => true,
],
// other entity
],
Another suggested the content negotiation
type might be set to json
instead of HalJson
: both (everything really) is set to HalJson
:
'zf-content-negotiation' => [
'controllers' => [
'Country\\V1\\Rest\\Country\\Controller' => 'HalJson',
'Country\\V1\\Rest\\Currency\\Controller' => 'HalJson',
],
],
The ApiSkeletons vendor package has this by design. I've opened an issue on Github some months back.
To ensure you receive back collections:
AllowRemoveByValue
strategy of Doctrine. extract
function to return a Collection, either filled or emptyThat'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' => [
'Application\\V1\\Entity\\ApplicationHydrator' => [
// other config
'strategies' => [
'relatedToManyPropertyName' => \Application\Strategy\UniDirectionalToManyStrategy::class,
],
],
],
Now collections should be returned.
Quick note: this will only work as a strategy for the *ToMany side.