With doctrine ODM, can I embed many subdocuments in a main document? - php

I try to save this JSON:
{
"vendorId": "vendor-fc162cdffd73",
"company": {
"companyId": "bcos1.company.1806cf97-a756-4fbf-9081-fc162cdffd73",
"companyVersion": 1,
"companyName": "Delivery Inc.",
"address": {
"streetAddress": "300 Boren Ave",
"city": "Seattle",
"region": "US-WA",
"country": "US",
"postalCode": "98109",
"storeName": "Seattle Store",
"coordinates": {
"latitude": "45.992820",
"longitude": "45.992820"
}
},
"emailAddress": "johndoe#amazon.com",
"phoneNumber": "1234567890",
"websiteUrl": "delivery.com",
"creationDate": "2022-03-06T21:00:52.222Z"
},
"creationDate": "2022-04-06T21:00:52.222Z"
}
Company is a subdocument this has address and address has coordinates subdocument.
When I try to save with Hydratation, see example:
https://www.doctrine-project.org/projects/doctrine-laminas-hydrator/en/3.0/basic-usage.html#example-4-embedded-entities
I got this error:
1) AppTest\Services\AccountsServiceTest::testNewAccount with data set #0 (array('{"companyId":"bcos1.com...222Z"}', '', ''))
array_flip(): Can only flip STRING and INTEGER values!
vendor/doctrine/doctrine-laminas-hydrator/src/DoctrineObject.php:488
vendor/doctrine/doctrine-laminas-hydrator/src/DoctrineObject.php:355
vendor/doctrine/doctrine-laminas-hydrator/src/DoctrineObject.php:165
src/App/Document/Repository/AccountRepository.php:67
In DoctrineObject line 488
protected function toOne(string $target, $value): ?object
{
$metadata = $this->objectManager->getClassMetadata($target);
if (is_array($value) && array_keys($value) !== $metadata->getIdentifier()) {
// $value is most likely an array of fieldset data
$identifiers = array_intersect_key(
$value,
array_flip($metadata->getIdentifier())
);
$object = $this->find($identifiers, $target) ?: new $target();
return $this->hydrate($value, $object);
}
return $this->find($value, $target);
}
My code:
$vendorAccountId = uniqid('vendor-account-id-');
$account = new Account();
$hydrator->hydrate($data, $account);
My main Entity:
<?php
namespace App\Document\Entity;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document(db="awesome-company", collection="Account", repositoryClass="App\Document\Repository\AccountRepository")
*/
class Account
{
/** #MongoDB\Id(name="_id") */
private string $id;
/** #MongoDB\Field(type="string", name="vendorAccountId") */
private string $vendorAccountId;
/**
* #return string
*/
public function getVendorAccountId(): string
{
return $this->vendorAccountId;
}
/**
* #param string $vendorAccountId
*/
public function setVendorAccountId(string $vendorAccountId): void
{
$this->vendorAccountId = $vendorAccountId;
}
/**
* #MongoDB\EmbedOne(targetDocument=Company::class)
*/
private Company $company;
/**
* #MongoDB\Field(type="string",name="realm")
**/
private $realm;
/**
* #MongoDB\Field(type="string",name="domain")
**/
private $domain;
/**
* #MongoDB\Field(type="date",name="created_at")
**/
private \DateTime $createdAt;
public function __construct()
{
$this->company = new Company();
$this->createdAt = new \DateTime();
}
/**
* #return mixed
*/
public function getCompany()
{
return $this->company;
}
/**
* #param mixed $company
*/
public function setCompany($company): void
{
$this->company = $company;
}
/**
* #return mixed
*/
public function getRealm()
{
return $this->realm;
}
/**
* #param mixed $realm
*/
public function setRealm($realm): void
{
$this->realm = $realm;
}
/**
* #return mixed
*/
public function getDomain()
{
return $this->domain;
}
/**
* #param mixed $domain
*/
public function setDomain($domain): void
{
$this->domain = $domain;
}
/**
* #return \DateTime
*/
public function getCreatedAt(): \DateTime
{
return $this->createdAt;
}
/**
* #return string
*/
public function getId(): string
{
return $this->id;
}
/**
* #param string $id
*/
public function setId(string $id): void
{
$this->id = $id;
}
}
Company embed document:
<?php
namespace App\Document\Entity;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/** #MongoDB\EmbeddedDocument * */
class Company
{
/**
* #MongoDB\Field(type="string",name="company_id")
**/
private string $companyId;
/**
* #MongoDB\Field(type="int",name="company_version")
**/
private int $companyVersion;
/**
* #MongoDB\Field(type="string",name="company_name")
**/
private string $companyName;
/**
* #MongoDB\EmbedOne(targetDocument=Address::class)
*/
private Address $address;
/**
* #MongoDB\Field(type="string",name="email_address")
**/
private string $emailAddress;
/**
* #MongoDB\Field(type="string",name="phone_number")
**/
private string $phoneNumber;
/**
* #MongoDB\Field(type="string",name="website_url")
**/
private string $websiteUrl;
/**
* #MongoDB\Field(type="date",name="creation_date")
**/
private \DateTime $creationDate;
public function __construct()
{
$this->address = new Address();
}
public function getCompanyId(): string
{
return $this->companyId;
}
public function setCompanyId($companyId)
{
$this->companyId = $companyId;
}
public function getCompanyVersion(): int
{
return $this->companyVersion;
}
public function setCompanyVersion($companyVersion)
{
$this->companyVersion = $companyVersion;
}
public function getCreationDate(): \DateTime
{
return $this->creationDate;
}
public function setCreationDate($creationDate)
{
$this->creationDate = $creationDate;
}
public function getWebsiteUrl(): string
{
return $this->websiteUrl;
}
public function setWebsiteUrl($websiteUrl)
{
$this->websiteUrl = $websiteUrl;
}
public function getPhoneNumber(): string
{
return $this->phoneNumber;
}
public function setPhoneNumber($phoneNumber)
{
$this->phoneNumber = $phoneNumber;
}
public function getEmailAddress(): string
{
return $this->emailAddress;
}
public function setEmailAddress($emailAddress)
{
$this->emailAddress = $emailAddress;
}
public function getAddress(): Address
{
return $this->address;
}
public function setAddress(Address $address)
{
$this->address = $address;
}
public function getCompanyName(): string
{
return $this->companyName;
}
public function setCompanyName($companyName)
{
$this->companyName = $companyName;
}
}

What you're trying to do is perfectly fine from ODM's perspective - you can have as many embeddables deep as you want. Unless there is something fishy going on in your Address or Coordinates embedded documents I would expect a bug in the laminas-hydrator package. The fact that ORM does not allow nested embeddables makes this scenario more likely. Best would be to try creating a failing test case and send a pull request with it.
In the meantime you can leverage ODM's hydrator:
$hydrator = $this->dm->getHydratorFactory()->getHydratorFor(Account::class);
$hydrator->hydrate(new Account(), $data, [Query::HINT_READ_ONLY => true]);
Please note the Query::HINT_READ_ONLY passed as a 3rd argument to hydrate. With this hint ODM will not mark hydrated objects as managed which is what you need for later insertion. Hydrated EmbedOne objects should be good to go without the hint, but EmbedMany may not work correctly without it. Please see https://github.com/doctrine/mongodb-odm/issues/1377 and https://github.com/doctrine/mongodb-odm/pull/1403 for more details about said hint.

Thank you #malarzm. I do this:
$coordinates = new Coordinates();
$hydrator->hydrate($data['company']['address']['coordinates'], $coordinates);
unset($data['company']['address']['coordinates']);
$address = new Address();
$hydrator->hydrate($data['company']['address'], $address);
unset($data['company']['address']);
$company = new Company();
$hydrator->hydrate($data['company'], $company);
unset($data['company']);
$account = new Account();
$hydrator->hydrate($data, $account);
$address->setCoordinates($coordinates);
$company->setAddress($address);
$account->setCompany($company);
I realized unset() for each embed sud-document and finally I set each sub-document with set method of my entity.
It was the only way to do work fine.

Related

PHP Doctrine loading associated objects causes error

I have two object, named Osoba and Adresa. Both of them are correctly mapped with doctrine annotations. Osoba and Adresa are in one to one association.
I am able to load Osoba and Adresa separately with entity manager. As long as Osoba does not have property Adresa populated, it works fine. But when I save Osoba with property Adresa, and trying to retrieve the object from database, an error is thrown.
This is how I try to fetch the object Osoba. The Osoba with id 13 in database contains an id pointing to Adresa table.
$osoba = $entityManager->find("Osoba", 13);
And the error thrown is
Fatal error: require(): Failed opening required 'C:\Users\xxx\AppData\Local\Temp\__CG__Adresa.php' (include_path='C:\xampp\php\PEAR') in C:\xampp\htdocs\vendor\doctrine\common\lib\Doctrine\Common\Proxy\AbstractProxyFactory.php on line 206
Here are my Osoba and Adresa entities.
<?php
declare(strict_types = 1);
require_once('Krajina.php');
require_once('Obec.php');
require_once ("BaseDAO.php");
use \Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="mr_adresa")
*/
class Adresa extends BaseDAO
{
/**
* #ORM\Column(type="string", name="orientacne_cislo")
*/
private $orientacneCislo;
/**
* #ORM\Column(type="string")
*/
private $psc;
/**
* #ORM\Column(type="string", name="supisne_cislo")
*/
private $supisneCislo;
/**
* #ORM\Column(type="string")
*/
private $ulica;
/**
* #ORM\OneToOne(targetEntity="Krajina")
* #ORM\JoinColumn(name="krajina_id", referencedColumnName="id")
*/
private $m_Krajina;
/**
* #ORM\OneToOne(targetEntity="Obec")
* #ORM\JoinColumn(name="obec_id", referencedColumnName="id")
*/
private $m_Obec;
public function getOrientacneCislo(): string
{
return $this->orientacneCislo;
}
public function setOrientacneCislo(string $orientacneCislo): void
{
$this->orientacneCislo = $orientacneCislo;
}
public function getPsc(): string
{
return $this->psc;
}
public function setPsc(string $psc): void
{
$this->psc = $psc;
}
public function getSupisneCislo(): string
{
return $this->supisneCislo;
}
public function setSupisneCislo(string $supisneCislo): void
{
$this->supisneCislo = $supisneCislo;
}
public function getUlica(): string
{
return $this->ulica;
}
public function setUlica(string $ulica): void
{
$this->ulica = $ulica;
}
public function getMKrajina(): Krajina
{
return $this->m_Krajina;
}
public function setMKrajina(Krajina $m_Krajina): void
{
$this->m_Krajina = $m_Krajina;
}
public function getMObec(): Obec
{
return $this->m_Obec;
}
public function setMObec(Obec $m_Obec): void
{
$this->m_Obec = $m_Obec;
}
}
<?php
declare(strict_types = 1);
require_once('Krajina.php');
require_once('Pohlavie.php');
require_once('Adresa.php');
require_once ("BaseDAO.php");
use \Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="mr_osoba")
*/
class Osoba extends BaseDAO
{
/**
* #ORM\Column(type="date", name="datum_narodenia")
*/
private $datumNarodenia;
/**
* #ORM\Column(type="string")
*/
private $email;
/**
* #ORM\Column(type="string", name="iban_dotacia")
*/
private $ibanDotacia;
/**
* #ORM\Column(type="string", name="iban_sponzor")
*/
private $ibanSponzor;
/**
* #ORM\Column(type="string")
*/
private $meno;
/**
* --#ORM\Column(type="datetime", name="platnost_ku_datumu")
*/
// private $platnostKuDatumu;
/**
* #ORM\Column
*/
private $priezvisko;
/**
* #ORM\Column(name="rodne_cislo")
*/
private $rodneCislo;
/**
* #ORM\OneToOne(targetEntity="Krajina")
* #ORM\JoinColumn(name="krajina_id", referencedColumnName="id")
*/
private $m_Krajina;
/**
* #ORM\Column(name="pohlavie", type="string")
*/
private $m_Pohlavie;
/**
* #ORM\OneToOne(targetEntity="Krajina")
* #ORM\JoinColumn(name="statna_prislusnost_id", referencedColumnName="id")
*/
private $m_StatnaPrislusnost;
/**
* #ORM\OneToOne(targetEntity="Adresa")
* #ORM\JoinColumn(name="dorucovacia_adresa_id", referencedColumnName="id")
*/
private $m_DorucovaciaAdresa;
/**
* #ORM\OneToOne(targetEntity="Adresa")
* #ORM\JoinColumn(name="trvale_bydlisko_id", referencedColumnName="id")
*/
private $m_TrvaleBydlisko;
public function getDatumNarodenia()
{
return $this->datumNarodenia;
}
public function setDatumNarodenia($datumNarodenia): void
{
$this->datumNarodenia = $datumNarodenia;
}
public function getEmail(): string
{
return $this->email;
}
public function setEmail(string $email): void
{
$this->email = $email;
}
public function getIbanDotacia(): string
{
return $this->ibanDotacia;
}
public function setIbanDotacia(string $ibanDotacia): void
{
$this->ibanDotacia = $ibanDotacia;
}
public function getIbanSponzor(): string
{
return $this->ibanSponzor;
}
public function setIbanSponzor(string $ibanSponzor): void
{
$this->ibanSponzor = $ibanSponzor;
}
public function getMeno(): string
{
return $this->meno;
}
public function setMeno(string $meno)
{
$this->meno = $meno;
}
public function getPlatnostKuDatumu(): DateTime
{
return $this->platnostKuDatumu;
}
public function setPlatnostKuDatumu(DateTime $platnostKuDatumu): void
{
$this->platnostKuDatumu = $platnostKuDatumu;
}
public function getPriezvisko(): string
{
return $this->priezvisko;
}
public function setPriezvisko(string $priezvisko): void
{
$this->priezvisko = $priezvisko;
}
public function getRodneCislo(): string
{
return $this->rodneCislo;
}
public function setRodneCislo(string $rodneCislo): void
{
$this->rodneCislo = $rodneCislo;
}
public function getMKrajina(): Krajina
{
return $this->m_Krajina;
}
public function setMKrajina(Krajina $m_Krajina): void
{
$this->m_Krajina = $m_Krajina;
}
public function getMPohlavie(): Pohlavie
{
return $this->m_Pohlavie;
}
public function setMPohlavie(Pohlavie $m_Pohlavie): void
{
$this->m_Pohlavie = $m_Pohlavie;
}
public function getMStatnaPrislusnost(): Krajina
{
return $this->m_StatnaPrislusnost;
}
public function setMStatnaPrislusnost(Krajina $m_StatnaPrislusnost): void
{
$this->m_StatnaPrislusnost = $m_StatnaPrislusnost;
}
public function getMDorucovaciaAdresa(): Adresa
{
return $this->m_DorucovaciaAdresa;
}
public function setMDorucovaciaAdresa(Adresa $m_Adresa): void
{
$this->m_DorucovaciaAdresa = $m_Adresa;
}
public function getMTrvaleBydlisko(): Adresa
{
return $this->m_TrvaleBydlisko;
}
public function setMTrvaleBydlisko(Adresa $m_TrvaleBydlisko): void
{
$this->m_TrvaleBydlisko = $m_TrvaleBydlisko;
}
}
Okay, I found out what the problem was.
Doctrine connects to database through proxy object that are generated by doctrine itself. There are two options when creating configuration for doctrine - either you set dev mode to true or false. When dev mode is set to false, it does not generate proxies each time, only once. So you need to set dev mode to true at least once, or generate the classes yourself , that is explaied in this answer: https://stackoverflow.com/a/20231349/1869111

Zend Expressive API does not return contents of objects

I'm creating a small API, mostly for learning purposes, but, I might implement it into a project I'm working on. So far, I have installed the zend expressive skeleton application and set up my models and entities. I'm able to query the database and get results, but, when I return the results as a JSON Response, I can only see a list of empty arrays for each result. I would like to be able to return the actual objects that are being returned from the database instead of converting them to arrays.
HomePageHandler.php
<?php
declare(strict_types=1);
namespace App\Handler;
use App\Entity\Product;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Diactoros\Response\JsonResponse;
use Zend\Expressive\Router;
use Zend\Expressive\Template\TemplateRendererInterface;
use App\Model\ProductModel;
class HomePageHandler implements RequestHandlerInterface
{
/** #var string */
private $containerName;
/** #var Router\RouterInterface */
private $router;
/** #var null|TemplateRendererInterface */
private $template;
private $productModel;
public function __construct(
string $containerName,
Router\RouterInterface $router,
?TemplateRendererInterface $template = null,
ProductModel $productModel
) {
$this->containerName = $containerName;
$this->router = $router;
$this->template = $template;
$this->productModel = $productModel;
}
public function handle(ServerRequestInterface $request) : ResponseInterface
{
$data = $this->productModel->fetchAllProducts();
return new JsonResponse([$data]);
//return new HtmlResponse($this->template->render('app::home-page', $data));
}
}
I'm expecting a JSON Response returned with a list of 18 "Product" entities. My results look like.
[[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}]]
Let me know if there is any other code you would like to see.
Thanks in advance!
Edited with Product.php code
<?php
/**
* Created by PhpStorm.
* User: Brock H. Caldwell
* Date: 3/14/2019
* Time: 4:04 PM
*/
namespace App\Entity;
class Product
{
protected $id;
protected $picture;
protected $shortDescription;
protected $longDescription;
protected $dimensions;
protected $price;
protected $sold;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #param mixed $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return mixed
*/
public function getPicture()
{
return $this->picture;
}
/**
* #param mixed $picture
*/
public function setPicture($picture)
{
$this->picture = $picture;
}
/**
* #return mixed
*/
public function getShortDescription()
{
return $this->shortDescription;
}
/**
* #param mixed $shortDescription
*/
public function setShortDescription($shortDescription)
{
$this->shortDescription = $shortDescription;
}
/**
* #return mixed
*/
public function getLongDescription()
{
return $this->longDescription;
}
/**
* #param mixed $longDescription
*/
public function setLongDescription($longDescription)
{
$this->longDescription = $longDescription;
}
/**
* #return mixed
*/
public function getDimensions()
{
return $this->dimensions;
}
/**
* #param mixed $dimensions
*/
public function setDimensions($dimensions)
{
$this->dimensions = $dimensions;
}
/**
* #return mixed
*/
public function getPrice()
{
return $this->price;
}
/**
* #param mixed $price
*/
public function setPrice($price)
{
$this->price = $price;
}
/**
* #return mixed
*/
public function getSold()
{
return $this->sold;
}
/**
* #param mixed $sold
*/
public function setSold($sold)
{
$this->sold = $sold;
}
public function exchangeArray($data)
{
$this->id = (!empty($data['id'])) ? $data['id'] : null;
$this->picture = (!empty($data['picture'])) ? $data['picture'] : null;
$this->shortDescription = (!empty($data['shortDescription'])) ? $data['shortDescription'] : null;
$this->longDescription = (!empty($data['longDescription'])) ? $data['longDescription'] : null;
$this->dimensions = (!empty($data['dimensions'])) ? $data['dimensions'] : null;
$this->price = (!empty($data['price'])) ? $data['price'] : null;
$this->sold = (!empty($data['sold'])) ? $data['sold'] : null;
}
}
You need to either make the properties public, or implement the JsonSerializable interface in your Product entity. All of its properties are protected, which is fine, but that means they aren't exposed when the object is JSON encoded.
Here are some brief examples:
class Example1 { protected $a = 1; }
class Example2 { public $b = 2; }
class Example3 implements JsonSerializable {
protected $c = 3;
public function jsonSerialize() {
return ['c' => $this->c];
}
}
echo json_encode([new Example1, new Example2, new Example3]);
The result:
[{},{"b":2},{"c":3}]
If you choose to implement JsonSerializable, exactly how you do it is up to you, but you just need a jsonSerialize() method that returns the properties you want in the JSON result in a format accessible to json_encode (an object with public properties or an array).

Api-platform - Virtual property - Collection denormalization

I have Reservation entity, which contains some ReservationRev(isions) collection. ReservationRev contains PersonStay collection.
In Reservation I have virtual property personStays (getter, add, remove, setter).
When I try to POST Reservation with personStays I get an error - denormalizer for Collection can't be found.
class Reservation
/**
* #var Collection
* #Doctrine\ORM\Mapping\OneToMany(
* targetEntity="Zakjakub\OswisAccommodationBundle\Entity\ReservationRev",
* mappedBy="container",
* cascade={"all"},
* orphanRemoval=true
* )
*/
protected $revisions;
public function __construct(Nameable $nameable, ...) {
$this->revisions = new ArrayCollection();
$this->addRevision(new ReservationRev($nameable));
...
}
final public function addPersonStay(?PersonStay $personStay): void
{
if (!$this->getPersonStays()->contains($personStay)) {
$newRevision = clone $this->getRevisionByDate();
$newRevision->addPersonStay($personStay);
$this->addRevision($newRevision);
}
}
final public function getPersonStays(?\DateTime $dateTime = null): Collection
{
return $this->getRevisionByDate($dateTime)->getPersonStays() ?? new ArrayCollection();
}
final public function removePersonStay(?PersonStay $personStay): void
{
if ($this->getPersonStays()->contains($personStay)) {
$newRevision = clone $this->getRevisionByDate();
$newRevision->removePersonStay($personStay);
$this->addRevision($newRevision);
}
}
final public function setPersonStays(?Collection $personStays): void
{
$newRevision = clone $this->getRevisionByDate();
$newRevision->setPersonStays($personStays ?? new ArrayCollection());
$this->addRevision($newRevision);
}
class ReservationRev
/**
* #var Collection
* #Doctrine\ORM\Mapping\ManyToMany(
* targetEntity="Zakjakub\OswisAccommodationBundle\Entity\PersonStay",
* inversedBy="reservationRevisions",
* cascade={"all"}
* )
* #Doctrine\ORM\Mapping\JoinTable(name="accommodation_reservations_person_stays")
*/
protected $personStays;
/**
* #var Reservation
* #Doctrine\ORM\Mapping\ManyToOne(
* targetEntity="Zakjakub\OswisAccommodationBundle\Entity\Reservation",
* inversedBy="revisions"
* )
* #Doctrine\ORM\Mapping\JoinColumn(name="container_id", referencedColumnName="id")
*/
protected $container;
public function __construct(Nameable $nameable, ...) {
$this->personStays = new ArrayCollection();
...
}
final public function addPersonStay(?PersonStay $personStay): void
{
if ($personStay) {
return;
}
if (!$this->personStays->contains($personStay)) {
$this->personStays->add($personStay);
$personStay->addReservationRevision($this);
}
}
final public function removePersonStay(?PersonStay $personStay): void
{
if ($personStay) {
return;
}
if ($this->personStays->removeElement($personStay)) {
$personStay->removeReservationRevision($this);
}
}
final public function setPersonStays(Collection $personStays): void
{
foreach ($this->personStays as $personStay) {
if (!$personStays->contains($personStay)) {
$this->removePersonStay($personStay);
}
}
foreach ($personStays as $personStay) {
if (!$this->personStays->contains($personStay)) {
$this->addPersonStay($personStay);
}
}
}
/**
* #return Collection
*/
final public function getPersonStays(): Collection
{
return $this->personStays ?? new ArrayCollection();
}
class PersonStay
/**
* #var Person
* #Doctrine\ORM\Mapping\ManyToOne(
* targetEntity="Zakjakub\OswisAccommodationBundle\Entity\Person",
* inversedBy="personStays",
* cascade={"all"}
* )
* #Doctrine\ORM\Mapping\JoinColumn(name="person_id", referencedColumnName="id")
*/
protected $person;
/**
* #var Collection
* #Doctrine\ORM\Mapping\ManyToMany(
* targetEntity="Zakjakub\OswisAccommodationBundle\Entity\ReservationRev",
* mappedBy="personStays"
* )
*/
protected $reservationRevisions;
/**
* #var Room
* #Doctrine\ORM\Mapping\ManyToOne(
* targetEntity="Zakjakub\OswisAccommodationBundle\Entity\Room",
* inversedBy="personStays",
* cascade={"persist"}
* )
* #Doctrine\ORM\Mapping\JoinColumn(name="room_id", referencedColumnName="id")
*/
protected $room;
public function __construct(
?Person $person = null,
?ReservationRev $reservationRevision = null,
?Room $room = null,
...
) {
$this->setPerson($person);
$this->reservationRevisions = new ArrayCollection();
$this->addReservationRevision($reservationRevision);
$this->setRoom($room);
...
}
/**
* #param ReservationRev|null $reservationRev
*/
final public function addReservationRevision(?ReservationRev $reservationRev): void
{
if (!$reservationRev) {
return;
}
if (!$this->reservationRevisions->contains($reservationRev)) {
$this->reservationRevisions->add($reservationRev);
$reservationRev->addPersonStay($this);
}
}
/**
* #return Person
*/
final public function getPerson(): ?Person
{
return $this->person;
}
/**
* Set person.
*
* #param Person|null $person
*/
final public function setPerson(?Person $person): void
{
if (null !== $this->person) {
$this->person->removePersonStay($this);
}
if ($person && $this->person !== $person) {
$person->addPersonStay($this);
$this->person = $person;
}
}
/**
* #return Room|null
*/
final public function getRoom(): ?Room
{
return $this->room;
}
/**
* #param Room|null $room
*/
final public function setRoom(?Room $room): void
{
if (null !== $this->room) {
$this->room->removePersonStay($this);
}
if ($room && $this->room !== $room) {
$room->addPersonStay($this);
$this->room = $room;
}
}
/**
* #return Collection
*/
final public function getReservationRevisions(): Collection
{
return $this->reservationRevisions ?? new ArrayCollection();
}
/**
* #param ReservationRev|null $reservationRev
*/
final public function removeReservationRevision(?ReservationRev $reservationRev): void
{
if (!$reservationRev) {
return;
}
if ($this->reservationRevisions->removeElement($reservationRev)) {
$reservationRev->removePersonStay($this);
}
}
POST
{
"personStays": null (or [] or [<some person stays>]),
"description": "string",
"note": "string"
}
Error
Could not denormalize object of type Doctrine\\Common\\Collections\\Collection, no supporting normalizer found.
Symfony 4.2.2
Api-platform 2.3.6

Decode multiple model in php symfony from json to object

I would like to convert a json into a object / model.
If the json is only one-dimensional, it works perfectly.
But if it is multidimensional, only the outer (user) is converted, but not the inner (company), this remains an array.
Can you help me with this?
The Models:
<?php
namespace AppBundle;
class Company {
/**
* #var string
*/
protected $companyName = '';
/**
* #return string
*/
public function getCompanyName()
{
return $this->companyName;
}
/**
* #param string $companyName
* #return void
*/
public function setCompanyName($companyName)
{
$this->companyName = $companyName;
}
}
class User {
/**
* #var \AppBundle\Company
*/
protected $company = null;
/**
* #var string
*/
protected $username = '';
/**
* #return \AppBundle\Company
*/
public function getCompany() {
return $this->company;
}
/**
* #param \AppBundle\Company $company
* #return void
*/
public function setCompany($company) {
$this->company = $company;
}
/**
* #return string
*/
public function getUsername() {
return $this->username;
}
/**
* #param string $username
* #return void
*/
public function setUsername($username) {
$this->username = $username;
}
}
?>
Convert json to model:
<?php
namespace AppBundle\Controller;
class DefaultController extends \Symfony\Bundle\FrameworkBundle\Controller\Controller
{
public function indexAction()
{
// Initialize serializer
$objectNormalizer = new \Symfony\Component\Serializer\Normalizer\ObjectNormalizer();
$jsonEncoder = new \Symfony\Component\Serializer\Encoder\JsonEncoder();
$serializer = new \Symfony\Component\Serializer\Serializer([$objectNormalizer], [$jsonEncoder]);
// Set test model
$company = new \AppBundle\Company();
$company->setCompanyName('MyCompany');
$user = new \AppBundle\User();
$user->setCompany($company);
$user->setUsername('MyUsername');
// Serialize test model to json
$json = $serializer->serialize($user, 'json');
dump($user); // Model ok, Company is instance of \AppBundle\Company
dump($json); // json ok + valide
// Deserialize json to model
$user = $serializer->deserialize($json, \AppBundle\User::class, 'json');
dump($user); // Error: Company is now array instead instance of \AppBundle\Company
// Denormalize json to model
$userArray = $serializer->decode($json, 'json');
$user = $serializer->denormalize($userArray, \AppBundle\User::class);
dump($user); // Error: Company is now array instead instance of \AppBundle\Company
}
}
?>
I solved the problem.
On the one hand you need PHP 7. Annotations I have not yet tested.
Then the variable has to be set correctly in setCompany().
public function setCompany(Company $company) {
$this->company = $company;
}
And the ReflectionExtractor() must be used.
use Symfony\Component\Serializer\Normalizer;
use Symfony\Component\PropertyInfo\Extractor;
$objectNormalizer = new ObjectNormalizer(
null,
null,
null,
new ReflectionExtractor()
);
You only need deserialize(), because it decode() and denormalize().
http://symfony.com/doc/current/components/serializer.html
Full fixed code:
Company class:
class Company {
/**
* #var string
*/
protected $companyName = '';
/**
* #return string
*/
public function getCompanyName() {
return $this->companyName;
}
/**
* #param string $companyName
* #return void
*/
public function setCompanyName($companyName) {
$this->companyName = $companyName;
}
}
User class:
class User {
/**
* #var \AppBundle\Company
*/
protected $company = null;
/**
* #var string
*/
protected $username = '';
/**
* #return \AppBundle\Company
*/
public function getCompany() {
return $this->company;
}
/**
* #param \AppBundle\Company $company
* #return void
*/
public function setCompany(Company $company) {
$this->company = $company;
}
/**
* #return string
*/
public function getUsername() {
return $this->username;
}
/**
* #param string $username
* #return void
*/
public function setUsername($username) {
$this->username = $username;
}
}
?>
Controller class:
<?php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller;
use Symfony\Component\Serializer\Normalizer;
use Symfony\Component\PropertyInfo\Extractor;
use Symfony\Component\Serializer;
class DefaultController extends Controller {
public function indexAction() {
$objectNormalizer = new ObjectNormalizer(
null,
null,
null,
new ReflectionExtractor()
);
$jsonEncoder = new JsonEncoder();
$serializer = new Serializer([$objectNormalizer], [$jsonEncoder]);
$company = new \AppBundle\Company();
$company->setCompanyName('MyCompany');
$user = new \AppBundle\User();
$user->setCompany($company);
$user->setUsername('MyUsername');
$json = $serializer->serialize($user, 'json');
dump($user, $json);
$user2 = $serializer->deserialize($json, \AppBundle\User::class, 'json');
dump($user2);
}
}
?>

zf2 doctrine odm collection hydration

Hi everybody Im using doctrine ODM and have trouble with hydrator. I can't make extract on document with embed collection nor reference Class. the extract result for these class give me object and i really need to have them in array for rest module which is consumed by backbone implementation.
Here a example class :
Analyse.php Document
<?php
namespace Application\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\Common\Collections\Collection;
/**
* Application\Document\Analyse
*
* #ODM\Document(collection="analyse")
*/
class Analyse extends BaseDocument
{
/**
* #ODM\Id
*/
protected $id;
/**
* #ODM\Field(type="string")
*/
protected $nom;
/**
* #ODM\Field(type="string")
* #ODM\Index
*/
protected $alias;
/**
* #ODM\EmbedMany(targetDocument="ElementsAnalyse")
*/
protected $elements = array();
public function __construct()
{
parent::__construct();
$this->elements = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function setNom($nom)
{
$this->nom = $nom;
return $this;
}
public function getNom()
{
return $this->nom;
}
public function setAlias($alias)
{
$this->alias = $alias;
return $this;
}
public function getAlias()
{
return $this->alias;
}
public function addElements(Collection $elements)
{
foreach ($elements as $element) {
$this->elements->add($element);
}
}
public function removeElements(Collection $elements)
{
foreach ($elements as $item) {
$this->elements->removeElement($item);
}
}
public function getElements()
{
return $this->elements;
}
}
ElementAnalyse.php Collection
<?php
namespace Application\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as ODM;
use Doctrine\Common\Collections\Collection;
/**
* Application\Document\Valeurnormales
*
* #ODM\EmbeddedDocument
*
*/
class ElementsAnalyse
{
/**
* #ODM\Field(type="string")
*/
protected $nom;
/**
* #ODM\Field(type="string")
*/
protected $unite;
/**
* #ODM\EmbedMany(targetDocument="ValeurNormales")
*/
protected $valeurnormales = array();
/**
* #ODM\Field(type="string")
*/
protected $type;
public function __construct()
{
$this->valeurnormales = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Set nom
*/
public function setNom($nom)
{
$this->nom = $nom;
return $this;
}
/**
* Get nom
*/
public function getNom()
{
return $this->nom;
}
/**
* Set unite
*/
public function setUnite($unite)
{
$this->unite = $unite;
return $this;
}
/**
* Get unite
*
* #return string
*/
public function getUnite()
{
return $this->unite;
}
/**
* add valeurnormales
*/
public function addValeurnormales(Collection $vn)
{
foreach ($vn as $item) {
$this->valeurnormales->add($item);
}
}
public function removeValeurnormales(Collection $vn)
{
foreach ($vn as $item) {
$this->valeurnormales->removeElement($item);
}
}
/**
* Get valeurnormales
*/
public function getValeurnormales()
{
return $this->valeurnormales;
}
/**
* Set type
*
* #param $type
* #return Analyse
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* Get type
*
* #return Type
*/
public function getType()
{
return $this->type;
}
/**
* toArray function
*/
public function toArray()
{
return get_object_vars($this);
}
/**
* fromArray function
*
*/
public function fromArray(array $array)
{
$objects = $this->toArray();
foreach($array as $item => $value)
{
if(array_key_exists($item, $objects))
{
$this->$item = $value;
}
}
}
}
Here my getList Method
public function getList()
{
$hydrator = new DoctrineHydrator($entityManager, 'Application\Document\Analyse');
$service = $this->getAnalyseService();
$results = $service->findAll();
$data = $hydrator->extract($results);
return new JsonModel($data);
}
And obviously var_dump($data['elements']) return object Collection or proxy class
Can You help me. Anything will be appreciated it been 2 weeks i can't make it work.
Read about Hydrator Strategy out there but i don't knnow how to implement it.
Currently, the Doctrine ODM implementation does not provide recursion for embedded objects and references.
If you use var_dump() on your $hydrator->extract($results), you'll see that all your embeds/references are there in their original object format.
What you can do here is to use Zend\Stdlib\Hydrator\Strategy, and define your own logic for extraction/hydration. Doctrine extends Zend Framework 2's hydrators and strategies.

Categories