Doctrine2 query orderBy with specific values first - php

I am trying to apply several filters to one dql in Symfony using Doctrine. I want order it by several columns (although for the moment I am having problems just with one column) and I want show first values that matches with specific values. I will write a simple example to illustrate it about the result that I am searching:
col1 col1
---- -----
A B
B => A
C C
W W
I was searching information about how to make it but I am a bit confused due to that some people says that I can't do it directly, other people says that it is possible using case when or if/else. I tried to use case when but without success. The code that I am using is the following:
Updated code and error
$query = $this->createQueryBuilder('article')
->where('article.title LIKE :article')
->setParameter('title', '%'.$term.'%')
->addSelect('(CASE WHEN article.to = \'WOR\' THEN 1 ELSE 0 END) AS to')
->addOrderBy('article.to', 'ASC')
->getQuery();
And if I want set the "B" value as parameter, should I use setParameter after the addSelect?
Right now with the abovecode I am getting the following error:
Key "title" for array with keys "0, to" does not exist in result.html.twig at line 177.
Information related about how I call this method into my controller and pass it to twig template:
$prodMan = $this->get('app.article.manager');
$articles = $prodMan->getResults((string)"t", $page);
$limit = 50;
$maxPages = ceil($articles->count() / $limit);
$thisPage = $page;
return $this->render('result.html.twig', compact('categories', 'maxPages', 'thisPage', 'articles', 'images'));
And the twig template where I have the error:
<td><p>{{articles.title}}></p></td>
Result of {{ dump(articles) }}
Paginator {#475 ▼
-query: Query {#484 ▼
-_state: 2
-_dql: "SELECT article, (CASE WHEN article.to = 'WOR' THEN 1 ELSE 0 END) AS to FROM AppBundle\Entity\Article article WHERE article.title LIKE :title ORDER BY article.to ASC"
-_parserResult: null
-_firstResult: 0
-_maxResults: 50
-_queryCache: null
-_expireQueryCache: false
-_queryCacheTTL: null
-_useQueryCache: true
#parameters: ArrayCollection {#483 ▼
-elements: array:1 [▼
0 => Parameter {#480 ▼
-name: "title"
-value: "%t%"
-type: 2
}
]
}
#_resultSetMapping: null
#_em: EntityManager {#105 …10}
#_hints: []
#_hydrationMode: 1
#_queryCacheProfile: null
#_expireResultCache: false
#_hydrationCacheProfile: null
}
-fetchJoinCollection: true
-useOutputWalkers: null
-count: 143
}
I executed the same dql query without the case when (with that dql I haven't any problem), and I compare the dumps in Twig and the only difference that I see is that in the other dql I am getting #483 and #482 indexes instead of #484 and #480 respectly
And I can't var_dump any position of the array, but the array has the right number of results, although I can't check if results are sorted in the right way
I am stuck with this problem and if someone could lead me to the right answer I'd be very grateful!

I'm not sure if your query will work, but what if you change this line like so:
->addSelect('(CASE WHEN article.to = \'B\' THEN 1 ELSE 0 END) AS HIDDEN to')
Escaping the quote, or:
->addSelect("(CASE WHEN article.to = 'B' THEN 1 ELSE 0 END) AS HIDDEN to")
That way the B is in quote. Again, not sure about the query itself.
EDIT #2 - Based on dump of articles
Looks like the dump is still a query.
You need to get the results like so:
$articles = $query->getResult();
Then pass the articles to you twig and render it.
Normally done like so:
return $this->render('result.html.twig', array(
'articles' => $articles,
));
See if that works.
You might need changes, but the above code gives you some idea of what to do.

Related

Laravel, display data where all Ids are matched

In controller index function
I'm picking up which news ids are matching with a pack id:
$content_pack_news_array = DB::table('content_pack_news')->where('content_pack_id' , $content_pack_id)->get();
Using dd I get this result which I need to access news_id of all elements in it
Illuminate\Support\Collection {#3017 ▼
#items: array:2 [▼
0 => {#2376 ▼
+"news_id": 2
+"content_pack_id": 2
}
1 => {#3010 ▼
+"news_id": 4
+"content_pack_id": 2
}
]
}
How to return data that matched with ids:
"news_id": 2
"news_id": 4
Inside:
$news = News::with(['media', 'assigned_content_packs'])->where('id' , $news_id)->get();
If I use
$content_pack_news = DB::table('content_pack_news')->where('content_pack_id' , $content_pack_id)->first();
It works but it gets only first matching item to display.
Any help appreciated.
You can use pluck, to get the ids out.
$newsIds = DB::table('content_pack_news')
->where('content_pack_id' , $content_pack_id)
->pluck('news_id');
Pluck works great in combination with whereIn(), that checks a column against an array.
$news = News::with(['media', 'assigned_content_packs'])
->whereIn('id' , $newsIds)
->get();
You can do it in single query using sub query as:
$news = News::with(['media', 'assigned_content_packs'])
->whereIn('id', function($query) use($content_pack_id){
$query->select('news_id')
->from('content_pack_news')
->where('content_pack_id', $content_pack_id);
})
->get();
if i correct you only asking the first.
$content_pack_news = DB::table('content_pack_news')->where('content_pack_id' , $content_pack_id)->first();
change it to get(); then you get all records.
$content_pack_news = DB::table('content_pack_news')->where('content_pack_id' , $content_pack_id)->get();

Laravel Getting id's from a table and inserting them into another table

Trying to get matching id's from a table and inserting them again in the same table under differnet relationship.
$contentPack = ContentPack::find($id);
$cloned_pack_goals = DB::table('content_pack_goal')->where('content_pack_id' , $contentPack->id)->get();
$cloned_pack_goal_ids = $cloned_pack_goals->goal_id;
Produces Exception
Exception
Property [goal_id] does not exist on this collection instance.
dd($cloned_pack_goals); outputs:
Illuminate\Support\Collection {#2466 ▼
#items: array:2 [▼
0 => {#3129 ▼
+"goal_id": 4
+"content_pack_id": 2
}
1 => {#2467 ▼
+"goal_id": 9
+"content_pack_id": 2
}
]
}
How to get goal_ids from the output to insert them into the same table again but with a different relation?
$newPack = $contentPack->replicate();
DB::table('content_pack_goal')->insert(['content_pack_id' => $newPack->id,'goal_id' => $cloned_pack_goal_ids]);
Am doing something wrong when getting the ID's and when inserting them. tried using ->first(); it works but only one id gets inserted
$cloned_pack_goals is a collection, so you need to exclude goal_ids from all collection records separately.
This snippet may help you:
$cloned_pack_goal_ids = DB::table('content_pack_goal')->where('content_pack_id' , $contentPack->id)->pluck('goal_id')->toArray();
foreach($cloned_pack_goal_ids as $key => $goal_id) {
DB::table('content_pack_goal')->insert(['content_pack_id' => $newPack->id,'goal_id' => $goal_id]);
}
To get an array of only the Ids, use pluck() and toArray()
$cloned_pack_goal_ids = DB::table('content_pack_goal')
->where('content_pack_id' , $contentPack->id)
->pluck('goal_id') // returns a collection of only Ids.
->toArray(); // returns an array from the collection.
Write your query in this format this will give you the require output:
$cloned_pack_goals = DB::table('content_pack_goal')->where('content_pack_id' , $contentPack->id)->get()->toArray();
$cloned_pack_goal_ids = $cloned_pack_goals[0]->goal_id;

Execute SQL functions with Laravel 5

I have SQL functions stored on my database.
However, I can not call them.
$nb = DB::select('SELECT nb_seances_archivees()');
The result is :
array:1 [▼
0 => {#186 ▼
+"nb_seances_archivees": 0
}
]
But the desired result is just 0.
Thank's for help !
By default DB::select return an array of objects, you can use collections to get the first result:
$nb = collect(DB::select('SELECT nb_seances_archivees() AS nb'))->first()->nb;
Or directly access the first object in the array:
$nb = DB::select('SELECT nb_seances_archivees() AS nb')[0]->nb;
If you want to pass parameters then you should do:
DB::select('SELECT nb_seances_archivees(?) AS nb', [$parameter]);

How to compare against fields that are `null` (empty)?

I have a table in a CakePHP 3 application called downloads which has a column called master. The field type is set to TINYINT(1)
I can find any records where downloads.master == 1 like this:
$query = $this->Downloads->find()->where(['master' => true]);
But Cake won't let me query for ones where downloads.master !== 1. None of these work, and all return an empty array/object when the query is executed:
$query = $this->Downloads->find()->where(['master' => false]);
$query = $this->Downloads->find()->where(['master' => 0]);
$query = $this->Downloads->find()->where(['master' => null]);
$query = $this->Downloads->find()->where(['master' => '']);
What do you use as the condition to make this possible? My thinking was that it should be false since that's the opposite to true, but as with most things in CakePHP 3 they like to make it more complicated than necessary...
I've examined the records in my table using phpMyAdmin and there are indeed both records where master == 1 and master == null so it's not a case of there's zero results to return.
A column being NULL is not the same as being 0 (ie false-ish from the point of view of the ORM in case of a boolean-ish column type). If you want to compare against NULL, then you must issue a query with IS NULL, which is a SQL/DBMS requirement, not a CakePHP requirement.
CakePHP however requires you to be specific about what you want to do, as passing null does not neccesarily have to mean that you want to compare against SQL NULL, depending on the context.
Long story short, use the IS operator:
where(['master IS' => null])
Similarly use IS NOT for a negated condition. You can also pass user input as the value, the ORM will test the value and convert the IS and IS NOT operators into = and != respectively in case a non-null value is being passed.
See also
Cookbook > Database Access & ORM > Query Builder > Automatic IS NULL Creation

Laravel 5 route binding variables not accessible in query builder

I'm using route binding to determine if each part of the URL is related to the previous. I'm trying to access the route parameter/variable ($stage_number) in my query builder but no luck. To confuse things, if I substitute the variable with a hard value it works, e.g. 4
How do I use the $stage_number variable in my query?
/*
* Route
*/
Route::get('/application/{application_id}/stage/{stage_number}', [
'as' => 'application.stage',
'uses' => 'ApplicationController#stage'
]);
/*
* Route Service Provider
*/
// Application
$router->bind('application_id', function($application_id)
{
$application = Application::find($application_id);
if (! $application)
{
abort(404);
}
return $application;
});
// Stage
$router->bind('stage_number', function($stage_number)
{
$application = $this->getCurrentRoute()->application_id;
$stage = collect($application->stages)->where('order', $stage_number)->all();
if (! $stage)
{
abort(404);
}
return $stage;
});
Update in response to patricus:
Thanks for the information about calling where() on collections; I did not realise it handled differently to the query builder. Updating my where() for the collection works perfectly - thanks. However, I am still having trouble when using the query builder:
// Route with data
/application/1/stage/4
// Actual data returned
array:4 [▼
0 => array:2 [▼
"name" => "Information about the University or Education Course"
"order" => 3
]
1 => array:2 [▼
"name" => "Information about your education to date"
"order" => 4
]
2 => array:2 [▼
"name" => "Other Information"
"order" => 5
]
3 => array:2 [▼
"name" => "Declaration"
"order" => 6
]
]
// Desired data to be returned
array:1 [▼
0 => array:2 [▼
"name" => "Information about your education to date"
"order" => 4
]
]
Regardless of what order I specify in my route I seem to get everything returned that is not null unless I choose 1 or 2 (which are rows with no order number and excluded from the array example above) and then I get that row returned with all of the other rows that is not null (as shown in the example above) but any other null rows are excluded. Is this a problem caused by the relationship on my Application object?
public function stages()
{
return $this->hasMany('App\Entities\Application\Stage', 'type_id')->orWhereNull('type_id')->orderBy('order', 'asc');
}
Update:
Setting the foreign_key and local_key on my relationship seemed to resolve the other issues:
public function stages()
{
return $this->hasMany('App\Entities\Application\Stage', 'type_id', 'type_id')->orWhereNull('type_id')->orderBy('order', 'asc');
}
You're actually calling the where() method on a Collection object, not on a Builder object.
The where() method on the Collection works a little differently. First, it can only do an equals comparison. Second, by default, it does a strict equals (===) comparison, and this is your issue. Since your $stage_number is a string, and your order field is most likely an integer, the strict comparison doesn't return any results.
You can change the comparison to a loose equals (==) by passing false as the third parameter. You can also use the whereLoose() method, which does the same thing.
Also note, assuming that stages is a hasMany or belongsToMany relationship on the Application object, there is no need for the call to collect(). $application->stages will already return a Collection.
// pass false as third parameter
$stage = $application->stages->where('order', $stage_number, false)->all();
// or use whereLoose
$stage = $application->stages->whereLoose('order', $stage_number)->all();
Now, having said all that, you probably want to be calling where() on the query builder. While $application->stages will give a Collection of related stage objects, $application->stages() returns the Relation object for the relationship, which gives you access to the query builder. Since you have access to the builder, you can add your where clause to the query being run and will provide a performance boost (and also doesn't care about variable type).
$stage = $application->stages()->where('order', $stage_number)->get();
Note that get() will return a Collection object that contains the matching stage objects. If order is unique and you want to get that one stage object, you can use first() instead of get():
$stage = $application->stages()->where('order', $stage_number)->first();

Categories