Laravel, many-to-many relationship to multiple models - php

Looking for a simple way of doing the following in Laravel 5.
Imagine these models and interface
Competition
User
Team
Participant (Interface which User and Team implements)
A competition will have a many-to-many relationship to is participants, which can be either users or teams. So I need the competition_participant pivot table to have the following columns to define the models
competition_id
participant_id
participant_type
But how do I write a relation on the Competition model, so it knows which model it should fetch from the database, and at the same time return a collection of mixed models or of the Interface type?

You had some confusions about which tables should get which columns.
Your participants table needs to be a table for managing the polymorphic relations of users and teams. It receives a particpatable_id and particpatable_type as well as an id and timestamps() at the very minimum.
Then you need an additional table called competition_participant which will manage the belongs to many between competitions and your polymorphic table participants.
This will allow you to relate your participants to your competitions. This way, you can grab all participants by a competition, call the $participant->participatable attribute on it which would return either an App\User or an App\Team depending on what type the participant is.
This is all tested.
Migrations
public function up()
{
Schema::create('competitions', function(Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
Schema::create('teams', function(Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
Schema::create('participants', function(Blueprint $table) {
$table->increments('id');
$table->integer('participatable_id');
$table->string('participatable_type');
$table->timestamps();
});
Schema::create('competition_participant', function(Blueprint $table) {
$table->integer('competition_id');
$table->integer('participant_id');
});
}
Models
class Competition extends Model
{
public function participants()
{
return $this->belongsToMany(Participant::class);
}
}
class Participant extends Model
{
public function participatable()
{
return $this->morphTo();
}
public function competitions()
{
return $this->belongsToMany(Competition::class);
}
}
class Team extends Model
{
public function participants()
{
return $this->morphMany(Participant::class, 'participatable');
}
}
class User extends Authenticatable
{
public function participants()
{
return $this->morphMany(Participant::class, 'participatable');
}
}
Seeds
public function run()
{
$faker = Faker\Factory::create();
// Seed Users Table
DB::table('users')->delete();
$users = [];
for($i = 0; $i < 100; $i++) {
$users[] = [
'name' => $faker->name,
'email' => $faker->email,
'password' => Hash::make($faker->password),
'created_at' => new DateTime,
'updated_at' => new DateTime
];
}
DB::table('users')->insert($users);
// Seed Teams Table
DB::table('teams')->delete();
$teams = [];
for($i = 0; $i < 20; $i++) {
$teams[] = [
'name' => 'Team ' . ucwords($faker->domainWord),
'created_at' => new DateTime,
'updated_at' => new DateTime
];
}
DB::table('teams')->insert($teams);
// Seed Participants Table
DB::table('participants')->delete();
// Insert some of our users as participants
$users = App\User::limit(20)->orderByRaw('rand()')->get();
foreach($users as $user) {
$user->participants()->create([]);
}
// Insert some of the teams as participants
$teams = App\Team::limit(10)->orderByRaw('rand()')->get();
foreach($teams as $team) {
$team->participants()->create([]);
}
// Seed Competitions Table
DB::table('competitions')->delete();
$competitions = [];
for($i = 0; $i < 10; $i++) {
$competitions[] = [
'name' => $faker->company,
'created_at' => new DateTime,
'updated_at' => new DateTime,
];
}
DB::table('competitions')->insert($competitions);
// Seed Competitions Participants Relationships
DB::table('competition_participant')->delete();
// Sign up each participant to 3 random competitions
$participants = App\Participant::all();
$competitions = App\Competition::all();
foreach($participants as $participant) {
$participant->competitions()->sync($competitions->shuffle()->take(3));
}
}
Usage
$competition = App\Competition::with('participants')->has('participants')->first();
foreach($competition->participants as $participant) {
echo get_class($participant->participatable); // Will output either App\User or App\Team
echo "<br />";
}

This is just a copy paste from the Laravel documentation about many to many polymorphic relationships.
To help you understand better this is the mapping between your models and the models that are on the Laravel documentation:
users == posts
teams == videos
competitions == tags
competitionables == tags
This how you should implement your polymorphic relationship, remember whenever you feel confused just look at the documentation and the mapping between your models and the documention models:
Tables:
users
id - integer
name - string
teams
id - integer
name - string
competitions
id - integer
name - string
competitionables
competition_id - integer
competitionable_id - integer
competitionable_type - string
Model Structure
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Get all of the Competition for the user.
*/
public function competitions()
{
return $this->morphToMany('App\Competition', 'competitionable');
}
}
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Team extends Model
{
/**
* Get all of the Competition for the Team.
*/
public function competitions()
{
return $this->morphToMany('App\Competition', 'competitionable');
}
}
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Competition extends Model
{
/**
* Get all of the users that are assigned to this Competition.
*/
public function users()
{
return $this->morphedByMany('App\User', 'competitionable');
}
/**
* Get all of the Teams that are assigned to this Competition.
*/
public function teams()
{
return $this->morphedByMany('App\Video', 'competitionable');
}
}
Retrieving The Relationship
$user = App\User::find(1);
foreach ($user->competitions as $competition) {
//
}
or the other way
$competition = App\Competition::find(1);
foreach ($competition->users as $user) {
// do something with your users
}
foreach ($competition->teams as $team) {
// do something with your teams
}

Related

Join two database tables in Laravel controller

I have two tables that I want to join in the controller - thought_journal_entries and emotions. A thought journal entry can contain many emotions and the foreign key in the thought_journal_entries table is em_id.
This is an example thought journal entry where the user selected emotions with id 1, 3, 5
This is the emotions table
This is the method I'm using to store data within my thought_journal_entries table
public function store(Request $request)
{
$this->validate($request, [
'thought_entry' => 'required'
]);
$entry = new ThoughtJournalEntry;
$entry->user_id = auth()->user()->id;
$entry['entry_date'] = date('Y-m-d H:i');
$entry->thought = $request->input('thought_entry');
$entry->em_id = $request->has('emotions') ? $request->get('emotions') : [];
$entry->tt_id = $request->has('thinking_traps') ? $request->get('thinking_traps') : [];
$entry->balanced_thought = $request->input('balanced_thought');
$entry->save();
return redirect('/dashboard');
}
In your example em_id column it's not a foreign key, it's a string column as I see.
Therefore, you can't execute a JOIN query for these tables. In your case, I can recommend create a third table thought_journal_entry_emotions.
Here example of code for migration file 2020_02_29_143059_create_thought_journal_entry_emotions_table.php:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateThoughtJournalEntryEmotionsTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('thought_journal_entry_emotions', function (Blueprint $table) {
$table->integer('thought_journal_entry_id')->unsigned();
$table->integer('emotion_id')->unsigned();
$table->foreign('thought_journal_entry_id')
->references('id')
->on('thought_journal_entries')
->onUpdate('cascade')
->onDelete('cascade');
$table->foreign('emotion_id')
->references('id')
->on('emotions')
->onUpdate('cascade')
->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('thought_journal_entry_emotions');
}
}
Then you have to add relationships to your models Emotion and ThoughtJournalEntry.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Emotion extends Model
{
public function thoughtJournalEntries() {
return $this->belongsToMany(ThoughtJournalEntry::class, 'thought_journal_entry_emotions',
'emotion_id', 'thought_journal_entry_id');
}
}
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class ThoughtJournalEntry extends Model
{
public function emotions() {
return $this->belongsToMany(Emotion::class, 'thought_journal_entry_emotions',
'thought_journal_entry_id', 'emotion_id');
}
}
After that you can attach Emotions to ThoughtJournalEntry in your controller using this code:
$thoughtJournalEntry = ThoughtJournalEntry::find(1);
$emotion1 = Emotion::find(1);
$emotion2 = Emotion::find(2);
$emotion3 = Emotion::find(3);
$thoughtJournalEntry->emotions()->sync([$emotion1->id, $emotion2->id, $emotion3->id]);
And finally you can load your ThoughtJournalEntry with Emotions in your controller using this code:
$thoughtJournalEntry = ThoughtJournalEntry::with('emotions')->find(1);
dd($thoughtJournalEntry);
If you wanna validate and store Emotions relations you must update your store() method (add new validate rule and sync()).
Here example:
public function store(Request $request)
{
$this->validate($request, [
'thought_entry' => 'required',
'emotions' => 'array|max:3',
'emotions.*' => 'exists:emotions,id'
]);
$entry = new ThoughtJournalEntry;
$entry->user_id = auth()->user()->id;
$entry['entry_date'] = date('Y-m-d H:i');
$entry->thought = $request->input('thought_entry');
$entry->tt_id = $request->has('thinking_traps') ? $request->get('thinking_traps') : [];
$entry->balanced_thought = $request->input('balanced_thought');
$entry->save();
$entry->emotions()->sync($request->get('emotions'));
return redirect('/dashboard');
}
Joining the table will be a little trickier since the reference value isn't present.
But if you trying to get the names of emotions from using the id stored in the array.
You will need to first save the emotions array in a variable.
$em = ["1","3","5"]
$em = ["1","3","5"];
foreach ($em as $e) {
$emotions = Emotions::find($e * 1); //am using * 1 just make sure its int
$emotions->em_name;
}
I hope that helps.
this should done using Many to Many Relationships, you are using string to store array(thats not mysql way). (but looks like you are going to save space in 'thought_journal_entries')
you can use like this:
$journal_entries = thought_journal_entries::find(1);
$icon_ids = json_decode($journal_entries->em_id); // if this column is json
$emocions = emotions::whereIn('id', $icon_ids)->get();
but this executing two quarries, that may affect db and server performance

Laravel Why pivot table is empty?

I have a simple relationship in laravel eroquent
Here is the bidders table creation
public function up()
{
Schema::create('bidders', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('params_name');
$table->string('params_value');
$table->string('bidder_name');
$table->timestamps();
});
}
Here is bidder_parameter table creation
public function up()
{
Schema::create('bidder_parameters', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('bidder_id');
$table->foreign('bidder_id')->references('id')->on('bidders')->onDelete('cascade');
$table->timestamps();
});
}
Here is a bidder model
class Bidder extends Model
{
protected $table = 'bidders';
protected $fillable = [
"params_name",
"params_value",
"bidder_name"
];
public function page()
{
return $this->hasMany('App\Page');
}
public function parameters()
{
return $this->hasMany('App\BidderParameter');
}
}
and here is BidderParameter model
class BidderParameter extends Model
{
public function parameters()
{
return $this->belongsTo('App\Bidder');
}
}
Here is parameter controller for inserting data to database
public function store(Request $request){
// dd($request);
if($request->ajax())
{
$rules = array(
'params_name.*' => 'required',
'params_value.*' => 'required',
'bidders_name.*' => 'required'
);
$error = Validator::make($request->all(), $rules);
if($error->fails())
{
return response()->json([
'error' => $error->errors()->all()
]);
}
$params_name = $request->params_name;
$params_value =$request->params_value;
$bidders_name =$request->bidders_name;
for($count = 0; $count < count($params_name); $count++)
{
$data = array(
'params_name' => $params_name[$count],
'params_value' => $params_value[$count],
'bidders_name' => $bidders_name[$count],
);
$insert_data[] = $data;
// dd($insert_data);
}
Bidders:insert($insert_data);
return response()->json([
'success' => 'Data Added successfully.'
]);
}
}
Now when I submit data to the database 'bidders tablehave data saved into it butbidder_parameter` is empty
What is wrong with my code?
Many to many relationship contains 3 tables. 2 tables are main and 3rd table is combination of those 2 tables' primary keys.
For example
User can have many roles
and
Role can belong to many users
so that requires many to many relationship
So we need a database schema like this:
Users table -> (id, name ,...) e.g. Jonn Doe with id 1
Roles table -> (id, name, ...) e.g. SUPER_ADMIN with 1
role_user table (id, user_id, role_id) role_id is foreign key of roles table and user_id is foreign key of users table
Now in Model classes:
In User Model
public function roles(){
return $this->belongsToMany(Role::class,'role_user','user_id','role_id');
}
Now in Roles class
public function users(){
return $this->belongsToMany(User::class,'role_user','role_id','user_id');
//note keys are in opposite order as in roles() method
}
Now you can call function from User instance and Role instance where you want
$user->roles // as collection
$user->roles() // as eloquent instance
Also
$role->users // as collection
$role->users() // as eloquent instance
You can read more here

Laravel 5.7 - Get all users from current user groups

This this the structure that I currently use
Model User :
class User extends Authenticatable implements MustVerifyEmail
public function groups()
{
return $this->belongsToMany('App\Group')
->withTimestamps();
}
Model Group :
class Group extends Model
public function users() {
return $this->belongsToMany('App\User')
->withTimestamps();
}
Pivot table :
Schema::create('group_user', function (Blueprint $table) {
$table->integer('group_id');
$table->integer('user_id');
$table->integer('role_id')->nullable();
$table->timestamps();
});
I would like to get all the users who are in same groups of the current user and don't return duplicate users (a user can be in multiple groups).
The ideal functions would be :
$user->contacts()
// Return an object with (unique) contacts of all current user groups
AND
$user->groupContacts($group)
// Witch is the same as $group->users
// Return an object with all the users of the current group
There is the not functional function i'm working on (Model User.php) :
public function contacts()
{
$groups = $this->groups;
$contacts = new \stdClass();
foreach ($groups as $key => $group) :
$contacts->$key = $group->users;
endforeach;
return $contacts;
}
I'm really not an expert with table structures so if there is a better way to do this, i'm only at the beginning of this personnal project so nothing is written in stone.
Optional stuffs :
Exclude current user from the list
With roles from pivot table
With softdelete
You can try something like this on your User model
public function contacts()
{
// Get all groups of current user
$groups = $this->groups()->pluck('group_id', 'group_id');
// Get a collection of all users with the same groups
return $this->getContactsOfGroups($groups);
}
public function groupContacts($group)
{
// Get a collection of the contacts of the same group
return $this->getContactsOfGroups($group);
}
public function getContactsOfGroups($groups)
{
$groups = is_array($groups) ? $groups : [$groups];
// This will return a Collection Of Users that have $groups
return \App\User::whereHas('groups', function($query) use($groups){
$query->whereIn('group_id', $groups);
})->get();
}

Laravel Defining Relationships

I have one table named Content which is a master table like
Content : id content_name created_at updated_at
and another table Course like
Course table have many content_id
Course : id content_id course_name created_at updated_at
I have created relation like this.
Content Model
class Content extends Eloquent {
protected $table = 'contents';
protected $guarded = array('id');
public function course()
{
return $this->belongsTo('Course');
}
}
Course Model
class Course extends Eloquent {
protected $table = 'courses';
protected $guarded = array('id');
public function content()
{
return $this->hasMany('Content');
}
}
When i am fething the data like this
$courses=Course::find(1)->content;
It throws error like
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'contents.course_id' in 'where clause' (SQL: select * from contents where contents.course_id = 1)
I am unable to rectify the problem in relations as I am new to laravel.
Close, but you have your relationships backwards. The table that has the foreign key is the one that belongsTo the other one. In this case, your course table has the foreign key content_id, therefore Course belongs to Content, and Content has one or many Courses.
class Content extends Eloquent {
public function course() {
return $this->hasMany('Course');
}
}
class Course extends Eloquent {
public function content() {
return $this->belongsTo('Content');
}
}
in your migrations(create_courses_table) , make sure to set the foreign key like that ,
$table->integer('content_id)->unsigned()->index();
$table->foreign('content_id')
->references('id')
->on('contents')
->onDelete('cascade');
I don't really understand your table design, maybe it should be something like this.
Taking your statement: "Course table have many content_id". I perceive that you are saying that 1 course can have multiple content, is that right? If yes, you might want to change your table design to something like below
Course
======
id
course_name
created_at
updated_at
Content
=======
id
course_id (set this as FK)
content_name
created_at
updated_at
migration code for content
public function up()
{
Schema::create('content', function(Blueprint $table)
{
$table->engine = 'InnoDB';
$table->increments('id');
$table->integer('course_id')->unsigned();
$table->string('content_name');
});
Schema::table('content',function($table)
{
$table->foreign('course_id')->references('id')->on('course')->onDelete('cascade');
});
}
Then in your model
class Course extends Eloquent
{
protected $table = 'course';
public function content()
{
return $this->hasMany('content', 'course_id', 'id');
}
}
class Content extends Eloquent
{
protected $table = 'content';
public function course()
{
return $this->belongsTo('course', 'course_id', 'id');
}
}
Then to access your data via eager loading
$course = Course::with('content')->get();
OR
$content = Content::with('course')->get();
This is about determining associations. Your associations should be:
Content
Content has many courses
class Content extends Eloquent {
protected $table = 'contents';
protected $guarded = array('id');
public function courses()
{
return $this->hasMany('Course');
}
}
Course
The course belongs to content.
class Course extends Eloquent {
protected $table = 'courses';
protected $guarded = array('id');
public function content()
{
return $this->belongsTo('Content');
}
}
So you can do query association.
For finding content -> courses:
$courses = Content::find(1)->courses;
For finding course -> content:
$content = Course::find(1)->content;

Laravel save method taking wrong table name

Hi all i am trying to create a campaign. In the process of campaign i will assign certain products to that campaign. So i have to insert details after post campaign creation into two tables one is campaigns and another one is campaignsproducts. So i created two models for each of them on the same names.
I am trying to insert records into both tables on post action using save method. I am able to insert into campaigns but when it comes to campaignsproducts it says campaigns_products table not exists.
In my db my table name was CampaignsProducts. Please help where i am going wrong. Please find my migration, model and post action code below.
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCamapignproductsTable extends Migration {
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('campaignproducts', function(Blueprint $table)
{
$table->increments('id');
$table->integer('campaign_id')->unsigned();
$table->integer('product_id')->unsigned();
$table->decimal('product_sell_cost', 10, 2);
$table->timestamps();
});
Schema::table('campaignproducts', function($table) {
$table->foreign('campaign_id')->references('id')->on('campaigns');
$table->foreign('product_id')->references('id')->on('products');
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('campaignproducts');
}
}
Model CampaignProducts.php
<?php
class CampaignProducts extends Eloquent {
public function camapigns(){
return $this->hasMany('Campaign');
}
public function products(){
return $this->hasMany('Product');
}
}
Post action in controller
public function postCampaign()
{
//validation rules for create product
$rules = array(
'campaign_name' => 'required|min:2',
'campaign_description' => 'required|min:2',
'campaign_startdate' => 'required|date_format:"Y-m-d"',
'campaign_enddate' => 'required|date_format:"Y-m-d"',
'campaign_urlname' => 'required|between:4,20',
'campaign_target' => 'required|integer|min:1',
'user_id' => 'required|integer|min:1'
);
$validator = Validator::make(Input::all(), $rules);
//procee the validation rules
if($validator->fails()) {
return Redirect::to('products/newcampaign')
->withErrors($validator)
->withInput();
} else {
echo "<pre>";
print_r(Input::all());
//store category data
$campaign = new Campaign;
$campaign->campaign_name = Input::get('campaign_name');
$campaign->campaign_description = Input::get('campaign_description');
$campaign->campaign_startdate = Input::get('campaign_startdate');
$campaign->campaign_enddate = Input::get('campaign_enddate');
$campaign->campaign_urlname = Input::get('campaign_urlname');
$campaign->campaign_target = Input::get('campaign_target');
$campaign->user_id = Input::get('user_id');
$campaign_id = $campaign->save();
$campaign_products = Input::get('productid');
$campaignproducts = new CampaignProducts;
foreach($campaign_products as $key => $id)
{
$campaignproducts->product_id = $key;
$$campaignproducts->product_sell_cost = $id;
$campaignproducts->campaign_id = $campaign_id;
$campaignproducts->save();
}
//redirect
Session::flash('message', 'Successfully created campaign!');
return Redirect::to('campaigns');
}
}
Add this to your CampaignProducts model:
class CampaignProducts extends Eloquent {
protected $table = 'campaignsproducts';
...
Or the other option is to change your table name when you create it instead:
Schema::create('campaign_products', function(Blueprint $table)

Categories