I've got a master table called Product with the following columns:
id
product_id
product_type
name
price
in_stock
upc
Where ’id' and 'product_id' are unique (id is the PK)
I'll have other tables for different kinds of products (types).
All these other tables will have Product’s properties plus
Other properties on their own depending on the type of product
(I.e. clothing, records, etc.).
So I created a Product model using Polymorphic relationships
as follows:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $fillable = [
'product_id',
'product_type',
'name',
'price',
'in_stock',
'upc'
];
public function categorizable()
{
return $this->morphTo();
}
}
And, for instance, a records model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Record extends Model
{
protected $fillable = [
'artist_id',
'title',
'label',
'code',
'format',
'number_of_discs',
'image',
'description'
];
public function products()
{
return $this->morphMany('\App\Product', 'categorizable');
}
public function artist()
{
return $this->belongsTo(Artist::class);
}
public function track()
{
return $this->hasMany(Track::class);
}
public function getItemDetails(int $itemId): array {
}
}
Whereas the columns for record are:
id
artist_id
product_id
title
label
This is the best way I could think of relating these tables.
My questions are:
Is there a better approach to this specific problem?
In this case (using polymorphic relationships), how would I insert a product?
How could I query a product in order to return data from
Both product table and record table? I mean, not a raw query
Since that I can do, but how to perform this query using
Eloquent?
Your code is perfect except product_id column in Record. You don't need that column, just remove it
how would I insert a product?
$product = Product::create([
'name' => $request->name,
'price' => $request->price,
'in_stock' => $request->in_stock
]);
$record->products()->save($product);
OR
$record->products()->create([
'name' => $request->name,
'price' => $request->price,
'in_stock' => $request-> in_stock,
'product_id' => $record->id,
'product_type' => get_class($record)
]);
If you need to create both then do it like this
$record = Record::create([
'artist_id' => $request->artist_id
'title' => $request->title,
'label' => $request->label,
'code' => $request->code,
]);
$product = Product::create([
'name' => $request->name,
'price' => $request->price,
'in_stock' => $request->in_stock
]);
$record->products()->save($product);
Fetch Data
$product = Product::with('categorizable')->find(2);
$product->categorizable; //this will be either Record, Cloth... instance
Similarly for record
$record = Record::with('products')->find(1);
$record->products; //it will give you product collection
For details you can look https://laravel.com/docs/5.1/eloquent-relationships#polymorphic-relations
Related
Good morning developers, iam working on ecommerce project using laravel and angular
in laravel i made two tables ( brands - products ) and i need the brand has many products
so i added this function to the Product model
public function brand()
{
return $this->belongsTo(Brand::class);
}
and i added this function to Brand model
public function products()
{
return $this->hasMany(Product::class);
}
the first question is how i could handle the relation in the products table migration file, is this right or there is better way?
$table->unsignedBigInteger('brand_id');
$table->foreign('brand_id')->references('id')->on('brands')->onDelete('cascade');
the second questions is should i define the brand_id in the $fillable products property or not
protected $fillable = ['name', 'brand_id', 'display', 'ram', 'storage', 'rear_cam', 'front_cam', 'img', 'price'];
the third question is how to handle the store product function and note iam using api calls between angular and laravel
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:100', 'display' => 'required|string|max:100', 'ram' => 'required|string|max:100', 'img' => 'required|image|mimes:jpg,png',
'storage' => 'required|string|max:100', 'rear_cam' => 'required|string|max:200', 'front_cam' => 'required|string|max:200',
'price' => 'required'
]);
if ($validator->fails()) {
$errors = $validator->errors();
return response()->json($errors);
}
$img = $request->file('img');
$ext = $img->getClientOriginalExtension();
$name = 'product-' . uniqid() . ".$ext";
$img->move(public_path('uploads/books/'), $name);
$name = $request->name;
$brand_id = $request->brand_id;
$display = $request->display;
$ram = $request->ram;
$storage = $request->storage;
$rear_cam = $request->rear_cam;
$front_cam = $request->front_cam;
$price = $request->price;
$product = Product::create(['name' => $name, 'brand_id' => $brand_id, 'display' => $display, 'img' => $name, 'ram' => $ram, 'storage' => $storage, 'rear_cam' => $rear_cam, 'front_cam' => $front_cam, 'price' => $price]);
$product->brand()->sync($request->brand_id);
$success = 'Product created successfully';
return response()->json($success);
}
when iam trying to store a product it says there is error with the sync function, what function should i use instead of it
Actually you don't need it to sync, In many-to-many relationships require an additional table called pivot table where you Managing Many-to-Many Relationships using attach-detach-sync method but in case you havnt situation like this.
Undo this Line
$product->brand()->sync($request->brand_id);
After Product created successfully. If you simply called products function to Brand model. You will seeing last created product are there.
Ex:-
Brand::with('products')->find($brand_id)
Thanks
the first question is how i could handle the relation in the products table migration file, is this right or there is better way?
It is indeed the good way to handle this.
the second questions is should i define the brand_id in the $fillable products property or not
It all depends on your use case. Typically you only define the attributes the user can modify at will, like his name. The concept of fillable is here to protect you from mass assignment.
For instance:
if I change the name attribute of a check box from accept_newsletter to is_admin
and if a column named is_admin exists
and if is_admin is in the fillable attributes
and you do something like that: $user->update($request->all());
Then I could potentially become an admin.
However, if is_admin isn't in the fillable attributes, you must explicitly do something like: $user->is_admin = $request->is_admin. Which is much more unlinkely to happen involuntarily.
In your case, the person who will add/edit a product will probably be an admin anyway so you can add brand_id to the fillable attributes.
For your third question, since you are using a hasMany relationship on the Brand model and a belongsTo in the Product model, all you have to do is $product->brand()->associate($request->brand_id);.
But keep in mind that here:
product = Product::create(['name' => $name, 'brand_id' => $brand_id, 'display' => $display, 'img' => $name, 'ram' => $ram, 'storage' => $storage, 'rear_cam' => $rear_cam, 'front_cam' => $front_cam, 'price' => $price]);
You already assign the brand_id to the product you are creating (it'll only work if you added brand_id to the fillable attributes). Think of the fillable attributes as "attributes that can be assigned within an array".
i have manytomany relation ship between categroy and product
category model
class Attribute extends Model implements Auditable
{
use HasFactory, AuditableTrait;
protected $fillable = ['category','sub_categ'];
public function products(): BelongsToMany
{
return $this->belongsToMany(Product::class);
}
}
product model
class Product extends Model implements Auditable
{
use HasFactory, AuditableTrait;
protected $table = 'products';
protected $fillable = ['name','price','description', 'details'];
public function products(): BelongsToMany
{
return $this->belongsToMany(Product::class);
}
}
the pivot table
Schema::create('attributes_products', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->foreignId('product_id')->constrained('products')->onUpdata('cascade')->onDelete('cascade');
$table->foreignId('attribute_id')->constrained('attributes')->onUpdata('cascade')->onDelete('cascade');
});
what should i do after this i did not undrestant how attach will work in pivot table and return it with the product as json response ?
edit
this is the schema i am working on
i want to give each product it's own category
and this is my product controller store function
public function store(Request $request)
{
$request->validate([
'name' => 'required',
'price' => 'required|numeric',
'description' => 'required',
'details' => 'required',
'stocks' => 'required|numeric',
//'discounts' => 'required|numeric'
]);
$product = Product::create($request->only('name','price','description', 'details'));
$product->stocks()->create([
'quantity' => $request->stocks,
'product_id' => $product->id
]);
$product->discounts()->create([
//'discount' => $request->discounts,
'product_id' => $product->id
]);
if($request->hasFile('images'))
{
foreach( $request->file('images') as $file)
{
$file->store('public/products');
$product->images()->create([
'product_id' => $product->id,
'file_path' => $file->hashName()
]);
}
}
$product->categories()->sync([
'product_id' => $product->id,
'attribute_id'=> 1
]);
}
In your product model check your relation.
public function categories()
{
return $this->belongsToMany(Category::class);
}
Also usually the pivot table needs to have only 2 Ids. So in your case only 2 columns: product_id & category_id.
Your table name by convention should be category_product, otherwise, you should specify it on the second parameter on the relationship.
Fix this too, you got a typo on update:
$table->foreignId('attribute_id')->constrained('attributes')->onUpdate('cascade')->onDelete('cascade');
And finally to attach:
$product = Product::find(1);
$product->categories()->attach($categoryId);
All is explained very well on documentation too: https://laravel.com/docs/8.x/eloquent-relationships
I was using these codes in my controller to get all the data from my 2 tables and it works fine
$All = Customers::with('order')->paginate(10);
return response()->json([
'code' => 0,
'success' => true,
'data' => $All
], 200);
Here is how I define the relationship between these 2 tables
class Customers extends Model
{
public function order()
{
return $this->hasMany(Orders::class, 'customer_id', 'id');
}
}
class Orders extends Model
{
public function customers()
{
return $this->belongsTo(Customers::class, 'customer_id', 'id');
}
}
Now my desire output is to hide the order id, order timestamps and change the customer_id to customer's name (the customer's name is not in my orders db table).
I'm using 'data' => DataResource::collection($All) in my controller and this is my DataResource
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'order' => $this->order
];
}
and of course the output is same with the image above.
My database structure:
orders table:
customer table:
Can anyone help me with that?
The answer is simple and basically a copy of the official documentation. You simply need to wrap your orders in an OrderResource as well.
// DataResource
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'order' => OrderResource::collection($this->order)
];
}
// OrderResource
public function toArray($request)
{
return [
'items' => $this->items,
'quantity' => $this->quantity
];
}
I don't really understand why you would want to include the customer_name in your orders when it is already present on the customers object one hierarchy above. But if you really want to add it, you should be able to do so with: 'customer_name' => $this->customers->name.
As a side note: you really should be more consistent with your naming. Why is the resource called DataResource when it is about Customers? Why is your model called Customers in plural form rather than Customer in singular, which is the convention (and more logical if you consider that one model represents one customer). Why is your belongsTo relation called customers() in plural when it returns one customer, while your hasMany relation is called order whereas it returns one or more orders?
Trying to get the product_id from table one then inserting it to table two, but gives back an error
1048 Column 'product_id' cannot be null (SQL: insert into product_details (product_id, category, brand, provider_id) values (, Peripherals, Riders, 1))
Code:
$product = Product::create([
'product_name' => $request['product_name'],
'quantity' => $request['quantity']
]);
$product->save();
$product_id = $product->id;
$productDetails = ProductDetails::create([
'product_id' => $product_id,
'category' => $request['category'],
'brand' => $request['brand'],
'provider_id' => $request['provider_id']
]);
Fillable for productdetails model
protected $fillable = ['product_id', 'category', 'brand', 'provider_id'];
Database structure: (Just remembered, just a while ago I made some changes on my database that was the time when this error popped out.)
Products:
Schema::create('products', function (Blueprint $table) {
$table->increments('product_id');
$table->string('product_name');
$table->integer('quantity');
Product Details:
Schema::create('product_details', function (Blueprint $table) {
$table->integer('product_id')->unsigned();
$table->string('category',255);
$table->string('brand',255);
$table->integer('provider_id')->unsigned();
$table->foreign('product_id')
->references('product_id')
->on('products')
->onDelete('cascade');
$table->timestamps();
});
This has been solved,
It was just a simple mistake on my part. I should've been using 'product_id' at '$product_id = $product->id' instead of 'id'. Apologies to everyone.
Before inserting into ProductDetails check if Product is saved or not. Also check your fillable field in your ProductDetails model whether you have added product_id or not.
$product = Product::create([
'product_name' => $request['product_name'],
'quantity' => $request['quantity']
]);
if($product) {
$productDetails = ProductDetails::create([
'product_id' => $product->id,
'category' => $request['category'],
'brand' => $request['brand'],
'provider_id' => $request['provider_id']
]);
}
Update your fillable field like below:
protected $fillable = ['product_id', 'category', 'brand', 'provider_id'];
Update
If your primary key is product_id, you should call $product->product_id
Hey you have to change your fillable property like this:
protected $fillable = ['product_id', 'category', 'brand', 'provider_id'];
I don't think you need to use save() method. after create() method you can get model object and get the id from it directly but make sure your id in db is id not product_id.
$product = Product::create([
'product_name' => $request['product_name'],
'quantity' => $request['quantity']
]);
if($product){
$productDetails = ProductDetails::create([
'product_id' => $product->id,
'category' => $request['category'],
'brand' => $request['brand'],
'provider_id' => $request['provider_id']
]);
}else{
echo "Something went wrong";
}
You shouldn't run a save() method after running a create; its redundant.
You can run a db transaction in a try and catch to know what exactly is going wrong.
try {
\DB::transaction(function () use($request) {
$product = Product::create([
'product_name' => $request['product_name'],
'quantity' => $request['quantity']
]);
$productDetails = ProductDetails::create([
'product_id' => $product->id,
'category' => $request['category'],
'brand' => $request['brand'],
'provider_id' => $request['provider_id']
]);
});
dd('successful');
} catch (\Exception $e) {
dd( $e->getMessage() );
}
You will need to specify either a fillable or guarded attribute on the model, as all Eloquent models protect against mass-assignment by default.
Fillable you specify which fields are mass-assignable in your model, So in the model you need to add also product_id:
Model:
protected $fillable = ['product_id', 'category', 'brand', 'provider_id'];
////only the field names inside the array can be mass-assign
Automatically assumes your primary key column is going to be id. In order for this to work correctly, you should set your primary key in your model product.php:
protected $primaryKey = 'product_id';
I am converting an internal API from HTML (back-end) processing to JSON (using Knockout.js) processing on the client-side to load a bunch of entities (vehicles, in my case).
The thing is our database stores sensitive information that cannot be revelead in the API since someone could simply reverse engineer the request and gather them.
Therefore I am trying to select specifically for every relationship eager-load the columns I wish to publish in the API, however I am having issues at loading a model relationship because it seems like Eloquent automatically loads every column of the parent model whenever a relationship model is eager loaded.
Sounds like a mindfuck, I am aware, so I'll try to be more comprehensive.
Our database stores many Contract, and each of them has assigned a Vehicle.
A Contract has assigned an User.
A Vehicle has assigned many Photo.
So here's the current code structure:
class Contract
{
public function user()
{
return $this->belongsTo('User');
}
public function vehicle()
{
return $this->belongsTo('Vehicle');
}
}
class Vehicle
{
public function photos()
{
return $this->hasMany('Photo', 'vehicle_id');
}
}
class Photo
{
[...]
}
Since I need to eager load every single relationship listed above and for each relationship a specific amount of columns, I need to do the following:
[...]
$query = Contract::join('vehicles as vehicle', 'vehicle.id', '=', 'contract.vehicle_id')->select([
'contract.id',
'contract.price_current',
'contract.vehicle_id',
'contract.user_id',
'contract.office_id'
]);
[...]
$query = $query->with(['vehicle' => function ($query) {
$query->select([
'id',
'trademark',
'model',
'registration',
'fuel',
'kilometers',
'horsepower',
'cc',
'owners_amount',
'date_last_revision',
'date_bollo_expiration',
'bollo_price',
'kilometers_last_tagliando'
]);
}]);
$query = $query->with(['vehicle.photos' => function ($query) {
$query->select([
'id',
'vehicle_id',
'order',
'paths'
])->where('order', '<=', 0);
}]);
$query = $query->with(['user' => function ($query) {
$query->select([
'id',
'firstname',
'lastname',
'phone'
]);
}]);
$query = $query->with(['office' => function ($query) {
$query->select([
'id',
'name'
]);
}]);
[...]
return $this->response->json([
'error' => false,
'vehicles' => $vehicles->getItems(),
'pagination' => [
'currentPage' => (integer) $vehicles->getCurrentPage(),
'lastPage' => (integer) $vehicles->getLastPage(),
'perPage' => (integer) $vehicles->getPerPage(),
'total' => (integer) $vehicles->getTotal(),
'from' => (integer) $vehicles->getFrom(),
'to' => (integer) $vehicles->getTo(),
'count' => (integer) $vehicles->count()
],
'banner' => rand(0, 2),
'filters' => (count($input) > 4),
'filtersHelpText' => generateSearchString($input)
]);
The issue is: if I do not eager load vehicle.photos relationship, columns are loaded properly. Otherwise, every single column of Vehicle's model is loaded.
Here's some pictures so you can understand:
Note: some information have been removed from the pictures since they are sensitive information.
You can set a hidden property on your models which is an array of column names you want to hide from being output.
protected $hidden = ['password'];