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.
Related
In PDO / SQL i have a simple query with "bigger than":
DELETE * FROM tablexyz WHERE access > :old
Whats the equivalent in Doctrine 2 ORM?
I have already written a little code:
$criteria = ['access'=>$old];
$dataRepo = $entityManager->getRepository('tablexyz')->findBy($criteria)->delete();
My problem is, i want to use the bigger than operator in Doctrine 2 ORM but i don't want to make a function or class for that. Does somebody know a better or shorter solution for that?
You can use either a query builder og just do a direct DQL (or SQL) query for it.
$qb = $entityManager->createQueryBuilder();
$qb->select('e')
->from('Entityxyz', 'e')
->where('e.access > :old')
->setParameter('old', $old);
$entities = $qb->getQuery()->getResult();
or
$query = 'SELECT e FROM AppBundle\Entity\Entityxyz WHERE e.access > :old';
$entities = $entityManager->createQuery($query)
->setParameter('old', $old)
->getResult();`
I'd suggest you do create a repository method for it though, it's the correct place to have it. Using the query builder inside the repository is also simpler as it knows which entity you're referencing (and can skip calling select and from)
class EntityxyzRepository extends EntityRepository
{
public function getNewerThan($newerThan)
{
$qb = $this->createQueryBuilder('e');
$qb->where('e.access > :newerThan')
->setParameter('newerThan', $newerThan);
return $qb->getQuery()->getResult();
}
}
In my symfony project I have two entities that are related via one to many.
I need to find the first and last child, so I use repository functions that look like this:
public function getFirstPost(Topic $topic)
{
$query = $this->createQueryBuilder('t')
->addSelect('p')
->join('t.posts', 'p')
->where('t.id = :topic_id')
->setParameter('topic_id' => $topic->getId())
->orderBy('p.id', 'ASC')
->setMaxResults(1)
->getQuery();
return $query->getOneOrNullResult();
}
public function getLastPost(Topic $topic)
{
$query = $this->createQueryBuilder('t')
->addSelect('p')
->join('t.posts', 'p')
->where('t.id = :topic_id')
->setParameter('topic_id' => $topic->getId())
->orderBy('p.id', 'DESC')
->setMaxResults(1)
->getQuery();
return $query->getOneOrNullResult();
}
So the only difference is in in ->orderBy(), for the first Post I use ASC and for the last I use DESC.
Now If I use one of those functions from my controller, the return the expected result and work just fine. But If I run them both at the same time from my controller, they return the same result, which they shouldn't.
My guess is that Doctrine caches these queries and the results somehow and that's why the return the same so I tried using $query->useResultCache(false) but that didn't do anything.
So my question is, why is this happening and how can I fix it?
Well, it is cache issue indeed, but mostly it is query issue. Instead of returning a post in these function you return the whole topic with joined posts.
What you can do is to rewrite these queries to select Post entity directly and join Topic entity to it which will be filtered by.
If you really(dont do this) need these queries to work you can detach first topic returned by one of those methods and then call the other method:
$this->getDoctrine()->getManager()->detach($firstTopic);
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():
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();
I'm using Doctrine 1.2 and Symfony 1.4.
In my action, I have two different query that return different result set. Somehow the second query seem to change the result (or the reference?) of the first one and I don't have any clue why..
Here is an example:
$this->categories = Doctrine_Query::create()
->from('Categorie AS c')
->innerJoin('c.Activite AS a')
->where('a.archive = ?', false)
->execute();
print_r($this->categories->toArray()); // Return $this->categories results, normal behavior.
$this->evil_query = Doctrine_Query::create()
->from('Categorie AS c')
->innerJoin('c.Activite AS a')
->where('a.archive = ?', true)
->execute();
print_r($this->categories->toArray()); // Should be the same as before, but it return $this->evil_query results instead!
Why Doctrine behave this way ? It's totally driving me crazy. Thanks!
To make it simple it seem like the Query 2 are hijacking the Query 1 result.
Use something like this between queries ($em - entity manager):
$em->clear(); // Detaches all objects from Doctrine!
http://docs.doctrine-project.org/en/2.0.x/reference/batch-processing.html
In the API docs for the toArray() method in Doctrine_Collection it says:
Mimics the result of a $query->execute(array(), Doctrine_Core::HYDRATE_ARRAY);
I suspect to answer this question to your satisfaction you're going to have to go through the source code.
This seems like it has to be an issue of $this->categories and $this->evil_query pointing to the same place. What are the results of $this->evil_query === $this->categories and $this->evil_query == $this->categories?
Hubert, have you tried storing the query into a separate variable and then calling the execute() method on it?
I mean something like:
$good_query = Doctrine_Query::create()
->from('...')
->innerJoin('...')
->where('...', false)
;
$evil_query = Doctrine_Query::create()
->from('...')
->innerJoin('...')
->where('...', true)
;
$this->categories = $good_query->execute();
$this->evil_query = $evil_query->execute();
It seems like both attributes (categories and evil_query) are pointing at the same object.
Erm, after both queries you print_r the result of the first query - change the last line to print_r($this->evil_query->toArray()); to see the difference :)