I have an issue with {{app.user}} and Entity relation.
My user has a ManyToOne relation with an entity CustomerGroup:
**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #ORM\HasLifecycleCallbacks()
*/
class User implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\CustomerGroup")
* #ORM\JoinColumn(nullable=false)
*/
private $CustomerGroup;
...
My CustomerGroup Entity uses VichUploaderBundle :
/**
* #ORM\Entity(repositoryClass="App\Repository\CustomerGroupRepository")
* #Vich\Uploadable
*/
class CustomerGroup
{
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="customer_logo", fileNameProperty="imageName", size="imageSize")
*
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*
* #var string
*/
private $imageName;
/**
* #ORM\Column(type="integer", nullable=true)
*
* #var integer
*/
private $imageSize;
public function __construct(?File $imageFile = null)
{
$this->customerEntities = new ArrayCollection();
$this->models = new ArrayCollection();
$this->masterTypes = new ArrayCollection();
$this->documents = new ArrayCollection();
$this->deployModels = new ArrayCollection();
$this->imageFile = $imageFile;
if (null !== $imageFile) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->dateUpd = new \DateTimeImmutable();
}
}
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $imageFile
*/
public function setImageFile(?File $imageFile = null): void
{
$this->imageFile = $imageFile;
if (null !== $imageFile) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->dateUpd = new \DateTimeImmutable();
}
}
public function getImageFile(): ?File
{
return $this->imageFile;
}
public function setImageName(?string $imageName): void
{
$this->imageName = $imageName;
}
public function getImageName(): ?string
{
return $this->imageName;
}
public function setImageSize(?int $imageSize): void
{
$this->imageSize = $imageSize;
}
public function getImageSize(): ?int
{
return $this->imageSize;
}
In my Twig template, I want to access the CustomerGroup's imageName from the user. What I tried :
{{ app.user.CustomerGroup.imageName }} -> null
{{ app.user.getCustomerGroup().getImageName() }} -> null
But, if I do : `{{ app.user.CustomerGroup.name}} --> I get the correct value
When I dump {{app.user}} :
User^ {#824 ▼
-id: 1
-email: "xxxxxxxxxxxxxx"
-roles: array:1 [▶]
-password: "xxxxxxxxxxxxxxx"
-CustomerGroup: CustomerGroup^ {#809 ▼
+__isInitialized__: false
-id: 1
-name: null
-abbreviation: null
-isActive: null
-customerEntities: null
-dateAdd: null
-dateUpd: null
-createdBy: null
-modifiedBy: null
-models: null
-masterTypes: null
-documents: null
-deployModels: null
-imageFile: null
-imageName: null
-imageSize: null
…2
}
-CustomerEntity: CustomerEntity^ {#754 ▶}
-customerSites: PersistentCollection^ {#842 ▶}
-dateAdd: DateTime #1566424800 {#827 ▶}
-dateUpd: DateTime #1566579539 {#826 ▶}
-createdBy: User^ {#824}
-modifiedBy: User^ {#824}
-firstName: "xxxxx"
-lastName: "xxxxxx"
-isActive: true
-isDeleted: false
}
If I Dump app.user.CustomerGroup:
CustomerGroup^ {#809 ▼
+__isInitialized__: false
-id: 1
-name: null
-abbreviation: null
-isActive: null
-customerEntities: null
-dateAdd: null
-dateUpd: null
-createdBy: null
-modifiedBy: null
-models: null
-masterTypes: null
-documents: null
-deployModels: null
-imageFile: null
-imageName: null
-imageSize: null
…2
}
The first try only works when I'm on a controller that returns the CustomerGroup entity.
Thanks for your help
Best,
Julien
I found an ackward solution !
If I want the imageName property to be loaded, I have to load the CustomerGroup relation somewhere IN the template. This way, the entity is loaded and I can access the imageName property.
Example :
{{app.user.CustomerGroup.imageName}} ==> result null
{{app.user.CustomerGroup.name}}
{{app.user.CustomerGroup.imageName}}
Results in :
Customer1
Customer1.png
So, I call the CustomerGroup.name somewhere in the top of my twig file (in the body class for example and then I can call the imageName property.
Related
I have an "user" entity with an unmapped property like this :
namespace App\Entity\User;
// ...
/**
* #var string
* #Groups({"readAnnounce", "readUser"})
*/
private $lastUrlImageProfilValid;
/**
* #return string
*/
public function getLastUrlImageProfilValid()
{
foreach ($this->imageProfil as $image){
if ($image->getIsValid()){
return $image->getUrl();
}
}
return null;
}
when i call this entity with REST, it work but not with graphql, it return this error:
Cannot return null for non-nullable field User.lastUrlImageProfilValid
Graphql code :
{
users(
first: 30,
) {
edges {
node {
lastUrlImageProfilValid
}
}
}
}
How can i set an unmapped property nullable ?
You need to make the property nullable in your PHPDoc:
/**
* #var null|string
* #Groups({"readAnnounce", "readUser"})
*/
private $lastUrlImageProfilValid;
/**
* #return null|string
*/
public function getLastUrlImageProfilValid()
Or, in PHP 7.4:
/**
* #Groups({"readAnnounce", "readUser"})
*/
private ?string $lastUrlImageProfilValid;
public function getLastUrlImageProfilValid(): ?string
Good day. There was such problem.
/**
* #ODM\Document
*/
class Post
{
/**
* #var string
*
* #ODM\Id
*/
private $id;
/**
* #var \DateTime
*
* #ODM\Date
*/
private $createdAt;
...
}
In controller persisted test document. At the base of enrolled:
{ "_id" : ObjectId("5603ece1147fe7322c8b4581"), "createdAt" : ISODate("2015-09-24T11:27:04Z") }
But when I make a selection from the controller, I get a null in createdAt:
Test {#531 ▼
-id: "5603ece1147fe7322c8b4581"
-createdAt: null
}
createdAt is not initialized. You can set it's value in the constructor.
public function __construct() {
$this->createdAt = new \DateTime("now");
}
I have a 1:m relationship between Subitem and SubitemColor. Now I would like to save some data inside an onFlush to modify some data for SubitemColor. The problem: I get the error message below when executing the controller you can see below too:
An exception occurred while executing 'INSERT INTO SubitemColor
(code, precio, pvp_recommended, file_name, activado, en_stock, area,
lets_fix_width_or_height_in_list, lets_fix_width_or_height_in_show,
position_level_0, position_level_1, position_brand, subitem_id,
color_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' with
params [2]:
SQLSTATE[HY093]: Invalid parameter number: number of bound variables
does not match number of tokens
public function onFlush(Event \OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
$updates = $uow->getScheduledEntityUpdates();
$insertions = $uow->getScheduledEntityInsertions();
/////////// NEW SUBITEM_IMAGE OR SUBITEM_COLOR UPLOADED //////////
foreach ($insertions as $entity) {
if ($entity instanceof SubitemColor) {
//$entity->setLetsFixWidthOrHeightInList("jander");
//$entity->setLetsFixWidthOrHeightInList('width');
//$entity->setLetsFixWidthOrHeightInShow('width');
$entity->setEnStock(2);
$metaSubitemColor = $em->getClassMetadata(get_class($entity));
$uow->computeChangeSet($metaSubitemColor, $entity);
$uow->persist($entity);
}
}
}
//controller - controller - controller - controller
$subitem = new Subitem();
$em = $this->getDoctrine()->getManager();
$subitem->setNombre("jls");
$subitemColor = new SubitemColor();
$subitem->addSubitemColor($subitemColor);
$em->persist($subitem);
$em->persist($subitemColor);
$metaSubitem = $em->getClassMetadata(get_class($subitem));
$em->flush();
Use recomputeSingleEntityChangeSet method instead of computeChangeSet
computeChangeSet method is supposed to be called by doctrine only and calls once for every entity that marked for persistence on flush operation.
When you load entity from database doctrine saves its data to originalEntityData array, then it checks if no original data exists for entity then this entity is new and doctrine saves its current data as original and fill change set with every field value.
On second call of computeChangeSet doctrine has original data for newly created entity and computes change set only for changed fields since last call of computeChangeSet method.
Thats why you should never call computeChangeSet.
I replicated your problem as you can see in the image below.
The problem is; persist() is being used once in your controller (which you cannot do without it) and once in your onFlush() listener (which you cannot do without it as well!!!) so for that reason you get that error.
Event onFlush is called inside EntityManager#flush() after the
changes to all the managed entities and their associations have been
computed.
You're calling persist in your controller and straight after that you're calling another persist in your listener before even flushing it in your controller.
SOLUTION
Based on what you're trying to do, onFlush is not what you need anyway so the one you should use is prePersist so look at the example below.
CONTROLLER
Please checkout entity examples I added at the bottom. As you noted it is 1:N so since child SubitemColor cannot exist without parent Subitem, we're using $subitemColor->setSubitem($subitem); oppose to your example.
public function createAction()
{
$subitem = new Subitem();
$subitemColor = new SubitemColor();
$subitem->setNombre('jls');
$subitemColor->setSubitem($subitem);
$em = $this->getDoctrine()->getManager();
$em->persist($subitem);
$em->persist($subitemColor);
$em->flush();
}
YML
services:
application_backend.event_listener.subitem:
class: Application\BackendBundle\EventListener\SubitemListener
tags:
- { name: doctrine.event_listener, event: prePersist }
LISTENER
namespace Application\BackendBundle\EventListener;
use Application\BackendBundle\Entity\SubitemColor;
use Doctrine\ORM\Event\LifecycleEventArgs;
class SubitemListener
{
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof SubitemColor) {
$entity->setEnStock(2);
}
}
}
RESULT
mysql> SELECT * FROM subitem;
Empty set (0.00 sec)
mysql> SELECT * FROM subitem_color;
Empty set (0.01 sec)
mysql> SELECT * FROM subitem;
+----+------+
| id | name |
+----+------+
| 1 | jls |
+----+------+
1 row in set (0.00 sec)
mysql> SELECT * FROM subitem_color;
+----+------------+------+----------+
| id | subitem_id | code | en_stock |
+----+------------+------+----------+
| 1 | 1 | NULL | 2 |
+----+------------+------+----------+
1 row in set (0.00 sec)
SUBITEM ENTITY
namespace Application\BackendBundle\Entity;
use Application\BackendBundle\Entity\SubitemColor;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="subitem")
*/
class Subitem
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="name", type="string", length=20)
*/
protected $nombre;
/**
* #ORM\OneToMany(targetEntity="SubitemColor", mappedBy="subitem", cascade={"persist", "remove"})
*/
protected $subitemColor;
/**
* Constructor
*/
public function __construct()
{
$this->subitemColor = new ArrayCollection();
}
/**
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #param string $nombre
* #return Subitem
*/
public function setNombre($nombre)
{
$this->nombre = $nombre;
return $this;
}
/**
* #return string
*/
public function getNombre()
{
return $this->nombre;
}
/**
* #param SubitemColor $subitemColor
* #return Subitem
*/
public function addSubitemColor(SubitemColor $subitemColor)
{
$this->subitemColor[] = $subitemColor;
return $this;
}
/**
* #param SubitemColor $subitemColor
*/
public function removeSubitemColor(SubitemColor $subitemColor)
{
$this->subitemColor->removeElement($subitemColor);
}
/**
* #return Collection
*/
public function getSubitemColor()
{
return $this->subitemColor;
}
}
SUBITEMCOLOR ENTITY
namespace Application\BackendBundle\Entity;
use Application\BackendBundle\Entity\Subitem;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="subitem_color")
*/
class SubitemColor
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="code", type="string", length=20, nullable=true)
*/
protected $code;
/**
* #ORM\Column(name="en_stock", type="integer", length=5, nullable=true)
*/
protected $enStock;
/**
* #ORM\ManyToOne(targetEntity="Subitem", inversedBy="subitemColor")
* #ORM\JoinColumn(name="subitem_id", referencedColumnName="id", onDelete="CASCADE", nullable=false)
*/
protected $subitem;
/**
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #param string $code
* #return SubitemColor
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* #return string
*/
public function getCode()
{
return $this->code;
}
/**
* #param integer $enStock
* #return SubitemColor
*/
public function setEnStock($enStock)
{
$this->enStock = $enStock;
return $this;
}
/**
* #return integer
*/
public function getEnStock()
{
return $this->enStock;
}
/**
* #param Subitem $subitem
* #return SubitemColor
*/
public function setSubitem(Subitem $subitem)
{
$this->subitem = $subitem;
return $this;
}
/**
* #return Subitem
*/
public function getSubitem()
{
return $this->subitem;
}
}
This may or may not solve your problem, but when I do this in my code I call $uow->persist($entity); then I call $uow->computeChangeSet($metaSubitemColor, $entity);
Order seems important here as you have persisted changes that then have to be recalculated in the unit of work. So persisting after calling computeChangeSet seems likely to cause problems.
I am working with form aimed at uploading the file and updating the database in Symfony2. I want to manually set value of book_id field and not to allow user to change it in the form. Thus in my controller before using doctrine to persist document I am calling:
$documents->setBookId('1');
Unluckilly I get error which indicates that the doctrine does not recognise the above hard coded value input.
An exception occurred while executing 'INSERT INTO Documents (book_id, marker, document_date, link, notes) VALUES (?, ?, ?, ?, ?)' with params [null, "fdd", "2015-04-04", null, "test"]:
To my mind this may be connected with the fact that book_id field is related to Books. Therefore probably I should use setBook function instead. Could you please advice how to do this properly?
My controler file looks like this:
/**
* This code is aimed at checking if the book is chosen and therefore whether any further works may be carried out
*/
$session = new Session();
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
// Authorization goes here
$documents = new Documents();
$form = $this->createForm(new DocumentsType(), $documents);
$form->add('save', 'submit', array('label' => 'Dodaj dokument'));
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$documents->upload();
$documents->setBookId('1');
$em->persist($documents);
$em->flush();
}
return $this->render('AppBundle:Documents:adddocuments.html.twig', array('form' => $form->createView()));
Document class:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Entity
* #ORM\Table(name="Documents")
* #ORM\HasLifecycleCallbacks
*/
class Documents
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Books", inversedBy="documents")
* #ORM\JoinColumn(name="book_id", referencedColumnName="id")
*/
protected $book;
/**
* #ORM\Column(type="integer")
*/
protected $book_id;
/**
* #ORM\Column(type="string", length=220)
*/
protected $marker;
/**
* #ORM\Column(type="date", length=220)
*/
protected $document_date;
/**
* #ORM\Column(type="string", length=220)
* #Assert\File(maxSize="6000000")
*/
protected $link;
/**
* #ORM\Column(type="text")
*/
protected $notes;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set book_id
*
* #param integer $bookId
* #return Documents
*/
public function setBookId($bookId)
{
$this->book_id = $bookId;
return $this;
}
/**
* Get book_id
*
* #return integer
*/
public function getBookId()
{
return $this->book_id;
}
/**
* Set marker
*
* #param string $marker
* #return Documents
*/
public function setMarker($marker)
{
$this->marker = $marker;
return $this;
}
/**
* Get marker
*
* #return string
*/
public function getMarker()
{
return $this->marker;
}
/**
* Set document_date
*
* #param \DateTime $documentDate
* #return Documents
*/
public function setDocumentDate($documentDate)
{
$this->document_date = $documentDate;
return $this;
}
/**
* Get document_date
*
* #return \DateTime
*/
public function getDocumentDate()
{
return $this->document_date;
}
/**
* Set link
*
* #param string $link
* #return Documents
*/
public function setLink($link)
{
$this->link = $link;
return $this;
}
/**
* Get link
*
* #return string
*/
public function getLink()
{
return $this->link;
}
/**
* Set notes
*
* #param string $notes
* #return Documents
*/
public function setNotes($notes)
{
$this->notes = $notes;
return $this;
}
/**
* Get notes
*
* #return string
*/
public function getNotes()
{
return $this->notes;
}
/**
* Set book
*
* #param \AppBundle\Entity\Books $book
* #return Documents
*/
public function setBook(\AppBundle\Entity\Books $book = null)
{
$this->book = $book;
return $this;
}
/**
* Get book
*
* #return \AppBundle\Entity\Books
*/
public function getBook()
{
return $this->book;
}
/*
* ### FILE UPLOAD PROCESS ###
*/
/**
* #Assert\File(maxSize="6000000")
*/
private $file;
/**
* Sets file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
public function getAbsolutePath()
{
return null === $this->path
? null
: $this->getUploadRootDir().'/'.$this->path;
}
public function getWebPath()
{
return null === $this->path
? null
: $this->getUploadDir().'/'.$this->path;
}
protected function getUploadRootDir()
{
// the absolute directory path where uploaded
// documents should be saved
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw up
// when displaying uploaded doc/image in the view.
return 'uploads/documents';
}
public function upload()
{
// the file property can be empty if the field is not required
if (null === $this->getFile()) {
return;
}
// use the original file name here but you should
// sanitize it at least to avoid any security issues
// move takes the target directory and then the
// target filename to move to
$this->getFile()->move(
$this->getUploadRootDir(),
$this->getFile()->getClientOriginalName()
);
// set the path property to the filename where you've saved the file
$this->path = $this->getFile()->getClientOriginalName();
// clean up the file property as you won't need it anymore
$this->file = null;
}
}
Okay, first since you're using ManyToOne relation, you don't actually need another property refering to the book - book_id. You can remove that and leave book only.
Then in your controller you have to query the database for that Book and set the that object your Document.
You can do it like this:
$bookId = 1; // Following your example, let's say tou already know the book ID.
$book = $em->getReference('AppBundle:Books', $bookId);
// Check if we actually found a record and then set it to Documents
// Looking at your entity mapping, your reference to Book can not be null,
// but doing an extra check never hurts, since this is just an example.
if( $book ) {
$documents->setBook($book);
}
-Update-
If you want to directly insert the bookID, then what is the purpose of having ManyToOne reference in your entity? Eventually you're going to have to start using doctrine's relations and objects properly. Also, the cool thing about getReference method is that you are getting a reference to an entity, without having to load the entity from the database - you get the so called Proxy objects.
The method EntityManager#getReference($entityName, $identifier) lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier
You can read further about this here.
I'll simplifly my code, I have te next:
Doctor entity:
use ...\...\Entity\Paciente;
class Doctor extends Usuario {
public function __construct() {
...
$this->pacientes = new ArrayCollection();
...
}
/**
* Número de colegiado - numColegiado
*
* #var string
*
* #ORM\Column(name="numColegiado", type="string", length=255, unique=true)
*/
protected $numColegiado;
/**
* #ORM\OneToMany(targetEntity="Paciente", mappedBy="doctor")
* #var \Doctrine\Common\Collections\ArrayCollection
*/
private $pacientes;
...
}
Paciente entity:
use \...\...\Entity\Doctor;
...
class Paciente extends Usuario {
}
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Doctor", inversedBy="pacientes")
* #ORM\JoinColumn(name="doctorNum", referencedColumnName="numColegiado", nullable=TRUE)
*
* #var type
*/
protected $doctor;
...
/**
* Set doctor
*
* #param Doctor $doctor
* #return Paciente
*/
public function setDoctor(Doctor $doctor = null)
{
$this->doctor = $doctor;
return $this;
}
/**
* Get doctor
*
* #return Doctor
*/
public function getDoctor()
{
return $this->doctor;
}
}
Ok, the matter is, when I execute that code (of course there is a relationship created and this object exists in the database):
\Doctrine\Common\Util\Debug::dump($paciente->getDoctor());
It prints that follows:
object(stdClass)#804 (28) { ["__CLASS__"]=> string(34) "Knoid\CorcheckBundle\Entity\Doctor" ["__IS_PROXY__"]=> bool(true) ["__PROXY_INITIALIZED__"]=> bool(false) ["id"]=> NULL ["numColegiado"]=> NULL ["pacientes"]=> NULL ["nombre"]=> NULL ["apellidos"]=> NULL ["dni"]=> NULL ["tipo"]=> NULL ["username"]=> NULL ["usernameCanonical"]=> NULL ["email"]=> NULL ["emailCanonical"]=> NULL ["enabled"]=> NULL ["salt"]=> NULL ["password"]=> NULL ["plainPassword"]=> NULL ["lastLogin"]=> NULL ["confirmationToken"]=> NULL ["passwordRequestedAt"]=> NULL ["groups"]=> NULL ["locked"]=> NULL ["expired"]=> NULL ["expiresAt"]=> NULL ["roles"]=> NULL ["credentialsExpired"]=> NULL ["credentialsExpireAt"]=> NULL }
As you can see, all the atributes of the "doctor" object are null, the object exists but it's empty, in my DB this object exists and it isn't empty.
Any idea of what's happening ?
This is because the proxy object is not initialised yet. One way to initialise it, is by querying the object e.g. $doctor->getId(). If you dump the object after that, you'll see that all the attributes are 'visible'
The answer of Thomas K worked for me in my own Bundle. If I translate what I did :
$myPaciente = $em->getRepository('MyBundle:Paciente')->findOneBy(array('numColegiado' => $value));
I add $myPaciente->getDoctor()->getName();
Then the initialisation was done and I could dump $myPaciente with all the information about the doctor related to it.