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
Related
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.
I have a parent model Library which has many Books. Eloquent makes it so easy to define the relationships. I have enabled softdeleting on the book mode using the SoftDeletes trait.
Problem:
When I delete a book instance, the deleted_at attribute on the book instance is set to the current timestamp (as expected).
But when I query all the books that belong to the Library, the result contains all the books, including the ones that were deleted.
I only want to get the books that have not been deleted.
class Library extends Model {
public function books() {
return $this->hasMany(Book::class);
}
}
class Book extends Model {
use SoftDeletes;
public function library() {
return $this->belongsTo(Library::class, 'library_id');
}
}
$softDeleteBook = Book::find(1);
$softDeleteBook->delete();
$books = Library::find(1)->books;
// $books contains even $softDeleteBook
// I do not want to get $softDeleteBook
#Elisha-Wigwe Chijioke. Have you added $table->softDeletes(); to your migration file.
Sample model goes like this
<?php
class Video extends Model
{
use SoftDeletes;
protected $dates = ['deleted_at'];
public function category()
{
return $this->belongsTo('LearnCast\Category');
}
}
?>
Take a look at a sample migration schema build up
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('videos', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
$table->softDeletes();
});
}
I hope this helps
Since i am a spanish speaker, i wrote the controllers and models of income and expense in spanish; while all the rest were on english..
I renamed and changed manually Routes, Controllers, Migrations and even Models.
And when i run php artisan migrate:reset this is my error.
Undefined table: 7 ERROR: relation "expenses" does not exist (SQL: alter table "expenses" drop column "location_id")**
I use psgql and laravel 5.3
This is my code:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Expense extends Model
{
protected $fillable = ['id', 'description', 'quantity'];
public function locations()
{
return $this->hasMany('App\Location');
}
public function icons()
{
return $this->hasMany('App\Icon');
}
public function types()
{
return $this->hasMany('App\Type');
}
public function stores()
{
return $this->hasMany('App\Store');
}
}
Migration:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateExpensesTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('expenses', function (Blueprint $table) {
$table->increments('id');
$table->float('quantity');
$table->string('description');
$table->integer('location_id')->unsigned();
$table->integer('icon_id')->unsigned();
$table->integer('type_id')->unsigned();
$table->integer('store_id')->unsigned();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('expenses');
}
}
Location Migration:
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateLocationsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('locations', function (Blueprint $table) {
$table->increments('id');
$table->string('address');
$table->float('lat');
$table->float('long');
$table->integer('store_id')->unsigned();
$table->timestamps();
$table->foreign('store_id')->references('id')->on('stores')->onDelete('cascade');
});
Schema::table('expenses', function (Blueprint $table) {
$table->foreign('location_id')->references('id')->on('locations')->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::table('expenses', function (Blueprint $table) {
$table->dropColumn('location_id');
});
Schema::dropIfExists('locations');
}
}
Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Location extends Model
{
protected $fillable = ['id', 'address', 'lat', 'long'];
public function expenses()
{
return $this->belongsTo('App\Expense');
}
public function stores()
{
return $this->hasOne('App\Store');
}
}
Hope you can help me.
When it says
relation "expenses" does not exist
It usually happens when your expenses table must have been dropped before migration:reset rolled back CreateLocationsTable.
Check the order of execution of your migrations.
As you were trying to reset, to fix it now, open your database, drop all tables manually and execute migrate again.
Schema::table('expenses', function (Blueprint $table) {
$table->foreign('location_id')->references('id')->on('locations')->onDelete('cascade');
});
change this to
Schema::alter('expenses', function (Blueprint $table) {
$table->foreign('location_id')->references('id')->on('locations')->onDelete('cascade');
});
I had this error. I had to resolve it by adding the prefix of the database to my model. There is a way to change this in the database, and it is supposed to be changed. it's something, like $user, public. It's the schema profile. Mine is changed when I login, but for some reason the model is not binding to schema. So I had to specify it in the model. Instead of
protected $table = 'table_name';
I had to do
protected $table = 'schema_name.table_name';
Note: By schema_name or table_name, I'm not referring for you to put schema_ then the name. That is just so it's easier to read.
The model is located in the model folder under the App/Models folder depending on which version of Laravel you using and how your Models are organized. If your model name is not the same as the table name, then you will need to put the protected $table. But if the schema is not there, then you will need to add that.
I do have this set in my DB_DATABASE_SECOND= in .env file. But it somehow still doesn't pick up the prefix.
But yeah. Pretty much I couldn't find the answer anywhere, but I
Solution found in:
Laravel assumes the database table is the plural form of the model name
https://laravel.com/docs/8.x/eloquent#table-names
you need to declare the singularity name of your table.
add this line
protected $table = 'expense'
at the end of your model file
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Expense extends Model
{
protected $fillable = ['id', 'description', 'quantity'];
public function locations()
{
return $this->hasMany('App\Location');
}
public function icons()
{
return $this->hasMany('App\Icon');
}
public function types()
{
return $this->hasMany('App\Type');
}
public function stores()
{
return $this->hasMany('App\Store');
}
protected $table = 'expense';
}
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.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
Hello im getting this error Illuminate\Database\QueryException with message 'SQLSTATE[42S22]: Column not found: 1054 Unknown column 'posts.user_id' in 'where clause' (SQL: select * frompostswhereposts.user_id= 1 andposts.user_idis not null)' and I don't know why if in my database I don't have user_id, I have id_user...
This is my migration table
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->string('user')->unique();
$table->string('email')->unique();
$table->string('password', 60);
$table->string('img');
$table->rememberToken();
$table->timestamps();
});
}
public function down()
{
Schema::drop('users');
}
}
This other is my posts migration archive
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddPosts extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('nombre');
$table->longText('contenido');
$table->unsignedInteger('id_user');
$table->timestamps();
});
Schema::table('posts', function($table) {
$table->foreign('id_user')->references('id')->on('users');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('posts');
}
}
this is my Post model
<?php
namespace NacionGrita;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $table = "posts";
protected $fillable = ['nombre', 'contenido', 'id_user'];
public function imagenes() {
return $this->belongsToMany('NacionGrita\Imagen');
}
public function categorias() {
return $this->belongsToMany('NacionGrita\Categoria');
}
public function tags() {
return $this->belongsToMany('NacionGrita\Tag');
}
public function user() {
return $this->belongsTo('NacionGrita\User');
}
}
and this is my users Model
<?php
namespace NacionGrita;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Model
{
protected $table = "users";
protected $fillable = [
'user', 'email', 'password', 'img'
];
public function posts() {
return $this->hasMany('NacionGrita\Post');
}
protected $hidden = [
'password', 'remember_token',
];
}
If I change my "posts" table column from id_user to user_id it works but I don't know why I have to change the column name if its supposed to works because I specified the foreigns keys or Im doing something wrong?
Thanks for help
In order to specify the foreign keys, you need to do so in the model when you define the relationships.
From the docs for a belongsTo relationship:
In the example above, Eloquent will try to match the user_id from the Phone model to an id on the User model. Eloquent determines the default foreign key name by examining the name of the relationship method and suffixing the method name with _id. However, if the foreign key on the Phone model is not user_id, you may pass a custom key name as the second argument to the belongsTo method
In other words, in your Post model where you define the relationship with the User, you need to add a second argument that specifies the foreign key name:
public function user() {
return $this->belongsTo('NacionGrita\User', 'id_user');
}
From the docs for a hasOne and hasMany relationship:
Eloquent assumes the foreign key of the relationship based on the model name. In this 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:
In other words, in your User model where you define the relationship with the Post, you need to once again add a second argument that specifies the foreign key name:
public function posts() {
return $this->hasMany('NacionGrita\Post', 'id_user');
}
Link to docs: https://laravel.com/docs/5.1/eloquent-relationships