Get children's posts in parents with Doctrine Extensions Tree Nested set - php

I'm using the nested set behaviour in Symfony2 with StofDoctrineExtension.
The category and post model are well configured, and the category tree works fine.
To show the posts of a category I use this query from my repository:
public function findAllPosts($category)
{
return $this->queryAllPosts($category)->getResult();
}
public function queryAllPosts($category)
{
$em = $this->getEntityManager();
$query = $em->createQuery('
SELECT p, c FROM AppBundle:Post p JOIN p.category c
WHERE c.slug = :category
ORDER BY p.created DESC
');
$query->setParameter('category', $category);
return $query;
}
But how could I do to show the posts of the children of the categories too?

If your CategoryRepository inherits from NestedTreeRepository you could do something like this:
$categories = $em->getRepository('XBundle:Category')
->childrenQueryBuilder($category)
->addSelect('posts')
->join('node.posts', 'posts')
->getQuery()
->getResult();
foreach ($categories as $category) {
$category->getPosts();
// do stuff
}

You should be able to do this in one query which will be close to this one as i am not pro SQL it usually takes me time and tests before i get it right but this is where i would start :
SELECT parent.* , children.* FROM
(SELECT p, c FROM AppBundle:Post p JOIN p.category c WHERE c.slug = :category) AS parent
INNER JOIN
(SELECT p1 FROM AppBundle:Post p1 JOIN p.category c1 ON c1.parent = parent.id ) AS children
not sure if you need to do the ON inside the inner select or the wrapper select for the join but you can try :)

I found the way. The query would be like this:
/*
* GET POSTS FROM PARENT AND CHILDREN
*/
public function getPostsParentAndChildren($children)
{
$em = $this->getEntityManager();
$posts = $em->createQueryBuilder()
->select(array('p', 'c'))
->from('AppBundle:Post', 'p')
->join('p.category', 'c')
->where('c.id IN (:children)')
->orderBy('p.created', 'DESC')
->getQuery();
$posts->setParameter('children', $children);
return $posts->getResult();
}
We pass an array with the children to the query, which we obtain with the function getChildren($categoryId). Remember that you have to pass the id (with this query), so you could get the ids like this:
$category = $repo->findOneBy(array('slug' => $slug1));
$children = $repo->getChildren($category);
$childrenIds[] = $category->getId();
foreach ($children as $child){
$id = $child->getId();
$childrenIds[] = $id;
}
$posts = $em->getRepository('AppBundle:Category')->getPostsParentAndChildren($childrenIds);

Related

prevent dql from entity joins

I want to write a DQL query that select post and join to another Entity's
here is my code
$dql = '
SELECT p , h ,t ,m
FROM App:Post p
LEFT JOIN p.mentions m
LEFT JOIN p.tags t
LEFT JOIN p.file h
WHERE p.user
IN (
SELECT f FROM App:User u
JOIN u.followers f
WHERE u.id = :uid
)
OR p.user = :uid ';
$query = $this->getEntityManager()
->createQuery($dql)
->setMaxResults(1)
->setParameters(['uid' => $user->getId()]);
$paginator = new Paginator($query, $fetchJoinCollection = true);
but the problem is the circular reference, for example, Post -> Tags -> Posts that is used in serialization and make project freeze and shows a blank page.
here is dump export
how can I handle that Except using loop look at PersistentCollection
UPDATE ::
here is my seriallizer code
$posts= [];
foreach ($paginator as $post) {
$posts[] = $post;
}
$serializer = SerializerBuilder::create()->build();
$gifts = $serializer->toArray($posts);
You can use serialization groups to avoid circular reference issues. Basically, this lets your define a group (or multiple ones) to each property, then you can ask only specific(s) group(s) to be serialized.
For symfony native serializer :
http://symfony.com/doc/current/serializer.html#using-serialization-groups-annotations
https://symfony.com/blog/new-in-symfony-2-7-serialization-groups
For JMS : https://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies

Select posts based on selected categories

I have following scenario:
user selects couple categories
user should see posts which belongs to these categories
post belong to one or more categories
I setup the database like this:
users -> category_user <- categories
posts -> categoriy_post <- categories
I managed to accomplish this, but I had to find all ids from these tables to find relevant posts. I need to make it simpler because this approach is blocking some other actions I need to do. This is my code:
$categoryIds = Auth::user()->categories;
$ids = array();
$t = array_filter((array)$categoryIds);
if(!empty($t)){
foreach ($categoryIds as $key => $value) {
$ids[] = $value->id;
}
}else{
return View::make("main")
->with("posts", null)
->with("message", trans("front.noposts"))->with("option", "Latest");
}
$t = array_filter((array)$ids);
if(!empty($t)){
$p = DB::table("category_post")->whereIn("category_id", $ids)->get();
}else{
return View::make("main")
->with("posts", null)
->with("message", trans("front.noposts"))->with("option", "Latest");
}
$postsIds = array();
foreach ($p as $key => $value) {
$postsIds[] = $value->post_id;
}
$t = array_filter((array)$postsIds);
if(!empty($t)){
$postIds = array_unique($postsIds);
$posts = Post::whereIn("id", $postsIds)
->where("published", "=", "1")
->where("approved", "=", "1")
->where("user_id", "!=", Auth::user()->id)
->orderBy("created_at", "desc")
->take(Config::get("settings.num_posts_per_page"))
->get();
return View::make("main")
->with("posts", $posts)->with("option", "Latest");
}else{
return View::make("main")
->with("posts", null)
->with("message", trans("front.noposts"))->with("option", "Latest");
}
How to do this properly without this bunch code?
Yes, there is Eloquent way:
$userCategories = Auth::user()->categories()
->select('categories.id as id') // required to use lists on belongsToMany
->lists('id');
if (empty($userCategories)) // no categories, do what you need
$posts = Post::whereHas('categories', function ($q) use ($userCategories) {
$q->whereIn('categories.id', $userCategories);
})->
... // your constraints here
->get();
if (empty($posts)) {} // no posts
return View::make() ... // with posts
Or even better with this clever trick:
$posts = null;
Auth::user()->load(['categories.posts' => function ($q) use (&$posts) {
$posts = $q->get();
}]);
if (empty($posts)) // no posts
return View... // with posts
Obviously, you can write joins, or even raw sql ;)
You can take those categories directly from the database from user records:
SELECT ...
FROM posts AS p
INNER JOIN category_post AS cp ON cp.id_post = p.id
INNER JOIN categories AS c on c.id = cp.id_category
INNER JOIN category_user AS cu ON cu.id_category = c.id
WHERE cu.id_user = 123 AND p.published = 1 AND ...
Joins in Laravel can be achieved, see the documentation: laravel.com/docs/queries#joins Maybe there is also an Eloquent way, I don't know, try searching :-)

Second counting query for pagination

I have a couple of pretty complex queries, and for each of them I have to write a second query counting results. So for example, in the model:
$dql = "SELECT u FROM AcmeBundle:Users u LEFT JOIN AcmeBundle:Products p WITH u.id = p.id";
I would have to create a duplicate query like this:
$countingQuery = "SELECT COUNT(u.id) FROM AcmeBundle:Users u LEFT JOIN AcmeBundle:Products p WITH u.id = p.id";
The main problem with that is that with every change in the first query, I would have to change the second either.
So I came up with another idea:
$countingSelect = "SELECT COUNT(u.id)";
$noncountingSelect = "SELECT u";
$dql = " FROM AcmeBundle:Users u LEFT JOIN AcmeBundle:Products p WITH u.id = p.id";
return $this->getEntityManager()->createQuery($noncountingSelect . $dql)
->setHint('knp_paginator.count', $this->getEntityManager()->createQuery($countingSelect . $dql)->getSingleScalarResult());
It works of course, but the solution seems quite ugly with larger selects.
How can I solve this problem?
I believe the Doctrine\ORM\Tools\Pagination\Paginator will do what you're looking for, without the additional complexity.
$paginator = new Paginator($dql);
$paginator
->getQuery()
->setFirstResult($pageSize * ($currentPage - 1)) // set the offset
->setMaxResults($pageSize); // set the limit
$totalItems = count($paginator);
$pagesCount = ceil($totalItems / $paginator->getMaxResults());
Code yanked from: http://showmethecode.es/php/doctrine2/doctrine2-paginator/
You can create a customer repository as explained in the docs and add your query to that with a minor edit like..
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
public function findProducts()
{
return $this->findProductsOrCountProducts();
}
public function findCountProducts()
{
return $this->findProductsOrCountProducts(true);
}
private function findProductsOrCountProducts($count = false)
{
$queryBuilder = $this->createQueryBuilder('u');
if ($count) {
$queryBuilder->select('COUNT(u.id)');
}
$query = $queryBuilder
->leftJoin('AcmeBundle:Products', 'p', 'WITH', 'u.id = p.id')
->getQuery()
;
if ($count) {
return $query->getSingleScalarResult();
} else {
return $query->getResult();
}
}
}
Then you can call your method using...
$repository = $this->getDoctrine()
->getRepository('AcmeBundle:Users');
// for products
$products = $repository->findProducts();
// for count
$countProducts = $repository->findCountProducts();
Note:
I know it's not best practice to just say look at the docs for the customer repository bit s here' the YAML mapping...
# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
Acme\StoreBundle\Entity\Product:
type: entity
repositoryClass: Acme\StoreBundle\Entity\ProductRepository
# ...

Symfony2 + doctrine's query builder - where != 1

I have a function that creates a query to my db, like this:
public function getList($u, $t, $ls, $lf) {
return $this->getEntityManager()
->createQuery('
SELECT
o,
u,
g,
r,
t,
p
FROM GameShelfUsersBundle:Own o
LEFT JOIN o.user u
LEFT JOIN o.game g
LEFT JOIN o.rate r
LEFT JOIN o.typo t
LEFT JOIN o.platforms p
WHERE u.id = :user
AND o.typo = :type
ORDER BY o.updated DESC
')
->setParameters(array(
'user' => $u,
'type' => $t
))
->setMaxResults($lf)
->setFirstResult($ls)
->getResult();
}
My problem is, how to set :type to be not in? I mean, I wanted to use it like this:
$type = '!= 1'
...
AND o.typo :type
...
'type' => $type
But it didn't worked at all. Using $type = -1 doesn't help either. Is there any way, other than creating if/else statement and duplicating query?
Why don't you use a query builder??
In that way you can easily customize your query, depending on some condition.
This is an example:
$q = $this
->createQueryBuilder('foo')
->select('foo')
->leftJoin('foo.bar', 'foobar')
->leftJoin('foobar.bar', 'foobarbar')
;
if($myVar > 0)
{
$q->where('foobarbar.var = :myVar');
}
else
{
$q->where('foobarbar.var = :staticValue');
}
[...]
Remember to call return $q->getResult(); at the end

Doctrine fetch join

First I will give an example with some pseudo code and then I will explain what is the problem. Let me say I have two entities User and Phonenumber. Their relation is one-to-many. In my UserRepository I can have something like that:
class UserRepository
{
public function getUser($id, $type)
{
$users = $this->createQuery("SELECT u, p FROM User u JOIN u.phonenumbers p
WHERE u.id = :id AND p.type = :type")
->setParameters(array(
'id' => $id,
'type' => $type,
))
->getResult();
return $users[0];
}
}
In my app if I have something like:
$user = $userRepo->getUser(1, 'home');
var_dump($user->getPhonenumbers()); // here phonenumbers collection is ok
$user = $userRepo->getUser(1, 'work');
var_dump($user->getPhonenumbers()); // Here phonenumbers collection is wrong.
// It's exactly the same as the previous one.
So my questions is: Is it possible to use fetch join (with different criteria) and to get the proper collection each time?
Fetch joining and filtering a collection are not things that work together quite well. Here's how you should do it:
SELECT
u, p
FROM
User u
JOIN
u.phonenumbers p
JOIN
u.phonenumbers p2
WHERE
u.id = :id
AND
p2.type = :type
This applies filtering on the second joined (and not hydrated) p2, which results in correct hydration and filtering.
Use querybuilder, it is much simpler.
public function getUser($id, $type)
{
return $this->createQueryBuilder("u")
->leftJoin("u.Phonenumbers", "p", "WITH", "p.type=:type")
->where("u.id=:id")
->setParameters(.....)
->getQuery()
->getOneOrNullResult() ;
}

Categories