How to setup has and belongs to many (HABTM) association between two models? - models

I am new to cake and I am facing one problem related with association.
I am having three tables: users, messages and messages_users and I am using HABTM relation for this in message model as:
var $hasAndBelongsToMany = array(
'Users' => array (
'className' => 'User',
'join_table' => 'messages_users',
'foreignKey' => 'message_id',
'associationForeignKey' => 'reciever_id',
'conditions'=> array('MessagesUser.reciever_id' => '$this->Session->read("Id")')
)
);
My problem is when any user will login,he must be able to see the list of messages he got along with the senders name in the table.
But for me this relation is fetching only messages_users data,
My controller is like this,
<?php
class MessagesController extends AppController {
var $name = 'Messages';
//var $scaffold;
function home($id = null)
{
//$this->Message->id=$id;
//$messages_users = $this->Message->MessagesUser->find('all',array('conditions'=>array('MessagesUser.reciever_id'=>$this->Session->read("Id"))));
$messages_users = $this->Message->MessagesUser->find('all',array('fields'=>array('id','name')));
// $this->set(compact('tags'));
print_r($messages_users);
$this->set('messages_users',$messages_users);
$i=0;
foreach ($messages_users as $messages_user):
$new = $messages_users[$i]['MessagesUser']['sender_id'];
$i++;
print_r($new);
$messages = $this->Message->Users->find('all',array('conditions'=>array('Users.id'=>$new)));
// print_r($messages);
$this->set('messages',$messages);
endforeach;
}
} ?>
So please anybody help me how to get the result for this.

From the CookBook:
Remember that associations are defined
'one way'. If you define User hasMany
Recipe that has no effect on the
Recipe Model. You need to define
Recipe belongsTo User to be able to
access the User model from your Recipe
model
Have you setup the relationship in your users model to your messages model?
Setting it up like you have you'll be able to access users from the messages model but not messages from the user model.
Reference The CookBook Models section on Has And Belongs To Many for more details.

Related

How to save associations on an instantiated object

How do I associate other associations before saving "parent"? I have a car that have many other parts:
A car has many seats
A car has many floor mats
A car has one mirror
etc.
The thing is, if either the seats or floor mats has any defects then the car cannot be created:
$car = new Car(...);
// Many mats
$mats = [new Mat(..), new Mat(..)];
// One mirror
$mirror = new Mirror(..);
// I need to put them all together.
// This does not work
$car->saveMany([$mats, $mirror, $mats]);
// Does not work
$car->mats()->saveMany($mats);
$car->associate($mirror);
// Car should not be saved if either any of its associations have an error.
$car->save();
The docs mentioned nothing about this example when instantiating a new object then save its associations: HasMany, HasOne, BelongsTo etc
I've looked at these but cannot get my head around it:
Saving related records in laravel
Eloquent push() and save() difference
One To Many associate
How to "associate" "car's" associations by calling "save()"?
I would suggest that you look into the validation functionallities of laravel. (https://laravel.com/docs/8.x/validation)
you can make nested validations, so for example if you want to validate the seats of a car you can make rules like this:
public function store(Request $request)
{
$validated = $this->validate($request, [
'name' => 'required|string',
'model' => 'required|exists:car_models,name',
'seats' => 'required|array',
'seats.*.color' => 'required',
'seats.*.width' => 'numeric',
'seats.*.fabric' => 'required|string',
]);
// create the car with all relation data
return $car;
}
The validation could be done as shown above, or via form request validation (https://laravel.com/docs/8.x/validation#form-request-validation).
That way, you can be sure that the users input is valid and will work before any of the models are created. After that you should create the car and add all the relations after. I would however suggest that you use the eloquent relations instead, by doing that you can write something like
// Create relation model with array of data
$car->seats()->create($seatData);
// Create relation models with collection of data
$car->seats()->createMany($seatsDataCollection);

Laravel Model Relations fetch automatically all other model relations too

I'm hoping someone can help me.
I have 3 models like User, Task and Subtask.
These are already linked via hasOne or hasMany.
Everything works fine.
Now I call the data via Task::where(..)->with(['user','subtask'])... and get the corresponding results.
The problem is that Subtask has a reference to User and I don't get the user information queried when I use the task model.
If I use the subtask model I get the user information.
How can I set up that all references to the queried models are also queried simultaneously from the database?
To return more relationships data at once, you can use the following mechanism:
$Data = $Task::where(...)
->with([
'user'=>function($userQuery){
// mechanism is recursive; you can extend it to infinity :)
$userQuery->with([
'other',
'relationships',
'here'=>function($hereQuery){ ... }
]);
},
'subTask',
'anotherRelationship' => function($anotherRelationship) {
$anotherRelationship->select('column');
$anotherRelationship->where('column2', 'whatever');
}
])
->get();
// dump data
dd($Data);
I don't know if you're looking for this -- if you want to load some relationships once the model is instantiated, you can append a magic variable $with inside your model code and specify which relationships you want to load:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
protected $fillable = ['col1', 'col2', 'col3', ...];
// specify which relationships you want to load automatically
protected $with = [
'users',
'anotherRelationship'=>function($anotherRelationshipQuery){
$anotherRelationshipQuery->select( ... );
},
'andSomeOtherRelationship'
...
];
...
}
Now you no longer need to manually load the relationships when retrieving data. They're loaded automatically:
$Data = $Tast::where( ... )->get();
dd($Data);

Add data to a Laravel pivot table

I have a database with 3 tables : users, events and user_event.
I create my event. Once my event is created, I would like to add users.
How can I add him via a form to add users?
My pivot table contain :
event_id, user_id
Do I need to create a UserEventController?
My model relations :
public function users()
{
return $this->belongsToMany('User')->withPivot('user_event');
}
For create an event :
public function store(Request $request)
{
$this->validate($request, [
'course_name' => 'required|max:255',
'academic_year' => 'required|max:4|min:4',
'exam_session' => 'required',
'date_event' => 'required'
]);
$event = new Event();
$event->course_name = $request->course_name;
$event->academic_year = $request->academic_year;
$event->exam_session = $request->exam_session;
$event->date_event = $request->date_event;
$event->save();
}
Thanks for your help !
So you need to use the attach()method :
$event->users()->attach($userId);
more informations here : Laravel Eloquent - Attach vs Sync
Since the event is already created, you can just use the event object to add an existing user, create a new one, or maybe even sync a list of users.
//create a new user
$event->users()->create(['name' => 'user name', 'email' => 'user email']);
//attach existing user
$event->users()->attach($userId);
//sync multiple existing users passing array of user ids
$event->users()->sync([1,2,4]);
You can see details about all of those methods and a few more here: https://laravel.com/docs/5.7/eloquent-relationships#inserting-and-updating-related-models

Laravel 5: How to use firstOrNew with additional relationship fields

I have a CMS that allows the user to save and create bike tours. Each bike tour also has categories, which are definined using Laravel's Many to Many relationship utilising an intermediary pivot table. At the point of saving a tour, we don't know if the tour is an existing one being edited, or a new one.
I think I should be using Laravel's firstOrNew method for saving the tour, and the sync method for saving categories. However, all the tutorials very simplistically just give the example of passing a single object to the function like so:
$tour = Tour::firstOrNew($attributes);
But what happens when my $attributes also contains extra stuff, like the categories which are linked to a relationship table, and which I will need to save in the next step? For example this very good tutorial gives the following example:
$categories = [7, 12, 52, 77];
$tour = Tour::find(2);
$tour->categories()->sync($categories);
But what happens if the category data is bundled with the data for the rest of the tour, and instead of using find I need to use firstOrNew to create the tour? Should I keep the categories in the $attributes while I instantiate the tour, then run the sync, then unset them before saving the tour, or...? Is there a better way to achieve this?
EDIT: To be clear, the $attributes variable in my example here is essentially the tour object data bundled together- just as the Laravel/Eloquent system would return it from the transaction using the belongsToMany method- with subequent modifications from the user). ie: here is a snapshot of what it contains:
array (
'id' => 1,
'uid' => '03ecc797-f47e-493a-a85d-b5c3eb4b9247',
'active' => 1,
'code' => '2-0',
'title' => 'Tour Title',
'url_title' => 'tour_title',
'distance_from' => 20,
'distance_to' => 45,
'price_from' => '135.00',
'price_to' => '425.00',
'created_at' => '2013-12-31 15:23:19',
'updated_at' => '2015-07-24 16:02:50',
'cats' => // This is not a column name!
array (
0 => 1,
1 => 7
),
)
All of these attributes are column names in my tours table, other than cats, which references another table via a hasMany relationship. Do I need to unset it manually before I can set this object class and save it with $tour->save?
I am looking for the cleanest most Laravel way to do it?
EDIT2: Here is the relationship defined in the Tours model:
class Tour extends Model
{
protected $guarded = [];
public function cats(){
return $this->belongsToMany('App\TourCategory', 'tour_cat_assignments', 'tour_id', 'cat_id');
}
}
you need to define $fillable property of your Tour model to tell eloquent which attributes to consider when using mass assignment so it will ignore categories related attributes silently. for ex.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Tour extends Model {
protected $fillable = ['name'] //... other attributes which are part of this model only and laravel will consider only these attributes and ignore category related attributes which you can consider later use.
}
You can use firstOrCreate. The data actually gets persisted using this method.
$categories = [7, 12, 52, 77];
$tour = Tour::firstOrCreate($attributes)->cats()->sync($categories);
Got to make sure the fields are mass-assignable to be able to use the firstOrCreate method though. So either set the fieldnames in the $fillable property or put this in the Tour model:
protected $guarded = [];
Since you have mentioned "CMS" and "subsequent modifications from user", I guess that you are getting your attributes from a Form which means you are getting a Request object/collection.
If that is the case then you can try
$tour = Tour::firstOrCreate($request->except('cats'));
$categories = [];
foreach($request->get('cats') as $key=>$value){
$categories[] = $value;
}
$tour->cats()->sync($categories);
However, if your $attributes us constructed as an array (probably with some manipulations on form data) as per your EDIT then in that case you may try:
$tour = Tour::firstOrCreate(array_except($attributes, ['cats']);
$categories = [];
foreach($attributes['cats'] as $key=>$value){
$categories[] = $value;
}
$tour->cats()->sync($categories);
In any case, you must have the mass assignable fields declared in $fillable property in your model i.e. Tour.
Hope this helps.

cakePHP: Why are my model associations not working as a plugin?

I have a relationship as follows.
Game -> hasMany Highscores
Highscore -> belongsTo Games, Users
When I run the MVC files standalone (within their respective places in the app dir), I get all belongsTo data associated with Highscores. However, when I run the same MVC files as a plugin, within the plugin dir, these associations are lost.
It seems to me that everythig is in order, but to no avail. I am fairly new to cakePHP so I'm sure it's something stupid. I can't for the life figure it out however.
Any help would be greatly appreciated.
I have referenced:
book.cakephp.org/view/117/Plugin-Models
trac.cakephp.org/ticket/3876
aranworld.com/article/143/cakephp-model-associations-from-within-plugin-directories
Are you setting up your relationships using the PluginName as the prefix in the joined Model's name?
That sounds awkward - example
<?php
class MyModel extends AppModel
{
public $name = "MyModel";
public $belongsTo = array(
'User' => array(
'className' => 'SparkPlug.User',
),
);
?>
I ended up using the bindModel method.
$this->Highscore->bindModel(
array('belongsTo' => array(
'User' => array(
'className' => 'SparkPlug.User'
)
)
)
);
Not ideal and still unsure why my relationships/associations are getting lost. But this will have to do.

Resources