Add additional fields into doctrine result (DTO) - php

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?

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;

Laravel eloquent from DB

I have a very complicated db query, but I would like to know or is a possibility to short it and make it easier by eloquent?
My Model and his db is :
class Order extends Model
{
public $timestamps = false;
public $incrementing = false;
public function products()
{
return $this->hasMany(OrderProducts::class);
}
public function statuses()
{
return $this->belongsToMany(OrderStatusNames::class, 'order_statuses', 'order_id', 'status_id');
}
public function actualKioskOrders()
{
return
$rows = DB::select("SELECT o.id, o.number, o.name client_name, o.phone,
o.email, o.created_at order_date, osn.name actual_status
FROM orders o
JOIN order_statuses os ON os.order_id = o.id
JOIN (SELECT o.id id, MAX(os.created_at) last_status_date FROM orders o
JOIN order_statuses os ON os.order_id = o.id GROUP BY o.id) t
ON t.id = os.order_id AND t.last_status_date = os.created_at
JOIN order_status_names osn ON osn.id = os.status_id
WHERE os.status_id != 3");
}
}
Of course you can. Laravel query builder implements everything you need.
See Laravel Docs: Query Builder, it have join methods, where clause methods and select methods.
You can do for example the following:
Order::select(['id','number', 'name', 'client_name'])
->where('status_id', '!=', 3)
->join('order_statuses', 'order_statuses.order_id, '=', 'orders.id')
->get()
That's just an example on how you can create queries. Chain many methods that you need to create your query, the docs show many ways to do it, including with more complex joins if you need.

Subquery with LIMIT in Doctrine [duplicate]

This question already has an answer here:
Doctrine 2 limit IN subquery
(1 answer)
Closed 5 years ago.
I'm trying to do a query that has a subquery with Doctrine. Right now it's giving me an error. My function in the repository is:
public function getRecentPlaylists($count = 3) {
$q = $this->_em->createQuery("
SELECT p.id,
p.featuredImage,
p.title,
p.slug,
a.firstName,
a.lastName,
a.slug as authorSlug,
(SELECT updated
FROM \Entities\Articles
ORDER BY updated DESC LIMIT 1) as updated
FROM \Entities\Playlist p
JOIN \Entities\Account a
ON p.account_id = a.id
")
->setMaxResults($count);
try{
return $q->getResult();
}catch(Exception $e){
echo $e->message();
}
}
This gives me this error:
[Semantical Error] line 0, col 210 near 'LIMIT 1) as updated FROM': Error: Class 'LIMIT' is not defined.
I'm almost giving up on Doctrine, I haven't been able to figure out how to do queries with subqueries or unions with subqueries. Any help with this function? Thanks!
You can quite easily add your own syntax to Doctrine to for example add LIMIT 1 to subqueries, as Colin O'Dell explained on his blog.
// AppBundle\DBAL\FirstFunction
<?php
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\AST\Subselect;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;
/**
* FirstFunction ::=
* "FIRST" "(" Subselect ")"
*/
class FirstFunction extends FunctionNode
{
/**
* #var Subselect
*/
private $subselect;
/**
* {#inheritdoc}
*/
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->subselect = $parser->Subselect();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
/**
* {#inheritdoc}
*/
public function getSql(SqlWalker $sqlWalker)
{
return '(' . $this->subselect->dispatch($sqlWalker) . ' LIMIT 1)';
}
}
# app/config/config.yml
doctrine:
# ...
orm:
# ...
dql:
string_functions:
FIRST: AppBundle\DBAL\FirstFunction
Use as follows:
$dqb->from('MyAppBundle:Foo', 'foo')
->leftJoin('foo.bar', 'bar', 'WITH', 'bar = FIRST(SELECT b FROM MyAppBundle:Bar b WHERE b.foo = foo AND b.published_date >= :now ORDER BY t.startDate)');
In this case you can use Doctrine's aggregate expression MAX to get the most recent date:
SELECT MAX(a.updated) FROM AppBundle:Article a
You don't need to use LIMIT.
What you need is to take out the inner query and make the DQL separately for that, then use the generated DQL inside
$inner_q = $this->_em
->createQuery("SELECT AR.updated FROM \Entities\Articles AR ORDER BY AR.updated DESC")
->setMaxResults(1)
->getDQL();
$q = $this->_em->createQuery("SELECT p.id,
p.featuredImage,
p.title,
p.slug,
a.firstName,
a.lastName,
a.slug as authorSlug,
(".$inner_q.") AS updated
FROM \Entities\Playlist p
JOIN \Entities\Account a
ON p.account_id = a.id
")
->setMaxResults($count);
try{
return $q->getResult();
}
catch(Exception $e){
echo $e->message();
}

Symfony & Doctrine getting a joined query to work

I've got an SQL query that returns all the rows in one table (country) which have a related entry in another table (ducks) but I'm struggling to turn this into DQL. This is a standard one-many relationship as each country can have multiple ducks, I believe it is all set up correctly as I can return ducks within a country and return the country a duck is in using standard code.
The working query is:
SELECT c.* FROM country c
INNER JOIN ducks d
ON c.id = d.country_id
GROUP BY c.country
ORDER BY c.country ASC
I've tried converting this to:
SELECT c FROM WfukDuckBundle:Country c
INNER JOIN WfukDuckBundle:Ducks d
ON c.id = d.country_id
GROUP BY c.country
ORDER BY c.country ASC
which produces the following error:
[Semantical Error] line 0, col 79 near 'd ON': Error: Identification Variable
WfukDuckBundle:Ducks used in join path expression but was not defined before.
I'm quite new to Symfony/Doctrine so I suspect it's probably something obvious!
I'm using Symfony 2.0.11 with doctrine
Update:
I've Also tried:
SELECT c FROM WfukDuckBundle:Country c
INNER JOIN c.ducks d
ON c.id = d.country_id
GROUP BY c.country
ORDER BY c.country ASC
where 'ducks' is defined in the Country class as:
/**
* #ORM\OneToMany(targetEntity="Ducks", mappedBy="country")
*/
protected $ducks;
public function __construct()
{
$this->ducks = new ArrayCollection();
}
the definition for country in the ducks class is:
/**
* #ORM\ManyToOne(targetEntity="Country", inversedBy="ducks")
* #ORM\JoinColumn(name="country_id", referencedColumnName="id")
*/
private $country;
Do yourself a favour and use the query builder. Easier to read and update and reuse your queries
<?php
namespace Vendor\Prefix\Repository;
use Doctrine\ORM\EntityRepository;
class SomeRepository extends EntityRepository
{
public function countryDucks()
{
// $em is the entity manager
$qb = $em->createQueryBuilder();
$qb
->select('country', 'duck')
->from('WfukDuckBundle:Country', 'country')
->innerJoin('country.ducks', 'duck')
->groupBy('country.country')
->orderBy('country.country', 'ASC')
;
$query = $qb->getQuery();
// Potential Hydration Modes
// --------------------------------
// Doctrine\ORM\Query::HYDRATE_OBJECT
// Will give you an array of your object entities
// --------------------------------
// Doctrine\ORM\Query::HYDRATE_ARRAY
// Will give you an array mimicking
// your object graph
// --------------------------------
return $query->getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
}
}

CakePHP, special select

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
);
}
}

Categories