Eloquent eager loading doesn't work with my naming convention - php

I have been stuck for most of the day getting an empty array any time I eager loaded product images while requesting products in my controller in Laravel.
public function ProductImages() {
return $this->hasMany('App\ProductImage', 'product_id'); // this matches the Eloquent model
}
I changed my code to make the FK 'test' and suddenly it has started returning the appropriate data I want back. I put the FK back to product_id but again am back to an empty array. Below are My product Model ProductImages Model and the migrations for both with the call Im making in the controlelr
Product Model
class Product extends Model
{
protected $fillable = array('name', 'url_name', 'sku', 'description', 'short_description', 'enabled', 'track_inventory', 'stock_level', 'allow_backorder', 'updated_user_id' );
//protected $hidden = array('id');
// LINK THIS MODEL TO OUR DATABASE TABLE ---------------------------------
// Database table is not called my_products
protected $table = 'products';
// each product HAS many product images
public function ProductImages() {
return $this->hasMany('App\ProductImage', 'productId'); // this matches the Eloquent model
}
}
Product Images Model
class ProductImage extends Model
{
protected $fillable = array('name', 'description', 'path', 'sort_order', 'product_id');
// DEFINE RELATIONSHIPS --------------------------------------------------
// each attribute HAS one product
public function Product() {
return $this->belongsTo('App\Product', 'id'); // this matches the Eloquent model
}
}
Product Migration
class CreateProductsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('url');
$table->string('sku');
$table->string('description');
$table->string('short_description');
$table->integer('enabled');
$table->integer('track_inventory');
$table->integer('stock_level');
$table->integer('allow_backorder');
$table->dateTime('updated_user_id');
$table->timestamps();
});
}
}
Product Images Migration
class CreateProductImagesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('product_images', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('description');
$table->string('path');
$table->integer('sort_order');
$table->integer('product_id')->unsigned();
$table->foreign('product_id')->references('id')->on('products');
$table->timestamps();
});
}
}
Product Controller Snippet
public function index()
{
//
$Products = Product::with('ProductImages','productTypes')->get();
//dd($Products);
return response()->json( $Products, 200);
}
If you could help me understand why this strange behavior is happening i would be very grateful.

Related

how to get the value of foreign key in Laravel

I have a problem with Laravel model relationships. I need to let users create new trucks. However, I need to store manufacturer's field as an id, not a title. So I decided to make two tables (manufacturers and trucks) that have one to many relationship (manufacturers have multiple trucks while one truck has one manufacturer).
Here's the migrations files.
Manufacturers table:
public function up()
{
Schema::create('manufacturers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('manufacturer');
$table->timestamps();
});
}
Trucks table:
public function up()
{
Schema::create('trucks', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('make_id');
$table->unsignedInteger('year');
$table->string('owner');
$table->unsignedInteger('owner_number')->nullable();
$table->text('comments')->nullable();
$table->foreign('make_id')->references('id')->on('manufacturers');
$table->timestamps();
});
}
Manufacturer.php model:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Manufacturer extends Model
{
/**
* #var string
*/
protected $table = 'manufacturers';
/**
* #var array
*/
protected $fillable = [
'manufacturer',
];
public function trucks(){
return $this->hasMany(Truck::class);
}
}
Truck.php model:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Truck extends Model
{
/**
* #var string
*/
protected $table = 'trucks';
/**
* #var array
*/
protected $fillable = [
'make_id', 'year', 'owner', 'owner_number', 'comments',
];
public function manufacturer(){
return $this->belongsTo(Manufacturer::class);
}
}
Controller file:
public function index()
{
$trucks = Truck::all();
return view('trucks.index')->with('trucks', $trucks);
}
index.blade.php
#foreach($trucks as $truck)
<tbody>
<tr>
<td>{{$truck->make_id}}</td> //I need this one to show manufacturers title
<td>{{$truck->year}}</td>
<td>{{$truck->owner}}</td>
<td>{{$truck->owner_number}}</td>
<td>{{$truck->comments}}</td>
</tr>
</tbody>
#endforeach
This view now shows the id. What I need to do to show manufacturers title(manufacturers.manufacturer) instead of the id? Thank you all in advance!
Your foreign key for manufacturer in trucks table is not manufacturer_id. In this case you need to declare it in your models:
return $this->belongsTo(Manufacturer::class, 'make_id' )
And
return $this->hasMany(Truck::class, 'make_id' )

Symfony\Component\Debug\Exception\FatalThrowableError (E_ERROR) Class 'Cart' not found

When calling a delete function on a product I get above error. The delete function works fine if i comment out the line where i detach the pivot table, however when deleting a product i would like to delete all entries in the pivot table as well. Does anyone know why this is happening?
The database has been migrated and seeded succesfully.
Pivot Table Migration:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateOrderProductTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('order_product', function (Blueprint $table) {
$table->integer('order_id');
$table->integer('product_id');
$table->float('price');
$table->integer('amount');
$table->primary(array('order_id', 'product_id'));
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('orders_products');
}
}
Product Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = ['name', 'price', 'stock', 'short_description', 'long_description'];
public function orders() {
return $this->belongsToMany('App\Order', 'order_product', 'product_id', 'order_id');
}
public function carts() {
return $this->belongsToMany('App\Cart', 'cart_product', 'product_id', 'cart_id');
}
}
Delete Function:
public function destroy($id)
{
if ($this->validateID($id)) {
$product = Product::find($id);
//$product->carts()->detach(); --THE PROBLEMATIC LINE
Product::destroy($id);
}
Session::flash('success', $product->name.' has been succesfully deleted.');
return redirect()->to('/products');
}
You did not provide a full namespace in your belongsToMany relationship.
Probably something like that (unless you have subfolders for models):
public function orders() {
return $this->belongsToMany('App\Order', 'order_product', 'product_id', 'order_id');
}
public function carts() {
return $this->belongsToMany('App\Cart', 'cart_product', 'product_id', 'cart_id');
}
Also, I would recommend adding this to your pivot migration:
Schema::create('order_product', function (Blueprint $table) {
// Also, you would need to make `order_id` and `product_id` unsigned,
// assuming your other `id` columns are `autoincrement` (which are unsigned by default)
$table->integer('order_id')->unsigned();
$table->integer('product_id')->unsigned();
$table->float('price');
$table->integer('amount');
$table->primary(array('order_id', 'product_id'));
$table->timestamps();
// Adds foreign key to orders
$table->foreign('order_id')
->references('id')
->on('orders')
// Automatically deletes the pivot, when related order is deleted
->onDelete('cascade');
// Adds foreign key to products
$table->foreign('product_id')
->references('id')
->on('products')
// Automatically deletes the pivot, when related cart is deleted
->onDelete('cascade');
});
Also your table in down() part of migration mismatches the actual table name in the up() part.

Laravel Categories

I'm learning php with laravel and trying to implement categories and subcategories for multiple on my project.
For Example: I have Books, Mobiles in my project
Books has its own categories and subcategories. Same goes for Mobile.
I have added another table with relation
Schema::create('category_product', function (Blueprint $table) {
$table->increments('id');
$table->integer('product_id')->unsigned();
$table->integer('category_id')->unsigned();
$table->unique(array('product_id', 'category_id'));
// foreign key constraints are optional (but pretty useful, especially with cascade delete
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
});
Category Database Schema
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->integer('parent_id')->nullable()->index();
$table->string('title')->unique();
$table->string('slug')->unique();
$table->string('description')->nullable();
$table->string('keywords')->nullable();
$table->timestamps();
});
category.php (Model)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Cviebrock\EloquentSluggable\Sluggable;
class Category extends Model
{
use Sluggable;
/**
* Return the sluggable configuration array for this model.
*
* #return array
*/
public function sluggable()
{
return [
'slug' => [
'source' => 'title'
]
];
}
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'categories';
/**
* Attributes that should be mass-assignable.
*
* #var array
*/
protected $fillable = [
'parent_id', 'title', 'description', 'slug'
];
public function parent()
{
return $this->belongsTo('Category', 'parent_id');
}
public function children()
{
return $this->hasMany('Category', 'parent_id');
}
public function categoryProduct(){
return $this->belongsToMany('CategoryProduct');
}
public function product(){
return $this->belongsToMany('Product');
}
}
Am i doing the right way as i didn't find a proper tutorial for this kind of approach. Do i need to create a CategoryProduct.php model and reference
public function categories(){
return $this->belongsToMany('Category');
}
public function products(){
return $this->belongsToMany('Product');
}
No, you don't need to create a model for a pivot table. Eloquent has many methods to make working with pivot tables a breeze. In your case you don't need a model.
But sometimes, when you're working with pivot table additional columns a lot it's a good idea to create a model for a pivot table.

Eloquent relationships: cloumn doesn't exist

I'm new to Laravel and am having a bit of a hard time cracking how relationships work. I'm building a simple e-commerce application, where each user has some orders, and order has one or many sub-orders, and each sub-order is linked to only one item (please don't comment on my scheme yet; for now I just need to figure out Eloquent and will be doing refactoring later :) ).
Following are my models:
class Order extends Model
{
//timestamp
protected $created_at;
public function sub_orders() {
return $this->hasMany('App\SubOrder');
}
public function user() {
return $this->belongsTo('App\User');
}
}
class SubOrder extends Model
{
protected $fillable = array('delivery_date', 'quantity', 'total_price', 'delivery_status');
public function item() {
return $this->hasOne('App\Item');
}
public function order() {
return $this->belongsTo('App\Order');
}
}
class Item extends Model
{
//note - slug is kind of categorization and is common to many items
protected $fillable = array('sku', 'name', 'slug', 'unit_price');
}
And here are the migrations:
class CreateOrdersTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->increments('id');
$table->timestamp('created_at');
//foreign keys
$table->unsignedInteger('user_id')->after('id');
$table->foreign('user_id')->references('id')->on('users') ->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('orders');
}
}
class CreateSubOrdersTable extends Migration
{
public function up()
{
Schema::create('sub_orders', function (Blueprint $table) {
$table->increments('id');
$table->date('delivery_date');
$table->decimal('quantity', 5, 2);
$table->decimal('total_price', 7, 2);
$table->enum('delivery_status', ['pending_from_farmer', 'ready_for_customer', 'out_for_delivery', 'delivered']);
//foreign keys
$table->unsignedInteger('order_id')->after('id');
$table->foreign('order_id')->references('id')->on('orders') ->onDelete('cascade');
$table->unsignedInteger('item_id')->after('order_id');
$table->foreign('item_id')->references('id')->on('items') ->onDelete('cascade');
});
}
public function down()
{
Schema::dropIfExists('sub_orders');
}
}
class CreateItemsTable extends Migration
{
public function up()
{
Schema::create('items', function (Blueprint $table) {
$table->increments('id');
$table->string('sku')->unique();
$table->string('name');
$table->string('slug');
$table->decimal('unit_price', 5, 2);
});
}
public function down()
{
Schema::dropIfExists('items');
}
}
The problematic expression is why I write App\Order::all()[0]->sub_orders[0]->item in my web.php and get the following error:
SQLSTATE[42703]: Undefined column: 7 ERROR: column items.sub_order_id does not exist
LINE 1: select * from "items" where "items"."sub_order_id" = $1 and ...
^ (SQL: select * from "items" where "items"."sub_order_id" = 1 and "items"."sub_order_id" is not null limit 1)
I don't understand why it's looking for sub_order_id in the items table. And what's the right way to go about doing it?
Overall: define the 1-to-1 relationship using hasOne or belongsTo will affect the target table where Laravel find the foreign key. hasOne assume there is a my_model_id in target table.And belongsTo assume there is a target_model_id in my table.
class SubOrder extends Model
{
public function item() {
return $this->hasOne('App\Item', 'id', 'item_id');
}
}
or
class SubOrder extends Model
{
public function item() {
return $this-> belongsTo('App\Item');
}
}
According to Laravel Doc
class User extends Model
{
/**
* Get the phone record associated with the user.
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
Eloquent determines the foreign key of the relationship based on the model name. In the above case, the Phone model is automatically assumed to have a user_id foreign key. If you wish to override this convention, you may pass a second argument to the hasOne method:
$this->hasOne('App\Phone', 'foreign_key', 'local_key');
Or Defining The Inverse Of The Relationship
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
In the example above, Eloquent will try to match the user_id from the Phone model to an id on the User model.
Your SubOrder item has relationship of type OneToOne (hasOne is bidirectional) with an Item.
So Eloquent expects to have sub_order_id in the items table.
So the solution is to define the inverse of this relationship (belongsTo) in the Item model

Building a dynamic query of a many-to-many relationship in Laravel 5

I am pretty new to the Laravel Eloquent ORM and am having difficulty building a dynamic query to query products of a category.
I parse the request object and return products according to what vars have been passed through. This is easy enough when I am querying a single Model but I want to know how to build a query dynamically if a category is passed through to. This is easy enough using standard MYSQL and PHP but I am unsure as to how this is achieved in LAravel.
Here is my code:
Product Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $primaryKey = 'id',
$table = 'products',
$fillable = array('title', 'SKU', 'description', 'created_at', 'updated_at');
public $timestamps = true;
/**
* Get the categories assoicated with the product
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*
*/
public function categories() {
return $this->belongsToMany('App\Category')->withTimestamps();
}
}
Category model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
/**
* Returns all products related to a category
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function products() {
return $this->belongsToMany('App\Product')->withTimestamps();
}
}
Inside my product controller I have this function to get products which calls a method 'filterProduct' in a class called 'filtervars':
public function index(Request $request)
{
return FilterVars::filterProduct($request->all());
}
And here is the filterProduct method:
public static function filterProduct($vars) {
$query = Product::query();
if((array_key_exists('order_by', $vars)) && (array_key_exists('order', $vars))) {
$query = $query->orderBy($vars['order_by'], $vars['order']);
}
if(array_key_exists('cat', $vars)) {
$query = $query->whereHas('categories', function($q) use ($vars){
return $q->where('category_id', $vars['cat']);
});
}
return $query->get();
The product database migration:
class CreateProductsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('products', function(Blueprint $table) {
$table->increments('id');
$table->string('title', 75);
$table->string('SKU')->unique();
$table->text('description')->nullable();
$table->timestamps();
});
}
And the migration which shows the structure of the categories table, the pivot table and foreign keys etc:
class CreateCategoriesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->string('description');
$table->timestamps();
});
Schema::create('category_product', function(Blueprint $table) {
$table->integer('product_id')->unsigned()->index();
$table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
$table->integer('category_id')->unsigned()->index();
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');
$table->timestamps();
});
}
I had a go at trying to incorporate the 'has' method on the query but this doesn't seem to work. can anyone advise as to where I am going wrong?
Thanks!
May be you need whereHas method
$query = $query->whereHas('categories', function($q) use ($vars) {
$q->where('id', $vars['cat']);
});
EDIT
You should use id column in whereHas method because you apply where condition to categories table, which hasn't category_id column
public static function filterProduct($vars) {
$query = Product::query();
if((array_key_exists('order_by', $vars)) && (array_key_exists('order', $vars))) {
$query = $query->orderBy($vars['order_by'], $vars['order']);
}
if(array_key_exists('cat', $vars)) {
$query = $query->whereHas('categories', function($q) use ($vars){
$q->where('id', $vars['cat']);
});
}
return $query->get();
}

Categories