Laravel package base relationship models - php

in our web application we want to have a simple package base cms, for having that we have users, packages, features and user_package table on database
each users can be have one package which we created
user_package for that
each user_package belongs to many users
each user_package has on packages
each packages can be have many features which we created
features table
each features belongs to many packages
when i try to get user package it, i think it should be:
user->user_package->package->[feature]
my models:
class User extends Authenticatable
{
//...
public function user_package()
{
return $this->hasOne(UserPackage::class);
}
}
class UserPackage extends Model
{
public function package()
{
return $this->belongsTo(Package::class);
}
}
class Package extends Model
{
public function feature(): HasMany
{
return $this->hasMany(Features::class);
}
}
class Features extends Model
{
public function package(): BelongsToMany
{
return $this->belongsToMany(Package::class);
}
}
migrations:
Schema::create('packages', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description')->nullable()->default('');
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent();
});
Schema::create('features', function (Blueprint $table) {
$table->id();
$table->foreignId('packages_id')->nullable()
->constrained()->cascadeOnUpdate()
->cascadeOnDelete();
$table->string('title');
$table->text('description')->nullable()->default('');
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent();
});
Schema::create('user_packages', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()
->cascadeOnUpdate()
->cascadeOnDelete();
$table->foreignId('packages_id')->nullable()->constrained()
->cascadeOnUpdate()
->cascadeOnDelete();
$table->longText('features')->nullable()->default('');
$table->integer('price');
$table->dateTime('start_date');
$table->dateTime('end_date');
$table->timestamp('created_at')->useCurrent();
$table->timestamp('update_at')->useCurrent();
});
now when i try to get data i get null in package relation ship
Route::get('/test',function(){
dd(auth()->user()->with(['user_package'=>function($query){
$query->with(['package'=>function($package){
$package->with('feature')->get();
}])->first();
}])->first());
});
output:
App\Models\User {#1319 ▼
#hidden: array:2 [▶]
#casts: array:1 [▶]
#connection: "mysql"
#table: "users"
...
#relations: array:1 [▼
"user_package" => App\Models\UserPackage {#1570 ▼
#connection: "mysql"
#table: "user_packages"
...
#dispatchesEvents: []
#observables: []
#relations: array:1 [▼
"package" => null
]
...
}

I think the crux of your question comes down to fetching nested relationships, which is as simple as auth()->user()->with('user_package.package.feature') but there is a problem with your relationships.
Your relationship between Package and Feature is broken; since the features table has a package_id column, a feature by definition cannot "belong to many" packages.
class User extends Authenticatable
{
public function user_package(): HasOne
{
return $this->hasOne(UserPackage::class);
}
}
class UserPackage extends Model
{
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function package(): BelongsTo
{
return $this->belongsTo(Package::class);
}
}
class Package extends Model
{
public function features(): HasMany
{
return $this->hasMany(Feature::class);
}
/**
* It never hurts to define both ends of the relationship
* even if they aren't being used
*/
public function user_packages(): HasMany
{
return $this->hasMany(UserPackage::class);
}
}
class Feature extends Model
{
public function package(): BelongsTo
{
return $this->belongsTo(Package::class);
}
}
Now, using the relationship, you can get the package features:
dump(Auth::user()->load('user_package.package.features'))
You've got some naming problems that I corrected in my examples above – relationship methods that return a single model should be singular, others should be plural. Class names should never be plural words (i.e. Feature not Features.)
Generally speaking, there's no need to create a pivot class if all it's doing is connecting two models. I'm not going to get into that since it would make for a much longer answer, but it's something to keep in mind.
And it's a matter of personal taste, but your class names should be one word only (i.e. Subscription instead of UserPackage) as it makes figuring out things like relationship names more intuitive.

Related

Laravel's eloquent relationships and MySQLs foreign keys - belongsTo not resulting in a relationship

I think I'm having a misunderstanding of Laravel's eloquent relationships (version 7).
Main question is: Is a MySQL foreign-key required for them to work? And is an FK required both ways?
Situation: I have users, I have accounts. Each user has one account, each account belongs to one user.
Users migration:
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table)
{
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('password');
$table->rememberToken();
$table->timestamp('updated_at');
$table->timestamp('email_verified_at')->nullable();
$table->timestamp('created_at')->useCurrent();
});
}
}
Accounts migration:
class CreateAccountsTable extends Migration
{
public function up()
{
Schema::create('accounts', function (Blueprint $table)
{
$table->id('owner');
$table->bigInteger('currency');
$table->integer('currencyGenerators');
$table->foreign('owner')->references('id')->on('users');
$table->timestamp('updated_at');
$table->dateTime('lastResourceUpdate')->nullable()->useCurrent();
$table->timestamp('created_at')->useCurrent();
});
}
}
Thus I have a MySQL FK accounts.owner referencing users.id.
User Model:
class User extends Authenticatable
{
...
public function account(): HasOne
{
return $this->hasOne(Account::class, 'owner');
}
}
Account Model:
class Account extends Model
{
...
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'id');
}
}
I can retrieve the user's account:
$users = User::all();
dd($users[0]);
App\User {#1030 ▼
#primaryKey: "id"
...
#relations: array:1 [▼
"account" => App\Account {#1185 ▶}
]
...
But I cant get the account's user:
$accounts = Account::all();
dd($accounts[0]);
App\Account {#1241 ▼
#primaryKey: "owner"
...
#relations: array:1 [▼
"user" => null
]
...
Do I need an FK in my users table referencing account's owner? (Because Laravel's documentation mentions return $this->hasOne('App\Phone', 'foreign_key'); on https://laravel.com/docs/7.x/eloquent-relationships#introduction.
Or what is my issue here?
You have to use 'owner' column in Account model
return $this->belongsTo(User::class, 'owner','id');

Limiting retrieved columns when using withPivot on belonsToMany relationship

I have a model called Shifts with a belongsToMany relationship to a shift_employee table that acts as a pivot table to record applications for employees to shifts. I also have a scope so that I can return applications with shift objects. Here is part my Shift model:
class Shift extends Model
{
//
use SoftDeletes;
use \App\Http\Traits\UsesUuid;
protected $guarded = [];
public function applications()
{
return $this->belongsToMany(Employee::class, 'shift_employee')->as('application')->withTimestamps()->withPivot('shortlisted');
}
...
public function scopeWithApplications($query)
{
$query->with('applications');
}
...
}
My shift_employee pivot table is pretty simple and the structure is shown below. I have one extra field to determine if an application has been shortlisted:
Schema::create('shift_employee', function (Blueprint $table) {
$table->primary(['employee_id', 'shift_id']);
$table->uuid('employee_id');
$table->uuid('shift_id');
$table->boolean('shortlisted')->default(false);
$table->timestamps();
$table->foreign('employee_id')
->references('id')
->on('employees');
$table->foreign('shift_id')
->references('id')
->on('shifts')
->onDelete('cascade');
});
Below is my API show function for retrieving shift info:
public function show($id)
{
$shift = Shift::where('id', $id)
->with...()
->withApplications()
->with...()
->first();
return response([
'shift' => $shift,
]);
}
This is the response that I'm getting:
"shift": {
"id": "2b91f55b-c0ff-4bdb-abc4-02604ba6a161",
"some_field": "some_value",
...
"applications": [
{
some_field: "some_value",
...
application: {
shift_id: "2b91f55b-c0ff-4bdb-abc4-02604ba6a161",
employee_id: "some_uuid",
created_at: ...,
updated_at: ...,
shortlisted: 0
}
},
{
...
}
]
...
}
What I want to do, is to replace the whole "application" inner object with only the field "shortlisted" from the pivot table so that it looks like this:
"shift": {
"id": "2b91f55b-c0ff-4bdb-abc4-02604ba6a161",
"some_field": "some_value",
...
"applications": [
{
some_field: "some_value",
...
shortlisted: 0
}
},
{
...
}
]
...
}
How can I do that? Ideally an eloquent call to something like withPivot but that excludes other fields and does not return an object. I couldn't find it in the docs, but does something like that exist?
i think that the most straightforward way is to make independent relation based on the pivot table using pivot model:
class ShiftEmployee extends Pivot
{
protected $table='shift_employee';
}
now the new relation in Shift Model:
class Shift extends Model
{
public function shortlistedApplications()
{
return $this->hasMany(ShiftEmployee::class,'shift_id');
}
public function scopeWithShortlistedApplications($query)
{
$query->with('shortlistedApplications:shift_id,shortlisted');
}
}
now this new scope would bring the data you want
What I think you need is to only load the shortlisted attribute of your employee's application in your scopeWithApllications:
public function scopeWithApplications($query)
{
$query->with('applications.application:id,shortlisted');
}
This will still return an Application instance as a relationship, but will only load it's shortlisted attribute. Then, after retrieval, you can map your collection in order to merge the application's attribute to your employee, if that's really important. But in terms of data shortage, this will do the trick.
In your application model use withPivot method. Like this:
public function applications(){
return $this->belongsToMany('App\Application')
->withPivot('shortlisted')
->withTimestamps();}
You can use this link for more clear example
https://laraveldaily.com/pivot-tables-and-many-to-many-relationships/

Associating multiple single instances of the same model in Laravel

I am working on a project in which there are events, which each relate to two single forms on two separate relations – booking and survey. These forms are identically constructed, making it seem unnecessary to use two entirely distinct form models – I instead wanted to use a polymorphic relation, but it appears that isn't possible.
What is the appropriate way to structure this relationship?
Events have one or no booking form
Events have one or no survey form
Forms are a separate, single table
What I have tried:
Polymorphic relationship: Not compatible with two relations to the same model.
Has one relationship: This used a booking_id and survey_id but refused to set either of these fields.
Has many relationship with a type field: Made it difficult to easily save the forms, as it wasn't possible to save to the single relationship. There was also no restriction on the number of forms.
class Event extends Model
{
public function booking()
{
return $this->hasOne(Form::class, 'id', 'booking_form_id');
}
public function survey()
{
return $this->hasOne(Form::class, 'id', 'survey_form_id');
}
}
...
class Form extends Model
{
public function event()
{
return $this->belongsTo(Event::class);
}
}
...
$event = new Event;
$event->name = 'Event';
$event->save();
$booking = new Form;
$booking->name = 'booking';
$event->booking()->save($booking);
$survey = new Form;
$survey->name = 'survey';
$event->survey()->save($survey);
...
Schema::create('events', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->unsignedInteger('booking_form_id')->nullable()->index();
$table->unsignedInteger('survey_form_id')->nullable()->index();
$table->timestamps();
});
Schema::create('forms', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
What would be preferable:
Using a polymorphic relationship which would allow forms to be used in other parts of the application.
Using multiple hasOne relationships to limit the number of forms to one for each type.
I think you got your param order wrong. It's hasOne($related, $foreignKey, $localKey)
class Event extends Model
{
/* if you haven't changed the default primary keys, $localKey should be equal to 'id' */
public function booking()
{
return $this->belongsTo(Form::class, 'booking_form_id');
}
public function survey()
{
return $this->belongsTo(Form::class, 'survey_form_id');
}
}
class Form extends Model
{
public function booking_event()
{
return $this->hasOne(Event::class, 'booking_form_id');
}
public function survey_event()
{
return $this->hasOne(Event::class, 'survey_form_id');
}
}
Now there's 2 ways you can go about this.
If a Form can belong to both kind of events, you need to return a collection when accessing $form->event.
If a Form can belong to only one kind of event, you need to guess which kind and return the model when accessing $form->event.
# Form model
# 1. can be achieved using an accessor. Cannot be eager loaded but can be appended with the $appends Model property
public function getEventsAttribute()
{
return collect([$this->booking_event, $this->survey_event]);
}
# Form model
# 2. can be achieved using a relationship that guesses which relation it should return. Since it returns a relationship, it can be eager loaded.
public function event()
{
return ($this->booking_event()->count() != 0) ? $this->booking_event() : $this->survey_event();
}

Laravel 5.6 Eloquent Relationship not returning value

This is a section of Two of my related models in Laravel
class Employee extends Authenticatable
{
use Notifiable;
protected $primaryKey = 'employee_id';
public $incrementing = false;
public function branch(){
return $this->belongsTo('App\Branch');
}
public function orders(){
return $this->hasMany('App\Order');
}
}
class Branch extends Model
{
protected $primaryKey = 'branch_id';
public $incrementing = false;
public function employees(){
return $this->hasMany('App\Employee');
}
}
And these are the migrations of the above two models (up function only)
public function up()
{
Schema::create('branches', function (Blueprint $table) {
$table->string('branch_id')->unique();
$table->timestamps();
$table->primary('branch_id');
});
}
public function up()
{
Schema::create('employees', function (Blueprint $table) {
$table->string('employee_id')->unique();
$table->string('name');
$table->string('password');
$table->string('job');
$table->string('branch_id');
$table->foreign('branch_id')->references('branch_id')->on('branches')->onDelete('cascade');
$table->rememberToken();
$table->timestamps();
$table->primary('employee_id');
});
}
When I run php artisan tinker and test out the relationship, I get the following output:
>>> namespace App
>>> $emp = new Employee()
=> App\Employee {#778}
>>> $emp = $emp->find('CMB-EMP-001')
=> App\Employee {#787
employee_id: "CMB-EMP-001",
name: "FirstName LastName",
job: "Root",
branch_id: "CMB",
created_at: "2018-04-11 17:24:53",
updated_at: "2018-04-11 17:24:53",
}
>>> $emp->branch()->get()
=> Illuminate\Database\Eloquent\Collection {#775
all: [],
}
As you can see, it returns an empty array instead of the branch id of the employee. What have I done wrong?
EDIT:
>>> $emp->branch
=> null
You have to specify the foreign key:
class Employee extends Authenticatable
{
public function branch(){
return $this->belongsTo('App\Branch', 'branch_id');
}
}
And use $emp->branch instead of $emp->branch()->get().
Change your belongsTo() relationship
public function branch(){
return $this->belongsTo('App\Branch', 'branch_id', 'branch_id');
}
To build on Jonas's point, and to provide more clarity for future viewers:
The actual signature for defining a belongsTo relationship is
belongsTo(relatedEntity, foreignKey, ownerKey, relationModel)
At present, in your use of belongsTo, you're only defining which related entity you are defining a relationship with. What you need to do is provide details of the owner key (rather than the foreign key), so in your case:
return $this->belongsTo('App\Branch', 'branch_id', 'branch_id');
Why is this the case?
Laravel's default behaviour for determining which column to utilise is:
For the foreign key, take the related entity name and add the _id suffix. I.e. branch_id
For the owner key, default to using "id", since Laravel's convention is that a model's primary key should be called "id".
Given that in your migrations you've modified Laravel's default approach, by naming your primary key "branch_id" rather than just "id", you need to tell Laravel how to relate the two models.

Laravel 5.1 Multiple Many to Many Relationship on the Same Model

I seen to of got tangled in Laravel's ORM with the following:
Scenerio: All Users have a Watchlist, the Watchlist contains other Users.
I can't seem the get the relationships to work correctly as they are cyclical, so far I have the following:
class UserWatchlist extends Model
{
protected $table = 'UserWatchlist';
public function Owner() {
return $this->belongsTo('App\User');
}
public function WatchedUsers() {
return $this->hasMany('App\User');
}
}
Schema::create('UserWatchlist', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('Users')->onDelete('cascade');
$table->integer('watched_id')->unsigned();
$table->foreign('watched_id')->references('id')->on('Users')->onDelete('cascade');
$table->timestamps();
});
class User extends Model
{
public function Watchlist() {
return $this->hasOne('App\UserWatchlist');
}
public function WatchedBy() {
return $this->belongsToMany('App\UserWatchlist');
}
}
It is not pulling through the correct in formation i'm expecting. Am I missing something fundamental?
Since UserWatchlist is a pivot table, i suppose you are facing a many to many relationship with both the elements of the relation being the same model (User)
If that is the case, you should not build a model for the pivot table UserWatchlist but all you have to do is to set the relation between the users through the pivot table:
class User extends Model
{
//get all the Users this user is watching
public function Watchlist()
{
return $this->belongsToMany('User', 'UserWatchlist', 'user_id', 'watched_id' );
}
//get all the Users this user is watched by
public function WatchedBy()
{
return $this->belongsToMany('User', 'UserWatchlist', 'watched_id', 'user_id' );
}
}
Check here for more info on many-to-many relationship

Categories