Laravel output model ordered by related table column - php

I have this query filter which works fine to query out the result. For example, if I need to find books which have the Author name something:
Book::with('user', 'author')
->whereHas('author', function ($query) use($filters) {
$query->filter($filters);
})->paginate(30);
But the thing is if I want to order the Books by the author names. I this case nothing happens.
How can I sort books by author name?
QueryFilter looks like this:
public function __construct(Request $request) {
$this->request = $request;
$this->filterService = new FilterService();
}
public function apply(Builder $builder) {
$this->builder = $builder;
foreach ($this->filters() as $name => $value) {
if (method_exists($this, $name)) {
call_user_func_array([$this, $name], array_filter([$value]));
}
}
return $this->builder;
}
public function filters() {
return $this->filterService->removeEmptyFieldsFromRequest($this->request);
}
And the book filter looks like this:
public function date($date) {
return $this->builder->where('date', $date);
}
public function name_sort($order = 'asc') {
return $this->builder->orderBy('name', $order);
}

You can use class join statement :
$books = Book::select('author.name AS author_name, countries.*')
->with('user')
->join('author', 'author.id', '=', 'book.author_id')
->orderBy('author.name', 'ASC')
->paginate(10);

Related

How to get only the searched data with where clause?

$data['ads'] = PostAd::where('category_id',$id)
->orwhere('district_id','LIKE','%'.$location.'%')
->orWhere('condition','LIKE','%'.$condition.'%')
->orWhere('price','>='.$min_price)
->orWhere('price','<='.$max_price)
->orWhere('fuel',$fuel)
->orWhere('anchalorpradesh',$anchal)
->orWhere('mileage',$mileage)
->orWhere('kilometers',$kilometers)
->orWhere('engine',$engine)
->get();
i want to show data whose category_id is $id. But whenever i try to search it shows me all the data in the database. Suppose i want to search data whose kilometer is 24. There is only one data whose kilometer is 24. But instead of showing that one data it shows me all the data in database.
Try something like this, adding conditional optionally based on search parameters choosen
$query = PostAd::query();
if ( isset($id) ) {
$query = $query->where('category_id',$id);
}
if ( isset($location) ) {
$query = $query->where('district_id', 'LIKE', '%' . $location . '%');
}
if ( isset($condition) ) {
$query = $query->where('condition', 'LIKE', '%' . $condition. '%');
}
$result = $query->get();
You can use the when method to conditionally add clauses to your queries depending on a value passing a “truth” test:
PostAd::query()
->when($request->get('category_id'), function ($query, $categoryId) {
$query->where('category_id', '=', $categoryId);
})
->paginate();
The closure you pass as the second argument will receive two arguments: a query builder instance that you can modify, and the value you passed as the first parameter to the when method.
You can also take this one step further and move your filtering logic to a dedicated class:
class PostAdFilters
{
protected $request;
protected $builder;
public function __construct(Request $request)
{
$this->request = $request;
}
public function apply(Builder $builder)
{
$this->builder = $builder;
foreach ($this->request->query() as $key => $value) {
// Convert something like `category_id` to `filterByCategoryId`
$methodName = 'filterBy' . Str::studly($key);
if (method_exists($this, $methodName)) {
// If the method exists, call it
call_user_func([$this, $methodName], $value);
}
}
// Return the modified query builder
return $this->builder;
}
private function filterByCategoryId($value)
{
$this->builder->where('category_id', '=', $value);
}
private function filterByKilometers($value)
{
$this->builder->where('kilometers', '=', $value);
}
// And so on...
}
class PostAd extends Model
{
public function scopeFilters(Builder $query, PostAdFilters $filters)
{
return $filters->apply($query);
}
}
You can then inject this class in your controller method, and apply it to your model:
public function search(PostAdFilters $filters)
{
return PostAd::filter($filters)->paginate();
}
This approach is based on https://laracasts.com/series/eloquent-techniques/episodes/4

Use eloquent relation on models returning by another method

I have a Category model which has belongsToMany relation with Product model via a pivot table called product_to_category
I can get all products in a Category with $category->products() and then apply a Filter scope to it to filter the result with parameters given in Request like this:
When I send this request :
http://site.dev/category/205?product&available&brand
I apply the parameters like this:
Category::find($id)->products()->filter($request)
The problem is when I want to get all product in a category and its children. The existing products relation gives me products in only given category.
I tried to modify the products() method in Category model as this:
public function products()
{
return DB::table('oc_product')
->join('oc_product_to_category', 'oc_product_to_category.category_id', '=', 'oc_product_to_category.category_id')
->join('oc_category_path', 'oc_category_path.category_id', '=', 'oc_category.category_id')
->whereIn('oc_product_to_category.category_id', $this->children(true));
}
But when I this code :
Category::find($id)->products()->filter($request)
I get this exception error:
(1/1) BadMethodCallException
Call to undefined method Illuminate\Database\Query\Builder::filter()
I know that filter scope is defined in Model class, but how can I apply that filter scope to QueryBuilder which is returned by modified products method?
Here are my classes :
Product model:
class Product extends Model {
public function scopeFilter( $request, QueryFilter $filters ) {
return $filters->apply( $request );
}
public function categories() {
return $this->belongsToMany( Category::class, 'product_to_category', 'product_id', 'category_id' );
}
}
Category model:
class Category extends Model
{
public function scopeFilter($query, QueryFilter $filters)
{
return $filters->apply($query);
}
public function children($id_only = false)
{
$ids = $this->hasMany(CategoryPath::class, 'path_id', 'category_id')
->join('category', 'category.category_id', '=', 'category_path.category_id')
->where('category.status', 1)
->pluck('category.category_id');
if ($id_only)
return $ids;
return self::find($ids);
}
public function parent()
{
$parent = DB::Select("SELECT cp.path_id AS category_id FROM category_path cp LEFT JOIN category_description cd1
ON (cp.path_id = cd1.category_id AND cp.category_id != cp.path_id)
WHERE cd1.language_id = '2' AND cp.category_id = " . $this->category_id);
return $parent;
}
public function products()
{
return $this->belongsToMany(Product::class, 'product_to_category', 'category_id', 'product_id');
}
}
QueryFilter class:
abstract class QueryFilter {
protected $request;
protected $builder;
public function __construct( Request $request ) {
$this->request = $request;
}
public function filters() {
return $this->request->all();
}
public function apply( Builder $builder ) {
$this->builder = $builder;
foreach ( $this->filters() as $name => $value) {
if (method_exists($this, $name)) {
call_user_func_array([$this, $name], array_filter([$value]));
}
}
return $this->builder;
}
}
CategoryFilter class:
class CategoryFilters extends QueryFilter
{
public function id($id)
{
return $this->builder->where('category_id', $id);
}
public function procons()
{
return $this->builder->with('pros', 'cons');
}
public function available()
{
return $this->builder->where('quantity', '>', 0);
}
public function optionValues()
{
return $this->builder->with('optionValues');
}
public function description()
{
return $this->builder->with('description');
}
public function images()
{
return $this->builder->with('images');
}
public function order($order)
{
$params = explode(',', $order);
$order = isset($params[0]) ? $params[0] : null;
$way = isset($params[1]) && strtolower($params[1]) == 'desc' ? $params[1] : 'asc';
if ($order) {
return $this->builder->orderBy($order, $way);
}
return $this->builder;
}
}

Laravel Eloquent - list of parent from a collection

I can get the list of Likes that User 1 did on a Media from Store 1
$medias = User::find(1)->likes()->with('media')->whereHas('media', function($q) {
$q->where('store_id', '=', 1);
})->get();
But i need to retrieve the list of medias, so i tried
$medias = User::find(1)->likes()->with('media')->whereHas('media', function($q) {
$q->where('store_id', '=', 1);
})->get()->media;
But then i get
Undefined property: Illuminate\Database\Eloquent\Collection::$media
class User extends Model
{
public function likes()
{
return $this->hasMany('App\Like');
}
}
class Media extends Model
{
public function store()
{
return $this->belongsTo('App\Store');
}
public function likes()
{
return $this->hasMany('App\Like');
}
}
class Like extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
public function media()
{
return $this->belongsTo('App\Media');
}
}
This is because you get media separate for each like. You should use:
$likes = User::find(1)->likes()->with('media')->whereHas('media', function($q) {
$q->where('store_id', '=', 1);
})->get();
foreach ($likes as $like) {
$media = $like->media;
// now you can do something with your media
}
EDIT
If you want to get only media, you should add to your User model the following relationship:
public function medias()
{
return $this->hasManyThrough('App\Media','App\Like');
}
Now to get your media, you should do :
$medias = User::find(1)->medias()->where('store_id', '=', 1)->get();
Made it work with
$store->medias()->whereHas('likes', function($q) use($user) {
return $q->where('user_id', '=', $user->id);
});

Global Query Scope and Relationship with Laravel 5

I'm trying to get familiar with eloquent and I've been playing around with some global query scopes, but i'm not having much success when it comes to the effect that it has on relationships.
I have two models; product and category, each with some global query scopes added.
Products:
use productConditions;
protected $table = 'product';
public $timestamps = false;
private $websiteDetails;
public function __construct(){
parent::__construct();
$this->websiteDetails = session('website');
}
public function scopeByCategory($query, $categoryId){
return $query->whereHas('categories', function($q) use ($categoryId){
$q->where('id', $categoryId);
});
}
public function categories(){
return $this->belongsToMany('App\Category', 'product_category', 'product_id', 'category_id');
}
category:
use categoryConditions;
protected $table = 'category';
public $timestamps = false;
public function products() {
return $this->belongsToMany('App\Product', 'product_category', 'category_id', 'product_id');
}
I'm using traits to boot the global scopes and the files are as follows:
So for products:
public function apply(Builder $builder, Model $model)
{
$builder->where('state', 'a');
$builder->where('stock_level', '>', 0);
}
public function remove(Builder $builder, Model $model){
$query = $builder->getQuery();
foreach((array) $query->wheres as $key => $where){
if($where['column'] == 'state'){
unset($query->wheres[$key]);
}
if($where['column'] == 'stock_level'){
unset($query->wheres[$key]);
}
}
$query->wheres = array_values($query->wheres);
}
and for categories
public function apply(Builder $builder, Model $model)
{
$websiteDetails = session('website');
$builder->where('website_id', $websiteDetails['id']);
}
public function remove(Builder $builder, Model $model){
$query = $builder->getQuery();
foreach((array) $query->wheres as $key => $where){
if($where['column'] == 'website_id'){
unset($query->wheres[$key]);
}
}
$query->wheres = array_values($query->wheres);
}
Because there are multiple records for the category field with a specific id, due to the face that there are multiple website profiles. I wanted to set a global query scope for categories -> website_id.
So this works beautifully when doing some like this:
$category = Category::with('products')->first();
$category->products;
It gets all the categories with the specified website_id and then pulls in the products.
However, it doesn't work, when I set up a query scope in the model, to do essentially the same thing, but the other way round. So, this doesn't work:
$category = Product::byCategory(2)->get();
Unless, I delete the global query scope in the category model and modify the whereHas closure to:
public function scopeByCategory($query, $categoryId){
return $query->whereHas('categories', function($q) use ($categoryId){
$q->where('id', $categoryId)->where('website_id', $this->websiteDetails['id']);
});
}
but doing it this way, means I can no longer query the Category model, without setting up some sort of byWebsite query scope method.
Could somebody tell me if I'm somehow doing it wrong, or suggest another solution to my problem.
Many Thanks
Try overwriting the query method in the category model, it should work for the categories.
public function newQuery($excludeDeleted = true)
{
$websiteDetails = session('website');
return parent::newQuery()->where('website_id', $websiteDetails['id']);
}
If it doesnt work as product relation try this:
public function categories(){
return $this->belongsToMany('App\Category', 'product_category', 'product_id', 'category_id')
->where('website_id', $this->websiteDetails['id']);
});
}

Laravel 4.1 eager loading

I'm having trouble on the eager loading.
Let's say I have models of Members, TrainingCategory, TrainingCategoryResult and Registration
Member Model:
public function registration() {
return $this->hasMany('Registration', 'member_id');
}
public function trainingResults(){
return $this->hasMany('trainingResult', 'member_id');
}
public function trainingCategoryResults() {
return $this->hasMany('TrainingCategoryResult', 'member_id');
}
TrainingCategory Model:
public function trainings() {
return $this->hasMany('Training', 'id');
}
public function trainingCategoryResults() {
return $this->hasMany('trainingCategoryResult', 'category_id');
}
TraningCategoryResult Model:
public function category() {
return $this->belongsTo('TrainingCategory', 'id');
}
public function member() {
return $this->belongsTo('Member', 'id');
}
Registration Model:
public function course() {
return $this->belongsTo('Course', 'course_id');
}
public function member() {
return $this->belongsTo('Member', 'id');
}
I am trying to eager load all the registration info and its related info including the TraningCategoryResult info but I not sure how to get that TraningCategoryResult which required two foreign keys (category_id and member_id), is there any way to do that?
Here is my code atm:
$members= Member::where(function($query) use ($id, $site) {
$query
->where('id', '=', $id)
->where('site', '=', $site);
});
$members= $members
->with('registration.course',
'registration.course.traningCategories',
->get(['member.id']);
Thank you.
This will not work Member::with('categoryResult')->with('registration')->get()
You can make a new relation in Member Model
public function categoryResult()
{
return $this->belongsTo('Category')->with('Registration');
}
//and then call
Member::with('categoryResult')->get();
You could use a few options to achieve that:
OPTION 1: create a variable relationship
Change your relation in the Member model
public function trainingCategoryResults($category_id = null) {
if(empty($category_id))
return $this->hasMany('TrainingCategoryResult', 'member_id');
else
return $this->hasMany('TrainingCategoryResult', 'member_id')
->where('category_id', $category_id);
}
The code above might have limitations and it doesn't take advantage of many laravel features, but it will work.
OPTION 2: Access from relationship
You can keep everything as is, and load as follow:
$members= Member::where(function($query) use ($id, $site) {
$query
->where('id', '=', $id)
->where('site', '=', $site);
})
->where('id', $member_id) // set the id of the memeber
->with(array(
'traningCategoryResults' => function($q)use($category_id){
$q->where('category_id', $category_id); // This makes sure you get only desired results
}
))
In this way you will have only what you need, assuming you know the $category_id

Categories