I was trying to create a select query with doctrine but it didn't work.
I have three tables, Product(ID, Name), Client(ID, Name), Orders(product, client).
I need to select and show all the orders made by a client, and also the product name.
How can I make this particular query using doctrine?
Sorry if it's a banal question...
Produt:
<?php
namespace Example\Bundle\CrudBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*/
class Product
{
/**
* #var string
*/
private $name;
/**
* #var string
*/
private $prize;
/**
* #var integer
*/
private $id;
/**
* Set name
*
* #param string $name
* #return Product
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set prize
*
* #param string $prize
* #return Product
*/
public function setPrize($prize)
{
$this->prize= $prize;
return $this;
}
/**
* Get prize
*
* #return string
*/
public function getprize()
{
return $this->prize;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
Client:
<?php
namespace Example\Bundle\CrudBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Client
*/
class Client
{
/**
* #var string
*/
private $name;
/**
* #var string
*/
private $age;
/**
* #var integer
*/
private $id;
/**
* Set name
*
* #param string $name
* #return Client
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set age
*
* #param string $age
* #return Client
*/
public function setAge($age)
{
$this->age = $age;
return $this;
}
/**
* Get age
*
* #return string
*/
public function getAge()
{
return $this->age;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
Orders:
<?php
namespace Example\Bundle\CrudBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Orders
*/
class Orders
{
/**
* #var integer
*/
private $idProduct;
/**
* #var integer
*/
private $idIngrediente;
/**
* Set idProduct
*
* #param integer $idProduct
* #return Orders
*/
public function setIdProduct($idProduct)
{
$this->idProduct = $idProduct;
return $this;
}
/**
* Get idProduct
*
* #return integer
*/
public function getIdProduct()
{
return $this->idProduct;
}
/**
* Set idClient
*
* #param integer $idClient
* #return Orders
*/
public function setIdClient($idIngrediente)
{
$this->idClient= $idClient;
return $this;
}
/**
* Get idClient
*
* #return integer
*/
public function getIdClient()
{
return $this->idClient;
}
}
This is how I was trying to make the query, by first checking if the product is in the table and then selecting the fields I need from both the Orders and the Product tables.
class ProductRepository extends EntityRepository
{
public function findAllOrderedByName()
{
return $this->getEntityManager()
->createQuery(
'SELECT * FROM ExampleCrudBundle:Product p ORDER BY p.name ASC'
)
->getResult();
}
}
public function findOneByIdJoinedToCategory($id)
{
$query = $this->getEntityManager()
->createQuery('
SELECT p, c FROM ExampleCrudBundle:Product p
JOIN p.category c
WHERE p.id = :id'
)->setParameter('id', $id);
try {
return $query->getSingleResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}
you might want to use $em->getRepository("ExampleCrudBundle:Product")->createQueryBuilder('p')
->leftJoin("p.category c")
;
You need to read more about doctrine, on one hand, you dont need to define the table for ManyToMany relation, doctrine will do it for you, on the other hand, defining such table isn't enough as you need to show doctrine that there is a relation: product - category.
What you do now is:
"Get me a product where category is X" but there is no information about category in your product entity
Please read http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html about "ManyToMany" relation, and relations at all.
Have you read the documentation?
http://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html
This is how you do a select query:
<?php
$query = $em->createQuery('SELECT u FROM MyProject\Model\User u WHERE u.age > 20');
$users = $query->getResult();
Related
I'm training with Entities and generating CRUD controller for my Entities. I did Task and Category Entities:
Category Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Category
*
* #ORM\Table(name="category")
* #ORM\Entity(repositoryClass="AppBundle\Repository\CategoryRepository")
*/
class Category
{
/**
* #var int
*
* #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;
/**
* #ORM\ManyToMany(targetEntity="Task", mappedBy="categories")
*/
private $tasks;
public function __construct() {
$this->tasks = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
public function getTasks()
{
return $this->tasks;
}
public function setTasks(Task $task)
{
$this->tasks = $task;
}
public function __toString() {
return $this->name;
}
}
?>
Task Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Task
*
* #ORM\Table(name="task")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TaskRepository")
*/
class Task
{
/**
* #var int
*
* #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;
/**
* #var \DateTime
*
* #ORM\Column(name="datetime", type="datetime")
*/
private $datetime;
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="tasks")
* #ORM\JoinTable(name="categories_tasks")
*/
private $categories;
public function __construct() {
$this->categories = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Task
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set datetime
*
* #param \DateTime $datetime
*
* #return Task
*/
public function setDatetime($datetime)
{
$this->datetime = $datetime;
return $this;
}
/**
* Get datetime
*
* #return \DateTime
*/
public function getDatetime()
{
return $this->datetime;
}
public function getCategories()
{
return $this->categories;
}
public function setCategories(Category $categories)
{
$this->categories = $categories;
}
public function __toString() {
return $this->name;
}
}
?>
I generated CRUD controllers for that Entities and it works almost fine - I can edit categories for Task. When I add new Task I also can choose 0, 1 or more categories. But it's not working in second way. When I add new category or edit existed I can choose 0, 1 or more categories from select list, I "save" and it goes further without exceptions but when I check in database or simply editing this category chosen tasks are not matched. I see category in database but #ORM\JoinTable(name="categories_tasks") has no new row. I think it has relation with #ORM\JoinTable(name="categories_tasks") because when I moved that part of code to Category Entity then I had inverse situation.
Task entity here is the relationship owner(it has the inversedBy annotation in the class), that is why adding categories to tasks then saving saves the categories to the relationship. For the categories to set the relationships you need to iterate through the tasks and have them set the id of the category, then save the tasks.
Not sure if there is a better solution. But I am confident of my diagnosis of the problem.
I think you need to setup your addCategory, removeCategory and also addTask , removeTask methods properly:
Task.php
public function addCategory(Category $category)
{
if (!$this->categories->contains($category)) {
$this->categories->add($category);
$category->addTask($this);
}
}
public function removeCategory(Category $category)
{
if ($this->categories->contains($category)) {
$this->categories->remove($category);
$category->removeTask($this);
}
}
Same for Category.php
public function addTask(Task $task)
{
if (!$this->tasks->contains($task)) {
$this->tasks->add($task);
$task->addCategory($this);
}
}
public function removeTask (Task $task)
{
if ($this->tasks->contains($task)) {
$this->tasks->remove($task);
$task->removeCategory($this);
}
}
I use Symfony doctrine to set and get data from my MySQL database. I can push new data without any problem but when I try to get them with a findAll for exemple, I get an array with the good length but nothing in.
Here's my controller:
namespace KGN\CoreBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Doctrine\ORM\EntityManagerInterface;
use KGN\CoreBundle\Entity\Appointment;
use KGN\CoreBundle\Entity\Testy;
class AdminController extends Controller
{
public function indexAction()
{
return $this->render('KGNCoreBundle:Admin:index.html.twig');
}
public function aptAction()
{
$rep = $this->getDoctrine()
->getRepository('KGNCoreBundle:Testy');
$testy = $rep->findAll();
// return new Response('This is for show : '. count($testy) );
return new JsonResponse($testy);
}
public function createAction()
{
$em = $this->getDoctrine()->getManager();
$testy = new Testy();
$testy->setTitre('Magnifique');
$testy->setName('Helicoptere');
$em->persist($testy);
$em->flush();
return new Response('This is for create');
}
}
and what I get on my view page
[{},{}]
And it's true that there is 2 elements in my SQL table.
( I have create my entity with php bin/console doctrine:generate:entity without edition stuff in the "Testy" class or rep )
Entity/Testy
namespace KGN\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Testy
*
* #ORM\Table(name="testy")
* #ORM\Entity(repositoryClass="KGN\CoreBundle\Repository\TestyRepository")
*/
class Testy
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="titre", type="string", length=255)
*/
private $titre;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set titre
*
* #param string $titre
*
* #return Testy
*/
public function setTitre($titre)
{
$this->titre = $titre;
return $this;
}
/**
* Get titre
*
* #return string
*/
public function getTitre()
{
return $this->titre;
}
/**
* Set name
*
* #param string $name
*
* #return Testy
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
}
associed rep
namespace KGN\CoreBundle\Repository;
/**
* TestyRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class TestyRepository extends \Doctrine\ORM\EntityRepository
{
}
Hi The Function findAll Return the correct answer but its return as array of Objects
And JsonResponse can't desplay Object.
to fixe that you have to create a custom function in your repository that return An array exemple
public function getAll() {
$qb = $this->createQueryBuilder('u');
return $qb->getQuery()->getArrayResult();
}
$em = $this->getDoctrine()->getManager();
$records = $em->getRepository("KGNCoreBundle:Testy")->findAll();
Hope its help you
When i'm viewing www.example.com/product/10 this product is link to a User Id. Now i would like to show only Comment that is link to the User id of the product a i'm viewing.
The table Comment is link to the table User, so each comment have is user id. The table Product (Post) is also link to the table user , each product have is user id.
For now the table Comment and Product are not yet link , i think they should be so i can perform a query, but i'm not sure.
I'm using FosCommentBundle for the comment.
Controller:
public function productAction($id, Request $request)
{
$session = $this->getRequest()->getSession();
$em = $this->getDoctrine()->getManager();
$findEntities = $em->getRepository('FLYBookingsBundle:Post')->findBy(array('id' => $id));
$entities = $this->get('knp_paginator')->paginate($findEntities, $this->get('request')->query->get('page', 1), 9
);
if ($session->has('cart'))
$cart = $session->get('cart');
else
$cart = false;
if (!$entities) {
throw $this->createNotFoundException('Unable to find Post entity.');
}
$id = 'thread_id';
$thread = $this->container->get('fos_comment.manager.thread')->findThreadById($id);
if (null === $thread) {
$thread = $this->container->get('fos_comment.manager.thread')->createThread();
$thread->setId($id);
$thread->setPermalink($request->getUri());
// Add the thread
$this->container->get('fos_comment.manager.thread')->saveThread($thread);
}
$comments = $this->container->get('fos_comment.manager.comment')->findCommentTreeByThread($thread);
return $this->render('FLYBookingsBundle:Post:product.html.twig', array('entity' => $entities,
'cart' => $cart,'comments' => $comments,
'thread' => $thread));
}
Product.html.twig
<div id="fos_comment_thread" data-thread="{{ thread.id }}">
{% include 'FOSCommentBundle:Thread:async.html.twig' with {
'comments': comments,
'thread': thread
} %}
Comment.php
<?php
namespace Application\Sonata\CommentBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\CommentBundle\Entity\Comment as BaseComment;
use FOS\CommentBundle\Model\SignedCommentInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Application\Sonata\UserBundle\Entity\User;
/**
* #ORM\Entity
* #ORM\ChangeTrackingPolicy("DEFERRED_EXPLICIT")
*/
class Comment extends BaseComment implements SignedCommentInterface
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\generatedValue(strategy="AUTO")
*/
protected $id;
/**
* Thread of this comment
*
* #var Thread
* #ORM\ManyToOne(targetEntity="Application\Sonata\CommentBundle\Entity\Thread")
*/
protected $thread;
/**
* Author of the comment
*
* #ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\User")
* #var User
*/
protected $author;
/**
* Sets the author of the Comment
*
* #param UserInterface $user
*/
public function setAuthor(UserInterface $author)
{
$this->author = $author;
}
/**
* Gets the author of the Comment
*
* #return UserInterface
*/
public function getAuthor()
{
return $this->author;
}
public function getAuthorName()
{
if (null === $this->getAuthor()) {
return 'Anonymous';
}
return $this->getAuthor()->getUsername();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
Post.php
<?php
namespace FLY\BookingsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Application\Sonata\UserBundle\Entity\User;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use JMS\SecurityExtraBundle\Annotation\Secure;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Form\Type\VichImageType;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Symfony\Component\Form\Extension\Core\Type\FileType;
/**
* Post
*
* #ORM\Table(name="post")
* #ORM\Entity(repositoryClass="FLY\BookingsBundle\Entity\PostRepository")
* #Vich\Uploadable
*/
class Post
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #Assert\Length(min=2, max=20)
*
* #ORM\Column(name="username", type="string", length=45, nullable=true)
*/
private $username;
/**
* #var string
*
* #Assert\Regex("/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+#[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/")
*
* #ORM\Column(name="email", type="string", length=45, nullable=false)
*/
private $email;
/**
* #ORM\ManyToOne(targetEntity="FLY\BookingsBundle\Entity\Tva")
* #ORM\joinColumn(onDelete="SET NULL")
*/
private $tva;
/**
* #ORM\ManyToOne(targetEntity="FLY\BookingsBundle\Entity\Media")
* #ORM\joinColumn(onDelete="SET NULL")
*/
private $image;
/**
* #var string
*
* #Assert\Length(max=350)
*
* #ORM\Column(name="description", type="text" , length=125, nullable=true)
*/
private $description;
/**
* #var float
*
* #Assert\NotBlank()
*
*
* #Assert\Regex(
* pattern= "/^[1-9]\d{0,7}(?:\.\d{1,4})?$/",
* message= "The First number can't start with 0"
* )
*
* #ORM\Column(name="price", type="float")
*/
private $price;
/**
*
*
* #ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\User")
* #ORM\JoinColumn(onDelete="CASCADE")
* #Security("user.getId() == post.getUser()")
*/
private $user;
/**
* #ORM\OneToOne(targetEntity="Quantity", cascade={"remove"})
* #ORM\JoinColumn(name="sold_id", referencedColumnName="id")
*/
protected $sold;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set username
*
* #param string $username
*
* #return Post
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
/**
* Get username
*
* #return string
*/
public function getUsername()
{
return $this->username;
}
/**
* Set email
*
* #param string $email
*
* #return Post
*/
public function setEmail($email)
{
$this->email = $email;
return $this;
}
/**
* Get email
*
* #return string
*/
public function getEmail()
{
return $this->email;
}
/**
* Set price
*
* #param float $price
* #return Post
*/
public function setPrice($price)
{
$this->price = $price;
return $this;
}
/**
* Get price
*
* #return float
*/
public function getPrice()
{
return $this->price;
}
/**
* #return User
*/
public function getUser()
{
return $this->user;
}
/*
* #param User $user
*/
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
/**
* Set tva
*
* #param \FLY\BookingsBundle\Entity\Tva $tva
* #return Post
*/
public function setTva(\FLY\BookingsBundle\Entity\Tva $tva)
{
$this->tva = $tva;
return $this;
}
/**
* Get tva
*
* #return \FLY\BookingsBundle\Entity\Tva
*/
public function getTva()
{
return $this->tva;
}
/**
* Set image
*
* #param \FLY\BookingsBundle\Entity\Media $image
* #return Post
*/
public function setImage(\FLY\BookingsBundle\Entity\Media $image)
{
$this->image = $image;
return $this;
}
/**
* Get image
*
* #return \FLY\BookingsBundle\Entity\Media
*/
public function getImage()
{
return $this->image;
}
/**
* Set description
*
* #param string $description
* #return Post
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set sold
*
* #param integer $sold
* #return Post
*/
public function setSold($sold)
{
$this->sold = $sold;
return $this;
}
/**
* Get sold
*
* #return integer
*/
public function getSold()
{
return $this->sold;
}
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="product_image", fileNameProperty="imageName")
*
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
private $imageName;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTime
*/
private $updatedAt;
/**
* 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 $image
*
* #return User
*/
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
if ($image) {
// 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->updatedAt = new \DateTime('now');
}
return $this;
}
/**
* #return File
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* #param string $imageName
*
* #return User
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
return $this;
}
/**
* #return string
*/
public function getImageName()
{
return $this->imageName;
}
}
ADD:
<?php
namespace Application\Sonata\CommentBundle\Entity;
use FLY\BookingsBundle\Entity\Post;
use Application\Sonata\UserBundle\Entity\User;
use Doctrine\ORM\EntityRepository;
class CommentRepository extends EntityRepository
{
/**
* Get Comments for a single Product.
* #param Post $post
* #return mixed
*/
public function getCommentsForSinglePost(Post $post)
{
$qb = $this->createQueryBuilder('c')
->where('c.post = :postId')
->setParameter('postId', $post->getId());
$query = $qb->getQuery();
return $query->execute();
}
}
Relation from Comment to Post(Product):
/**
* #ORM\ManyToOne(targetEntity="FLY\BookingsBundle\Entity\Post")
* #ORM\JoinColumn(nullable=true)
*/
private $post;
/**
* Set post
*
* #param \FLY\BookingsBundle\Entity\Post $post
* #return Comment
*/
public function setUser(\FLY\BookingsBundle\Entity\Post $post = null)
{
$this->post = $post;
return $this;
}
/**
* Get post
*
* #return \FLY\BookingsBundle\Entity\Post
*/
public function getPost()
{
return $this->post;
}
modication in controller:
public function productAction($id, Request $request, $post)
{
$session = $this->getRequest()->getSession();
$em = $this->getDoctrine()->getManager();
$findEntities = $em->getRepository('FLYBookingsBundle:Post')->findBy(array('id' => $id));
$entities = $this->get('knp_paginator')->paginate($findEntities, $this->get('request')->query->get('page', 1), 9
);
if ($session->has('cart'))
$cart = $session->get('cart');
else
$cart = false;
if (!$entities) {
throw $this->createNotFoundException('Unable to find Post entity.');
}
$post = 'thread_id';
$thread = $em->getRepository('ApplicationSonataCommentBundle:Comment')->getCommentsForSinglePost($post);
if (null === $thread) {
$thread = $this->container->get('fos_comment.manager.thread')->createThread();
$thread->setId($post);
$thread->setPermalink($request->getUri());
// Add the thread
$this->container->get('fos_comment.manager.thread')->saveThread($thread);
}
$comments = $this->container->get('fos_comment.manager.comment')->findCommentTreeByThread($thread);
return $this->render('FLYBookingsBundle:Post:product.html.twig', array('entity' => $entities,
'cart' => $cart,'comments' => $comments,'post' => $post,
'thread' => $thread));
}
Error:
Controller
"FLY\BookingsBundle\Controller\PostController::productAction()"
requires that you provide a value for the "$post" argument (because
there is no default value or because there is a non optional argument
after this one).
Considering you have Many-To-One association from Comment to Product. Here is how you should write a new method in CommentRepository to get list of comments for a single product.
/**
* Get Comments for a single Product.
* #param Product $product
* #return mixed
*/
public function getCommentsForSingleProduct(Product $product)
{
$qb = $this->createQueryBuilder('c')
->where('c.product = :productId')
->setParameter('productId', $product->getId());
$query = $qb->getQuery();
return $query->execute();
}
You can add any other condition as per your requirement. or add sort, or pagination etc..
Note : I haven't added user association. Let me know if you need anything else.
I'm starting to struggle with finding a solution for this online/offline so maybe someone has an answer or suggestion of best approach.
The problem is that a Question entity has a many-to-many relation tags which should return Tag entities in alphabetical order. The generated SQL is correct, but the collection which is returned doesn't follow the order.
I'm starting to lean towards using:
a) lifecycle events to call a method which will sort the tags collection.
b) on getTags() method call a method which will check if collection was sorted (for performance to avoid multiple sorts) and then sort the collection if it's the first time.
Does anyone know why collection is not sorted or which solution will be best?
Some info of code:
Generated SQL
SELECT
q0_.id AS id0,
q0_.question AS question1,
t1_.id AS id2,
t1_.name AS name3
FROM question q0_
LEFT JOIN questions_tags q2_ ON q0_.id = q2_.question_id
LEFT JOIN tag t1_ ON t1_.id = q2_.tag_id
ORDER BY
q0_.question ASC,
t1_.name ASC
Available data
INSERT INTO `question` (`id`,`question`) VALUES (1,'Who are you?');
INSERT INTO `questions_tags` (`question_id`,`tag_id`) VALUES (1,1);
INSERT INTO `questions_tags` (`question_id`,`tag_id`) VALUES (1,2);
INSERT INTO `questions_tags` (`question_id`,`tag_id`) VALUES (1,3);
INSERT INTO `tag` (`id`,`name`) VALUES (1,'personal');
INSERT INTO `tag` (`id`,`name`) VALUES (2,'jobs');
INSERT INTO `tag` (`id`,`name`) VALUES (3,'achievements');
Partial dump of getQuestions() method
Doctrine\ORM\PersistentCollection {#3131
-owner: AppBundle\Entity\Question {#3118
-id: 1
-question: "Who are you?"
-tags: Doctrine\ORM\PersistentCollection {#3131}
}
-coll: Doctrine\Common\Collections\ArrayCollection {#3127
-_elements: array:3 [
0 => AppBundle\Entity\Tag {#3121
-id: 1
-name: "personal"
}
1 => AppBundle\Entity\Tag {#3123
-id: 2
-name: "jobs"
}
2 => AppBundle\Entity\Tag {#3074
-id: 3
-name: "achievements"
}
]
}
}
Question entity
<?php
namespace AppBundle\Entity;
use AppBundle\Entity\Tag;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* Question
*
* #ORM\Table(name="question")
* #ORM\Entity(repositoryClass="AppBundle\Entity\QuestionRepository")
*/
class Question
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="question", type="text")
*/
private $question;
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Tag", inversedBy="questions", cascade={"persist"})
* #ORM\JoinTable(name="questions_tags")
* #ORM\OrderBy({"name"="ASC"})
*/
private $tags;
public function __construct()
{
$this->tags = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set question
*
* #param string $question
* #return Question
*/
public function setQuestion($question)
{
$this->question = $question;
return $this;
}
/**
* Get question
*
* #return string
*/
public function getQuestion()
{
return $this->question;
}
/**
* Add tags
*
* #param \AppBundle\Entity\Tag $tags
* #return Question
*/
public function addTag(Tag $tags)
{
$this->tags[] = $tags;
return $this;
}
/**
* Remove tags
*
* #param \AppBundle\Entity\Tag $tags
*/
public function removeTag(Tag $tags)
{
$this->tags->removeElement($tags);
}
/**
* Get tags
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
return $this->tags;
}
}
Tag entity
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* Tag
*
* #ORM\Table(name="tag")
* #ORM\Entity(repositoryClass="AppBundle\Entity\TagRepository")
*/
class Tag
{
/**
* #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=128)
*/
private $name;
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Question", mappedBy="tags")
*/
private $questions;
public function __construct()
{
$this->questions = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Tag
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add questions
*
* #param \AppBundle\Entity\Question $questions
* #return Tag
*/
public function addQuestion(\AppBundle\Entity\Question $questions)
{
$this->questions[] = $questions;
return $this;
}
/**
* Remove questions
*
* #param \AppBundle\Entity\Question $questions
*/
public function removeQuestion(\AppBundle\Entity\Question $questions)
{
$this->questions->removeElement($questions);
}
/**
* Get questions
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getQuestions()
{
return $this->questions;
}
public function __toString()
{
return $this->name;
}
}
QuestionRepository
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
class QuestionRepository extends EntityRepository
{
/** #return Question[] */
public function getQuestions()
{
return $this->createQueryBuilder('q')
->select(['q', 't'])
->leftJoin('q.tags', 't')
->orderBy('q.question', 'ASC')
->getQuery()
->execute()
;
}
}
EDIT 2015-03-27:
For the time being I chose to extend the question entity with sort method:
<?php
// ...
class Question
{
// ...
/** #var boolean a flag to check if tags collection was sorted */
private $isTagsSorted = false;
// ...
/**
* Get tags
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
$this->sortTagsAlphabetically();
return $this->tags;
}
/** #return void */
private function sortTagsAlphabetically()
{
if ($this->isTagsSorted) {
return;
}
$tags = $this->tags->toArray();
usort($tags, function ($a, $b) {
return $a->getName() > $b->getName();
});
$this->tags = new ArrayCollection($tags);
}
}
You need a subquery to achieve this, because the order by affect the main query so obviously it will try to order first by question it does it and then try to order the questions by tag (not the tags). So something like this should do it :
SELECT
q0_.id AS id0,
q0_.question AS question1,
t1_.id AS id2,
t1_.name AS name3
FROM question q0_
LEFT JOIN questions_tags q2_ ON q0_.id = q2_.question_id
LEFT JOIN (select * from tag t1_ ORDER BY t1_.name ) t1_
ON t1_.id = q2_.tag_id
ORDER BY
q0_.question ASC,
t1_.name ASC
For the DQL i am sorry but i think you will have to do it the old fashion way NativeSql since doctrine doesnt support joining subqueries with dql :
https://groups.google.com/forum/#!msg/doctrine-user/0rNbXlD0E_8/xMNiQgp9c3QJ
But nativesql would be fine :) also :
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/native-sql.html
Just use the query i gave you, change it a bit to be readable then use :
$sql = "your query"
$connection = $this->getEntityManager()->getConnection();
$query = $connection->prepare($sql);
$query->execute();
$results = $query->fetchAll();
return $results;
The annotation of ordering is used only if the content is not already fetched, as example if you have fetch from the dm only the .. if you call the get.. method the result is sorted.
In your case, you get all the data in one shot, so the annotation doesn't work as expected.
In your case you can add a mehod in the Tag entity for sort the data on the fly, as example:
public function getSortedTags()
{
$criteria = Criteria::create()
->orderBy(array("name" => Criteria::ASC));
return $this->tags->matching($criteria);
}
Hope this help
Let say I have 2 tables in my database : lesson and group. Group can have 1 lesson per year.
Then I will have 2 entities, lesson entity :
<?php
namespace Sifo\AdminBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class Lesson
{
/**
* #var integer
*/
private $id;
/**
* #var string
*/
private $name;
/**
* Set id
*
* #param integer $id
* #return DftGrupMapel
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
and group entity :
<?php
namespace Sifo\AdminBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class Group
{
/**
* #var integer
*/
private $id;
/**
* #var \Sifo\AdminBundle\Entity\MstLesson
*/
private $idLesson;
/**
* #var integer
*/
private $year;
/**
* Set id
*
* #param integer $id
* #return DftGrup
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set idLesson
*
* #param \Sifo\AdminBundle\Entity\MstLesson $idLesson
* #return DftGrup
*/
public function setIdLesson(\Sifo\AdminBundle\Entity\MstLesson $idLesson = null)
{
$this->idLesson = $idLesson;
return $this;
}
/**
* Get idGrup
*
* #return \Sifo\AdminBundle\Entity\MstGrup
*/
public function getIdLesson()
{
return $this->idLesson;
}
/**
* Set year
*
* #param integer $year
* #return DftGrup
*/
public function setYear($year)
{
$this->year = $year;
return $this;
}
/**
* Get year
*
* #return integer
*/
public function getYear()
{
return $this->year;
}
The problem is, in group entity I don't have available string to show for __toString(). I fell not good for making new name field in group entity and save lesson name there.
lesson.id and group.idLesson is foreign key.
How I can get name field in lesson entity to show in __toString() group without create new field?
This is an ORM. Your Group class shouldn't really care about what it's associated Lesson's id is... it should just have a reference to the Lesson object itself:
<?php
namespace Sifo\AdminBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
class Group
{
private $id; // An integer
private $lesson; // A Lesson object
private $year; // An integer, probably
Once $lesson is a Lesson object, you can refer to it in your Group::__toString():
private function __toString() {
return $this->lesson->getName();
}
Here's the documentation on association mapping, which should be helpful in mapping the relationship between Group and Lesson:
http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html
public function setIdLesson(\Sifo\AdminBundle\Entity\MstLesson $idLesson = null)
{
$this->idLesson = $idLesson;
return $this;
}
Here,you don't need the return statement.