Doctrine2 Symfony2 Paginator fails with orderBy - php

Using this code within a Repo
$builder = $this->createQueryBuilder("s");
$builder->addOrderBy("s.name", "ASC");
$pagi = new Paginator($builder->getQuery(), true);
$data = $pagi->getIterator()->getArrayCopy();
Results in this error
SQLSTATE[HY000]: General error: 3065 Expression #1 of ORDER BY clause
is not in SELECT list, references column 'dctrn_result.name_5' which
is not in SELECT list; this is incompatible with DISTINCT") in
AppBundle:Schools/Search:results_list.html.twig at line 33.
name is a valid column name for the s table. This did work but moving to a new machine and installing a new version of MySQL has broken this completely. There are some references to this issue http://www.doctrine-project.org/jira/browse/DDC-1800
But no actual solution other than removing the s. which also fails
$builder->addOrderBy("name", "ASC");
I am a complete loss of what to do, as this seems to be a fairly major issue within Doctrine2's paginator.
I am using MySQL 5.7.9.

For given error, explicit select statement should do the job.
$builder = $this->createQueryBuilder("s");
$builder->select("s")
->orderBy("s.name", "ASC");
But I would recommend using KnpPaginatorBundle if you want to use pagination in many different cases. That component is more flexible.

I think you must downgrade your mySql version to 5.6

Related

Laravel's query builder JSON selector `field->key` causes syntax error

So, I want to query the notifications table in Laravel by comparing a certain ID with the data column. This is how data column looks like:
{
"Message": "some message",
"id": 3
}
Now, I need to select all the notifications that have an ID that is equal to 3. Here is how I was trying to do it:
DB::table('notifications')->where('data->id', '3')->get();
But this throws the following error:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an
error in your SQL syntax; check the manual that corresponds to your
MariaDB server version for the right syntax to use near '>'$."id"' =
?' at line 1 (SQL: select * from notifications where
data->'$."id"' = 3)
I am losing my mind here, can anyone help me out?
There's nothing wrong with your query. It's your environment.
Problem
Laravel's MySqlGrammar translates the field->key notation in field names (on Laravel side) into field->'$.key'-style extractions (on MySQL side):
/**
* Wrap the given JSON selector.
*
* #param string $value
* #return string
*/
protected function wrapJsonSelector($value)
{
$path = explode('->', $value);
$field = $this->wrapValue(array_shift($path));
$path = collect($path)->map(function ($part) {
return '"'.$part.'"';
})->implode('.');
// Here:
return sprintf('%s->\'$.%s\'', $field, $path);
}
I just confirmed that MariaDB does not support the -> extraction operator as an alias to the JSON_EXTRACT() function. However, the same query works against a vanilla MySQL 5.7 server.
Assuming this test table:
╔════╤══════════════════╗
║ id │ payload ║
╟────┼──────────────────╢
║ 1 │ {"a": 1, "b": 2} ║
╚════╧══════════════════╝
A query that uses the -> extraction operator:
SELECT payload->"$.b" FROM test;
fails against MariaDB 10.2.8 while it yields a correct 2 against a MySQL 5.7.19 server.
Solutions
The right solution depends on what you're using on production.
Replace MariaDB
If you're using MySQL, replace MariaDB with MySQL in your development env. On a macOS machine managed by homebrew, it'd be as easy as:
brew services stop mysql
brew uninstall mariadb
brew install mysql
brew services start mysql
your data will remain intact.
Rewrite your queries
However, if you're using MariaDB in production, you need to rewrite your queries to use JSON_EXTRACT() function as Elias already mentioned. As you can see you need to be much more verbose with the Laravel API.
The above query would be:
SELECT JSON_EXTRACT(payload, "$.b") FROM test;
I made Laravel MariaDB driver for json support. Get it here: ybr-nx/laravel-mariadb
The problem is that only MySQL supports the -> operator, then the query will fail on MariaDB, but:
Both MariaDB and MySQL supports the JSON_EXTRACT function, that parses the JSON and get a value from it.
You can solve it by doing a query like this:
SELECT * FROM notifications WHERE JSON_EXTRACT(`notifications.data`, "$.id") = 5
To do it with Laravel's Query Builder, you'll need to use DB::raw method:
DB::table('notifications')->where(DB::raw('JSON_EXTRACT(`notifications.data`, "$.id")'), '=', 3);
Please try it and tell me if go wrong.
Note: The JSON_EXTRACT() is only available on MySQL >= 5.7 or MariaDB >= 10.2.3
Thanks to #Sepehr, I didn't know that the -> was an MySQL Operator, neither that JSON type existed. :-)
The issue is with the ->, i think you have a var called $data with some collection in it.
If that so then the correct way is:
DB::table('notifications')->where($data->id, '3')->get();
Or if you have a Model related to notifications table then:
Notification::where($data->id, '3')->get();
If the Model is called Notification (following Eloquent convention)
But if you tried to find all ID's equals 3 then just:
DB::table('notifications')->where('id', '3')->get();

How to return field names in correct case

I had the need to include in one of my system's DQL queries, a subquery with LIMIT clause. As Doctrine doesn't support it, I changed it to a native query. Yet the native query is returning lower case fields instead of the correct case.
The case is that as this is working code, I had some views depending on this names and it's much harder to change all names.
But I found here http://bit.ly/1Ht1ojH, that this aspect can be configured in Doctrine. So I tried this code:
$conn = $this->getConnection();
$conn->setAttribute(Doctrine_Core::ATTR_FIELD_CASE, CASE_NATURAL);
$res = $conn->query("select MyCasedField from whatever")->fetchAll();
Yet I'm getting the error "Attempted to call method "setAttribute" on class "Doctrine\ORM\EntityManager".
I tried with manager also with same result.
Now I now I can make some code to translate the fields, but I find that the configure solution to be much more clean.
Someone knows why symfony doesn't let me configure the connection ?
Also if there is any way of using LIMIT in a DQL's subquery I would find it better.
There is no LIMIT keyword in DQL. To use this functionality you can call method setMaxResults($limit) on your query object. It is also can be applied to Query Builder.
$query = $this->getEntityManager()
->createQuery('SELECT p FROM Product p')
->setMaxResults($limit);

Laravel: getting a single value from a MySQL query

I'm trying get a single value from MySQL database using laravel but the problem I'm getting an array . this is my query result in MySQL command line:
select groupName from users;
+-----------+
| groupName |
+-----------+
| Admin |
+-----------+
my laravel function:
public static function PermitAddNewUser(){
$username=Session::get('key');
$data=DB::select("select groupName from users where username='$username';");
return $data;
}
expected $data value is a string with value= Admin
but what i get is : [{"groupName":"Admin"}]
yet another edit: As of version 5.2 pluck is not deprecated anymore, it just got new behaviour (same as lists previously - see side-note below):
edit: As of version 5.1 pluck is deprecated, so start using value instead:
DB::table('users')->where('username', $username)->value('groupName');
// valid for L4 / L5.0 only
DB::table('users')->where('username', $username)->pluck('groupName');
this will return single value of groupName field of the first row found.
SIDE NOTE reg. #TomasButeler comment: As Laravel doesn't follow sensible versioning, there are sometimes cases like this. At the time of writing this answer we had pluck method to get SINGLE value from the query (Laravel 4.* & 5.0).
Then, with L5.1 pluck got deprecated and, instead, we got value method to replace it.
But to make it funny, pluck in fact was never gone. Instead it just got completely new behaviour and... deprecated lists method.. (L5.2) - that was caused by the inconsistency between Query Builder and Collection methods (in 5.1 pluck worked differently on the collection and query, that's the reason).
Edit:
Sorry i forgot about pluck() as many have commented :
Easiest way is :
return DB::table('users')->where('username', $username)->pluck('groupName');
Which will directly return the only the first result for the requested row as a string.
Using the fluent query builder you will obtain an array anyway.
I mean The Query Builder has no idea how many rows will come back from that query.
Here is what you can do to do it a bit cleaner
$result = DB::table('users')->select('groupName')->where('username', $username)->first();
The first() tells the queryBuilder to return only one row so no array, so you can do :
return $result->groupName;
Hope it helps
As of Laravel >= 5.3, best way is to use value:
$groupName = \App\User::where('username',$username)->value('groupName');
or
use App\User;//at top of controller
$groupName = User::where('username',$username)->value('groupName');//inside controller function
Of course you have to create a model User for users table which is most efficient way to interact with database tables in Laravel.
[EDIT]
The expected output of the pluck function has changed from Laravel 5.1 to 5.2. Hence why it is marked as deprecated in 5.1
In Laravel 5.1, pluck gets a single column's value from the first result of a query.
In Laravel 5.2, pluck gets an array with the values of a given column. So it's no longer deprecated, but it no longer do what it used to.
So short answer is use the value function if you want one column from the first row and you are using Laravel 5.1 or above.
Thanks to Tomas Buteler for pointing this out in the comments.
[ORIGINAL]
For anyone coming across this question who is using Laravel 5.1, pluck() has been deprecated and will be removed completely in Laravel 5.2.
Consider future proofing your code by using value() instead.
return DB::table('users')->where('username', $username)->value('groupName');
Using query builder, get the single column value such as groupName
$groupName = DB::table('users')->where('username', $username)->pluck('groupName');
For Laravel 5.1
$groupName=DB::table('users')->where('username', $username)->value('groupName');
Or, Using user model, get the single column value
$groupName = User::where('username', $username)->pluck('groupName');
Or, get the first row and then split for getting single column value
$data = User::where('username', $username)->first();
if($data)
$groupName=$data->groupName;
Continuing, here is how you do it in Laravel 7.x or 8.x
$email = DB::table('users')->where('name', 'John')->value('email');
source: trust me
Updated based on #Shuhad zaman comment
On laravel 5.6 it has a very simple solution:
User::where('username', $username)->first()->groupName;
It will return groupName as a string.

Mongo Doctrine Query Builder Select does not work. Bug?

$dm = $this->get('doctrine.odm.mongodb.document_manager');
$query = $dm->createQueryBuilder('MyBundle:Listing')
->select('title')
->field('coordinates')->geoNear(
(float)$longitude,
(float)$latitude
)->spherical(true);
$classifieds_array = $classifieds->toArray();
$data = array('success'=>true,'classifieds' => $classifieds_array,
'displaymessage' => $classifieds->count(). " Search Results Found");
Even though I am selecting just one field, for my result set, I am getting every thing back in collection along with title. Is this a bug?
NOTE: I commented out the ->field('coordinates')->geoNear((float)$longitude, (float)$latitude)->spherical(true) line and now the select seems to work. This is crazy.
The geoNear command in MongoDB doesn't seem to support filtering result fields, according to the documentation examples. Only a query option is supported to limit matched documents.
In your case, it also looks like mixing up the geoNear() and near() builder methods in Doctrine. Since you're operating on the coordinates field, the appropriate syntax would be near(). geoNear() is a top-level method to tell the builder you wish to use the command, which doesn't require a field name since it uses the one and only geospatial index on the collection.
For usage examples, I would advise looking at the query and builder unit tests in the Doctrine MongoDB library.

Using Query Builder in Symfony 2

I am trying to use Query Builder in Symfony2 to get some records from a database. I run the normal query in SQL and it returns the correct results. The query is
SELECT pg.name, pg.description
FROM pm_patentgroups pg
LEFT JOIN pm_portfolios pp ON pp.id = pg.portfolio_id
I want to use the exact query using Doctorine query builder in Symfony2. What I have tried so far is
$repository = $this->getDoctrine()
->getRepository('MunichInnovationGroupBundle:PmPatentgroups');
$query = $repository->createQueryBuilder('pg')
->from('pm_patentgroups', 'pg')
->leftJoin('pg','pm_portfolios','pp','pp.id = pg.portfolio_id')
->getQuery();
$portfolio_groups = $query->getResult();
but its giving me the following error:
Warning: Missing argument 1 for Doctrine\ORM\EntityRepository::createQueryBuilder()
I am new to Symfony2 and Doctorine. Can you please tell me what is going wrong here?
Thanks
You are missing the alias when using createQueryBuilder. Since you have the repository you can drop the from portion and just use
$query = $repository->createQueryBuilder('pg')
Something like:
$qb = $this->getDoctrine()->createQueryBuilder();
$qb->addSelect('pm_patentgroups');
$qb->addSelect('pm_portfolios');
$qb->from('MunichInnovationGroupBundle:PmPatentgroups','pm_patentgroups');
$qb->leftJoin('pm_patentgroups.pm_portfolios','pm_portfolios');
This assumes you have your two entities properly related.
Lots of examples in the D2 manual. Just keep in mind that query builder works with objects, not sql.
And by the way, your error message comes from the fact that the entity repository (as opposed to the entity manager) requires an alias.

Categories