Doctrine2 w/ child entities in the same table - php

I have a database table that generally (because of a NDA) has the structure of:
Category:
id(int) parent_id(int) title(string) description(text)
Note that I cannot change the table's schema, so that's not a possible solution to my problem.
I need to be able to get all of these categories' info, including a list of all their children, but I'm not quite sure how to do it. I'm fairly certain that I'll need to use recursion, but I'm not sure if there's anything built into Doctrine that can aid in it, or if I need to write raw DQL or even SQL. I currently have the following in my Symfony controller:
$em = $this->get('doctrine')->getManager();
$categoryQuery = $em->getRepository('Acme\MyBundle\Entity\Category')->findBy(array(), array('sortPosition' => 'asc'));
$categories = array();
foreach ($categoryQuery as $category) {
$categories[] = array(
'id' => $category->getId(),
'parent' => $category->getParent(),
'title' => $category->getTitle(),
'description' => $category->getDescription(),
);
}
There's another wrinkle: I need to output this info in JSON.
Ultimately, I'm not sure
A. How to create a query that will get me both a category and all of its child info (which can have more children)
B. How to then output that info as JSON
Any ideas would be greatly appreciated

If you have the category <> category relation defined in doctrine:
$categoryQuery = $em->getRepository('Acme\MyBundle\Entity\Category')
->createQueryBuilder('c')
->leftJoin('c.subCategories sc') // assuming your category <> category relation is called "subCategories
->getQuery()
->getResult()
;
Then in each record of $categoryQuery you will have a Category and its children in ->subCategories (you dont really need the join there)
If you dont have a relation defined in doctrine, then:
$query = $em->createQuery(
'SELECT c
FROM AcmeMyBundle:Category c
LEFT JOIN AcmeMyBundle:Category sc on sc.parent_id = c.id
ORDER BY sortPosition ASC'
);
$categories = $query->getResult();
And return them as JSON
return new JSONResponse($categories);

A. Recursive selection is ... hard/impossible without a path.
See : MySQL parent children one query selection
B. You can use this in Symfony2 :
Symfony\Component\HttpFoundation\JsonResponse

Related

SQL request with an array of arguments

I have a problem with a SQL(dql) request,
I have a table with Books(Media), a table with categories, and a table who associate the two: CategoryAffiliation (a book can have multiple categories)
In the Client side, i can choose to search for books by checking multiple categories. Then the client send a array of categories to the server and he must return the books who have at least one of those categories.
$books = [];
if (isset($data['categories'])) {
foreach ($data['categories'] as $key => $value){
$query = $em->createQuery("SELECT m
FROM AppBundle:Media m, AppBundle:Category c, AppBundle:CategoryAffiliation mc
WHERE m.idMedia = mc.idMedia
AND mc.idCategory = :key
GROUP by m.idMedia"
)->setParameter('key',$key);
$result = $query->getResult();
foreach ($result as $book) {
array_push($books, $book);
}
}
}
This is my solution for now but this is not great, i can't ORDER BY the result..
I receive an array of duplicates books, and then in the client side I merge them to delete the duplicates.
I am searching a way to have a single request to fetch all the data at once
Thanks
I don't have access to my stuff to test it but this could do it. I'm just not sure how setParameter will handle the fact that I put ' inside the string. I think that you will need to get the raw information. I can't remember how symfony and doctrine manage that, it's been too long ;)
If you can't find an other way, you could build it in the string directly... which I don't really recommend.
$query = $em->createQuery("SELECT m
FROM AppBundle:Media m, AppBundle:Category c, AppBundle:CategoryAffiliation mc
WHERE m.idMedia = mc.idMedia
AND mc.idCategory IN (:key)
GROUP by m.idMedia"
)->setParameter('key',implode("','",array_keys($data['categories'])));

Symfony2 using Query builder to load a specific ManyToMany object

My product can have many categories. In one part of the object however, I need to get a specific Category. So instead of getting all the categories and then in a for loop search for specific one, I need to get only this specific category. For that I am using query builder.
public function findProduct($id) {
$qb = $this->createQueryBuilder('p')
->addSelect(array('p', 'cat')) // many to many table
->addSelect(array('p', 'category')) // the category entity
->leftJoin('p.category', 'cat')
->leftJoin('cat.category', 'category')
->andWhere("category.id = 15") // error here
->SetParameter('id', $id);
return $qb->getQuery()->getOneOrNullResult();
}
With this query I am easily able to do $product->getCategory[0]([] since array) and get only the category that I need(in this example category with id=15)
THE PROBLEM:
However if the product doesnt have a category with a specific id.. It returns whole product null..
So If i do:
$product = $em->getRepository('MpShopBundle:Product')->findProduct($id); = null
But instead it should be like this:
$product = $em->getRepository('MpShopBundle:Product')->findProduct($id); = object
$product->getCategory() = null
How can I make this work in query builder? Is that even possible?
This should work. Instead of constraining your whole query (which is what that does) just constrain the join).
leftJoin('cat.category', 'category', 'WITH', 'category.id = 15')
This way you should get your product always & category only if id == 15.

Aggregating fields to Fetched Objects in Doctrine2

EDIT:
Further research: Looks like the answer lies on changin default Hydrator for a customized. Doctrine2 allows you to change it just by sending his name as a parameter:
$query->getResult('CustomHydrator');
Dont forget to register it first, in your config.yml file:
doctrine:
orm:
hydrators:
CustomHydrator: \your\bundle\Hydrators\CustomHydrator
My relationship between Blog and Comment entity is one to many. 1 Blog has N Comments
After researching about how to add an extra field to a fetched object in Doctrine 2 I found Aggregate Fields, it is a good article but just talk about to get balance from a single Account, it never says what we should do when working with an array of Accounts, it may sound silly but let me explain my situation.
In my case is not about accounts and entries, is about blogs and comments.
What I'm trying to do is to list a number of blogs and just show how many comments it has without loading any comment information, in other words I want to translate this query to the Doctrine2 World.
'SELECT b.*, COUNT( b.id ) AS totalComments FROM `blogs` b LEFT JOIN comments c ON b.id = c.blog_id GROUP BY b.id LIMIT 8'
and the result that I expect is an array of Blog Objects with a totalComments attribute setted correctly, like this:
array (size=8)
0 =>
object Blog
'id' => int 330
'title' => string 'title blog'
// Added field, not visible in table DB. Came through query COUNT() statement
'totalComments' => int 5
// ... more attributes
1 => ...
//more object blogs
);
I just can't achieve this, the best I could do was this:
Creating and fetching Query:
$qb = $this->createQueryBuilder('b')
->select('b, c')
->addSelect('count(b.id) as nComments')
->leftJoin('b.comments', 'c')
->groupBy('b.id')
return $qb->getQuery()->getResult();
and the result I'm getting is an array of arrays, where position 0 has Blog Object and position "totalComments"
// var_dump($result)
array (size=8)
0 =>
array(2) =>
0 =>
object Blog
'id' => int 330
'title' => string 'title blog'
// ... more attributes
"totalComments" => int 5
1 => ...
);
I also tried to make my own Hydrator but I just started using Doctrine2 and found myself kinda lost.
I hope been enough clear. I can give any other information if needed.
Thanks in advance!
You either have to name the fields you want, or have a mixed result ( like your 2nd example). So for a flat array:
$qb = $this->createQueryBuilder('b')
->select('b.title, b.author')
->addSelect('count(c.id) as nComments')
->leftJoin('b.comments', 'c')
->groupBy('b.id')
return $qb->getQuery()->getArrayResult();
Or a mixed result:
$qb = $this->createQueryBuilder('b')
->select('b')
->addSelect('count(c.id) as nComments')
->leftJoin('b.comments', 'c')
->groupBy('b.id')
return $qb->getQuery()->getResult();
After few days I came up with this solution.
I had to add a totalComments attribute to my Blog Entity Class and his get/set methods, and tweak a bit my getLatestBlogs function:
function getLatestBlogs(){
$qb = $this->createQueryBuilder('b')
->select('b, c')
->addSelect('count(b.id) as totalComments')
->leftJoin('b.comments', 'c')
->groupBy('b.id');
$result = $qb->getQuery()->getResult();
//tweaking original result
foreach($result as $row){
$row[0]->setTotalComments($row['totalComments']);
$blogList[] = $row[0];
}
return $blogList;
}
Doing it this way I finally get a simple array of Blog Objects, and it just took an extra loop.
After this I realized that would be nice to have a general function who can work with any Entity, so I made the next function:
function fixResult($qResult){ //Receives $qb->getQuery()->getResult();
if(is_array($qResult)){
$list = array();
$keys = array_keys($qResult[0]); //Getting all array positions from first row
$object = $qResult[0][0]; //Getting the actual object fetched
foreach($keys as $key){ //Searching for existing set methods in the Object
$method = "set".ucfirst($key);
if(method_exists($object,$method))
$methods[$key] = $method;
}
foreach($qResult as $row){ //Calling set methods for every row fetched and storing into a new array
foreach($methods as $key => $met){
$row[0]->$met($row[$key]);
$list[] = $row[0];
}
}
return $list;
}
else return false;
}
I hope somebody else find it useful.

How do I agree a new element on a array after do a query?

I'm trying to include a new element on a array that is filled with a query result.
For example, I have an array called $event with $event['name'], $event['date'], $event['price'].
Now, I want to add $event['category']. This one is not declared on DB event table, but $event is an array of my code. It not depends of the DB event table, no?
So... how I can put $event['cateogory'] inside event in my Class code of CodeIgniter?
I tried to put it directly, and the error show that "category" index is not defined.
$events = $this->Event_model->get_all_events();
foreach ($events as $event) {
$event['category'] = $this->Category_model->get_category($event['idCategory']);
}
$data{
'events' => $events,
}
$this->load->view('events_list',$data);
Thank you all
Rather than trying to iterate over every result and adding the category (which you can do if you follow the comment made by Tularis), you should let SQL add the category by using SQL Joins.
In the Code Igniter Documentation on Active Records, you'll find information about joining Tables in Code Igniter.
Here's a simple example from the documentation adjustet to your needs:
$this->db->select('*');
$this->db->from('events');
$this->db->join('categories', 'categories.id = events.idCategory');
$query = $this->db->get();
// Produces:
// SELECT * FROM blogs
// JOIN comments ON comments.id = blogs.id

MANY_MANY Yii query limiting to list of related records

I have a Yii app that contains Products and Interests with a MANY_MANY relationship. These are mapped, obviously, with the following relation:
'interests'=>array(self::MANY_MANY, 'Interest', 'interest_product_assignment(product_id,interest_id)'),
I wish to query Products using CDbCriteria like so:
$products = Product::model()->with('interests')->findAll($criteria);
This query is working fine. I need to extend it to limit it to only certain interests that I have the ids of stored in an array. I believe this should be possible with something like:
$products = Product::model()->with(
'interests',
array('condition' => {not_sure_what_to_put_here})
)->findAll($criteria);
I'm not sure how to finish the above query and have been looking for a while. It's not that I can't find anything on this, but I can't understand anything I've dug up.
Can anyone spot how to complete this query?
EDIT
What I've tried upon Telvin's suggestion:
$products = Product::model()->with(
'interests',
array('condition' => "interests_interests.interest_id IN ($selectedInterestsString)")
)->findAll($criteria);
Not adding an 'IN' statement to query.
array of provided ids:
$array_ids = array('1','24','350','4609', ....)
$array_ids_str = '"' . implode('","', array_values($array_ids)) . '"';
$products = Product::model()->with(array(
'interests'=> array('condition' => "interest_id_column IN ($array_ids_str)"
)))->findAll($criteria);

Categories