Global Query Scope and Relationship with Laravel 5 - php

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']);
});
}

Related

One to Many Relationship - Laravel & Mysql

In my application, Users can have many products. Now, i am trying to display the users phone number for a every displayed products.
In my products table, there is a column user_id for the respective users.
This is how my model looks like
User Model
public function products()
{
return $this->belongsTo('Models\Database\User','user_id');
}
Product Model
class Product extends BaseModel
{
protected $fillable = ['user_id','type', 'name', 'slug', 'sku', 'description',
'status', 'in_stock', 'track_stock', 'qty', 'is_taxable', 'page_title', 'page_description'];
// protected $guarded = ['id'];
public static function getCollection()
{
$model = new static;
$products = $model->all();
$productCollection = new ProductCollection();
$productCollection->setCollection($products);
return $productCollection;
}
public function users()
{
return $this->hasMany('\Models\Database\Product');
//->withTimestamps();
}
public function categories()
{
return $this->belongsToMany(Category::class);
}
public function reviews()
{
return $this->hasMany(Review::class);
}
public function prices()
{
return $this->hasMany(ProductPrice::class);
}
public function orders()
{
return $this->hasMany(Order::class);
}
public function users()
{
return $this->hasMany('Models\Database\Product');
}
And in my view, this is how i try to get the respective user's phone number
<p>{{$product->users->phone}}</p>
But i get an error like
SQLSTATE[42S22]: Column not found: 1054 Unknown column
'products.product_id' in 'where clause' (SQL: select * from products
where products.product_id = 1 and products.product_id is not
null)
You should do:
User Model
public function products()
{
return $this->hasMany('Models\Database\Product');
}
Product Model
public function user()
{
return $this->belongsTo('Models\Database\User');
}
In your blade:
{{ $product->user->phone }}
You have got your relationship models inverted,
change them:
In your User model:
public function products()
{
return $this->hasMany('Models\Database\Product');
}
In your Product model:
public function user()
{
return $this->belongsTo('Models\Database\User','user_id');
}
And then you could access the properties like:
<p>{{$product->user->phone}}</p>
Link to the Docs

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 relation returning empty

I want to get all the books under a certain category. The category has many subjects and books are linked to subjects.
My code:
class BookCategory extends Model {
public function subjects() {
return $this->hasMany('BookSubject', 'category_id', 'id');
}
public function getBooksAttribute() {
return Books::whereHas('subjects', function ($query) {
return $query->join('book_category', 'book_category.id', '=', 'book_subject.subject_id')
->where('book_category.id', $this->id);
})->get();
}
}
and my Books model:
class Books extends Model {
public function subjects() {
return $this->belongsToMany('BookSubject', 'book_by_subject', 'book_id', 'subject_id');
}
}
If I do:
$cats = \App\Models\BookCategory::all();
foreach ($cats as $c) {
echo $c->books->count();
}
It's always returning 0 for all the rows. What I'm I doing wrong?
I believe the problem is in your subquery:
return $query->join('book_category', 'book_category.id', '=', 'book_subject.subject_id')
->where('book_category.id', $this->id);
I'm pretty sure book_subject.subject_id should be book_subject.category_id
Hard to tell without seeing your db schema.

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

Laravel return property from third table

I have three tables:
products
product_types
product_categories
products belongs to product_types and product_types belongs to product_categories.
How can I access a column from product_categories from products?:
ProductTypes model:
class ProductTypes extends Eloquent {
public function category() {
return $this->belongsTo('ProductCategories');
}
}
Products model:
class Product extends Eloquent {
protected $table = 'products';
public function brands() {
return $this->belongsTo('ProductBrands', 'brand_id', 'id');
}
public function ages() {
return $this->belongsTo('ProductAges', 'age_id', 'id');
}
public function types() {
return $this->belongsTo('ProductTypes', 'type_id', 'id');
}
public function images() {
return $this->hasMany('ProductImages');
}
public function reviews() {
return $this->hasMany('ProductReviews');
}
public function toArray() {
$ar = $this->attributes;
$ar['brand'] = $this->brand;
$ar['age'] = $this->age;
$ar['type'] = $this->type;
return $ar;
}
public function getBrandAttribute() {
$brands = $this->brands()->first();
return (isset($brands->brand) ? $brands->brand : '');
}
public function getAgeAttribute() {
$ages = $this->ages()->first();
return (isset($ages->age) ? $ages->age : '');
}
public function getTypeAttribute() {
$types = $this->types()->first();
return (isset($types->type) ? $types->type : '');
}
}
I have tried:
$productData->types()->category()->category
But this gives an error saying the method doesn't exist.
Sorry about the title, couldn't think of one.
The problem is, that you're not executing the query when doing types() and therefore you're calling category() on the query builder instance and not the ProductTypes model.
You can use the dynamic properties for accessing the result of the relationship.
$productData->types->category->category
Also consider renaming your relationships. Currently you have "types" for example but it only returns one type, because its a one-to-many relation. "type" would make more sense.

Categories