Query an inherited table via a relation table - symfony

I want to achieve a pretty simple query in theory but I didn't manage to make it work: I want the number of active CVs grouped by Elo (which is an attribute in an inherited table).
The error:
[Semantical Error] line 0, col 22 near 'elo FROM MyNamespace\CvBundle\Entity\Cv':
Error: Class MyNamespace\CvBundle\Entity\Cv\Feature has no field or association named elo.
It complains about not having a field in MyNamespace\CvBundle\Entity\Cv\Feature which is true because it's the "master" table. This field is contained in the MyNamespace\CvBundle\Entity\Cv\Lol which is a table inherited from Cv\Feature
Here's the query:
// CvRepository.php
public function getStats()
{
$query = $this->createQueryBuilder('c')
->select('COUNT(f.id), f.elo')
->leftJoin('c.feature', 'f')
->groupBy('f.elo')
->where('f INSTANCE OF MyNameSpace\CvBundle\Entity\Cv\Lol')
->andWhere('c.active = :active')
->andWhere('c.expiresAt > :now')
->setParameters(array(
'now' => new \DateTime("now"),
'active' => 1,
))
->getQuery();
return $query->execute();
}
And the the table Cv:
// Cv.php
/**
* #ORM\Table(name="cv")
* #ORM\Entity(...)
*/
class Cv
{
/**
* #ORM\OneToOne(targetEntity="MyNameSpace\CvBundle\Entity\Cv\Feature", cascade={"all"})
*/
protected $feature;
}
The Feature.php
/**
* #ORM\Entity()
* #ORM\Table(name="cv_feature")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({"lol" = "Lol", ...})
*/
abstract class Feature
{
/**
* #ORM\OneToOne(targetEntity="MyNameSpace\CvBundle\Entity\Cv")
* #ORM\JoinColumn(name="cv_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $cv;
And the Lol.php
/**
* #ORM\Entity()
*/
class Lol extends Feature
{
/**
* #var integer $elo
*
* #ORM\Column(name="elo", type="string")
*/
private $elo;
....

Pretty sure you will have to move $elo to your Feature class.
Your 'where instance of' will restrict the results to Lol classes but I doubt if DQl is smart enough to realize that all features will then be lol's.
You could probably change Cv to point to Lol but thats probably not what you want either.
You could also implement the group by in php.
But try this and verify it works:
abstract class Feature
{
/**
* #ORM\OneToOne(targetEntity="MyNameSpace\CvBundle\Entity\Cv")
* #ORM\JoinColumn(name="cv_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $cv;
/**
* #var integer $elo
*
* #ORM\Column(name="elo", type="string")
*/
protected $elo;
You would only put getter/setters for elo on your Lol class. So it's basically hidden from it's siblings. And it's already going to be in the database table anyway. You might even be able to keep it as private and add it to Lol only so siblings would have no access to it at all. Not sure about that but I think doctrine might still hydrate it.

Related

How to override the id's annotation in a Doctrine inheritnce hiearchy

I'm working with Symfony and MySQL and I'm trying to follow some convention across all my table, one of them is to keep each id's colmun name in the format id_tablename (see diagram). So i kept the id name generated by Symfony in the classes, but I want to replace each field in the database by id_product, id_tire, etc, ...
For that i'm using the Column annotation, e.g:
abstract class Product
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer", name="id_product")
*/
private $id;
// ...
}
And for each child class, I use AttributeOverride annotation as explained in the doc, like bellow
/**
* #ORM\Entity(repositoryClass=TireRepository::class)
* #ORM\AttributeOverrides(
* #ORM\AttributeOverride(
* name = "id",
* column=#ORM\Column(name="id_tire")
* )
* )
*/
class Tire extends Product
{
// ...
}
But when attempting a php bin/console make:migration I got the error The column type of attribute 'id' on class 'App\Entity\Tire' could not be changed.
Did I miss something ?
Edit: I tried to override another attribute ($name) with the following code that work:
/**
* #ORM\Entity(repositoryClass=RimRepository::class)
* #ORM\AttributeOverrides(
* #ORM\AttributeOverride(
* name = "name",
* column=#ORM\Column(name="name_rim")
* )
* )
*/
class Rim extends Product
{
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
// ...
}
But even by doing the same thing with $id attribute, I still have the same error message.
Seem like Doctrine have difficult to work with renamed fields too, when you have relations betweens classes. So for now I keep the default id name for each table in database, to continue working.
Please check correct example below.
Looks like you just missing type="integer" in AttributeOverride
use Doctrine\ORM\Mapping as ORM;
/**
* #MappedSuperclass
*/
abstract class Product
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer", name="id_product")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*/
protected $name;
// ...
}
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\AttributeOverrides(
* #ORM\AttributeOverride(
* name = "id",
* column=#ORM\Column(name="id_tire", type="integer")
* )
* )
*/
class Tire extends Product
{
// ...
}
As result migration SQL will be similar to following
$this->addSql('CREATE TABLE tire (id_tire INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id_tire))');
It seem like it's a problem with how Doctrine works. As my system require many relations between entities, I didn't noticed it, but without relations, evrything works fine if they are correctly mapped. For exemple with:
Parent class
/**
* #ORM\Entity(repositoryClass=ProductRepository::class)
*/
class Product
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer", name="id_product")
*/
private $id;
// ...
}
Child classes
/**
* #ORM\Entity(repositoryClass=RimRepository::class)
* #ORM\AttributeOverrides(
* #ORM\AttributeOverride(
* name = "id",
* column=#ORM\Column(type="integer", name="id_rim")
* )
* )
*/
class Rim extends Product
{
/**
* #ORM\Column(type="integer")
*/
private $id;
// ...
}
/**
* #ORM\Entity(repositoryClass=TIreRepository::class)
* #ORM\AttributeOverrides(
* #ORM\AttributeOverride(
* name = "id",
* column=#ORM\Column(type="integer", name="id_tire")
* )
* )
*/
class Tire extends Product
{
/**
* #ORM\Column(type="integer")
*/
private $id;
// ...
}
Will generate those tables in database. But with relations at any level of the hiearchy like in this case, Doctrine will fail to retrive renamed column with annotations. Si in my case I had to keep the default id name across all tables in order to let Doctrine find what he excpect when making relations between tables.
I tried to remove all relation from child class and keep those from parent class, also the opposite, but Doctrine alwas still searching column id while looking for relation/contraints:
$this->addSql('ALTER TABLE picture ADD CONSTRAINT FK_16DB4F894584665A FOREIGN KEY (product_id) REFERENCES product (id)');
It's seem like impossible to do right now, with complex database structure.

how to make a property to be "merged" or initialized when deserializing with JMSSerializerBundle

I'm using JMSSerializer - along with the Doctrine constructor - in order to deserialize an object sent.
My (simplified) entities are the following. I omit the code I think is useless:
Widget
{
protected $id;
/**
* #ORM\OneToMany(
* targetEntity="Belka\Iso50k1Bundle\Entity\VarSelection",
* mappedBy="widget",
* cascade={"persist", "remove", "detach", "merge"})
* #Serializer\Groups({"o-all-getCWidget", "i-p2-create", "o-all-getWidget", "i-p3-create", "i-p2-editWidget"})
* #Type("ArrayCollection<Belka\Iso50k1Bundle\Entity\VarSelection>")
*/
protected $varsSelection;
}
/**
* #ORM\Entity()
*
* #ORM\InheritanceType("SINGLE_TABLE")
*
* #ORM\DiscriminatorColumn(
* name="vartype",
* type="string")
*
* #ORM\DiscriminatorMap({
* "PHY" = "PhyVarSelection"
* })
*
* #ORM\HasLifecycleCallbacks()
*/
abstract class VarSelection
{
/**
* #Id
* #Column(type="integer")
* #GeneratedValue("SEQUENCE")
* #Serializer\groups({"o-all-getCWidget", "o-all-getWidget", "i-p2-editWidget"})
*/
protected $id;
}
class PhyVarSelection extends VarSelection
{
/**
* #var PhyVar
*
* #ORM\ManyToOne(
* targetEntity="Belka\Iso50k1Bundle\Entity\PhyVar",
* cascade={"persist", "merge", "detach"})
*
* #ORM\JoinColumn(
* name="phy_var_sel",
* referencedColumnName="id",
* nullable=false)
*/
protected $phyVar;
}
class PhyVar extends Variable
{
/**
* #ORM\Column(type="string")
* #ORM\Id
*
* #Serializer\Groups({"o-p2-getCMeters", "o-all-getWidget"})
* #Assert\Regex("/(PHY)_\d+_\d+_\w+/")
*/
protected $id;
/**
* #ORM\Column(type="text", name="varname")
* #Serializer\Groups({"o-p2-getCMeters", "o-all-getWidget", "o-all-getCWidget"})
*/
protected $varName;
...
}
I try to deserialize an object that represents a Widget entity already persisted, along with which an array of varselection with their own id specified - if already persisted - and without their own id if they are new and to be persisted.
Deserialization works:
$context = new DeserializationContext();
$context->setGroups('i-p2-editWidget');
$data = $this->serializer->deserialize($content, $FQCN, 'json', $context);
but $data has always Widget::$varsSelection[]::$phyVar as a proxy class initialized, with only the id properly set. What I have to do so as to have it all is:
foreach ($data->getVarsSelection() as $varSel) {
$varSel->getVar();
}
why is that? How can have it initialized already? I don't want to spend time cycling and fetching data from DB again.
edit
I've added a domain of the entities so as to get the idea of what I'm deserializing
I figured out myself the hows and whys of this behavior:
since I'm sending a JSON like the following:
{
"id": <widgetID>,
"vars_selection": {
"id": <varSelectionID>,
"vartype": "PHY"
}
}
JMSSerializer's Doctrine ObjectConstructor simply tries to finds just two Entities: Widget and VarSelection by executing the following line:
$object = $objectManager->find($metadata->name, $identifierList);
in other words: Doctrine's EntityManager tries to find the Entity identified by its ID. Hence, well'get the unitialized proxy classes.
As far as I know, find cannot specify an hydration mode. Hence, two are the ways to handle this:
Specify fetch="EAGER" on PhyVarSelection::$phyVar. Quite costly, when we do not need it though;
Replace the ObjectConstructor by calling the repository and make a DQL, which will have the EAGER option properly set. Something like $query->setFetchMode("PhyVarSelection", "phyVar", \Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER);

ManyToMany relationship with extra fields in symfony2 orm doctrine

Hi i have that same question as here: Many-to-many self relation with extra fields? but i cant find an answer :/ I tried first ManyToOne and at the other site OneToMany ... but then i could not use something like
public function hasFriend(User $user)
{
return $this->myFriends->contains($user);
}
because there was some this problem:
This function is called, taking a User type $user variable and you then use the contains() function on $this->myFriends.
$this->myFriends is an ArrayCollection of Requests (so different type than User) and from the doctrine documentation about contains():
The comparison of two elements is strict, that means not only the value but also the type must match.
So what is the best way to solve this ManyToMany relationship with extra fields? Or if i would go back and set the onetomany and manytoone relationship how can i modify the hasFriend method? To example check if ID is in array collection of ID's.
EDIT: i have this table... and what i need is:
1. select my friends... and my followers ...check if i am friend with him or not. (because he can be friend with me and i dont have to be with him... like on twitter). I could make manytomany but i need extra fields like: "viewed" "time when he subscribe me" as you can see at my table.
And make query like this and then be able in twig check if (app.user.hasFriend(follower) or something like that)
$qb = $this->createQueryBuilder('r')
->select('u')
->innerJoin('UserBundle:User', 'u')
->Where('r.friend_id=:id')
->setParameter('id', $id)
->orderBy('r.time', 'DESC')
->setMaxResults(50);
return $qb->getQuery()
->getResult();
I was trying to have a many to many relationship with extra fields, and couldn't make it work either... The thing I read in a forum (can't remember where) was:
If you add data to a relationship, then it's not a relationship anymore. It's a new entity.
And it's the right thing to do. Create a new entity with the new fields, and if you need it, create a custom repository to add the methods you need.
A <--- Many to many with field ---> B
would become
A --One to many--> C (with new fields) <-- One to many--B
and of course, C has ManyToOne relationships with both A and B.
I searched everywhere on how to do this, but in the end, it's the right thing to do, if you add data, it's no longer a relationship.
You can also copy what contains usually do, or try to overwrite it in a custom repository, to do whatever you need it to do.
I hope this helps.
I'm adding another answer since it has nothing to do with my original answer. Using the new info you posted, I'm calling the table/entity you posted "Follower". The original entity, "User".
What happens if you create the following associations:
namespace Acme\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\UserBundle\Entity\User
*
* #ORM\Table()
* #ORM\Entity
*/
class User
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Acme\FollowerBundle\Entity\Follower", mappedBy="followeduser")
*/
protected $followers;
/**
* #ORM\OneToMany(targetEntity="Acme\FollowerBundle\Entity\Follower", mappedBy="followeeuser")
*/
protected $followees;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function __construct()
{
$this->followers = new \Doctrine\Common\Collections\ArrayCollection();
$this->followees = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add followers
*
* #param Acme\FollowerBundle\Entity\Follower $follower
*/
public function addFollower(\Acme\FollowerBundle\Entity\Follower $follower)
{
$this->followers[] = $follower;
}
/**
* Add followees
*
* #param Acme\FollowerBundle\Entity\Follower $followee
*/
public function addFollowee(\Acme\FollowerBundle\Entity\Follower $followee)
{
$this->followees[] = $followee;
}
/**
* Get followers
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFollowers()
{
return $this->followers;
}
/**
* Get followees
*
* #return Doctrine\Common\Collections\Collection
*/
public function getFollowees()
{
return $this->followees;
}
}
namespace Acme\FollowerBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Acme\FollowerBundle\Entity\Follower
*
* #ORM\Table()
* #ORM\Entity
*/
class Follower
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="followers")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $followeduser;
/**
* #ORM\ManyToOne(targetEntity="Acme\UserBundle\Entity\User", inversedBy="followees")
* #ORM\JoinColumn(name="followee_id", referencedColumnName="id")
*/
protected $followeeuser;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set followeduser
*
* #param Acme\UserBundle\Entity\User $followeduser
*/
public function setFolloweduser(\Acme\UserBundle\Entity\User $followeduser)
{
$this->followeduser = $followeduser;
}
/**
* Get followeduser
*
* #return Acme\UserBundle\Entity\User
*/
public function getFolloweduser()
{
return $this->followeduser;
}
/**
* Set followeeuser
*
* #param Acme\UserBundle\Entity\User $followeeuser
*/
public function setFolloweeuser(\Acme\UserBundle\Entity\User $followeeuser)
{
$this->followeeuser = $followeeuser;
}
/**
* Get followeeuser
*
* #return Acme\UserBundle\Entity\User
*/
public function getFolloweeuser()
{
return $this->followeeuser;
}
}
I'm not sure if this would do the trick, I really don't have much time to test it, but if it doesn't, I thnk that it's on it's way. I'm using two relations, because you don't need a many to many. You need to reference that a user can have a lot of followers, and a follower can follow a lot of users, but since the "user" table is the same one, I did two relations, they have nothing to do with eachother, they just reference the same entity but for different things.
Try that and experiment what happens. You should be able to do things like:
$user->getFollowers();
$follower->getFollowedUser();
and you could then check if a user is being followed by a follower whose user_id equals $userThatIwantToCheck
and you could search in Followers for a Follower whose user = $user and followeduser=$possibleFriend

Notice: Undefined index in vendor\doctrine\orm\lib\Doctrine\ORM\UnitOfWork.php line 2735 (Symfony 2.1)

I have got two entities associated with OneToOne association.
This is how it all looks like:
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
* #ORM\Table(name="a")
*/
class A
{
....
/**
* #ORM\OneToOne(targetEntity="B", cascade={"all"}, orphanRemoval=true)
* #ORM\JoinColumn(name="b_id", referencedColumnName="id", nullable=true)
*/
private $b;
}
/**
* #ORM\Entity
* #ORM\Table(name="b")
*/
class B
{
....
/**
* #ORM\Column(type="string", length=20)
*/
protected $type;
/**
* #ORM\Column(type="integer")
*/
protected $aId;
}
What I am trying to do is to crate lifeCycleCallback preUpdate on A class.
While calling preUpdate A class already has its on ID. I have tried to do something like this:
/**
* #ORM\PreUpdate
*/
public function update()
{
if (is_null($this->getB()))
$this->b = new B();
$this->b->setAId($this->id)->setType('A'/* classname */);
}
B class has to store classname in type filed and object id in aId field. I cannot use foreign keys here.
The problem is that the above update function returns this error:
Notice: Undefined index: 000000006914b7950000000033b7b784 in C:...\vendor\doctrine\orm\lib\Doctrine\ORM\UnitOfWork.php line 2735
Any ideas what this error means and how to solve it?
EDIT
This is how I fetch A object before passing it to edit form:
$this->getEntityManager()
->createQueryBuilder()
->select('a,b')
->from('MyBundle:A', 'a')
->leftJoin('a.b','b')
->where('a.id = :aId')
->setParameter('aId', $id)
->getQuery()->getSingleResult();
I do edit only A object (in form) and want to create associated B object on first A update and make some changes to this B object on every next update of A.
EDIT2
The above error no longer displays. However changes made to B object in #ORM\PreUpdate of A object are never persisted even when association to be has cascade={"all"}.
Here is the example:
/**
* inside A class !
*
* #ORM\PreUpdate
*/
public function update()
{
$this->lastModified = new \DateTime();
$this->getB()
->setSomething('something'); // <-- this is never stored to db by cascade
}

Single Table Inheritance - discriminator map

I am needing to make a formula to DiscriminatorMap in my class, because I have a lot of class, and I can't discrimine each one.
The discr can be the name of the class.
it's possible? (with annotation, xml or other)
/**
* #ORM\Entity
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"MidUpperArmCircumference" = MidUpperArmCircumference", "KneeHeight" = "KneeHeight"})
*/
thanks.
Look this link maybe it'll help you.
https://medium.com/#jasperkuperus/defining-discriminator-maps-at-child-level-in-doctrine-2-1cd2ded95ffb
I simply left out the DiscriminatorMap annotation and Doctrine automatically used the chield's class name as a discriminator:
/**
* #ORM\Entity()
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
*/
abstract class AbstractContent
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
}
/**
* #ORM\Entity()
*/
class Page extends AbstractContent
{
}
Now when I create a new Page() Doctrine creates an AbstractContentand a Page with an FK to the AbstractContent and sets the AbstractContent's type attribute to page.
This perfect as it let's you generate as many subclasses as you like even in other Bundles without your Superclass (in my case AbstractContent) needing to know about them.
But keep in mind that so far this isn't officially documented. Tested with Doctrine ORM 2.3.
This is an old question. Doctrine supports single table inheritance pretty well.
The below example is from official docs
<?php
namespace MyProject\Model;
/**
* #Entity
* #InheritanceType("SINGLE_TABLE")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
// ...
}
/**
* #Entity
*/
class Employee extends Person
{
// ...
}
Read more about it here

Resources