I've tried to search for this error but the fact that I haven't found anything leads me to believe that I'm doing something silly. I'll include the relevant code below, but basically I'm using multiple table inheritance (or Class Table Inheritance) and trying to use the Doctrine ORM findBy() method to query based on the discriminator column, which results in the following ORMException being thrown: "Unrecognized field: type".
Here is the code that triggers the exception:
// $this->em is an instance of \Doctrine\ORM\EntityManager
$repository = $this->em->getRepository('JoeCommentBundle:Thread');
return $repository->findOneBy(array(
'type' => $this->type,
'related_id' => $id
));
Here is the relevant code for the 'base' abstract entity:
<?php
namespace Joe\Bundle\CommentBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="comment_threads")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap( {"story" = "Joe\Bundle\StoryBundle\Entity\StoryThread"} )
*/
abstract class Thread
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="related_id", type="integer")
*/
protected $relatedId;
/** MORE FIELDS BELOW.... **/
And finally, here is the code for the concrete thread entity:
<?php
namespace Joe\Bundle\StoryBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Joe\Bundle\CommentBundle\Entity\Thread as AbstractThread;
/**
* #ORM\Entity
* #ORM\Table(name="story_comment_threads")
*/
class StoryThread extends AbstractThread
{
/**
* #ORM\OneToOne(targetEntity="Story")
* #ORM\JoinColumn(name="story_id", referencedColumnName="id")
*/
protected $story;
}
I've double checked my schema, and the type column definitely exists, so I'm not sure what could be causing this. Any ideas? Thanks.
Rob, when querying your actually using the parent entity and trying to filter on the discriminator value. Instead, work on the repository relative to the child entity you want to fetch. Doctrine will do the rest for you. So in your case you want to get the repository for StoryThread.
$repository = $this->em->getRepository('JoeCommentBundle:StoryThread');
return repository->find($id);
You cannot use the discriminator column as a standard entity property.
Instead you may do the following:
$dql = 'SELECT e FROM JoeCommentBundle:Thread e
WHERE e.related_id = :related_id AND e INSTANCE OF :type';
$query = $em->createQuery($dql);
$query->setParameters(array(
'type' => $this->type,
'related_id' => $id
));
$record = $query->getSingleResult();
Related
I have a problem on fixtures with fzaninotto/faker on a symfony project, i have some table with relations ManyToOne who not complete automaticaly when I load fixtures since i have update bundles on composer.
There is an error and a message who tell me to add cascad persist but if i do that the entry is create twice. The is my code and my error message, sorry for my bad english...
Project entity:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #ORM\Table(name="`user`");
*/
class User implements UserInterface, \Serializable
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100, nullable=true)
*/
private $firstname;
/**
* #ORM\Column(type="string", length=100, nullable=true)
*/
private $lastname;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Role")
* #ORM\JoinColumn(nullable=false)
*/
private $role;
Role Entity:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\RoleRepository")
*/
class Role
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100, unique=true)
*/
private $name;
/**
* #ORM\Column(type="string", length=100, unique=true)
*/
private $code;
/**
* #ORM\OneToMany(targetEntity="App\Entity\User", mappedBy="role")
*/
private $users;
Fixtures file:
<?php
namespace App\DataFixtures;
use Faker\Factory;
use App\Entity\Tag;
use App\Entity\Role;
use App\Entity\User;
use App\Entity\Skill;
use App\Entity\Follow;
use App\Entity\Statut;
use App\Entity\Techno;
use App\Entity\Comment;
use App\Entity\Project;
use App\Entity\Request;
use App\DataFixtures\Provider;
use Faker\ORM\Doctrine\Populator;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
$generator = Factory::create('fr_FR');
// Ajout provider custom Provider
$generator->addProvider(new Provider($generator));
$populator = new Populator($generator, $manager);
// REMPLIT LES TABLES SIMPLES
// table "role"
$populator->addEntity(
Role::class,
2,
[
'name' => function () use ($generator) {
return $generator->unique()->randomElement(['Administrateur', 'Utilisateur']);
}
]
);
// table "user"
$populator->addEntity(
User::class,
10,
[
'firstname' => function () use ($generator) {
return ($generator->firstName());
},
'lastname' => function () use ($generator) {
return ($generator->lastName());
},
]
);
And my error message when i load fixtures:
In ORMInvalidArgumentException.php line 105:
Multiple non-persisted new entities were found through the given association graph:
* A new entity was found through the relationship 'App\Entity\User#role' that was not configured to cascade persist operations
for entity: Administrateur. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or conf
igure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}).
* A new entity was found through the relationship 'App\Entity\User#role' that was not configured to cascade persist operations
for entity: Utilisateur. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configu
re cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}).
Same issue with 1.9 version, so just downgrade :
1/ Remove faker folder in vendor.
2/ Change composer.json from
"fzaninotto/faker": "^1.9"
to
"fzaninotto/faker": "1.8"
3/ Run composer update
--> Breath now...
Lets imagine I have an Entity like this
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
*
* #ORM\Table(name="entity")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ MyEntityRepository")
*/
class Entity
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Kind")
*/
private $kind;
}
I want to query them and order them by kind.
Here what I do that in my repository
<?php
namespace AppBundle\Repository;
class MyEntityRepository extends \Doctrine\ORM\EntityRepository
{
public function getAllOrdered()
{
return $this->createQueryBuilder('e')
->join('e.kind', 'k')
->orderBy('k.id', 'ASC')
->getQuery()
->getResult();
}
}
This work great but that completely ignore all line where the kind is null.
So how can I retrieve and order all entities even if the kind is null ?
You are using an inner join. This is a more efficient query because it only retrieves records where there is a match between the 2 tables.
If you want to pick up null values, you need to use leftJoin. This should be used with care as these queries are heavier than innerJoin because all records in the base table are considered instead of just the matches.
<?php
namespace AppBundle\Repository;
class MyEntityRepository extends \Doctrine\ORM\EntityRepository
{
public function getAllOrdered()
{
return $this->createQueryBuilder('e')
->leftJoin('e.kind', 'k')
->orderBy('k.id', 'ASC')
->getQuery()
->getResult();
}
}
I have two entities related by a OneToMany relation:
<?php
namespace CRMBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* User
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="CRMBundle\Entity\ContactRepository")
*/
class User
{
/*...*/
/**
* #ORM\OneToMany(targetEntity="CRMBundle\Entity\Message", mappedBy="user", cascade={"persist"})
* #ORM\OrderBy({"datetime" = "DESC"})
*/
protected $messages;
/*...*/
}
And
<?php
namespace CRMBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Message
*
* #ORM\Table()
* #ORM\Entity
*/
class Message
{
/*...*/
/**
* #ORM\ManyToOne(targetEntity="CRMBundle\Entity\User", inversedBy="messages")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="SET NULL")
*/
private $user;
/**
* #var \DateTime
*
* #ORM\Column(name="Datetime", type="datetime", nullable=true)
*/
private $datetime;
/*...*/
}
My question is how to create a query in the UserController to get every user with the last message (i.e. the most recent according to the datetime attribute) of each user?
I think what you are looking for is in one of my previous answers to one of my own questions ^^
You have to use a subquery to select dynamically the most recent datetime value of one user's messages, and to join the message having this value.
To do this, you must define the (sub) query selecting the MAX value of message.datetime:
$qb2= $this->createQueryBuilder('ms')
->select('MAX(ms.datetime) maxDate')
->where('ms.user = u')
;
And then use it in your join clause, the whole function being in your UserRepository:
$qb = $this->createQueryBuilder('u');
$qb ->leftJoin('u.messages', 'm', 'WITH', $qb->expr()->eq( 'm.datetime', '('.$qb2->getDQL().')' ))
->addSelect('m');
Your User (each of them) will then have a messages Collection, containing one (or null if no message from the user) message, which you will get this way:
$user->getMessages()->first();
But if you use the lazy loading function of Symfony, as you have already defined an orderby annotation on your user.messages attribute, calling
$user->getMessages()->first()
should return to you the most recent message (but will also load all the others silently).
To avoid this silent second DB query, you can join it directly to the query requesting your users:
$qb = $this->createQueryBuilder('u');
$qb ->leftJoin('u.messages', 'm')
->addSelect('m');
In the example below $type can be both 'brand' or 'category', and $slug will be either a brand or a category.
How would I approach this when I want to filter my results on a category and a brand at the same time?
public function getGroupAction($slug, $type = null, $grouped = true)
{
$group = $this->getDoctrine()
->getRepository('AudsurShopBundle:'.$type)
->findOneBy(array( 'name' => $slug ))
->getProducts();
return $this->render('AudsurShopBundle:Default:productOverview.html.twig', array(
'group' => $group
)
);
}
To do what you want to do, you have to use Class table inheritance with the system of a discriminator column.
For your example, create the following entity :
<?php
namespace ...
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discriminator", type="string")
* #ORM\DiscriminatorMap({"category" = "Category", "brand" = "Brand"})
*/
abstract class NameHolder
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
// Getters and setters
}
and then make your 2 entities inherit this class:
<?php
namespace ...;
use Doctrine\ORM\Mapping as ORM;
/**
* FirstChild
*
* #ORM\Table()
* #ORM\Entity
*/
class Category extends NameHolder
{
// all methods and properties except from the "name"
}
and:
<?php
namespace ...;
use Doctrine\ORM\Mapping as ORM;
/**
* FirstChild
*
* #ORM\Table()
* #ORM\Entity
*/
class Brand extends NameHolder
{
// all methods and properties except from the "name"
}
So now you can make a query like this:
$group = $this->getDoctrine()
->getRepository('AudsurShopBundle:NameHolder')
->findOneBy(array('name' => $slug))
->getProducts();
This will return an array collection with both Brand and Category entities.
However I am not sure a NameHolder class really makes sense. An other solution would be to make separate queries for both entities without changing any of your entities, but that's not what you seem to be looking for.
I have the following two entities:
<?php
namespace Site\AnnonceBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Site\UserBundle\Entity\User;
/**
* Site\AnnonceBundle\Entity\Sujet
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Site\AnnonceBundle\Entity\SujetRepository")
*/
class Sujet
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
//Some code...
/**
*
* #ORM\ManyToOne(targetEntity="Site\UserBundle\Entity\User")
*/
private $user;
//getter/setter....
user Entity(FOSUserBundle) :
<?php
namespace Site\UserBundle\Entity;
use FOS\UserBundle\Entity\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table()
*
*/
class User extends BaseUser{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function getId() {
return $this->id;
}
}
when I created a "Sujet", I made(in SujetController.php):
$em = $this->getDoctrine()->getEntityManager();
$sujet->setResolu(false);
$em->persist($sujet);
$em->flush();
its works, But the inserted "Sujet" in the database refer to user null... so in the second version i made this:
$em = $this->getDoctrine()->getEntityManager();
$sujet->setResolu(false);
$sujet->setUser(new User($this->get('session')->get('user_id'))) ;//the user is already in the DB
$em->persist($sujet);
$em->flush();
but i get this error:
A new entity was found through the relationship 'Site\AnnonceBundle\Entity\Sujet#user' that was not configured to cascade persist operations for entity: . Explicitly persist the new entity or configure cascading persist operations on the relationship. If you cannot find out which entity causes the problem implement 'Site\UserBundle\Entity\User#__toString()' to get a clue.
I do not understand, I have already worked with another ORM (JPA) and it works in that way ...
how to tell "Sujet" about what is related to an entity already existing in database?
(sorry if my english is bad)
EDIT : its worked for me :
$user = $this->get('security.context')->getToken()->getUser();
$sujet->setUser($user);
$em->persist($sujet);
$em->flush();
Just in case, the error came from the fact you created a new User and linked it to the sujet without persisting it (as there is no cascade, the entity was linked to a none persisted entity, resulting in the error).
Your edit suggests you found a way to get the current User (this one is persisted, unlike the "new User") you made before.
You could also have done :
$repository = $this->getDoctrine()
->getEntityManager()
->getRepository('YourBundle:User');
$user = $repository->find($iduser);
$sujet->setUser($user);
It would have been a good solution if you wanted to make the edit "for another user".