Virtual (generated) columns in Doctrine - php

I was wondering if there was still no way to work with fake or generated (virtual?) properties in Doctrine?
I take an old example: a Person entity with a firstname and a lastname (based on a table with these two fields only).
Is it possible to create a virtual property fullName which would not be linked to any column in the table and which could be accessible without calling a handmade getFullName?
I obviously don't want to have to do it myself with the QueryBuilder.
/**
* #ORM\Column(name="LASTNAME", type="string", length=100, nullable=true)
*/
private $lastname;
/**
* #ORM\Column(name="FIRSTNAME", type="string", length=100, nullable=true)
*/
private $firstname;
/**
* #ORM\Column(type="string")
*/
private string $fullName = "";
public function __construct()
{
$this->fullName = $this->firstname . " " . $this->firstname;
}

I just came across this question on a slightly different subject... but it includes the solution to my issue.
It's actually enough to add an #ORM\HasLifecycleCallbacks on the Person entity and add an #ORM\PostLoad() on the virtual property (unmapped) init function.
Here is the final working solution:
/**
* Person
*
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class Person
{
/**
* #ORM\Column(name="LASTNAME", type="string")
*/
private $lastname;
/**
* #ORM\Column(name="FIRSTNAME", type="string")
*/
private $firstname;
//Non mapped property
private $fullName;
/**
* #ORM\PostLoad()
*/
public function initFullName()
{
$this->fullName = $this->lastname . ' ' . $this->firstname;
}
}

Related

One entity having 2 ManyToOne relations to the same other entity

I'm working with Symfony5. I have 2 entities with relationships, Character and Wedding.
Each Character can have many Wedding.
Each Wedding is related to 2 different Character.
/**
* #ORM\Entity(repositoryClass=CharacterRepository::class)
*/
class Character
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=50, nullable=true)
*/
private $firstName;
/**
* #ORM\Column(type="string", length=50, nullable=true)
*/
private $lastName;
[...]
And Wedding :
/**
* #ORM\Entity(repositoryClass=WeddingRepository::class)
*/
class Wedding
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=4, nullable=true)
*/
private $startYear;
/**
* #ORM\Column(type="string", length=4, nullable=true)
*/
private $endYear;
[...]
}
I tried to set 2 different ManyToOne relations in Wedding entity (persona1 and persona2) but persona1 and persona2 had the same inversedBy="weddings", so it's not working.
I tried 2 ManyToMany relations too, but Doctrine didn't like it :
// Character
/**
* #ORM\ManyToMany(targetEntity=Wedding::class, mappedBy="persona1")
*/
private $weddings1;
/**
* #ORM\ManyToMany(targetEntity=Wedding::class, mappedBy="persona2")
*/
private $weddings2;
// Wedding
/**
* #ORM\ManyToMany(targetEntity=Character::class, inversedBy="weddings1")
*/
private $persona1;
/**
* #ORM\ManyToMany(targetEntity=Character::class, inversedBy="weddings2")
*/
private $persona2;
The mappings App\Entity\Wedding#persona2 and App\Entity\Character#weddings are inconsistent with each other.
What is the good relationship, ManyToOne or ManyToMany, since each Wedding is related to 2 Characters ? How to make it works with Doctrine ?
Thanks for all suggestion !
Ash
I would suggest to have ManyToMany relation between Wedding and Character entities if there is no such specific reason or differentiation between character 1 and character 2
class Character
{
// ...
/**
* #ORM\ManyToMany(targetEntity=Wedding::class, mappedBy="characters")
*/
private $weddings;
}
class Wedding
{
// ...
/**
* #ORM\ManyToMany(targetEntity=Character::class, inversedBy="weddings")
*/
private $characters;
}
This way you can scale it in future if you have more characters to be assigned to a wedding
If there are any strict actions that need to be performed specific to character 1 or character 2 depends on your needs, then you can use OneToMany and ManyToOne as
class Character
{
// ...
/**
* #ORM\OneToMany(targetEntity=Wedding::class, mappedBy="characterA")
*/
private $weddingsA;
/**
* #ORM\OneToMany(targetEntity=Wedding::class, mappedBy="characterB")
*/
private $weddingsB;
}
class Wedding
{
// ...
/**
* #ORM\ManyToOne(targetEntity=Character::class, inversedBy="weddingsA")
*/
private $characterA;
/**
* #ORM\ManyToOne(targetEntity=Character::class, inversedBy="weddingsB")
*/
private $characterB;
}

easyadmin 3 - Sorting by linked entity’s property instead of id

I have one entity Hike which have relation with another named Department
<?php
class Hike
{
private $id;
private $name;
private $description;
/**
* #ORM\ManyToOne(targetEntity=Department::class, inversedBy="hikes")
* #ORM\JoinColumn(nullable=false)
* #Assert\NotBlank(message="libdepartmentRequired")
*/
private $department;
// ...
}
<?php
class Department
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=3)
*/
private $department_code;
/**
* #ORM\Column(type="string", length=255)
*/
private $department_nom;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Hike", mappedBy="department")
*/
private $hikes;
// ...
}
In easyAdmin3 rendering is fine like
But when I sorting by department I see that easyAdmin3 sort by department's id, and I would like sorting by department_nom
I saw many solutions but they all using easyAdmin2 and easy_admin.yaml which no longer exists now.
There is a way to achieve that ?
Use configureCrud. Something like this should do the trick.
public function configureCrud(Crud $crud): Crud
{
return $crud
->setDefaultSort(['departement' => 'DESC'])
;
}
You can also use the filter to get the resultat wanted.

Entity relationship being set to null on update of form with removed form element

I have a page to record a survey entity that has a one to many relationship with a question entity, that is each survey can have many questions. Questions are composed of a question and answer property.
My goal is to have a form that only displays questions elements if they have not already had answers that have been submitted and persisted...
The form I want is nested the survey form contains 1..n question forms.
Say there are two question form elements on the page, name and age. If I submit the name and leave age blank name is persisted, and age remains blank. I then go back and submit age. Age is persisted, and name is still in the database but the foreign key survey_id is set to null on the question table, and the relationship is lost.
In oder to hide the answers I am removing them using form events on the PRE_SET_DATA event if the model contains data for the given element.
Does anyone have any suggestions for things to look for here?
I have changed the submit method from handle request to $form->submit (with the clearMissing flag set to false) but the survey_id is still being set to null. The submission is being done from the survey controller, do I have to also explicitly tell the question subforms to not 'clearMissing'?
*Edit: The solution I am considering is to add back the 'deleted' form elements and data on the PRE_SUBMIT form event, I think that would ensure that Doctrine won't assume the missing data requires a delete. I am not sure how sane this is though and if there's a better way?
Controller:
/**
* #Route("/client/{surveyIdentifier}/{clientIdentifier}", name="client_survey_form", methods={"GET","POST"})
*/
public function clientSurveyForm(Request $request, $surveyIdentifier, $clientIdentifier,
QuestionRepository $questionRepository): Response
{
$survey = $this->getDoctrine()->getRepository(Survey::class)
->findOneBy(['surveyIdentifier' => $surveyIdentifier]);
$form = $this->createForm(ClientSurveyType::class, $survey, [
]);
$form->submit($request->get($form->getName()), false);
if ($form->isSubmitted() && $form->isValid()) {
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('client_confirmation',
[
'surveyIdentifier' => $surveyIdentifier,
'clientIdentifier' => $clientIdentifier
]
);
}
}
Survey Entity:
/**
* #ORM\Entity(repositoryClass="App\Repository\SurveyRepository")
*/
class Survey
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Question", mappedBy="survey", cascade={"persist"})
*/
private $questions;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Staff", inversedBy="surveys", cascade="persist")
*/
private $staff;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Client", inversedBy="surveys")
*/
private $client;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Job", inversedBy="surveys")
*/
private $job;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $type;
/**
* #ORM\Column(type="string", length=128, nullable=true)
*/
private $surveyIdentifier;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $clientSurveyUrl;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $clientComments;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $clientImpedimentForSurvey;
Question Entity:
/**
* #ORM\Entity(repositoryClass="App\Repository\QuestionRepository")
*/
class Question
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $question;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $answer;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $answerType;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $answerChoices;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Survey", inversedBy="questions")
*/
private $survey;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $completionDate;
/**
* #ORM\OneToMany(targetEntity="App\Entity\QuestionTransactionLog", mappedBy="question")
*/
private $questionTransactionLogs;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $attachmentFilename;
As discussed, instead of trying to make the "automagic" work, you can go the fast and crude way of retrieving the survey from the DB and manually updating the answers based on what was submitted by the user.
I hope this helps. I will update my answer if I find something more useful

Symfony 3 create Atribute of a manyToMany relation in a form

I try to create a form that allows me to create a new profile and to answer some questions during the creation.
My questions are already in the database.
My 3 entities are subject- question - answer.
Subject :
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=60, unique=true)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Answer", mappedBy="Subject", cascade={"persist"})
*/
private $Answers;
Answer:
/**
* #ORM\Column(name="text", type="string", length=255)
*/
private $text;
/**
* #ORM\ManyToOne(targetEntity="Subject",inversedBy="Answer")
* #ORM\JoinColumn(name="subject_id", referencedColumnName="id")
* #ORM\JoinColumn(nullable=false)
*/
private $Subject;
/**
* #ORM\ManyToOne(targetEntity="Question",inversedBy="Answer")
* #ORM\JoinColumn(name="question_id", referencedColumnName="id")
* #ORM\JoinColumn(nullable=false)
*/
private $Question;
Question
/**
* #var string
*
* #ORM\Column(name="text", type="string", length=255)
*/
private $text;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Answer", mappedBy="Question")
*/
private $Answer;
I tried many solutions but I dont know how to deal with the form creation and controller data reception. Any help for find the right method to use will be apreciated.

Symfony & doctrine - return json encoded object with extra property and properly encoded DateTime

I am sorry for the title being so non-descriptive but the question I am asking is way to broad to fit in 10 words.
I have an Entity called Offlicence.
/**
* #ORM\Table(name="offlicences")
* #ORM\Entity(repositoryClass="Boozator\LocatorBundle\Entity\Repository\OfflicenceRepository")
* #ORM\HasLifecycleCallbacks
*/
class Offlicence
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="text")
*/
protected $name;
/**
* #ORM\Column(type="text")
*/
protected $address;
/**
* #ORM\Column(type="string")
*/
protected $postcode;
/**
* #ORM\Column(type="string")
*/
protected $phone;
/**
* #ORM\Column(type="text")
*/
protected $notes;
/**
* #ORM\Column(type="string", nullable=true)
*/
protected $latitude;
/**
* #ORM\Column(type="string", nullable=true)
*/
protected $longitude;
/**
* #ORM\Column(type="datetime")
*/
protected $created;
/**
* #ORM\Column(type="datetime")
*/
protected $updated;
I also have an OfflicenceRepostitory with a method of getNearest()
public function getNearest($address)
{
//geocode the address
$location = $this->geocode($address);
if(!$location)
{
return false;
}
$latitude = $location[self::LATITUDE_TAG];
$longitude = $location[self::LONGITUDE_TAG];
return $this->createQueryBuilder('o')
->select('o')
//3960 for miles & 6371 for kilometers
->addSelect("(6371 * ACOS(SIN(RADIANS($latitude)) * SIN(RADIANS(o.latitude)) + COS(RADIANS($latitude)) * COS(RADIANS(o.latitude)) * COS(RADIANS(o.longitude) - RADIANS($longitude)))) as distance")
->having("distance < 5")
->orderBy("distance")
->getQuery()
->getResult();
}
And the controller that gets a list of shops
public function getNearbyAction($address)
{
$em = $this->getDoctrine()
->getEntityManager();
$locator = $em->getRepository('BoozatorLocatorBundle:Offlicence');
$shops = $locator->getNearest($address);
$ret = json_encode($shops);
return new Response($ret, 200, array('Content-Type'=>'application/json'));//make sure it has the correct content type
}
As a result I am getting json encoded array of 2 elements: the array representation of the object (although DateTime objects are dodgy) and a distance.
I am trying to find a cleanest way to return an array of all details and distance as one element, merged. Ideally I'd like to add a new property "distance" to the Offlicence so that it is populated by the query but does not have a corresponding column in a database. I am also trying to json_encode the data I am getting but DateTime objects are converted into another ugly looking array. Can you please tell me what to look at as I am new to Symfony and it's endless world of helpers and extensions?

Categories