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": "[email protected]",
"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
orCoordinates
embedded documents I would expect a bug in thelaminas-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:
Please note the
Query::HINT_READ_ONLY
passed as a 3rd argument tohydrate
. With this hint ODM will not mark hydrated objects as managed which is what you need for later insertion. HydratedEmbedOne
objects should be good to go without the hint, butEmbedMany
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.