i have 2 entities.
the simple logic is that a user has many delivery address, so in the future he will be able to choose one of them for their deliverys.
first entity, Direccion (address).
<?php
use Doctrine\ORM\Mapping as ORM;
/**
* Direccion
*
* #ORM\Table(name="direccion", indexes={#ORM\Index(name="id_usuario",
columns={"id_usuario"})})
* #ORM\Entity
*/
class Direccion
{
/**
* #var string
*
* #ORM\Column(name="Calle", type="string", length=100, nullable=false)
*/
private $calle;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \Usuario
*
* #ORM\ManyToOne(targetEntity="Usuario", inversedBy="direcciones", fetch="EAGER")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id_usuario", referencedColumnName="id" )
* })
*/
private $idUsuario;
/**
* Set calle
*
* #param string $calle
* #return Direccion
*/
public function setCalle($calle)
{
$this->calle = $calle;
return $this;
}
/**
* Get calle
*
* #return string
*/
public function getCalle()
{
return $this->calle;
}
/**
* Set idUsuario
*
* #param \Usuario $idUsuario
* #return Direccion
*/
public function setIdUsuario(\Usuario $idUsuario = null)
{
$this->idUsuario = $idUsuario;
return $this;
}
/**
* Get idUsuario
*
* #return \Usuario
*/
public function getIdUsuario()
{
return $this->idUsuario;
}
}
and the second entity is, Usuario (User)
<?php
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Usuario
*
* #ORM\Table(name="usuario")
* #ORM\Entity
*/
class Usuario
{
/**
* #var string
*
* #ORM\Column(name="nombre", type="string", length=255, nullable=true)
*/
private $nombre;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Direccion", mappedBy="id_usuario", cascade={"persist"})
*/
private $direcciones;
public function __construct()
{
$this->direcciones = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Set nombre
*
* #param string $nombre
* #return Usuario
*/
public function setNombre($nombre)
{
$this->nombre = $nombre;
return $this;
}
/**
* Get nombre
*
* #return string
*/
public function getNombre()
{
return $this->nombre;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function getDirecciones()
{
return $this->direcciones;
}
public function setDirecciones($direcciones)
{
$this->direcciones = $direcciones;
return $this;
}
}
i have already readed many blogs and similar questions , but i can't get the property $direcciones hydrated or filled with the associated data, when i try to achieve that on the inverse side of the relation (OneToMany), with $usuario->getDirecciones();
i'm working in with this code in a non MVC architecture, to this point everything works like charm in getting creating and updating through a DAO wich uses the Doctrine sentences,so i know that in the ending points (updating, creating,persist and flush,retrieving data with find, findBy,etc), everything works fine.
when i try to fill the ArrayCollection calling from the Service layer where i use both classes (Usuario-InverseSide and Direccion-OwnerSide), nothing happens, the $direcciones arrayCollection property on Usuario instance, doesn't fetch anything, even when i tried establishing fetch="EAGER"on the annotations.
for the rest of the data or properties, everything works fine, all the other attributes of the user get filled just fine.
For some reason, the annotations are not being considered, donĀ“t know why.
i've spend a few days trying to figure out how to acomplish this way of accessing the associated data, didn't wanted to use DQL, but at this point i think i will take that road.
I tried something hard-coded to test and, the result is the same, $direcciones doesn't get his data.Obvious are already discarted, that exact ID for a Usuario(user) has his related direcciones(address) setted.
$usuario = $this->usuarioDAO->find(20);
$direcciones = $usuario->getDirecciones();
var_dump($direcciones);
return $usuario;
the relation was only defined with a FK called id_usuario on Direccion on a mysql InnoDb table.
don't know if i should be setting something else on the Usuario table.
or if there is something wrong in the way i store the entities on my project.
please help me, any recommendation will be appreciated.
doctrine 2, hwo do get data from the inverse side (many to one)
Provided that your bidirectional mapping between Direccion and Usuaro is correct, you need to use setter methods on both sides.
And the other thing, when using ArrayCollection, an easier (perhaps the only way) is to add elements one by one.
Therefore, instead of setDirecciones() you add this method to Usuario class:
public function addDireccion(Direccion $direccion) {
$direccion->setIdUsuario($this);
$this->direcciones->add($direccion);
}
Hope that helps.
And, it would be better to name it $usuario and setUsuario instead od setIdUsuario because you work with objects and PHP should not be concerned about actual database field names (to put it that way).
i resolved it.
happens that for some reason, the php annotations were not being actually readed, or on a different way to say it, it was listening to the XML annotations first, so i put the xml sentence on the inverse side of the relation (usuario) inside the tags
<one-to-many field="direcciones" target-entity="Direccion" mapped-by="idUsuario" />
and it worked, now i got polish the data result of the consult so i can send a cleaner/clearer object to the front-end.
Related
I would like to serialize my objects into text fields in order to store a representation of them for traceability.
What I really want is a JSON representation of the entity's properties, and whenever there is an object, I would like a JSON representation of that as well, but only on that first level, I don't want it to dig deeper into what possible objects and relations there is below that, I'm happy with the object ID's
Many objects reference other objects, and from this documentation https://symfony.com/doc/current/components/serializer.html#handling-circular-references it appears this can be easily handled by just storing the object's ID rather than serializing the entire object (again). But in my case it doesn't work =) Am I missing something critical here?
Entity
<?php
namespace App\Entity;
use App\Repository\RegularServiceHoursRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=RegularServiceHoursRepository::class)
*/
class RegularServiceHours
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="datetime")
*/
private $open;
/**
* #ORM\Column(type="datetime")
*/
private $close;
/**
* #var object \App\Entity\ACRGroup
*
* #ORM\ManyToOne(targetEntity="\App\Entity\ACRGroup", inversedBy="regularServiceHours")
* #ORM\JoinColumn(name="acr_group", referencedColumnName="id", nullable=false)
*/
protected $ACRGroup;
public function getId(): ?int
{
return $this->id;
}
public function getOpen(): ?\DateTimeInterface
{
return $this->open;
}
public function setOpen(\DateTimeInterface $open): self
{
$this->open = $open;
return $this;
}
public function getClose(): ?\DateTimeInterface
{
return $this->close;
}
public function setClose(\DateTimeInterface $close): self
{
$this->close = $close;
return $this;
}
/**
* Set aCRGroup
*
* #param \App\Entity\ACRGroup $aCRGroup
*
* #return DebitPeriod
*/
public function setACRGroup(\App\Entity\ACRGroup $aCRGroup)
{
$this->ACRGroup = $aCRGroup;
return $this;
}
/**
* Get aCRGroup
*
* #return \App\Entity\ACRGroup
*/
public function getACRGroup()
{
return $this->ACRGroup;
}
/**
* Get debitTimeSeconds
*
* #return int
*/
public function getTimeSeconds()
{
$open = $this->getOpen();
$close = $this->getClose();
$r = $close->format('U') - $open->format('U');
return $r;
}
}
Controller
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Serializer;
public function log($type,$message,$unit=null,$previous=null,$current=null) {
//We only log successful operations.
//If you want to log also errors, be very careful to not have anything persisted already, in que to be flushed. The flush below will store possible entities under conflict check and entities that sent you here due to a failed validation/conflict check, entities that weren't meant to be saved but rejected and forgotten.
if ($type == 'success') {
$encoder = new JsonEncoder();
$defaultContext = [
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) {
return $object->getId();
},
];
$normalizer = new ObjectNormalizer(null, null, null, null, null, null, $defaultContext);
$serializer = new Serializer([$normalizer], [$encoder]);
dd($serializer->serialize($current, 'json'));
It never gets to the dump action, it chews for 30 seconds until memory is exhausted, and then it says:
Error: Maximum execution time of 30 seconds exceeded
Have I somehow missed where to set a default depth of 1? (I understood depth 1 to be the default when nothing was set).
Pay attention to Serialization Groups Attributes. With this attribute you can select the desired data when serializing an object.
Specify groups to the desired object properties:
class RegularServiceHours
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #Groups({"default"})
*/
private $id;
/**
* #ORM\Column(type="datetime")
*/
private $open;
/**
* #ORM\Column(type="datetime")
* #Groups({"default"})
*/
private $close;
/**
* #var object \App\Entity\ACRGroup
*
* #ORM\ManyToOne(targetEntity="\App\Entity\ACRGroup", inversedBy="regularServiceHours")
* #ORM\JoinColumn(name="acr_group", referencedColumnName="id", nullable=false)
* #Groups({"default"})
*/
private $ACRGroup;
}
Also specify a group for the ACRGroup identifier
class ACRGroup
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #Groups({"default"})
*/
private $id;
}
And just specify the group when serializing the object
$serializer->serialize($current, 'json', ['groups' => ['default']]);
I think that should solve your problem
The title may not be so clear so I'll explain in detail here (I must miss something obvious but I can't figure out what).
I'm using the Vich uploader bundle to store pictures in my project. I have two entity linked with an unidirectional one to one relation, the first is the owner and contain the annotation pointing to the second entity containing the file.
This is the code part from the first entity :
...
/**
* #ORM\OneToOne(targetEntity="Cartong\MyBundle\Entity\Mysql\EntityContainingTheFile")
* #ORM\JoinColumn(name="photo_id", referencedColumnName="id")
*/
private $photo;
...
And the one containing the file :
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
* #Vich\Uploadable
*/
class EntityContainingTheFile extends FileUpload
{
/**
* #var UploadedFile
* #Vich\UploadableField(mapping="my_pictures", fileNameProperty="filename")
*/
protected $file;
/**
* #return UploadedFile
*/
public function getFile()
{
return parent::getFile();
}
/**
* #param UploadedFile $file
*/
public function setFile(File $file)
{
return parent::setFile($file);
}
}
The FileUpload code is here too. It basically containing the file description (I'm using other entity that are extending this class) :
/**
* #ORM\MappedSuperclass
* #Vich\Uploadable
*/
class FileUpload
{
/**
* #var integer : stock the unique id of the file
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string : stock the format of the file
*
* #ORM\Column(name="format", type="string", length=255)
*/
private $format;
/**
* var string : stock the original name of the file
*
* #ORM\Column(name="alt", type="string", length=255)
*/
private $alt;
/**
* #var integer : stock the size of the file (ko)
*
* #ORM\Column(name="size", type="integer")
*/
private $size;
/**
* #var \DateTime
*
* #ORM\Column(name="updated_at", type="datetime", nullable=true)
*/
private $updatedAt;
/**
* #var string $filename
*
* #ORM\Column(name="filename", type="string", length=255)
*/
protected $filename;
protected $file;
The file upload is working well, everything is stored at the right place in my project and the DB. The problem occur when I try to retrieve what I just store trough the first entity.
This is the kind of code I have in my controller :
$repo = $this->container->get('doctrine')->getRepository('CartongMSFBundle:MyFirstEntity');
$test = $repo->find($theEntityWithAFile);
The object returned containing all the expected information except the photo, where all the fields are null.
So if I'm trying to get the specific file trough a findById in the "file" repo it's working but when I'm trying to get it trough my first entity it's not.
Any idea ? (maybe a mistake in the annotations ?)
It seems like a typical doctrine hydration issue. In case of associations, doctrine by default doesn't load from database associated entities, until it is needed (e.g. you call $myFirstEntity->getPhoto()->getFormat()). This is called lazy loading.
If you want your associated entity to be loaded along with your first entity you should set doctrine fetch option to EAGER:
/**
* #ORM\OneToOne(targetEntity="EntityContainingTheFile", fetch="EAGER")
* #ORM\JoinColumn(name="photo_id", referencedColumnName="id")
*/
private $photo;
I have 3 tables : Profile - Permission - ProfilePermissionValue
Profile and Permission are classic entities, and ProfilePermissionValue is an association of a Profile.id, Permission.id, and an extra field representing the value of the permission for the profile.
When I add a Permission, I want a new row being inserted in ProfilePermissionValue for each Profile.
Same on reverse, when I add a new Profile, ... And same on delete by the way.
The question : Is there a way to do it with Doctrine (Symfony 3) functionalities, or I need to code it myself ?
I think you look at the permission <-> profile more strictly than you should. Basically in almost every ACL I worked with there was a assumption - when something is not allowed, it`s disallowed (or when something is not disallowed is allowed which is more dangerous). Which significantly reduce amount of data, you must save.
So when you create your entities like this
<?php
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
*/
class Permission
{
// id column
/**
* #ORM\Column(type="string")
* #var string
*/
private $name;
/**
* #return string
*/
public function getName()
{
return $this->name;
}
}
and
<?php
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
*/
class User
{
// id column
// name column
/**
* #ORM\ManyToMany(targetEntity=Permission::class)
* #ORM\JoinTable(name="allowed_permissions",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="permission_id", referencedColumnName="id")}
* )
* #var Permission[]|Collection
*/
private $allowedPermissions;
/**
* #return Permission[]
*/
public function getAllowedPermissions()
{
return $this->allowedPermissions->toArray();
}
}
you can simply implement your own class for interface AuthorizationCheckerInterface as
<?php
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class Authorizator implements AuthorizationCheckerInterface
{
/**
* #param string $name
* #param User $user
* #return bool
*/
public function isGranted($name, $user)
{
foreach ($user->getAllowedPermissions() as $permission) {
if ($permission->getName() === $name) {
return TRUE;
}
}
return FALSE;
}
}
without any needs of having deny permission in your database.
I have an API developed in Symfony2 but when i send a request to it the response returns with 204Mb for only 40 rows... This is the code:
$em = $this->getDoctrine()->getManager();
$themes = $em->getRepository("KlickpagesAdminBundle:Theme")->findAll();
return $themes;
Im use FOSRestBundle to serialize and returns the json.
How i can resolve this?
Aa #Cerad said it is very like because of relations to other entities and lazy loading going in circles
For a quick test exclude all fields from the serilazition, except few scalar ones like so:
use JMS\Serializer\Annotation\Expose;
use JMS\Serializer\Annotation\ExclusionPolicy;
/**
* Group
*
* #ExclusionPolicy("all")
*/
class Group implements GroupInterface
{
/**
* #Expose
* #var integer
*/
private $id;
/**
* #Expose
* #var string
*/
private $title;
/**
* Relation to privilegesis not explicitly exposed.
* #var Privilege[]
*/
private $privileges;
/**
* Relation to Users not explicitly exposed.
* #var User[]
*/
private $users;
...
The important parts are exclusionStrategy and expose antations.
If this will help, you got for sure a circles serialization of your annotations and the right solution is to define serialization groups, lets say like this:
/**
* #Expose
* #Groups({"groupDetail", "userAuthenticate"})
*
* #var Privilege[]
*/
private $privileges;
/**
* #Expose
* #Groups({"groupDetail"})
*
* #var User[]|ArrayCollection
*/
private $users;
You can then define by which group should be the response serialized on your controller or programatically.
// controllerAction
/*
* #Annotations\View(serializerGroups={"Default","groupDetail"})
*/
public function getGroupAction($groupId) { ... }
// programatically
...
/** #var $context SerializationContext */
$context = SerializationContext::create();
$serializationGroups = ['Default', 'GroupDetail'];
$context->setGroups($serializationGroups);
$view = $this->view($collection, 200);
$view->setSerializationContext($context);
return $this->handleView($view);
...
Resources: http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies
I'm having issues setting up my Doctrine request properly.
I have two tables (PROPRIETE and PHOTO), one PROPRIETE can have many PHOTO
Therefore, I'd like to make a SELECT that will return an array of PROPRIETE where which one includes an array of it's own PHOTOs (not sure if I'm clear though...)
This is what my Popriete class looks like
class Propriete
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="libelle", type="string", length=255)
*/
private $libelle;
/**
* #ORM\ManyToOne(targetEntity="VillaPrivee\UserBundle\Entity\User")
* #ORM\JoinColumn(onDelete="CASCADE")
*/
private $proprietaire;
/**
* #ORM\OneToMany(targetEntity="VillaPrivee\MainBundle\Entity\Photo", mappedBy="propriete")
*/
private $photo;
And then the Photo class
class Photo
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="path", type="string", length=255)
*/
private $path;
/**
* #ORM\ManyToOne(targetEntity="VillaPrivee\MainBundle\Entity\Propriete")
* #ORM\JoinColumn(onDelete="CASCADE")
*/
private $propriete;
And finally, my Doctrine request (that successfully returns a list of Propriete, but nothing about their photos)
public function getProprietesByCriteria($ville, $rooms) {
$qb = $this->createQueryBuilder('p');
$qb->where('p.commune = :ville AND p.nbChambres >= :rooms')
->setParameter('ville', $ville)
->setParameter('rooms', $rooms);
return $qb->getQuery()->getResult();
}
I've tried with a leftJoin, but it seems that I don't know how to use that stuff...
Thanks guys for your help
I thing you're mixing different concepts in your code. Using Doctrine you should forget about trying to get multiple arrays with a query.
You only need to invoke the getPhoto() method on any propriete object.
So, you can use a code similar to:
$props = $this->getProprietesByCriteria($ville, $rooms);
foreach($props as $prop)
{
$prop->getPhoto(); // <--will return an array of the `PHOTO` related to this `PROPIETE`
}