Why is save() is changing the model value? - laravel

Here is where I save the game:
public function storeGame() {
$player = new Player(['player_name' => Request::get('host_name')]);
$player->save();
$game = new Game(Request::all());
$game->host_id = $player->id;
$game->save();
$player->game_id = $game->id;
$player->save();
return redirect('lobby');
}
Here is the eloquent relationship:
class Game extends Model {
protected $table = 'games';
protected $fillable = ['game_name','password','round_time','num_rounds','num_players','roles_bool'];
protected $hidden = ['password'];
public function host() {
return $this->hasOne('App\Player', 'id');
}
public function players() {
return $this->hasMany('App\Player', 'id');
}
}
The problem is when I save the game, the value of the host id is changing. These are the results of a var_dump() after saving.
$game->host_id:
int 2
$game->host->id:
string '1' (length=1)
Why is this happening and how do I stop it?
Thanks for help in advance.

Duncan - I'm thinking this has something to do with your Host relationship. I don't see a host_id anywhere in your fillable fields on your Games model.
You should have a host_id fillable field on your Games model and then the relationship would look like this:
public function host() {
return $this->hasOne('App\Player', 'host_id');
}

You probably loaded related host object before and it hasn't been "refreshed" after you changed host id. Actually you shouldn't change relations ids directly, there are dedicated methods to do that: http://laravel.com/docs/4.2/eloquent#inserting-related-models

Related

Laravel - How to use eloquent ORM to populate a foreign key column when getting all results from a table?

I have setup my model as following:
class Items extends Model {
use HasFactory;
protected $table = 'item';
protected $primaryKey = 'id';
protected $connection = 'mysql';
public $timestamps = false;
protected $fillable = ['user_id', 'title', 'desc', 'start_datetime', 'due_datetime', 'priority', 'status'];
public function getManager() {
return $this->belongsTo(User::class, 'user_id');
}
public function getAssignees() {
return $this->belongsToMany(User::class);
}
}
I am getting all items using the controller method below, what I want to do is to populate the user_id field in each of the items using getManager() method I declared in my Item model. I know how to do this when getting only one item, but how to populate every record when getting all of them?
public function getall() {
try {
$items = Item::get();
return response()->json(['items' => $items], 200);
} catch (Throwable $err) {
return response()->json($err, 400);
}
}
I have tried this but no luck:
public function getall() {
try {
$items = Item::get();
$items = array_map(function ($el) {
return $el->manager = $el->getManager()->get();
}, $items);
return response()->json(['items' => $items], 200);
} catch (Throwable $err) {
return response()->json($err, 400);
}
}
There are a few things here that I have some concerns about. Your code may work, but you are also doing more than you need to and not using Laravel how it was meant to be used.
Model Name
Your model name is Items, but it should be singular, Item. This helps Laravel automate things so you have less work to do.
https://laravel.com/docs/8.x/eloquent#eloquent-model-conventions
class Item extends Model {
Database Settings
You've set the $table, $primaryKey, and $connection attributes, but these should be automatic. You can probably remove them.
protected $table = 'items'; // assuming your model name is Item, this would automatically be 'items'
protected $primaryKey = 'id'; // default is already 'id'
protected $connection = 'mysql'; // default is your main db, probably already 'mysql', unless if you have multiple db connections
Timestamps
I'm not sure why you'd want to turn timestamps off. You definitely can but I always find it helpful to know when something was created or last updated. Since Laravel handles the timestamps for you, I'd suggest leaving it on, but it's up to you.
https://laravel.com/docs/8.x/eloquent#timestamps
public $timestamps = false;
Manager Relationship
Your manager relationship is getManager but should just be manager. It will still work, but isn't how Laravel was meant to work. I would suggest changing it to manager(), and not specifying the column name. This would make the column name automatically manager_id, so you'd have to update that. Or you can keep the column name 'user_id'.
https://laravel.com/docs/8.x/eloquent-relationships#one-to-many-inverse
public function manager() {
return $this->belongsTo(User::class);
}
Assignees Relationship
Same as with the Manager relationship, you should change getAssignees() to assignees(). I'm assuming you already have a database migration set up for your 'item_user' table that Laravel will look for. If not, check the Laravel docs on how to set it up.
https://laravel.com/docs/8.x/eloquent-relationships#many-to-many
public function assignees() {
return $this->belongsToMany(User::class);
}
Retrieving Items
Finally, with the above changes, getting all Items should be easy. To load the relationships, use the $with method. This is called Eager Loading. Check the docs for more info.
https://laravel.com/docs/8.x/eloquent-relationships#eager-loading
$items = Item::with('manager','assignees')->get();
Returning Response Codes
You were returning your responses incorrectly. You do not need to set the response code 200, as this is the default. If you are going to set it to something else, put the code in the response() method, instead of the json() method.
https://laravel.com/docs/8.x/responses
return response()->json(['items' => $items]);
return response($err,400);
Now putting it all together, your Item model should look something like this:
class Item extends Model {
use HasFactory;
protected $fillable = ['manager_id', 'title', 'desc', 'start_datetime', 'due_datetime', 'priority', 'status'];
public function manager() {
return $this->belongsTo(User::class);
}
public function assignees() {
return $this->belongsToMany(User::class);
}
}
public function getall() {
try {
$items = Item::get()
->transform(function($el){
$el->manager = $el->getManager()->get();
);
return response()->json(['items' => $items], 200);
} catch (Throwable $err) {
return response()->json($err, 400);
}
}
Try the transform method on your results and it would work.
https://laravel.com/docs/8.x/collections#method-transform
the transform function would basically just iterate over the results and do whatever it is told to like a for loop but for collections.
Also, to make your query efficient avoid the use of loading the relation in the transform function and and use with function of laravel to make it efficient

using '1' instead of user_id as second parameter to belongsTo

I have this situation where I want (hope/wish) to use a particular number e.g 1 directly as second parameter instead of using user_id. I was wondering if there was a to achieve this? or is it just not possible?
For example, currently I have relationship like this.
class orders extends Model
{
protected $tableName = 'orders';
protected $primarykey = 'id';
protected $fillable = [
'user_id',
'qty',
.
.
];
public function user()
{
return $this->belongsTo('App\User', user_id)->withDefault(['name' => 'N/A']);
}
But I was hoping if there was some way I can add another relationship like this?
public function main_user()
{
return $this->belongsTo('App\SpecialUser', 1)->withDefault(['name' => 'N/A']);
}
As can be see there is no column in this table that references SpecialUser table. However, I want a specific user to be accessed on that table using relationship.
I hope I am clear enough and sorry in advance for my newb way of explaining it because I am one.
Thanks in advance.
Please, try this code :
public function mainUser()
{
return $this->belongsTo('App\SpecialUser');
}
public function mainUserOne()
{
return $this->mainUser()->where('id', 1);
}
Controller :
Use App\Order;
$mainUser = (new Order)->mainUserOne()->first();

in model get specific attribute from belongsTo() relation without appending the whole collection

let's say that I want to get the author's name inside the book model
// app/Book.php
protected $appends = ['author_name'];
public function author() {
return belongsTo(Author::class, 'author_id');
}
public function getAuthorNameAttribute() {
return $this->author->name;
}
but this would append the whole author collection to the final book collection as well, that would bump up the loading time when trying to load like 100 books, right now I work it around by removing the author after getting the name like this
// app/Book.php
protected $appends = ['author_name'];
public function author() {
return belongsTo(Author::class, 'author_id');
}
public function getAuthorNameAttribute() {
$authorName = $this->author->name;
unset($this->author);
return $authorName;
}
is there a better way to do it? or did I miss any function from eloquent?
Cheers
Try to add following code in your app/Book.php
// app/Book.php
protected $hidden = ['author'];

In Laravel - Add a variable to a model, without putting it in the database

I have a Team-model that has been used several places, and which returns the fields from the database in an API-endpoint.
It's currently accessed and returned like this:
$team = Team::find(1)
return $team;
I would like to add a calculated variable to that returned Collection. I imagined that I could add it to the constructor of the Model, and thereby get it with all the places where the Team-model is currently used, like this:
class Team extends Model
{
protected $table = 'teams';
protected $fillable = [
'id',
'created_at',
'updated_at',
'team_name'
];
public $number_of_players;
public function __construct( array $attributes = [] ){
$this->number_of_players = 3; //This number should be calculated
parent::__construct( $attributes );
}
}
But that doesn't work.
How do I add a variable to all the places, where the Team-model is fetched?
I also looked into API Resources. I looks like that that could be a solution, but I found it pretty verbose and a long-haired solution (plus, I couldn't get it to work either).
You can use accessor/mutator
Suppose you have a relationship
Team->Player (Team hasMany Players)
You can do like
in Team model
class Model extends Model {
public function players()
{
return $this->hasMany(Player::class, 'team_id', 'id');
}
}
now you can make it
<?php
class Model extends Model {
protected $appends = ['number_of_players'];
public function players()
{
return $this->hasMany(Player::class, 'team_id', 'id');
}
public function getNumberOfPlayersAttribute()
{
return $this->players->count();
}
}
And then access the players count of a team like App/Team::find(1)->number_of_players

Laravel hasMany relationship issue

I have a problem about table relationship in models. when I try to add hasMany relation there is an error popping up.
Call to undefined relationship [Plan100] on model [App\AllPlan].
This is the main table model places
protected $table = "places";
public $with = ["AllPlan"];
public function allplans()
{
return $this->hasMany("App\AllPlan");
}
And AllPlan table model
protected $table = "all_plans";
public function place()
{
return $this->belongsTo("App\Place");
}
No problem 'till here. I can see the AllPlan data inside the Places table on json response... But, the problem is popping up when I try to add hasMany relation into AllPlan table like below.
Now AllPlan table model looks like this.
protected $table = "all_plans";
public $with = [
"Plan100",
"Plan90",
];
public function place()
{
return $this->belongsTo("App\Place");
}
public function plan()
{
return $this->hasMany(
"App\Plan100",
"App\Plan90"
);
}
And the Plan100 table model look like this:
public function plan()
{
return $this->belongsTo("App\AllPlan");
}
But it's giving me an error. But I am not very sure where do I do wrong. Thank you.
Seems to me that you are trying to create two new relations, but this can't be done inside one function. Create two functions and refactor your code like this:
public function plan100()
{
return $this->hasMany(App\Plan100", 'foreign_key');
}
public function plan90()
{
return $this->hasMany(App\Plan90", 'foreign_key');
}

Resources