I have question about QueryBuilder.
I have two entities: Product, Attribute, which are in many to many relations (Each Product can have many attributes, and each attribudes can have many products)
I create product repository function findByAttributes()
public function findByAttributes($attributes)
{
$qb = $this->createQueryBuilder('p')
->join('p.attributes', 'a')
->where('a.slug = :slug1 OR a.slug = :slug2')
->setParameter('slug1', 'red')
->setParameter('slug2', 'blue')
;
return $qb->getQuery()->getResult();
}
When i use OR it work fine, but when i switch to AND result is empty.
With AND I want to select only products who have both attributes.
Where I am wrong?
Tnx
I got it!
public function findByAttributes(array $attributes)
{
$qb = $this->createQueryBuilder('p');
foreach ($attributes as $i => $attribute) {
$qb->join('p.attributes', 'a'.$i)
->andWhere('a'.$i.'.slug = :slug'.$i.'')
->setParameter('slug'.$i, $attribute);
}
return $qb->getQuery()->getResult();
}
Related
I am building a store, where I have to display to the user all products in a given category and all other products that are contained in the subsequent subcategories of the currently accessed one. The categories have the N+1 problem since there can be infinite subcategories. I want to be able to filter trough these products and also to be able to paginate them.
This is my categories model:
class CatalogCategory extends Model
{
public function parent()
{
return $this->belongsTo('App/CatalogCategory','parent_id');
}
public function children()
{
return $this->hasMany($this,'parent_id')
->orderBy('order_place','ASC')
->with('children');
}
/*
* Return products, that belong just to the parent category.
*/
public function products()
{
return $this->hasMany('App\CatalogProduct','parent_id')
->where('is_active', 1)
->whereDate('active_from', '<=', Carbon::now('Europe/Sofia'))
->orderBy('created_at','DESC');
}
/*
* Return all products contained in the parent category and its children categories.
*/
public function all_products()
{
$products = $this->products;
foreach ($this->children as $child) {
$products = $products->merge($child->all_products());
}
return $products;
}
}
The all_products() method returns all of the products, that I want, but since it's a collection i'm unable to paginate or filter through it. My question is if there is a better way to retrieve the products and how to retrieve them so, that i can query them for filtering and paginate them?
You could use nested set technique to store categories.
Nested set technique allows to retrieve all descendants or ancestors for a certain node in hierarchical structures in one query.
You could try this package: https://github.com/lazychaser/laravel-nestedset. Imho it's the best implentation of nested set in laravel.
Installation and configuring will cost you 10 min.
After that you could retrieve your products something like this:
public function products($slug)
{
//first query: retrieving current category
$category = CatalogCategory
::where('slug', $slug)
->first();
//second query: retrieving all category descendants and self ids.
$categoryIds = $category
->descendants
->pluck('id')
->push($category->id);
//third query: retrieving all products.
$products = CatalogProduct
::whereIn('parent_id', $categoryIds)
->where('is_active', 1)
->whereDate('active_from', '<=', Carbon::now('Europe/Sofia'))
->orderBy('created_at', 'desc');
->paginate(50);
return view('path_to_view', compact('products', 'category'));
}
Table A = Inventory | Table B = ItemAssociation | Table C = ItemValue
I have Table A, B and C. A and B have a one-to-one relationship, B and C have a one to one relationship. I'm currently using the HasManyThrough relationship to arrive at this:
public function item(){
return $this->hasManyThrough('App\ItemValue','App\ItemAssociation','id','id');
}
And in my controller:
public function orm(){
$inventory = Inventory::getAssocBySteamID(76561198124900864)->get();
$i = 0;
foreach($inventory as $inv){
$this->data[$i] = $inv->item()->get();
$i++;
}
return $this->data;
}
Where Inventory::getAssocBySteamID:
public static function getAssocBySteamID($id){
return SELF::where('steamid64','=',$id);
}
This returns all the data I need, however, I need to order this by a column in Table C, the ItemValue model.
Any help would be greatly appreciated.
You can add ->orderBy('TableName', 'desc') to the getAssocBySteamID function.
Or to the query in the Orm() function before the ->get()
Also for Where clauses in Eloquent's QB you don't need the "="sign. You can just do where('steamid',$id)
Do not use static function use scopes. Try using join query like this:
// Inventory model method
public function scopeAssocBySteamID($query, $id) {
$query->where('steamid64', $id)
->join('ItemValue'. 'ItemValue.id', '=', 'Inventory.ItemValue_id')
->select('Inventory.*')
->orderBy('ItemValue.item_price')
}
And then:
public function orm(){
$inventory = Inventory::assocBySteamID(76561198124900864)->get();
$i = 0;
foreach($inventory as $inv){
$this->data[$i] = $inv->item()->get();
$i++;
}
return $this->data;
}
Check all table and field names befor testing this example.
I got stuck here been trying from 2-3 hours.
I have a many to many relation:
class Category extends Model
{
public function news()
{
return $this->belongsToMany('App\News');
}
}
class News extends Model
{
public function categories()
{
return $this->belongsToMany('App\Category');
}
}
I am trying to get latest 5 news of the related categories:
$front_categories = Category::with(array(
'news'=>function($query){
$query->where('publish','1')->orderBy('created_at', 'desc')->take(5);}))
->where('in_front', 1)->get();
The above query is not working for me it give a total of five results instead of 5 result for each categories.
Based on what I know about Laravel, you could try doing it this way instead.
class Category {
public function recentNews()
{
return $this->news()->orderBy('created_by', 'DESC')
->take(5);
}
}
// Get your categories
$front_categories = Category::where('in_front', 1)->get();
// load the recent news for each category, this will be lazy loaded
// inside any loop that it's used in.
foreach ($front_categories as $category) {
$category->recentNews;
}
This has the same effect as Lê Trần Tiến Trung's answer and results in multiple queries. It also depends on if you're reusing this functionality or not. If it is a one-off, it may be better to put this somewhere else. Other ways could also be more dynamic, such as creating a method that returns the collection of categories and you can ask it for a certain number:
class CategoriesRepository {
public static function getFrontCategories(array $opts = []) {
$categories = Category::where('in_front', 1)->get();
if (!empty($opts) && isset($opts['withNewsCount']))
{
foreach ($categories as $category)
{
$category->recentNews = static::getRecentNewsForCategory(
$category->id,
$opts['withNewsCount']
);
}
}
return $categories;
}
}
$front_categories = CategoriesRepository::getFrontCategories([
'withNewsCount' => 5
]);
I think, Because you do eager loading a collection which has more than one record.
To solve it, you need to loop
$front_categories = Category::where('in_front', 1)->get();
foreach ($front_categories as $fCategory) {
$fCategory->load(['news' => function($query) {
$query->where('publish','1')->orderBy('created_at', 'desc')->take(5);
}]);
}
This solution will do many queries to DB. If you want to do with only 1 query, checkout this Using LIMIT within GROUP BY to get N results per group?
I have a model called School and it has many Students .
Here is the code in my model:
public function students()
{
return $this->hasMany('Student');
}
I am getting all the students with this code in my controller:
$school = School::find($schoolId);
and in the view:
#foreach ($school->students as $student)
Now I want to order the Students by some field in the students table. How can I do that?
You have a few ways of achieving this:
// when eager loading
$school = School::with(['students' => function ($q) {
$q->orderBy('whateverField', 'asc/desc');
}])->find($schoolId);
// when lazy loading
$school = School::find($schoolId);
$school->load(['students' => function ($q) {
$q->orderBy('whateverField', 'asc/desc');
}]);
// or on the collection
$school = School::find($schoolId);
// asc
$school->students->sortBy('whateverProperty');
// desc
$school->students->sortByDesc('whateverProperty');
// or querying students directly
$students = Student::whereHas('school', function ($q) use ($schoolId) {
$q->where('id', $schoolId);
})->orderBy('whateverField')->get();
you can add orderBy to your relation, so the only thing you need to change is
public function students()
{
return $this->hasMany('Student');
}
to
public function students()
{
return $this->hasMany('Student')->orderBy('id', 'desc');
}
To answer the original question, the students dynamic property can also be accessed as a relationship method.
So you have this to fetch all students:
$students = $school->students;
Now as a relationship method, this is equivalent:
$students = $school->students()->get();
Given this, you can now add in some ordering:
$students = $school->students()->orderBy('students.last_name')->get();
Since eloquent will be performing a join, make sure to include the table name when referencing the column to order by.
You can also add this to your students method if you want to set a default order that $school->students will always return. Check out the documentation for hasMany() to see how this works.
For Many to one relation I found one answer on:
https://laracasts.com/discuss/channels/eloquent/order-by-on-relationship
$order = 'desc';
$users = User::join('roles', 'users.role_id', '=', 'roles.id')
->orderBy('roles.label', $order)
->select('users.*')
->paginate(10);
this can save day... of anyone
You can use this like this:
$students = $school->students()->orderBy('id', 'desc');
You can also use
$students = $school->students()->orderBy('id', 'desc')->paginate(10);
I would like create my own method findBy().
I have two entities: Film and Genre. The purpose of this custom findBy() method is :
join the Film entity with the Genre entity, to retrieve all my films
and the associated genres,
keeping the parameters of the basic method which are: $criteria,
$orderBy , $limit and $offset.
Indeed, I use those parameters to make a paging.
Previously I made a custom findAll method with the join between the two entities :
<?php
public function myFindAll()
{
$films = $this->createQueryBuilder('f')
// leftJoin because I need all the genre
->leftJoin('f.genres', 'g')
->addSelect('g.label')
->groupBy('f.id')
->getQuery()
->getArrayResult();
// $genres contains all the genres and the associated movies
return ($films);
}
I don't know how to include the rest of parameters.
How about slice() ?
$genders = $em->getRepository('models\Gender')->findAll()->slice($offset, $lenght);
Also, you can make use of your function like:
public function myFindAll($criteria, $orderBy, $limit, $offset)
{
$films = $this->createQueryBuilder('f')
// leftJoin because I need all the genre
->leftJoin('f.genres', 'g')
->addSelect('g.label')
->groupBy('f.id')
->add('orderBy', "f.{$orderBy} ASC")
->getQuery()
->getArrayResult()
->slice($offset, $limit);
// $films contains all the genres and the associated movies
return ($films);
}
EDIT:
The slice() function acts as pagination function:
$page1 = $films->slice(0, 15); // retrieve films from 0 to 15 position
$page2 = $films->slice(10, 7); // retrieve films from 10 to 17 position
Now, if you want to use some criterias values you can make something like this:
public function myFindAll($criteria, $orderBy, $limit, $offset)
{
$films = $this->createQueryBuilder('f');
foreach($criteria as $column => $value)
$films->where($column, $value);
$films
->leftJoin('f.genres', 'g')
->addSelect('g.label')
->groupBy('f.id')
->add('orderBy', "{$orderBy[0]} {$orderBy[1]}");
->getQuery()
->getArrayResult()
->slice($offset, $limit);
// $genres contains all the genres and the associated movies
return ($films);
}
I am not sure if where function will override the previous conditions, but at least it can lead you to find the correct query
setFirstResult() and setMaxResult()
Also, there is another option that you can use:
public function myFindAll($criteria, $orderBy, $limit, $offset)
{
$films = $this->createQueryBuilder('f');
foreach($criteria as $column => $value)
$films->where($column, $value);
$films
->leftJoin('f.genres', 'g')
->addSelect('g.label')
->groupBy('f.id')
->add('orderBy', "f.{$orderBy[0]} {$orderBy[1]}")
->setFirstResult($offset)
->setMaxResults($limit)
->getQuery()
->getArrayResult();
// $genres contains all the genres and the associated movies
return ($films);
}