I am trying to retrieve the inverse side of the one to many relationship where the method is camelCase. i.e
Owning Class: OneToMany
class Brand extends Model
{
public function products()
{
return $this->hasMany(Product::class);
}
}
Owned Class
class Product extends Model
{
public function MyBrand()
{
return $this->belongsTo(Brand::class);
}
}
retrieve the inverse related model like this:
$product = Product::find(1);
$brand = $product->my_brand;
dd($brand->name);
Error, Trying to get property 'name' of non-object
I also tried this:
$brand = $product->myBrand;
it did not work.
however, if i make my method like below it works:
public function brand()
{
return $this->belongsTo(Brand::class);
}
question is : how to make it work when the method is in CameCase ?
Try this,
public function my_brand()
{
return $this->belongsTo(Brand::class);
}
And call this relation as,
$product = Product::find(1);
$brand = $product->my_brand;
dd($brand->name);
You need to change, method name in brand(), because Eloquent will automatically determine the proper foreign key column in the Brands table.
https://laravel.com/docs/7.x/eloquent-relationships#one-to-many
If you want to keep it MyBrand() you need to specify foreign key:
public function MyBrand()
{
return $this->belongsTo(Brand::class,'product_id');
}
Specify it's foreign key in relationship
class Product extends Model
{
public function MyBrand()
{
return $this->belongsTo(Brand::class,'brand_id');
//brand_id is foreign key in your product table
}
}
$product = Product::find(1)->with('MyBrand'); //But it will be good to eager load it
$product->MyBrand->name; //It will definitely return name now
Related
I'm currently facing a weird issue with one of my Laravel Models.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Gallery extends Model
{
protected $guarded = ['id'];
protected $with = ['member','photos', 'cover'];
public function member()
{
return $this->hasOne(Member::class, 'id', 'member')->setEagerLoads([]);
}
public function cover()
{
return $this->hasOne(Photo::class, 'id', 'cover')->setEagerLoads([]);
}
public function photos()
{
return $this->morphMany('App\Models\Photo', 'photoable')->setEagerLoads([]);
}
}
If I dump all galleries, each gallery has a cover which is a Instance of App\Models\Photo
$galleries = Gallery::all();
dump($galleries);
This also works with $galleries->toJson() and $galleries->toArray()
However, if I loop over galleries, cover is only an integer.
$galleries = Gallery::all();
foreach($galleries as $gallery){
dump($gallery->cover); // Integer instead of App\Models\Photo
}
While this returns a App\Models\Member:
$galleries = Gallery::all();
foreach($galleries as $gallery){
dump($gallery->member); //Instance of App\Models\Member
}
Laravel: 6.6.2
PHP: 7.4
Your Gallery model attribute $cover has the same name as relation.
Your model use $cover attribute which have integer value (foreign key to related model).
You could rename column cover for example to cover_id.
Relationship name cannot be the same as the column name in the table. Rename one or the other, would recommend to rename the column to cover_id.
You passed the wrong place of param in a relationship.
pass it as below.
public function member()
{
return $this->hasOne(Member::class, 'member','id')->setEagerLoads([]);
}
public function cover()
{
return $this->hasOne(Photo::class,'cover','id')->setEagerLoads([]);
}
Here example of laravel doc.
return $this->hasOne('App\Phone', 'foreign_key', 'local_key');
I have two models Product and Images. I changed the route key name on the product model to use the slug field and i'm now unable to load the hasMany relationship with the Image Model
Here is the Product Model
class Product extends Model
{
protected array $with = ['images'];
public function getKeyName()
{
return 'slug';
}
protected array $guarded = [];
public function images() : HasMany
{
return $this->hasMany(Image::class, 'product_id');
}
}
and the Image model
class Image extends Model
{
protected array $guarded = [];
public function image() : BelongsTo
{
return $this->belongsTo(Product::class);
}
}
so when I try
Product::first()->images
it just returns an empty collection
but without overriding the getKeyName() method, everything works fine
getKeyName() will get the primary key for the model. it supports to return id, after you change it to slug, it will return slug
And hasManyHere's the source code ;
The third parameter LocalKey will use getKeyName() when it's empty.
If you still want to use hasMany, you need to pass the third parameter like this:
public function images()
{
return $this->hasMany(Image::class, 'product_id', 'id');
}
This will convert the Eloquent query to database query, which will take the right local key products.id.
I have a database set up that I am not sure how to code in laravel. I am trying to basically get dynamic attribute names from custom input.
Here's the DB setup:
Category:
-ID
-Name
Product:
-ID
-Category_id
Product_Attribute:
-ID
-Category_id
-Attribute_Name
Attribute_value:
-ID
-Product_id
-Product_attribute_id
-Value
There can be multiple values for each attribute and I don't have a set list of attributes as it can change depending on the category/product. Some products have some attributes and some don't. Some will have the same key/name as in other categories but will rarely overlap for my purposes but i can work around that if need be.
I there a way to setup laravel so i can look through the keys / values as well as call them by name
echo $product->$Attribute_Name;
or
echo $product->attributes[$Attribute_Name];
or something similar
but i also need to pull all products where attribute name = y and attribute Value = X
select * from Products join Attribute_value on products.ID = Attribute_value.Product_id join Product_Attribute on Category_id = Products.Category_id and Product_Attribute.ID = Attribute_value.Product_attribute_id where Product_Attribute = '{attribute_name}' and Attribute_value = '{Attribute_value}'
This is only return the products but not with the associated data or the other attributes. I can't find and easy way of loading that data without having to build a class to populate it. Ideally I would like to be able to change the values and save them using the ORM similar to how a one to many relationship works.
I have seen this type of structure before in databases. I was wondering if there was a way to do this easily in laravel without having to create a bunch of custom functions to load the attributes for each product.
Thanks
I'm a little confused by your question, but what you first want to do is create all the models and relationships. You don't necessarily need a model for each of the four tables, but I'd strongly recommend it.
class Category
{
public function products(): HasMany
{
return $this->hasMany(Product::class);
}
public function productAttributes(): HasMany
{
return $this->hasMany(ProductAttribute::class);
}
}
class Product
{
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
public function attributeValues(): HasMany
{
return $this->hasMany(AttributeValue::class);
}
}
class ProductAttribute
{
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
public function attributeValues(): HasMany
{
return $this->hasMany(AttributeValue::class);
}
}
class AttributeValue
{
public function product(): BelongsTo
{
return $this->belongsTo(Product::class);
}
public function productAttribute(): BelongsTo
{
return $this->belongsTo(ProductAttribute::class);
}
}
This code expects you to consider the Laravel naming standards of tables and properties.
After defining the classes and relationships, you may load products with their attributes like this:
$products = Product::query()
->with('attributeValues.productAttribute')
->where('category_id', $categoryId)
->get();
Because this makes accessing an attribute by it's name a pain...
$product = $products->first();
$color = optional($product->attributeValues
->where('productAttribute.name', 'color')
->first())->value ?? 'white';
... you can also override the __get($name) method to add a nice accessor for your attributes:
class Product
{
public function __get(string $name)
{
if ($name === 'attrs') {
return (object) $this->attributeValues->mapWithKeys(function ($attributeValue) {
return [$attributeValue->productAttribute->name => $attributeValue->value];
});
}
return parent::__get($name);
}
}
After doing so, you should be able to access your attributes like this:
$product = $products->first();
$color = $product->attrs->color;
// or if you need to retrieve an attribute by name stored in a variable
$name = 'color';
$attr = $product->attrs->$name;
Of course you can also omit the (object) cast in the __get($name) accessor to return an array instead. You then receive this syntax: $product->attrs['color']. Either way, this will return an error if a property is not set / not in the array. Make sure to catch this. You may also want to add some caching to avoid building the attrs object/array over and over again.
Please note: The $attributes property is used by the Eloquent base model internally to store all the properties of a model. So this name is reserved and you should use something else like attrs instead.
Edit: Two more options for getters would be the following ones:
class Product
{
public function getColorAttribute(string $default = 'white'): string
{
return optional($this->attributeValues
->where('productAttribute.name', 'color')
->first())->value ?? $default;
}
public function getAttr(string $name, $default = null)
{
return optional($this->attributeValues
->where('productAttribute.name', $name)
->first())->value ?? $default;
}
}
Similarly, you could design a setter:
class Product
{
public function setAttr(string $name, $value): void
{
if ($this->hasAttr($name)) {
$attr = $this->attributeValues
->where('productAttribute.name', $name)
->first();
$attr->value = $value;
$attr->save();
} else {
throw new \Exception(sprintf('This product may not have the attribute [%s].', $name));
}
}
public function hasAttr(string $name): bool
{
return $this->attributeValues
->contains('productAttribute.name', $name);
}
}
So I have a model called data_storage and another model entity_states
I have to fetch the record from data_storage with entity_states where entity_state has data_storage_id and state_id.
How can I use eloquent to achieve this ?.
Or Ill have to use Query builder and use innerJoin?
Update1
My Actual Query
$this->values['new_leads'] = $data_storages->with('actions','states','sla')->where('wf_id',$wfid)->get();
My data_storage modal
class data_storages extends Model
{
//
protected $fillable = ['layout_id','member_id','company_id','team_id','data','status','wf_id'];
function actions()
{
return $this->hasMany('App\Models\ActionDataMaps', 'data_id', 'id' );
}
function states()
{
return $this->hasOne('App\Models\workflow_states','id','status');
}
function sla()
{
//Here I have to get those row from entity_states model where , data_storage_id and state_id
}
}
Thanks
Here's the more reasonable way to do it:
class DataStorage extends Model {
public states() {
return $this->belongsToMany(State::class,"entity_states");
}
}
class State extends Model {
public storages() {
return $this->belongsToMany(DataStorage::class,"entity_states");
}
}
Then you can eager-load related models via e.g.:
$storage = DataStorage::with("states")->first();
$storage->states->first()->column_in_related_state;
Or via the state:
$state = State::with("storages")->first();
$state->storages->first()->column_in_related_storage;
If there are additional columns in the pivot table entity_states then you can refer to them in the relationship as e.g.:
public states() {
return $this->belongsToMany(State::class)->withPivot("pivot_column");
}
In your model data_storage you can define a property / method entity_states to get them:
class data_storage extends Model
{
public function entity_states()
{
return $this->hasMany('App\entity_states','data_storage_id')->where('state_id ','=',$this->table());
}
}
Then you can access them in an instance by
$entityStatesOfDataStorage = $yourDataStorageInstance->entity_states;
See this link:
https://laravel.com/docs/5.3/eloquent-relationships
for Query Builder you may use this:
DB::table('data_storage')
->join('entity_states','data_storage.data_storage_id','=','entity_states.state_id')
->get();
For your reference Laravel Query Builder
Code:
<?php
class Catering extends \Eloquent {
protected $table = 'catering';
public $timestamps = FALSE;
public function offers() {
return $this->hasMany('Offer', 'cid');
}
}
class Offer extends \Eloquent {
protected $table = 'catering_offer';
public $timestamps = FALSE;
public function catering() {
return $this->belongsTo('Catering');
}
}
I am able to do
$offers = Catering::find(1)->offers;
but, the inverse is not working:
$catering = Offer::find(1)->catering;
is always returning NULL. Database has the right values.
Offer table has 2 columns:
primary(id), int(cid)
that references catering.id.
The question:
How can i access the reverse side of this relation?
You said that, I am able to do
$offers = Catering::find(1)->offers;
and in your Catering model you have
public function offers() {
return $this->hasMany('Offer', 'cid');
}
It seems like you've defined a different foreign key here (cid) to use it instead of the default one that laravel basically supposed to use, so, to do the reverse relation you have to do the same thing in your Offer model's catering function
public function catering() {
return $this->belongsTo('Catering', 'cid');
}
In the Laravel Documentation, it says that, you may override the conventional foreign key by passing a second argument to the hasMany method, like
return $this->hasMany('Offer', 'custom_key');
Same way, to define the inverse of the relationship on the Offer model, you can use the belongsTo method, like
return $this->belongsTo('Catering', 'custom_key'); // cid