CakePHP, special select - php

I will be blunt, I need the model list to be built from this special select:
SELECT * FROM posts p WHERE p.user_id IN
(SELECT u.id FROM users u, following f
WHERE u.id = f.followee_id AND f.follower_id = $id)
ORDERED BY p.created
LIMIT $limit;
Is it possible to put this as a function into the model? I am quite new to CakePHP, I must admit.
I can't use the find functions here because then the posts wouldn't be properly "mixed".

Yes, you can add your own method to the Model class that will execute the query you write.
But don't forget to sanitize your input (it is not done automatically when you write your own queries) and to add table prefixes.
App::import('Sanitize');
class YourModel extends AppModel {
public function specialSelect($id, $limit) {
$id = Sanitize::paranoid($id); // or use (int) to convert it to integer
$limit = Sanitize::paranoid($limit);
return $this->query(
'SELECT * FROM '.$this->tablePrefix.'posts p WHERE p.user_id IN '.
'(SELECT u.id FROM '.$this->tablePrefix.'.users u, '.$this->tablePrefix.'.following f '.
'WHERE u.id = f.followee_id AND f.follower_id = '.$id.') '.
'ORDER BY p.created LIMIT '.$limit
);
}
}

Related

Return ORM object and convert SQL to Doctrine QueryBuilder syntax

I'm creating a function that should return an array of User ORM object. The function should run a query to the DB and return the users where the users' contact persons has 1 company (not more or less). The relationship is like this: every user has one or more contact person and every contact person has one or more companies.
The SQL to locate these users are like this. We are using PHP 7.1, Symfony 3.4 and Doctrine 2.7.
The problem that I have is that I cannot manage to describe this in Doctrine QueryBuilder syntax so that an array of User ORM objects are returned. Can anybody give me some advice?
SELECT users.email
FROM company
INNER JOIN contact_person ON contact_person.id = company.belongs_to_contact_person_id
INNER JOIN users ON users.id = contact_person.belongs_to_user_id
GROUP BY users.email
HAVING COUNT(company.id) = 1
Depending on how your mapping is on your entities, you have multiple solution.
It would be nice if you can show us what you tried so we can see what you miss.
The best is to use the repository of the entity you whish to have an array of:
namespace App\Repository;
use App\Entity\User;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
/**
* #return User[]
*/
public function findUsersHavingAtLeastOneCompany():array
{
return $this->createQueryBuilder('user')
->join('user.contact', 'contact')
->join('contact.company', 'company')
->where('contact.company = 1')
->getQuery()
->getResult();
}
}
When using the createQueryBuilder function, it will auto populate the select and the from.
The getResult will return an array of entity (if you have not defined a select)
I fixed this by using the following code using createNativeQuery. It can probably be done by using fever lines of code, but it does the job for me :)
$em = $this->getEntityManager();
$sql = <<<SQL
SELECT users.id
FROM company
INNER JOIN contact_person ON contact_person.id = company.belongs_to_contact_person_id
INNER JOIN users ON users.id = contact_person.belongs_to_user_id
GROUP BY users.id
HAVING COUNT(company.id) = 1
SQL;
$rsm = new ResultSetMapping();
$rsm->addScalarResult('id', 'text');
$query = $em->createNativeQuery($sql, $rsm);
$locatedUsers = [];
foreach ($query->getResult() as $lUser) {
foreach ($lUser as $user) {
$locatedUser = $em->find("Project\User\User", $user);
array_push($locatedUsers, $locatedUser);
}
}
return $locatedUsers;

Add additional fields into doctrine result (DTO)

Im trying to get the best rated movies by average from my database and hydrate them nicely into a DTO with doctrine so i can work well later with it and integrate them e.g. into my api with api platform.
I already managed to get it work with a raw SQL query but i cannot manage to get it work with hydration into a DTO with doctrine.
namespace App\Repository;
use App\Entity\Movie;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
class MovieRepository extends ServiceEntityRepository
{
public function findBestRated()
{
$sql = "
SELECT m.*, AVG(Cast(r.rating as Decimal)) AS avg_rating, COUNT(r.id) AS count_rating
FROM `movie` m
JOIN rating r ON r.movie_id = m.id
GROUP BY m.id
ORDER BY avg_rating DESC
LIMIT 10
";
$connection = $this->getEntityManager()->getConnection();
$statement = $connection->prepare($sql);
$result = $statement->executeQuery();
return $result->fetchAllAssociative();
}
}
I would like to use a DTO like below:
namespace App\Dto;
use App\Entity\Movie;
class RatedMovie
{
public Movie $movie;
public float $averageRating;
public int $ratingCount;
public function __construct(Movie $movie, float $averageRating, int $ratingCount)
{
$this->movie = $movie;
$this->averageRating = $averageRating;
$this->ratingCount = $ratingCount;
}
}
I found some information on https://geek-week.imtqy.com/articles/en496166/index.html, but still i cannot get the hydration running. I already tried with ResultSetMapping and ResultSetMappingBuilder and a native doctrine query. With ResultSetMappingBuilder i can kind of simulate my raw sql query but the result is still an associative array and not mapped into the RatedMovie DTO.
public function findBestRated()
{
$rsm = new ResultSetMappingBuilder($this->getEntityManager());
$rsm->addScalarResult('id', 'movieId');
$rsm->addScalarResult('name', 'movieName');
$rsm->addScalarResult('avg_rating', 'averageRating', Types::FLOAT);
$rsm->addScalarResult('count_rating', 'countRating', Types::INTEGER);
$sql = "
SELECT m.*, AVG(r.rating) AS avg_rating, COUNT(r.id) AS count_rating
FROM `movie` m
JOIN rating r ON r.name_id = m.id
GROUP BY m.id
ORDER BY avg_rating DESC
LIMIT 10
";
$query = $this->getEntityManager()->createNativeQuery($sql, $rsm);
return $query->getResult();
}
I cannot get it running with newObjectMappings or the kind of strange syntax SELECT NEW DepartmentSalary(d.dept_no, avg_salary) FROM ... from the linked article. Any ideas?

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

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