No subclass association on Doctrine class inheritance - symfony

I’m trying to implement this:
With:
User
/**
* #ORM\Entity(repositoryClass="SG\UserBundle\Entity\UserRepository")
* #ORM\Table(name="users")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", columnDefinition="ENUM('lottery')"))
* #ORM\DiscriminatorMap({"lottery"="SG\Lottery\UserBundle\Entity\LotteryUser"})
*/
abstract class User extends Prospect
{
/**
* #ORM\OneToMany(targetEntity="SG\UserBundle\Entity\Subscription", mappedBy="user")
*/
protected $subscriptions;
}
LotteryUser
/**
* #ORM\Entity(repositoryClass="SG\Lottery\UserBundle\Entity\LotteryUserRepository")
* #ORM\Table(name="lottery_users")
*/
class LotteryUser extends User
{
// ...
}
Subscription
/**
* #ORM\Table(name="subscriptions")
* #ORM\Entity(repositoryClass="SG\UserBundle\Entity\SubscriptionRepository")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorMap({"lottery"="SG\Lottery\UserBundle\Entity\Subscription"})
* #ORM\DiscriminatorColumn(name="game", columnDefinition="ENUM('lottery')"))
*/
abstract class Subscription
{
/**
* #ORM\ManyToOne(targetEntity="SG\UserBundle\Entity\User", inversedBy="subscriptions")
* #ORM\JoinColumn(name="user")
*/
protected $user;
}
Lottery\Subscription
/**
* #ORM\Entity
* #ORM\Table(name="lottery_subscriptions")
*/
class Subscription extends \SG\UserBundle\Entity\Subscription
{
/**
* #ORM\ManyToOne(targetEntity="SG\Lottery\GameBundle\Entity\Package")
* #ORM\JoinColumn(name="package")
*/
protected $package;
}
But when getting the lottery users list with subscription and package tables joined:
$qb = $this->createQueryBuilder('u')
->leftJoin('u.subscriptions', 'sb')
->addSelect('sb')
->leftJoin('sb.package', 'pk')
->addSelect('pk');
It fails:
[Semantical Error] line 0, col 176 near 'p LEFT JOIN u.periods': Error: Class SG\UserBundle\Entity\Subscription has no association named package
I don’t have any idea how to deal correctly, in the Doctrine-way, with this case. I don’t know why Doctrine doesn’t join the subclass Lottery\Subscription to get the package association whereas the subscription entry has its discriminator column to lottery.
Thanks for helping!

Your Entity SG\UserBundle\Entity\Subscription doesn't have $package definition. You have to join to the Lottery\Subscription entity.
class LotteryUser extends User
{
/**
* #ORM\OneToMany(targetEntity="SG\UserBundle\Entity\Lottery\Subscription", mappedBy="user")
*/
protected $lotterySubscriptions;
}

Related

Symfony + JMSSerializer throw 500 - handleCircularReference

I'm trying to use the JMSSerializer with Symfony to build a simple json api.
So i have 2 simple Entities (1 User can have many Cars, each Car belongs to one User):
class Car
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="cars")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
}
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Car", mappedBy="user", orphanRemoval=true)
*/
private $cars;
}
Now i want to get all Cars with their User.
My Controller:
class CarController extends AbstractController
{
/**
* #param CarRepository $carRepository
*
* #Route("/", name="car_index", methods="GET")
*
* #return Response
*/
public function index(CarRepository $carRepository)
{
$cars = $carRepository->findAll();
$serializedEntity = $this->container->get('serializer')->serialize($cars, 'json');
return new Response($serializedEntity);
}
}
This will throw a 500 error:
A circular reference has been detected when serializing the object of
class \"App\Entity\Car\" (configured limit: 1)
Ok, sounds clear. JMS is trying to get each car with the user, and go to the cars and user ....
So my question is: How to prevent this behaviour? I just want all cars with their user, and after this, the iteration should be stopped.
You need to add max depth checks to prevent circular references.
This can be found in the documentation here
Basically you add the #MaxDepth(1) annotation or configure max_depth if you're using XML/YML configuration. Then serialize like this:
use JMS\Serializer\SerializationContext;
$serializer->serialize(
$data,
'json',
SerializationContext::create()->enableMaxDepthChecks()
);
Example Car class with MaxDepth annotation:
class Car
{
/**
* #\JMS\Serializer\Annotation\MaxDepth(1)
*
* [..]
*/
private $user;

Doctrine doesn't update/generate fields of ManyToOne and OneToMany

I have a superclass that currently works fine (all relations and properties are updating to the database)
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Mapping\JoinColumn;
use JMS\Serializer\Annotation as JMS;
/**
* Document
*
* #Table(name="document")
* #Entity(repositoryClass="AcmeBundleDocumentRepository")
*/
class Document
{
/**
* #var string
*
* #Column(name="id", type="string")
* #Id
* #GeneratedValue(strategy="UUID")
*/
protected $id;
/**
* #var string
* #Column(name="name", type="string", length=255)
*/
protected $name;
/**
* #var string
* #Column(name="type", type="string", length=255)
*/
protected $type;
/**
* #var boolean
* #Column(name="has_attachments", type="boolean")
*/
protected $hasAttachments;
/**
* #ManyToOne(targetEntity="Delivery")
* #JoinColumn(name="delivery_id", referencedColumnName="id", nullable=false)
* #JMS\Exclude()
*/
protected $delivery;
/**
* #OneToMany(targetEntity="Extension", mappedBy="document", cascade={"persist","remove"})
**/
protected $extensions;
public function __construct()
{
$this->extensions = new ArrayCollection();
}
/* getter and setters */
}
Now I've created a entity called Note that extends to Document entity
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Entity;
/**
* Note
*
* #Table(name="note")
* #Entity(repositoryClass="NoteRepository")
*/
class Note extends Document
{
}
I am suppose that the table/entity note should generate the same things of the class that extends. But not do it
I run php bin/console doctrine:schema:update -f
this only generates properties and not FK (foreing Keys), in this case #ManyToOne and #OneToMany.
Additionally maybe help us, i have those entities on the same database
I am doing something wrong ?
As per docs I think you're missing the #MappedSuperclass annotation or you're using Doctrine inheritance in the wrong way. Be aware that a MappedSupperClass is not an entity by itself instead is just a class for share common methods and properties among it is children classes (same inheritance concept that you should already know).
/**
* #MappedSuperclass
*/
class DocumentSuperClass
{
...
}
/**
* #Table(name="document")
* #Entity(repositoryClass="AcmeBundleDocumentRepository")
*/
class Document extends DocumentSuperClass
{
...
}
/**
* #Table(name="note")
* #Entity(repositoryClass="NoteRepository")
*/
class Note extends DocumentSuperClass
{
...
}

Doctrine Undefined index: UnitOfWork.php line 2873

I'm using FOSUserBundle with Symfony2. I'm attempting to use the Registration Email Confirmation functionality. When I hit the /confirmation/{confirmationToken} url Doctrine throws the following exception
Notice: Undefined index: 000000006553367d000000005133f603 in /Users/jacobspizziri/Documents/Projects/SRC3/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php line 2873
This occurs when it tries to hash the Group entity I constructed here:
spl_object_hash($entity)
and $entity looks like this:
Here are the relations I have setup in my entities:
/**
* #ORM\Entity
* #Gedmo\SoftDeleteable(fieldName="deletedAt")
*
* #author jacobspizziri
*/
class Group implements SoftDeletableInterface, TimestampableInterface {
/**
* #ORM\OneToMany(targetEntity="SRC\Bundle\UserBundle\Entity\User", mappedBy="group", cascade={"all"})
*/
protected $users;
}
/**
* #ORM\Entity
* #Gedmo\SoftDeleteable(fieldName="deletedAt")
*/
class User extends BaseUser implements SoftDeletableInterface, TimestampableInterface
{
/**
* #ORM\ManyToOne(targetEntity="SRC\Bundle\UserBundle\Entity\Group", inversedBy="users")
* #ORM\JoinColumn(name="group_id", referencedColumnName="id")
*/
protected $group;
}
My bundle extends FOSUserBundle however I am not extending FOSUserBundle\Entity\Group for my Group
whats going on here?

Undefined index: inverseJoinColumns while trying to define ManyToMany relationship between two entities

I have two entities: User and Company and the relationship between them is n:m. In my User.php entity I have this code:
/**
* #ORM\ManyToMany(targetEntity="PL\CompanyBundle\Entity\Company", mappedBy="users", cascade={"all"})
*/
protected $companies;
public function __construct() {
$this->companies = new \Doctrine\Common\Collections\ArrayCollection();
}
public function setCompanies(\PL\CompanyBundle\Entity\Company $companies) {
$this->companies = $companies;
}
public function getCompanies() {
return $this->companies;
}
And in my Company.php entity I have this other code:
/**
* #ORM\ManyToMany(targetEntity="Application\Sonata\UserBundle\Entity\User", mappedBy="companies")
*/
protected $users;
public function __construct() {
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
But I got this error:
ContextErrorException: Notice: Undefined index: inverseJoinColumns in
/var/www/html/apps/portal_de_logistica/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
line 1041
What is wrong in the mapping?
EDIT Refactor code
Following instructions from #ahmed-siouani I made the following changes:
User.php
/**
* #ORM\ManyToMany(targetEntity="PL\CompanyBundle\Entity\Company", inversedBy="users")
* #ORM\JoinTable(name="fos_user_user_has_company",
* JoinColumns={#ORM\JoinColumn(name="fos_user_user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="company_id", referencedColumnName="id")}
* )
*/
protected $companies;
where fos_user_user_has_company is the table added for the n:m relationship.
Company.php
/**
* #ORM\ManyToMany(targetEntity="Application\Sonata\UserBundle\Entity\User", mappedBy="companies")
*/
protected $users;
And now the error is:
AnnotationException: [Creation Error] The annotation #ORM\JoinTable
declared on property
Application\Sonata\UserBundle\Entity\User::$companies does not have a
property named "JoinColumns". Available properties: name, schema,
joinColumns, inverseJoinColumns
Any?
You may need to specify the joinColumns and the inverseJoinColumns when defining thejoinTable. For a bidirectional many-to-many definition is would be something like,
class User
{
// ...
/**
* Bidirectional - Many users have Many companies (OWNING SIDE)
*
* #ManyToMany(targetEntity="Company", inversedBy="users")
* #JoinTable(name="users_companies",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="company_id", referencedColumnName="id")}
* )
**/
private $companies;
public function __construct() {
$this->companies = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
While your Company class should be defined as follow,
class Company
{
// ...
/**
* Bidirectional (INVERSE SIDE)
*
* #ManyToMany(targetEntity="User", mappedBy="companies")
*/
private $users;
My fault was that both sides of the ManyToMany had been defined with mappedBy, but only one side should have been using mappedBy and the other side should have used inversedBy (this is normally defined at main entity, that controls the collection).
In addition to #Ahmed solution take care of typos since I made one and for that the second error I got. See my annotation said:
/**
* #ORM\ManyToMany(targetEntity="PL\CompanyBundle\Entity\Company", inversedBy="users")
* #ORM\JoinTable(name="fos_user_user_has_company",
* JoinColumns={#ORM\JoinColumn(name="fos_user_user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="company_id", referencedColumnName="id")}
* )
*/
protected $companies;
But right one is:
/**
* #ORM\ManyToMany(targetEntity="PL\CompanyBundle\Entity\Company", inversedBy="users")
* #ORM\JoinTable(name="fos_user_user_has_company",
* joinColumns={#ORM\JoinColumn(name="fos_user_user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="company_id", referencedColumnName="id")}
* )
*/
protected $companies;
Differences are in this line:
joinColumns={#ORM\JoinColumn(name="fos_user_user_id", referencedColumnName="id")},

Query an inherited table via a relation table

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.

Resources