Method items cannot be found - Many to Many Relationship - php

I have a products and items table with a many to many relationship and a pivot table (item_product).
This is how my model looks like. When i try to retrieve items belonging to a product(category) $selectedItems= $products->items()->get(); i get an error "Method items cannot be found" . I can't seem to find where my error is. Please help
Product
public function items()
{
return $this->belongsToMany('App\Item','item_product','product_id','item_id')
->withTimestamps();
}
Item
public function products()
{
return $this->belongsToMany('App\Product','item_product','item_id','product_id')
->withTimestamps();
}
Controller
$products = Product::all()->where('id',1);
$myItems= $products->items()->get();
return view('products',compact('myItems','products'));

$products not an instance of Product
Try this:
$product = Product::findOrFail(1);
$myItems = $product->items;
return view('products',compact('myItems','product'));
Explanation:
$product = Product::findOrFail(1);
This will fetch a single product model by ID, or throw an exception if that product does not exist.
$myItems = $product->items;
This will set $myItems to a collection of items. This is equivalent to:
$myItems = $product->items()->get();
I recommend you read the documentation on relationships.

Related

Laravel one-to-many update failure problem

class Video extends Model
{
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class, CategoryVideo::class, 'video_id','category_id');
}
}
DB::beginTransaction();
$video = Video::find($id);
$video->update($data);
// update categorie count
$items = $video->categories()->get(['category_id', 'video_count']);
foreach ($items as $item){
$item->video_count = Category::where('category_id',$item->category_id)->count();
$item->save();
//$item->update(['video_count' => Category::where('category_id',$item->category_id)->count()]);
}
DB::commit();
save and update have no effect
Ask for help
I want to find out the associated classification through the video, and update the number of videos under the classification
the second parameter in many to many relation is the pivot table model name, not the the pivot model name, so your relation should be like this:
public function categories(): BelongsToMany
{
return $this->belongsToMany(Category::class, 'category_video', 'video_id','category_id');
}
please replace the second parameter 'category_video' by the name of your pivot table.
you also have to select the primary key in categories when you get Category model instance:
$items = $video->categories()->get(['id','category_id', 'video_count']);

Eloquent: filter only rows which has related “children” rows

I want to filter only those categories which have at least one product. I write Category::with('products')->… but how do I filter out those empty categories? Those with no product?
app\Category.php:
class Category extends Model {
// ...
public function products() {
return $this->hasMany('App\Product');
}
// ...
}
app\Http\Controller\CategoriesController.php:
function getIndex() {
$categories = Category::with('products')->get();
return view('categories.index', compact('categories'));
}
You can use has in it
$users = Category::has('products')->get();
// only Categories that have at least one product are contained in the collection
has() is to filter the selecting model based on a relationship. So it acts very similarly to a normal WHERE condition. If you just use has('relation') that means you only want to get the models that have at least one related model in this relation.
I have found solution..
There’s an app function for that: has().
app\Http\Controller\CategoriesController.php:
function getIndex() {
$categories = Category::with('products')->has('products')->get();
return view('categories.index', compact('categories'));
}

Laravel - Eloquent 3 Table Query

I know there are several questions out there on 3 table joins, but the examples are simpler than my set up.
I have three tables: Items, Attributes, Categories.
`item.item_code = attributes.item_code`
`attributes.category_id = category.id`
Using eloquent, I can access attributes no problem with:
$items = Item::with('attributes')->paginate(15);
But I can't seem to get the relationship set correctly to retrieve the category name.
With a standard MySql query I'd use something like:
SELECT category_name FROM items
JOIN attributes on items.item_code = attributes.item_code
JOIN categories on attributes.pg3_id = categories.id
WHERE items.item_code = 40992264
How do I achieve this using eloquent?
Edit - My bad - Totally messed up the SQL. Updated to reflect the correct table names and include the second join
Update
My models currently look like this:
class Attributes extends Model
{
public function category(){
return $this->belongsTo(Category::class);
}
}
class Product extends Model
{
public function item()
{
return $this->belongsTo(Item::class);
}
}
class Category extends Model
{
public function attributes()
{
return $this->belongsTo(Attributes::class);
}
}
But this still isn't returning a result. I've tried using
$items = Item::with('attributes.category')->get();
as suggested, but this still throws an error. If I update the Product model to:
class Product extends Model
{
public function item()
{
return $this->belongsTo(Item::class);
}
public function category(){
return $this->belongsTo(Category::class);
}
}
I don't get an error, but the relationship returns null.
You can do
$items = Item::with('attributes.category')->get();
So you access the category relationship inside the attributes relationship.
For example:
foreach ($items as $item) {
foreach ($item->attributes as $attribute) {
echo $attribute->category->id; // Will print the category id.
}
}

Laravel eloquent model how to get data from relationship's table

I am developing a laravel application which has the following eloquent models
Product hasMany('App/Sku','products_id')
Sku belongTO('App/Product')
I have a controller 'ProductController' where the following code is available
public function index()
{
$products = Product::all();
foreach($products as $product){
$products_id = $product->products_id;
}
}
I am exposing RESTfull API which will allow my users to get all product details (including skus, shipping types etc..).
Suppose if I have an API GET : /products
The code which fetches all the product details will be some what the following
public function index()
{
$products = Product::all();
foreach($products as $product){
$products_id = $product->products_id;
$skus_data = Product::find($products_id)->skus;
}
// Now I have both the product details + skus which I can bundle into an array/json.
}
Now my question is , is this logic proper? In this case all the logics are in the controller since im using eloquent models I have a model for each table and the relationships are defined in it. Is there a way I can get all the details of a product/associated model (Products details (in table 1)+ Sku details (in table 2)) rather than using the below
foreach($products as $product){
$products_id = $product->products_id;
$skus_data = Product::find($products_id)->skus;
}
I am pretty new to laravel development and eloquent models. I will be using repository pattern for the development and in that case where does the aboe logic (Product+Sku combining) resides.
Please help out.
Yes, you can get the details of the products and skus without making one additional query per product using eager loading
( this is referred as the typical N+1 query problem where N is the number of the products )
Suppose the relation between your Product and Sku models model is:
Product
public function skus()
{
return hasMany('App/Sku','products_id');
}
To fetch the products data along with the sku data you can use the with method. In your controller:
Controller
$products = Product::with('skus')->get();
Then, in your views, you can get the info this way:
View
foreach ($products as $product)
{
//$product->skus is a collection of Sku models
dd( $product->skus );
}
For the repository question: if you want to use a repository you can place the eloquent-access part of your code inside the repository. So, for example you could have this method inside the repository:
ProductRepository
public function getProductsData()
{
//access eloquent from the repository
return Product::with('skus')->get();
}
then you can use your repository in your controller:
Controller
//inject the repository in the controller
public function __construct( ProductRepository $productRepo )
{
$this->productRepo = $productRepo;
}
//use the injected repository to get the data
public function index()
{
$products = this->productRepo->getProductsData();
}
If the repository pattern is used, do it like this.
public function index() {
$data = $this->passportRepository->with('user')->findWhere(['id'=>1]);
}
If I understand your question correctly, you can use eager loading.
public function index()
{
$products = Product::with('skus')->get();
}
This will give you an array of products that have a skus array in each product object.
You can try this:
public function index()
{
$products = Product::all();
foreach($products->skus as $product)
{
return $product;
}
}
This will give you the exact result in the object form.

How to set Eloquent relationship belongsTo THROUGH another model in Laravel?

I have a model Listing that inherits through its belongsTo('Model') relationship should inherently belong to the Manufacturer that its corresponding Model belongs to.
Here's from my Listing model:
public function model()
{
return $this->belongsTo('Model', 'model_id');
}
public function manufacturer()
{
return $this->belongsTo('Manufacturer', 'models.manufacturer_id');
/*
$manufacturer_id = $this->model->manufacturer_id;
return Manufacturer::find($manufacturer_id)->name;*/
}
and my Manufacturer model:
public function listings()
{
return $this->hasManyThrough('Listing', 'Model', 'manufacturer_id', 'model_id');
}
public function models()
{
return $this->hasMany('Model', 'manufacturer_id');
}
I am able to echo $listing->model->name in a view, but not $listing->manufacturer->name. That throws an error. I tried the commented out 2 lines in the Listing model just to get the effect so then I could echo $listing->manufacturer() and that would work, but that doesn't properly establish their relationship. How do I do this? Thanks.
Revised Listing model (thanks to answerer):
public function model()
{
return $this->belongsTo('Model', 'model_id');
}
public function manufacturer()
{
return $this->belongsTo('Model', 'model_id')
->join('manufacturers', 'manufacturers.id', '=', 'models.manufacturer_id');
}
I found a solution, but it's not extremely straight forward. I've posted it below, but I posted what I think is the better solution first.
You shouldn't be able to access manufacturer directly from the listing, since manufacturer applies to the Model only. Though you can eager-load the manufacturer relationships from the listing object, see below.
class Listing extends Eloquent
{
public function model()
{
return $this->belongsTo('Model', 'model_id');
}
}
class Model extends Eloquent
{
public function manufacturer()
{
return $this->belongsTo('manufacturer');
}
}
class Manufacturer extends Eloquent
{
}
$listings = Listing::with('model.manufacturer')->all();
foreach($listings as $listing) {
echo $listing->model->name . ' by ' . $listing->model->manufacturer->name;
}
It took a bit of finagling, to get your requested solution working. The solution looks like this:
public function manufacturer()
{
$instance = new Manufacturer();
$instance->setTable('models');
$query = $instance->newQuery();
return (new BelongsTo($query, $this, 'model_id', $instance->getKeyName(), 'manufacturer'))
->join('manufacturers', 'manufacturers.id', '=', 'models.manufacturer_id')
->select(DB::raw('manufacturers.*'));
}
I started off by working with the query and building the response from that. The query I was looking to create was something along the lines of:
SELECT * FROM manufacturers ma
JOIN models m on m.manufacturer_id = ma.id
WHERE m.id in (?)
The query that would be normally created by doing return $this->belongsTo('Manufacturer');
select * from `manufacturers` where `manufacturers`.`id` in (?)
The ? would be replaced by the value of manufacturer_id columns from the listings table. This column doesn't exist, so a single 0 would be inserted and you'd never return a manufacturer.
In the query I wanted to recreate I was constraining by models.id. I could easily access that value in my relationship by defining the foreign key. So the relationship became
return $this->belongsTo('Manufacturer', 'model_id');
This produces the same query as it did before, but populates the ? with the model_ids. So this returns results, but generally incorrect results. Then I aimed to change the base table that I was selecting from. This value is derived from the model, so I changed the passed in model to Model.
return $this->belongsTo('Model', 'model_id');
We've now mimic the model relationship, so that's great I hadn't really got anywhere. But at least now, I could make the join to the manufacturers table. So again I updated the relationship:
return $this->belongsTo('Model', 'model_id')
->join('manufacturers', 'manufacturers.id', '=', 'models.manufacturer_id');
This got us one step closer, generating the following query:
select * from `models`
inner join `manufacturers` on `manufacturers`.`id` = `models`.`manufacturer_id`
where `models`.`id` in (?)
From here, I wanted to limit the columns I was querying for to just the manufacturer columns, to do this I added the select specification. This brought the relationship to:
return $this->belongsTo('Model', 'model_id')
->join('manufacturers', 'manufacturers.id', '=', 'models.manufacturer_id')
->select(DB::raw('manufacturers.*'));
And got the query to
select manufacturers.* from `models`
inner join `manufacturers` on `manufacturers`.`id` = `models`.`manufacturer_id`
where `models`.`id` in (?)
Now we have a 100% valid query, but the objects being returned from the relationship are of type Model not Manufacturer. And that's where the last bit of trickery came in. I needed to return a Manufacturer, but wanted it to constrain by themodelstable in the where clause. I created a new instance of Manufacturer and set the table tomodels` and manually create the relationship.
It is important to note, that saving will not work.
$listing = Listing::find(1);
$listing->manufacturer()->associate(Manufacturer::create([]));
$listing->save();
This will create a new Manufacturer and then update listings.model_id to the new manufacturer's id.
I guess that this could help, it helped me:
class Car extends Model
{
public function mechanical()
{
return $this->belongsTo(Mechanical::class);
}
}
class CarPiece extends Model
{
public function car()
{
return $this->belongsTo(Car::class);
}
public function mechanical()
{
return $this->car->mechanical();
}
}
At least, it was this need that made me think of the existence of a belongsToThrough
You can do something like this (Student Group -> Users -> Poll results):
// poll result
public function studentGroup(): HasOneDeep
{
return $this->hasOneDeepFromRelations($this->user(), (new User())->studentGroup());
}

Categories