Laravel 5.6 Flexible Relationship Spanning Differing Tables - php

I have a table which will be addressed by a model in Laravel for stock movements. I would like to establish a relates to field which can be related to a number of different tables.
As per the attached diagram I need the relates_to_id to be one of the following...
Purchases.purchase_id
Invoices.invoice_id
And there may be more being added in the future as we find more records which may result in a stock movement occurring.
Now I have added a relates_to_type field to the database schema for the stock movements table so that I can specify which record type the particular movement record relates to. But I have been struggling to figure out how to establish the model for such a relationship to work or if it will even work that way or if I will need to have a separate field for each type of relationship as I wish to be able to read the related record through the ORM's related record scheme.

This is the exact use case for polymorphic relations, they supply your model as you wished for with an column indicating the relation type and the id of the related model.
See also this example from the laravel documentation:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Image extends Model {
/**
* Get all of the owning imageable models.
*/
public function imageable() {
return $this->morphTo();
}
}
class Post extends Model {
/**
* Get the post's image.
*/
public function image(){
return $this->morphOne('App\Image', 'imageable');
}
}
class User extends Model {
/**
* Get the user's image.
*/
public function image()
{
return $this->morphOne('App\Image', 'imageable');
}
}
Here every user and post can have an image and every image has an imageable which is either a user or a post.

Related

pivot "deleted" event when parent is deleted

I have 2 models: User and Role.
A user can have many roles.
A role can have many users.
I have a custom pivot model between these 2 models. This custom pivot model only exists because it uses a trait that listens/logs for events such as created, updated, deleted.
Let's say I have a role called moderator. When I attach() (or detach()) that role to 5 users, it does successfully fire 5 created (or deleted) events for the pivot table.
$roleModerator->users()->attach($anArrayOfFiveUsersHere);
So far so good.
My problem is the following: when I delete the moderator role itself, it does delete all pivot rows associated to the role, but it does not fire any deleted event for each deleted pivot rows.
Expected behavior: I want Laravel to fire deleted events for each rows it deletes in the pivot table when I ask it to delete the role.
Environment: PHP 7.3 / Laravel 6
One weird thing I noticed, if I add this to my Role model :
public static function boot()
{
parent::boot();
static::deleting(function (self $model)
{
//$model->users()->detach(); // <-- this fails firing deleted events.
//MyCustomPivot::query()->where('role_id', $model->id)->get()->each->delete(); // <-- this fails firing deleted events.
$model->users()->sync([]); // <--- this works!
});
}
sync([]) will work great and fire as many deleted events as it deletes pivot rows.
but detach(), although it accomplishes the same thing, won't fire any deleted event. Why is that? They are both from InteractisWithPivotTable.php and sync() does even call detach() itself!
Not 100% sure it's applicable to your situation, but according to this issue on Github, you need to do some setup in your models.
First, make sure you have a primary key column in your table and defined on your pivot model.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
class MyCustomPivot extends Pivot
{
public $primaryKey = "id";
public $incrementing = true;
}
Second, make sure you include your custom pivot model in your relationships.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
public function users()
{
return $this->belongsToMany(User::class)
->using(MyCustomPivot::class)
->withPivot('id');
}
}
class User extends Model
{
public function roles()
{
return $this->belongsToMany(Role::class)
->using(MyCustomPivot::class)
->withPivot('id');
}
}

Intermediate Tables & Laravel

I'm kind of new to the Eloquent (Pivot / Intermediate Tables) idea.
I am using Laravel 5.3 and the docs are making a little sense, but not enough. Unfortunatley!
I have a few scenarios that I'd like to try get data from...
I have the following DB Tables
Companies
Company_Offers
Offers
Company_Attributes
Attributes
In my scenarios the following is said of these DB Tables...
A company can have many offers
A company can have many attributes
An Offer can be associated with many companies
An attribute can be associated with many companies
I have created the 5 models to correspond to the 5 DB Tables.
I am trying to work out, How I get these relationships into my models?
Thank You!
You want to use the belongsToMany relationship. For example:
class Company extends Model
{
public function offers()
{
return $this->belongsToMany(App\Offer::class);
}
}
If you have setup your pivot table with company_id and offer_id this relationship will work automatically, and a pivot table of company_offer (singular version of model name in alphabetical order). If you didn't follow the naming convention you can specify the pivot table and foreign keys like so:
return $this->belongsToMany('App\Offer', 'Company_Offers', 'Company_ID', 'Offer_ID');
Actually in laravel you don't have to create models for pivot tables. So you are down to three models that will look more less like this:
<?php
/* /app/Company.php */
namespace App;
use Illuminate\Database\Eloquent\Model;
class Company extends Model
{
/**
* The offers that belong to the company.
*/
public function offers()
{
return $this->belongsToMany('App\Offer');
}
/**
* The attributes that belong to the user.
*/
public function attributes()
{
return $this->belongsToMany('App\Attribute');
}
}
<?php
/* /app/Offer.php */
namespace App;
use Illuminate\Database\Eloquent\Model;
class Offer extends Model
{
public function companies()
{
return $this->belongsToMany('App\Company');
}
}
<?php
/* /app/Attribute.php */
namespace App;
use Illuminate\Database\Eloquent\Model;
class Attribute extends Model
{
public function companies()
{
return $this->belongsToMany('App\Company');
}
}
More on how to use it to select or update those relations you can find here:
https://laravel.com/docs/5.3/eloquent-relationships#many-to-many

What is the difference between BelongsTo And HasOne in Laravel

Can any body tell me what is the main difference between
the BelongsTo and HasOne relationship in eloquent.
The main difference is which side of the relation holds relationship's foreign key. The model that calls $this->belongsTo() is the owned model in one-to-one and many-to-one relationships and holds the key of the owning model.
Example one-to-one relationship:
class User extends Model {
public function car() {
// user has at maximum one car,
// so $user->car will return a single model
return $this->hasOne('Car');
}
}
class Car extends Model {
public function owner() {
// cars table has owner_id field that stores id of related user model
return $this->belongsTo('User');
}
}
Example one-to-many relationship:
class User extends Model {
public function phoneNumbers() {
// user can have multiple phone numbers,
// so $user->phoneNumbers will return a collection of models
return $this->hasMany('PhoneNumber');
}
}
class PhoneNumber extends Model {
public function owner() {
// phone_numbers table has owner_id field that stores id of related user model
return $this->belongsTo('User');
}
}
BelongsTo is a inverse of HasOne.
We can define the inverse of a hasOne relationship using the belongsTo method.
Take simple example with User and Phone models.
I'm giving hasOne relation from User to Phone.
class User extends Model
{
/**
* Get the phone record associated with the user.
*/
public function phone()
{
return $this->hasOne('App\Phone');
}
}
Using this relation, I'm able to get Phone model data using User model.
But it is not possible with Inverse process using HasOne. Like Access User model using Phone model.
If I want to access User model using Phone, then it is necessary to add BelongsTo in Phone model.
class Phone extends Model
{
/**
* Get the user that owns the phone.
*/
public function user()
{
return $this->belongsTo('App\User');
}
}
You can refer this link for more detail.
One-to-one relationship: You, as a User, can have one (hasOne) Profile. And of course the inverse also applies. Profile (belongsTo) a User. A user can't have more than one profile and a profile can't belong to multiple users.
If you want to make One TO one relationship between two table then first you have to make "hasOne" Relation and If you want to make inversely table relationship then you make " "Belongs to"... IT is a simple difference between HasOne and Belongs to the relationship if you want to know about this
One To Many (Inverse)
Now that we can access all of a post's comments, let's define a relationship to allow a comment to access its parent post. To define the inverse of a hasMany relationship, define a relationship function on the child model which calls the belongsTo method:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
Here you can see a good example and see what the difference is between BelongsTo and HasOne relationship in eloquent.
Eloquent Relationships Cheat Sheet by Mahmoud Zalt https://link.medium.com/9lj9BAG8lR

Any issue with having two public functions, in different models, but with the same name?

I'm writing a web app using the Laravel framework. I have two models where I have created a public function, using the same name, to form an Eloquent Relationship. I want to know if this is bad practice, or if it will cause me any problems.
Here is my code for my WorkOrder model:
/**
* Get the aircraft that owns the work order.
*/
public function aircraft()
{
return $this->belongsTo(Aircraft::class);
}
Here is my code for my Customer model:
/**
* Get all of the aircraft for the customer.
*/
public function aircraft()
{
return $this->hasMany(Aircraft::class);
}
It's fine as is and will work with no issues, as #Ohgodwhy commented you'd want to pluralize the function name for hasMany or belongsToMany relationships. in this case however it's a moot point as aircraft is the plural of aircraft.

Laravel Eloquent can't get simple hasMany relation working

Background
I have an internationalized DB that stores its strings for different languages like this:
products
id
price
product_translation
id
product_id
language_id
name
description
languages
id
name (e.g. 'English', 'German')
code (e.g. 'en', 'de')
With appropriate Models for each table (Product, ProductTranslation, Language). In my views I want to fetch a list of products like this:
// get first 20 products, list name and price.
#foreach(Product::take(20)->get() as $product)
{{$product->translations->name}} {{$product->price}}
#endforeach
Problem
My app will return product names according to what the current App::getLocale() is set to (i.e. en and de).
I'm just starting out with Laravel's Eloquent, I'm unsure how to specify the correct relationships (or if I'm actually doing it correctly at all).
My attempt
I have specified a OneToMany relationship in between Product and ProductTranslation:
class Product extends \Eloquent {
protected $with = ['translations'];
public function translations()
{
return $this->hasMany('ProductTranslation');
}
}
This works fine but will return all the translations (we only want the current locale).
I then specify a OneToOne relationship between ProductTranslation and Language:
class ProductTranslation extends \Eloquent {
protected $with = ['language'];
public function language()
{
return $this->hasOne('Language')
->where('code', App::getLocale());
}
}
I know this doesn't work and I am stumped at what to do next. Does anyone have a cleaner approach?
class ProductTranslation extends \Eloquent {
protected $with = ['language'];
public function language()
{
return $this->hasOne('Language');
}
}
In route or controller
ProductTranslation::language()->where('code', '=', App::getLocale())->get();
To keep this in the model do this
public static function getLocale()
{
return static::language()->where('code', '=', App::getLocale())->get();;
}
Call the function using ProductTranslation::getLocale()
Laravel has built-in system for translations and with a little work you can make it work with the database, however that is probably not what it was designed for.
You can't fetch the ones you want cause relationships are based on ids (foreign keys) and not for string constraints or similar.
In your view you could look into filtering out the ones that are not for the language code you wanted using filter(): http://laravel.com/docs/4.2/eloquent#collections
Or You could consider moving the translations to the proper place as hard-coded: http://laravel.com/docs/4.2/localization and if that is not possible you could look into fetching the translations from database still using the method described by that link. Eventually it just returns the array of the translations and does not care how did you build the array, hard-coded or from database.

Categories