Api-platform - Virtual property - Collection denormalization - php

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

Related

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

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.

a PHPpunit test fail randomly

I have a Quiz class.
This class load 10 questions from a database depending on the level and the type of the quiz Object:
level 0 load the ten first, level 1 load the next ten and so on.
So in my test i create in a test database 30 questions.
Then i create quiz object with different level and i check that the first question in the quiz steps array match what i expect.
This test "quiz_contain_steps_depending_on_type_and_level()" failed randomly at least once every 5 launches.
This is the QuizTest class
<?php
namespace App\Tests\Quiz;
use App\Quiz\Question;
use App\Quiz\Quiz;
use App\Quiz\QuizQuestionRepositoryManager;
use App\Quiz\QuizStep;
use App\Quiz\QuizType;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ObjectRepository;
use Faker\Factory;
use Faker\Generator;
use ReflectionException;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Config\Definition\Exception\Exception;
class QuizTest extends KernelTestCase
{
use QuestionLoremTrait;
use PrivatePropertyValueTestTrait;
private Generator $faker;
private ?EntityManagerInterface $em;
private ObjectRepository $questionRepo;
private QuizQuestionRepositoryManager $quizQuestionManager;
protected function setUp(): void
{
$kernel = self::bootKernel();
$this->faker = Factory::create();
$this->em = $kernel->getContainer()->get('doctrine')->getManager();
$this->em->getConnection()->beginTransaction();
$this->questionRepo = $kernel->getContainer()->get('doctrine')->getRepository(Question::class);
$this->quizQuestionManager = new QuizQuestionRepositoryManager($this->questionRepo);
}
protected function tearDown(): void
{
parent::tearDown();
$this->em->getConnection()->rollBack();
$this->em->close();
$this->em = null;
}
/**
* #test
* #dataProvider provideQuizDataAndFirstQuestionExpectedIndex
* #param array $quizData
* #param int $firstQuestionExpectedIndex
* #throws ReflectionException
* #throws \Exception
*/
public function quiz_contain_steps_depending_on_type_and_level(array $quizData, int $firstQuestionExpectedIndex)
{
//We have questions in db
$questions = [];
for ($q = 1; $q <= 30; $q++) {
$question = $this->persistLoremQuestion($this->faker, $this->em);
$questions[] = $question;
}
$this->em->flush();
//When we create Quiz instance $quiz
$quiz = new Quiz($this->quizQuestionManager,quizData: $quizData);
//When we look at this $quiz steps property
$quizSteps = $quiz->getSteps();
/** #var QuizStep $firstStep */
$firstStep = $quizSteps[0];
//We expect
$this->assertNotEmpty($quizSteps);
$this->assertCount(10, $quizSteps);
//We expect if quiz is type normal and level variable questions depends of level:
$this->assertEquals($firstStep->getQuestion(), $questions[$firstQuestionExpectedIndex]);
}
public function provideQuizDataAndFirstQuestionExpectedIndex(): array
{
return [
[[], 0],
[['type' => QuizType::NORMAL, 'level' => '1'], 10],
[['type' => QuizType::NORMAL, 'level' => '2'], 20]
];
}
}
This is the Trait who generate fake question
<?php
namespace App\Tests\Quiz;
use App\Quiz\Question;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Faker\Generator;
Trait QuestionLoremTrait{
/**
* This function persist a aleatory generated question, you must flush after
* #param Generator $faker
* #param EntityManagerInterface $em
* #return Question
* #throws Exception
*/
public function persistLoremQuestion(Generator $faker, EntityManagerInterface $em): Question
{
$nbrOfProps = random_int(2,4);
$answerPosition = random_int(0, $nbrOfProps - 1);
$props = [];
for ($i = 0; $i < $nbrOfProps; $i++){
$props[$i] = $faker->sentence ;
}
$question = new Question();
$question
->setSharedId(random_int(1, 2147483647))
->setInfo($faker->paragraph(3))
->setStatement($faker->sentence ."?")
->setProps($props)
->setAnswerPosition($answerPosition)
;
$em->persist($question);
return $question;
}
}
This is my Quiz class:
<?php
namespace App\Quiz;
use Symfony\Component\Config\Definition\Exception\Exception;
class Quiz
{
/**
* Quiz constructor.
* #param QuizQuestionManagerInterface $quizQuestionManager
* #param array $quizData
* This array of key->value represent quiz properties.
* Valid keys are 'step','level','type'.
* You must use QuizType constant as type value
* #param string $type
* #param int $level
* #param int $currentStep
* #param array $steps
*/
public function __construct(
private QuizQuestionManagerInterface $quizQuestionManager,
private string $type = QuizType::FAST,
private int $level = 0,
private array $quizData = [],
private int $currentStep = 0,
private array $steps = [])
{
if ($quizData != []) {
$this->hydrate($quizData);
}
$this->setSteps();
}
private function hydrate(array $quizData)
{
foreach ($quizData as $key => $value) {
$method = 'set' . ucfirst($key);
// If the matching setter exists
if (method_exists($this, $method) && $method != 'setQuestions') {
// One calls the setter.
$this->$method($value);
}
}
}
public function getCurrentStep(): int
{
return $this->currentStep;
}
public function getLevel(): int
{
return $this->level;
}
public function getType(): string
{
return $this->type;
}
public function getSteps(): array
{
return $this->steps;
}
private function setCurrentStep($value): void
{
$this->currentStep = $value;
}
private function setLevel(int $level): void
{
$this->level = $level;
}
private function setType($type): void
{
if (!QuizType::exist($type)) {
throw new Exception("This quiz type didn't exist, you must use QuizType constante to define type", 400);
}
$this->type = $type;
}
private function setSteps()
{
$this->steps = [];
$questions = $this->quizQuestionManager->getQuestions($this->type, $this->level);
foreach ($questions as $question) {
$this->steps[] = new QuizStep(question: $question);
}
}
}
This is the Question class:
<?php
namespace App\Quiz;
use App\Repository\QuestionRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass=QuestionRepository::class)
*/
class Question
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private ?int $id;
/**
* #ORM\Column(type="integer")
*/
private ?int $sharedId;
/**
* #ORM\Column(type="string", length=1000, nullable=true)
* #Assert\Length(max=1000)
*/
private ?string $info;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private ?string $statement;
/**
* #ORM\Column(type="array")
*/
private array $props = [];
/**
* #ORM\Column(type="integer")
*/
private ?int $answerPosition;
public function getId(): ?int
{
return $this->id;
}
public function getSharedId(): ?int
{
return $this->sharedId;
}
public function setSharedId(int $sharedId): self
{
$this->sharedId = $sharedId;
return $this;
}
public function getInfo(): ?string
{
return $this->info;
}
public function setInfo(?string $info): self
{
$this->info = $info;
return $this;
}
public function getStatement(): ?string
{
return $this->statement;
}
public function setStatement(?string $statement): self
{
$this->statement = $statement;
return $this;
}
public function getProps(): ?array
{
return $this->props;
}
public function setProps(array $props): self
{
$this->props = $props;
return $this;
}
public function getAnswerPosition(): ?int
{
return $this->answerPosition;
}
public function setAnswerPosition(int $answerPosition): self
{
$this->answerPosition = $answerPosition;
return $this;
}
}
If anyone understands this behavior. I thank him in advance for helping me sleep better :-)
Thanks to #AlessandroChitolina comments.
The set of questions created in my test was not always recorded in the same order by my in my database.
So instead of testing the expected question from my starting $questions array, i retrieve the questions from the database in a new $dbQuestions array. That solve my problème.
This is the new test:
/**
* #test
* #dataProvider provideQuizDataAndFirstQuestionExpectedIndex
* #param array $quizData
* #param int $firstQuestionExpectedIndex
* #throws \Exception
*/
public function quiz_contain_steps_depending_on_type_and_level(array $quizData, int $firstQuestionExpectedIndex)
{
//We have questions in db
$questions = [];
for ($q = 1; $q <= 30; $q++) {
$question = $this->persistLoremQuestion($this->faker, $this->em);
$questions[] = $question;
}
$this->em->flush();
$dbQuestions = $this->questionRepo->findAll();
//When we create Quiz instance $quiz
$quiz = new Quiz($this->quizQuestionManager,quizData: $quizData);
//When we look at this $quiz steps property
$quizSteps = $quiz->getSteps();
/** #var QuizStep $firstStep */
$firstStep = $quizSteps[0];
//We expect
$this->assertNotEmpty($quizSteps);
$this->assertCount(10, $quizSteps);
//We expect if quiz is type normal and level variable questions depends of level:
$this->assertEquals($firstStep->getQuestion(), $dbQuestions[$firstQuestionExpectedIndex]);
}

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

Symfony2 - CalendarBundle - How to fetch user informations from database to render on calendar

So, i am new to Symfony and i'm trying to create a functional calendar based application with events rendered from the database using calendar-bundle.
Passing with the documentation i was able to make a relationship between users and events rendered on the calendar but i'm stuck passing particular data, more exactly the user name.
Below is it shown the EventEntity which is responsible for calendar event's details.
<?php
namespace ADesigns\CalendarBundle\Entity;
/**
* Class for holding a calendar event's details.
*
* #author Mike Yudin <mikeyudin#gmail.com>
*/
class EventEntity
{
/**
* #var mixed Unique identifier of this event (optional).
*/
protected $id;
/**
* #var string Title/label of the calendar event.
*/
protected $title;
/**
* #var string URL Relative to current path.
*/
protected $url;
/**
* #var string HTML color code for the bg color of the event label.
*/
protected $bgColor;
/**
* #var string HTML color code for the foregorund color of the event label.
*/
protected $fgColor;
/**
* #var string css class for the event label
*/
protected $cssClass;
/**
* #var \DateTime DateTime object of the event start date/time.
*/
protected $startDatetime;
/**
* #var \DateTime DateTime object of the event end date/time.
*/
protected $endDatetime;
/**
* #var boolean Is this an all day event?
*/
protected $allDay = false;
/**
* #var array Non-standard fields
*/
protected $otherFields = array();
public function __construct($title, \DateTime $startDatetime, \DateTime $endDatetime = null, $allDay = false)
{
$this->title = $title;
$this->startDatetime = $startDatetime;
$this->setAllDay($allDay);
if ($endDatetime === null && $this->allDay === false) {
throw new \InvalidArgumentException("Must specify an event End DateTime if not an all day event.");
}
$this->endDatetime = $endDatetime;
}
/**
* Convert calendar event details to an array
*
* #return array $event
*/
public function toArray()
{
$event = array();
if ($this->id !== null) {
$event['id'] = $this->id;
}
$event['title'] = $this->title;
$event['start'] = $this->startDatetime->format("Y-m-d\TH:i:sP");
if ($this->url !== null) {
$event['url'] = $this->url;
}
if ($this->bgColor !== null) {
$event['backgroundColor'] = $this->bgColor;
$event['borderColor'] = $this->bgColor;
}
if ($this->fgColor !== null) {
$event['textColor'] = $this->fgColor;
}
if ($this->cssClass !== null) {
$event['className'] = $this->cssClass;
}
if ($this->endDatetime !== null) {
$event['end'] = $this->endDatetime->format("Y-m-d\TH:i:sP");
}
$event['allDay'] = $this->allDay;
foreach ($this->otherFields as $field => $value) {
$event[$field] = $value;
}
return $event;
}
public function setId($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
public function setTitle($title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
public function setUrl($url)
{
$this->url = $url;
}
public function getUrl()
{
return $this->url;
}
public function setBgColor($color)
{
$this->bgColor = $color;
}
public function getBgColor()
{
return $this->bgColor;
}
public function setFgColor($color)
{
$this->fgColor = $color;
}
public function getFgColor()
{
return $this->fgColor;
}
public function setCssClass($class)
{
$this->cssClass = $class;
}
public function getCssClass()
{
return $this->cssClass;
}
public function setStartDatetime(\DateTime $start)
{
$this->startDatetime = $start;
}
public function getStartDatetime()
{
return $this->startDatetime;
}
public function setEndDatetime(\DateTime $end)
{
$this->endDatetime = $end;
}
public function getEndDatetime()
{
return $this->endDatetime;
}
public function setAllDay($allDay = false)
{
$this->allDay = (boolean) $allDay;
}
public function getAllDay()
{
return $this->allDay;
}
/**
* #param string $name
* #param string $value
*/
public function addField($name, $value)
{
$this->otherFields[$name] = $value;
}
/**
* #param string $name
*/
public function removeField($name)
{
if (!array_key_exists($name, $this->otherFields)) {
return;
}
unset($this->otherFields[$name]);
}
}
Besides this i have the CalendarEventListener.php which is responsible for data sent to render on the calendar:
<?php
namespace AppBundle\EventListener;
use ADesigns\CalendarBundle\Event\CalendarEvent;
use ADesigns\CalendarBundle\Entity\EventEntity;
use Doctrine\ORM\EntityManager;
class CalendarEventListener
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function loadEvents(CalendarEvent $calendarEvent)
{
$startDate = $calendarEvent->getStartEventDate();
$endDate = $calendarEvent->getEndEventDate();
// The original request so you can get filters from the calendar
// Use the filter in your query for example
$request = $calendarEvent->getRequest();
$filter = $request->get('filter');
// load events using your custom logic here,
// for instance, retrieving events from a repository
$companyEvents = $this->entityManager->getRepository('AppBundle:companyEvents')
->createQueryBuilder('companyEvents')
->where('companyEvents.startEventDate BETWEEN :startEventDate and :endEventDate')
->setParameter('startEventDate', $startDate->format('Y-m-d H:i:s'))
->setParameter('endEventDate', $endDate->format('Y-m-d H:i:s'))
->getQuery()->getResult();
// $companyEvents and $companyEvent in this example
// represent entities from your database, NOT instances of EventEntity
// within this bundle.
//
// Create EventEntity instances and populate it's properties with data
// from your own entities/database values.
foreach($companyEvents as $companyEvent) {
$eventEntity = new EventEntity($companyEvent->getEventName(),
//
$companyEvent->getStartEventDate(),
$companyEvent->getEndEventDate()
,null, true);
//optional calendar event settings
$eventEntity->setAllDay(true); // default is false, set to true if this is an all day event
$eventEntity->setBgColor('#3366ff'); //set the background color of the event's label
$eventEntity->setFgColor('#FFFFFF'); //set the foreground color of the event's label
$eventEntity->setUrl('http://www.google.com'); // url to send user to when event label is clicked
$eventEntity->setCssClass('my-custom-class'); // a custom class you may want to apply to event labels
//finally, add the event to the CalendarEvent for displaying on the calendar
$calendarEvent->addEvent($eventEntity);
}
}
}
And the companyEvents entity for structuring events:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="companyEvents")
*/
class companyEvents
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* events are created by users
* #ORM\ManyToOne(targetEntity="User", inversedBy="events")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
/**
* #ORM\Column(name="event_name")
*/
private $eventName;
/**
* #ORM\Column(name="event_date", type="datetime")
*/
private $eventDate;
/**
* #ORM\Column(name="startEventDate", type="datetime")
*/
private $startEventDate;
/**
* #ORM\Column(name="endEventDate", type="datetime")
*/
private $endEventDate;
/**
* Set eventName
*
* #param string $eventName
*
* #return companyEvents
*/
public function setEventName($eventName)
{
$this->eventName = $eventName;
return $this;
}
/**
* Get eventName
*
* #return string
*/
public function getEventName()
{
return $this->eventName;
}
/**
* Set eventDate
*
* #param string $eventDate
*
* #return CompanyEvents
*/
public function setEventDate($eventDate)
{
$this->eventDate = $eventDate;
return $this;
}
/**
* Get eventDate
*
* #return string
*/
public function getEventDate()
{
return $this->eventDate;
}
/**
* Set start event date
* #param string $startEventDate
*
* #return companyEvents
*/
public function setStartEventDate($startEventDate)
{
$this->startEventDate = $startEventDate;
return $this;
}
/**
*Get start event date
* #return string
*/
public function getStartEventDate()
{
return $this->startEventDate;
}
/**
* Set start event date
* #param string $endEventDate
*
* #return companyEvents
*/
public function setEndEventDate($endEventDate)
{
$this->endEventDate = $endEventDate;
return $this;
}
/**
*Get start event date
* #return string
*/
public function getEndEventDate()
{
return $this->endEventDate;
}
/**
* set user relationship
* #param string $user_id
*
* #return companyEvents
*/
public function setUser($user)
{
$this->user = $user;
return $this;
}
/**
* #return string
*/
public function getUser()
{
return $this->user;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
So, my problem is that instead of getEventName passed here:
$eventEntity = new EventEntity($companyEvent->getEventName(),
$companyEvent->getStartEventDate(),
$companyEvent->getEndEventDate()
,null, true);
i want to pass the particular user name which belongs to the specific event but when i change the getEventName() (WHICH RENDERS THE TITLE OF THE EVENT) with any other function (i have tried to even pass just the user_id from the entity), nothing else is shown in the calendar without dumping any error.
Any hints on resolving this problem will be hardly appreciated :) !
If you want to render the username in the event's title you can change the getEventName method to return the user name:
public function getEventName()
{
return $this->user->getUsername();
}
This way you won't have to change the CalendarEventListener code.
Remember to remove the eventName property and it's setter method from the companyEventsentity. You should also name the entity CamelCased and in the singular, like this CompanyEvent.

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