Doctrine2 query builder does not quote the string - php

Below is the code excerpt I have
$column_name = "ipAddress";
$qb = EntityManagerContainer::get()->createQueryBuilder();
$qb->select('u')
->from(BlacklistedIps::class, 'u');
if($search_term)
{
$clause = $qb->expr()->like("u.".$column_name, "'%$search_term%'");
$qb->where($clause);
}
$query = $qb->getQuery();
$result = $query->getResult();
It works absolutely fine (although it's open to SQL injection but that's another story).
My problem with this is the need to have "'%$search_term%'". Without this extra set of single quotes the query fails
Uncaught exception 'Doctrine\ORM\Query\QueryException' with message
'SELECT u FROM Orm\Entity\BlacklistedIps u WHERE u.ipAddress LIKE
%123% ORDER BY u.reason desc' in ***
I am not entirely sure I am doing it the right way. Because if I do, then there is a bug (mssing feature?) in Doctrine2. When I do
$qb->expr()->like("u.".$column_name, "%$search_term%");
then I am ABSOLUTELY sure that I am dealing with a string. When integers or booleans or floats, etc are compared to each other different operators are used, but definitely not LIKE. LIKE is used ONLY when dealing with strings, so quoting the string in DQL is exactly the only possible ->like method use case.
Please tell me I am doing something wrong. I've been using Doctrine2 for couple of days only and feel fascinated by it. But don't like strings not being quoted automatically for me.

it looks like a problem of how you use querybuilder. You should do something like that :
$qb ->where($qb->expr()->orX($qb->expr()->like('u.'.$column_name, $qb->expr()->literal("%$searchTerm%"))))
or
$qb->where($qb->expr()->like("u.".$column_name, array("%$searchTerm%")));
also to avoid sql injection, a good practice is to not pass user input in any querybuilder methods, use setParameter with ? or : instead.
$qb->where('u.'.$column_name.' LIKE :searchTerm')
$qb->setParameter('searchTerm', '%'.$searchTerm.'%')
or something like :
$qb->expr()->like('u.'.$column_name, '?1')
$qb->getQuery()->setParameter(1, '%' . $searchTerm . '%');

Notice the following:
how I've broken the query up for increased security of your database.
my use of "andWhere" throughout the query being built
how I've assigned the value of the executed query to $result
public function findPending($id)
{
$qb = $this->createQueryBuilder('o')
->addSelect('s')
->leftJoin('MyApp\\Model\\Entity\\Shipment', 's')
->orderBy('o.date_sent', 'DESC');
// Order has been sent and was not cancelled
$qb
->andWhere($qb->expr()->andX(
$qb->expr()->eq('o.date_cancelled','0000-00-00 00:00:00'),
$qb->expr()->neq('o.date_sent','0000-00-00 00:00:00')
));
$qb
->andWhere($qb->expr()->orX(
// Order doesn't have a shipment
$qb->expr()->isNull('s.order'),
// OR Order has a shipment
$qb->expr()->orX(
// Shipment has not been sent
$qb->expr()->eq('s.date_sent','0000-00-00 00:00:00'),
// OR Shipment has been sent AND it was cancelled
$qb->expr()->andX(
$qb->expr()->neq('s.date_sent','0000-00-00 00:00:00'),
$qb->expr()->eq('s.date_cancelled','0000-00-00 00:00:00')
)
)
));
$qb
->setMaxResults(6);
$result = $qb->getQuery()
->getResult();
return $result;
}
To see the query you've created add this before "$result"
$qb->getQuery():

Related

Symfony Doctrine query builder LIKE not working in PostgreSQL

So I have a UserRepository that contains the following code
namespace AppBundle\Repository;
use \Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
public function findByRole($role)
{
$qb = $this->_em->createQueryBuilder();
$qb->select('u')
->from($this->_entityName, 'u')
->where('u.roles LIKE :roles')
->setParameter('roles', '%"'.$role.'"%');
return $qb->getQuery()->getResult();
}
}
This seems to be working perfectly fine if my database is MySQL but if i change the database to PostgreSQL this query throws the following error
An exception occurred while executing 'SELECT p0_.id AS id_0,
p0_.username AS username_1, p0_.password AS password_2, p0_.is_active
AS is_active_3, p0_.roles AS roles_4, p0_.name AS name_5, p0_.street
AS street_6, p0_.city AS city_7, p0_.state AS state_8, p0_.zip_code AS
zip_code_9, p0_.phone_number AS phone_number_10, p0_.dob AS dob_11,
p0_.company_name AS company_name_12, p0_.company_slug AS
company_slug_13, p0_.company_logo AS company_logo_14,
p0_.company_details AS company_details_15, p0_.stripe_customer_id AS
stripe_customer_id_16, p0_.created_at AS created_at_17, p0_.updated_at
AS updated_at_18 FROM px_user p0_ WHERE p0_.roles LIKE ?' with params
["%\"ROLE_EMPLOYER\"%"]:
SQLSTATE[42883]: Undefined function: 7 ERROR: operator does not exist:
json ~~ unknown LINE 1: ...at AS updated_at_18 FROM px_user p0_ WHERE
p0_.roles LIKE $1 ^ HINT: No operator matches the given name and
argument type(s). You might need to add explicit type casts.
This is the first time I am working with PostgreSQL so I am not getting what the problem is. After playing around with it for a while if I change the generated query to by adding the following piece
WHERE
p0_.roles::text LIKE '%ROLE_EMPLOYER%'
Everything works fine. Note the ::text.
So now how can i add that to the query builder so it works with PostgreSQL as well.
I solved the problem with the module boldtrn/jsonb-bundle but it created an error depending on the version of Postgres used.
I also solved the issue without the module by a native query like this :
public function findEmailsByRole($role)
{
return $this->_em->getConnection()->executeQuery(
"SELECT email FROM public.utilisateur WHERE roles::text LIKE :role",
['role'=>'%"' . $role . '"%']
)->fetchAll();
}
You can create your Function Like this
PS: Im Working on PostgreSQL Too
public function findByRole($role) {
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('u')
->from($this->getEntityName(), 'u')
->where("u.roles LIKE '%$role%'")
;
return $qb->getQuery()->getResult();
}
I solved the problem by using JsonbBundle.
Following steps I took to fix it
$ composer require "boldtrn/jsonb-bundle
Updated the config.yml by adding the following in its respective place.
doctrine:
dbal:
types:
jsonb: Boldtrn\JsonbBundle\Types\JsonbArrayType
mapping_types:
jsonb: jsonb
orm:
dql:
string_functions:
JSONB_AG: Boldtrn\JsonbBundle\Query\JsonbAtGreater
JSONB_HGG: Boldtrn\JsonbBundle\Query\JsonbHashGreaterGreater
JSONB_EX: Boldtrn\JsonbBundle\Query\JsonbExistence
Changed the roles property to type jsonb
And inside the repository the following query worked
$query = $this->getEntityManager()->createQuery("SELECT u FROM AppBundle:User u WHERE JSONB_HGG(u.roles , '{}') LIKE '%EMPLOYER%' ");
$users = $query->getResult();
return $users;
The credit goes to Doctrine query postgres json (contains) json_array
public function findByRole($role)
{
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('u')
->from($this->getEntityName(), 'u')
->where($qb->expr()->like('u.roles', ':roles')
->setParameter('roles', $qb->expr()->literal('%'.$role.'%'));
return $qb->getQuery()->getResult();
}
Replaced your custom string DQL to the QueryBuilder syntax.
I don't know if it might be related to the syntax you've got within your like statement: '%".$var."%', which might bug it. Hope this helps you solve it.
Doctrine Querybuilder documentation like :
// Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%'))
public function like($x, $y); // Returns Expr\Comparison instance
Looks like from the PostgreSQL documentation on LIKE, you might need to do this:
$qb = $this->_em->createQueryBuilder();
$qb->select('u')
->from($this->_entityName, 'u')
->where('u.roles LIKE :roles')
->setParameter('roles', '\'%'.$role.'%\'');
return $qb->getQuery()->getResult();
Essentially having single quotes outside the percent signs by escaping them. I'm not sure if that will work, but can you try it?
Building up on #Lou Zito answer:
If you are dealing with Postgres please learn the power of json/b operations!
Casting a json/b array into text for like comparison is not a good approach. Cast it into jsonb instead (or even better: change your setup / migration(s) to use jsonb fields directly instead <3)
You can use ? operator, or the contains operator #> as an example.
See this full list of jsonb operators available
Please be aware, that ? will get interpreted as doctrine placeholder, you need to use ?? to escape it!
public function findByRole()
{
return $query = $this->getEntityManager()
->getConnection()
->executeQuery(<<<'SQL'
SELECT id FROM public.user
WHERE roles::jsonb ?? :role
ORDER BY last_name, first_name
SQL,
['role' => User::ROLE_XYZ]
)->fetchAllAssociative();
}
Hint: As you can see in the example above, I usually extract roles as public string constants in the User entity for easy access. I strongly recommend that as best practice as well.

How to use one query for different selects in laravel?

I thought it would be a good idea to define a query and use it for several selects or counts later, but it does not work. The second select has both wheres in the sql statement:
$query = Pic::where('pics.user_id',$user->id);
if($cat) $query->where('cat',$cat);
if($year) $query->where('jahrprod',$year);
$zb = $query->select('pics.id','pics.title','pics.created_at')
->where('pics.id', '>', $pic->id)
->orderBy('pics.id')
->take(2)
->get()->reverse();
$za = $query->select('pics.id','pics.title','pics.created_at')
->where('pics.id', '<', $pic->id)
->orderBy('pics.id')
->take(13)
->get();
Query:
SELECT `pics`.`id`, `pics`.`title`, `pics`.`created_at`
FROM `pics`
WHERE `pics`.`user_id` = '3'
AND `pics`.`id` > '2180'
AND `pics`.`id` < '2180'
ORDER BY `pics`.`id` ASC, `pics`.`id` ASC
LIMIT 13
I tried to "pass it as reference" i.e. &$query->select... but "only variables can be passed as reference".
How can I use the query , or save it, and use it for both actions. Is it possible?
You are updating object state with the statements when you do $query->where(), so yeah, when you're doing a second select, all conditions from the first one are still there. Thats the reason why these lines work without any assignments:
if($cat) $query->where('cat',$cat);
if($year) $query->where('jahrprod',$year);
To achieve described behaviour you would need to create an query object copy:
$query = Pic::where('pics.user_id',$user->id);
if($cat) $query->where('cat',$cat);
if($year) $query->where('jahrprod',$year);
$queryClone = clone $query;
$zb = $query->select('pics.id','pics.title','pics.created_at')
->where('pics.id', '>', $pic->id)
->orderBy('pics.id')
->take(2)
->get()->reverse();
$za = $queryClone->select('pics.id','pics.title','pics.created_at')
->where('pics.id', '<', $pic->id)
->orderBy('pics.id')
->take(13)
->get();
Notice that mere assignment would not work here:
$queryClone = $query;
Because that would pass object reference and would result in the same behaviour as in your case. Clone creates a full object copy.
http://php.net/manual/en/language.oop5.cloning.php

Doctrine WHERE IN Problems

Before I begin, I believe I have tried everything from this previous post: How to use WHERE IN with Doctrine 2
So I have a Silex application connected to a MySQL DB using Doctrine via composer (doctrine/dbal 2.2.*)
The query builder I am trying to run is this:
$qb = $this->db->createQueryBuilder();
$stmt = $qb->select('DAY(datefield1) x, COUNT(*) value')
->from('table1', 's')
->join('s', 'table2', 't', 't.key=s.key')
->where('MONTH(datefield1) = :billMonth')
->andWhere('YEAR(datefield1) = :billYear')
->andWhere('t.key IN (:keylist)')
->groupBy('x')
->orderBy('x', 'asc')
->setParameter(':billMonth', $month)
->setParameter(':billYear', $year)
->setParameter(':keylist', implode(",", $keylist))
->execute();
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
The parameters are (month=8)(year=2014)(keylist=array(1,2,3,4))
The query does not fail but it curiously doesn't contain all the data that it should.
I have tried ->setParameter(':keylist', $keylist) to use the raw array, and this didn't work.
I have tried this kind of syntax too:
$qb->add('where', $qb->expr()->in('r.winner', array('?1')));
However that threw up an error because the in method wasn't available in expression builder class.
Please will someone cast an eye over this and save me from having to hardcode my SQL?
OK seeing as this old thread has seen some action since I last looked I wanted to confirm that the issue is long resolved - a third parameter in setParameter allows you to inform Doctrine how to handle the array:
$qb = $this->db->createQueryBuilder();
$stmt = $qb
->select('*')
->from(self::DB_TABLE, 'x')
->where('x.service IN (:services)')
->orderBy('x.session_end', 'DESC')
->addOrderBy('x.direction', 'DESC')
->setParameter(':services', $services, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
->setFirstResult($offset)
->setMaxResults($count)
->execute();
$result = $stmt->fetchAll(\PDO::FETCH_ASSOC);
DB placeholders/parameters are for single values. You're passing in a monolithic string 1,2,3,4 due to calling implode() on the array. Given:
WHERE t.key IN (:keylist)
then this query will be executed as the equivalent of
WHERE t.key IN ('1,2,3,4')
^-------^---note the quotes
Since it's a string, and only one single string in the IN clause, it's the functional equivalent of
WHERE t.key = '1,2,3,4'
and not the
WHERE (t.key = 1 OR t.key = 2 OR ....)
you want it to be. Either set up multiple parameters, one for each value in your array, or embed your string in the query directly
->andWhere('t.key IN (' . implode(',', $keylist) . ')')
which of course opens you up to sql injection attack vulnerabilities.
If you build the sql query yourself you can use DBAL's PARAM_INT_ARRAY type:
use Doctrine\DBAL\Connection as DB;
$db->executeQuery('SELECT DAY(datefield1) x, COUNT(*) value
FROM table1 s
JOIN table2 t ON t.key=s.key
WHERE MONTH(datefield1) = :billMonth
AND YEAR(datefield1) = :billYear
AND t.key IN (:keylist)
GROUP BY x
ORDER BY x ASC',
array(':billMonth' => $month, ':billYear' => $year, ':keylist' => $keylist),
array(':billMonth' => \PDO::PARAM_INT, ':billYear' => \PDO::PARAM_INT, ':keylist' => DB::PARAM_INT_ARRAY
)->fetchAll(\PDO::FETCH_ASSOC);
Correct way to handle such IN clause is as simple as it can be:
$qb = $this->db->createQueryBuilder();
$stmt = $qb->(...)
->andWhere('t.key IN (:keylist)')
->setParameter(':keylist', $keylist)
->getQuery()
->getArrayResult();
I have used this dozen times and it works - doctrine is smart enough to handle your $keylist being array.
Other thing is that I don't know why you're using fetchAll method which is reduntant here - execute() is just enough. Maybe this is a root of your problem.
My suggestion: try to fire action in dev mode (app_dev.php) and check your app/logs/dev.log - you will find all sql queries performed. Verify that database returns data which you are expecting from Doctrine.
->add('where', $qb->expr()->andX(
$qb->expr()->in('r.winner', ':winner')
))
->setParameters(array(
"winner" => $winners,
"billMonth", $month,
// add all params here
));
You should use the in expression via the querybuilder like below, and change how you set the parameter:
->andWhere($qb->expr()->in('t.key', ':keylist'))
The complete code:
$qb = $this->db->createQueryBuilder();
$stmt = $qb->select('DAY(datefield1) x, COUNT(*) value')
->from('table1', 's')
->join('s', 'table2', 't', 't.key=s.key')
->where('MONTH(datefield1) = :billMonth')
->andWhere('YEAR(datefield1) = :billYear')
->andWhere('t.key')
->andWhere($qb->expr()->in('t.key', ':keylist'))
->groupBy('x')
->orderBy('x', 'asc')
->setParameter(':billMonth', $month)
->setParameter(':billYear', $year)
->setParameter(':keylist', $keylist)
->execute();
return $stmt->fetchAll(\PDO::FETCH_ASSOC);

Laravel using UNION in query builder

I have an SQL query that works fine and I'm trying to convert into fluent::
SELECT DISTINCT tags.tag
FROM tags, items
WHERE tags.taggable_type = 'Item'
AND items.item_list_id = '1'
UNION
SELECT DISTINCT tags.tag
FROM tags, itemlists
WHERE tags.taggable_type = 'ItemList'
AND itemlists.id = '1'
This is what I have so far in fluent, it all seems right as far as I can tell from the docs and the individual queries both work on their own, it's just when I UNION them it throws an error:
$itemTags = Tag::join('items', 'items.id', '=', 'tags.taggable_id')
->select('tags.tag')
->distinct()
->where('tags.taggable_type', '=', 'Item')
->where('items.item_list_id', '=', $itemList->id);
$itemListTags = Tag::join('itemlists', 'itemlists.id', '=', 'tags.taggable_id')
->select('tags.tag')
->distinct()
->where('tags.taggable_type', '=', 'ItemList')
->where('itemlists.id', '=', $itemList->id);
// the var_dump below shows the expected results for the individual queries
// var_dump($itemTags->lists('tag'), $itemListTags->lists('tag')); exit;
return $itemTags
->union($itemListTags)
->get();
I get the following error when I run it (I've also swapped from Ardent back to Eloquent on the model in case that made a difference - it doesn't):
Argument 1 passed to Illuminate\Database\Query\Builder::mergeBindings() must be an instance of Illuminate\Database\Query\Builder, instance of LaravelBook\Ardent\Builder given, called in path/to/root\vendor\laravel\framework\src\Illuminate\Database\Query\Builder.php on line 898 and defined
Looks like your models are using Ardent, not Eloquent:
...instance of LaravelBook\Ardent\Builder given, ...
And probably this might be a problem on Ardent, not Laravel.
Open an issue here: https://github.com/laravelbook/ardent.
EDIT:
Try to change use QueryBuilder instead of Eloquent:
Use this for QueryBuilder:
DB::table('tags')->
Instead of the Eloquent way:
Tag::
I know you mentioned wanting to use the query builder, but for complex queries that the builder might throw fits on, you can directly access the PDO object:
$pdo = DB::connection()->getPdo();

Doctrine setParameter and Invalid parameter number

After many tries, I think I finally know the documentation by heart.
Then, I need your help .. I don't understand why Doctrine show me this error :
Invalid parameter number: number of bound variables does not match
number of tokens
Here is my code :
$qb = $this->em->createQueryBuilder();
$qb->select('m')
->from('Entities\Marque', 'm')
->leftJoin('m.magasin', 'ma')
->where('m.nom = :marque AND ma.nom LIKE :magasin')
->setParameter('marque', $marque)
->setParameter('magasin', '%'.$matchesNumber[1].'%');
$results = $qb->getQuery()->getArrayResult();
Thank you in advance for your answer.
This also happens if you accidentally use more than one where(), which happened to me once.
$em = $this->getEntityManager();
$query = $em->createQueryBuilder()
->from('AppBundle:SomeEntity', 's')
->select('s')
->where('s.foo = :foo')
->where('s.bar = :bar') // <- HERE
->setParameter('foo', 'Foo Value')
->setParameter('bar', 'Bar Value');
Should be:
$em = $this->getEntityManager();
$query = $em->createQueryBuilder()
->from('AppBundle:SomeEntity', 's')
->select('s')
->where('s.foo = :foo')
->andWhere('s.bar = :bar') // <- CHANGE TO andWhere()
->setParameter('foo', 'Foo Value')
->setParameter('bar', 'Bar Value');
Hope this helps someone.
I presume ->setParameter overrides the previous one.
For multiple Parameters use:
->setParameters(['key1' => $value1, 'key2' => $value2])
See Doctrine Upgrade:
From now on, parameters in queries is an ArrayCollection instead of a simple array. This >affects heavily the usage of setParameters(), because it will not append anymore parameters >to query, but will actually override the already defined ones. Whenever you are retrieving a >parameter (ie. $query->getParameter(1))
Doctrine Upgrade Description
Maybe that also applies to setParameter?
I'm so sorry .. I just found my error .. Later, like more later in my code .. I type a new query with my old "$qb" ..
I'm such a noob !
$qb = $this->em->createQueryBuilder();
$parameters = array('marque'=>$marque, 'magasin'=>'%'.$matchesNumber[1].'%');
$qb->select('m')
->from('Entities\Marque', 'm')
->leftJoin('m.magasin', 'ma')
->where('m.nom = :marque')
->andWhere('ma.nom LIKE :magasin')
->setParameters($parameters);
$results = $qb->getQuery()->getArrayResult();

Categories