How to save associations on an instantiated object - laravel

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);

Related

Loading relation of new model in Laravel

I just want to get hasOne relation of New model.
Can I load it before I will save it.
I have $user = new User(['order_id' => $order_id]);
How can I get $user->order; without build new request?
As others have already pointed out the model must be saved before retrieving any relations.
My usual approach for this is using lazy eager loading:
// Create new model with data
$newModel = Model::create([
'column' => $value
]);
// Use load to retrieve any relations
return $newModel->load('myRelationship');

how to define methods for a model that has a 1: 1 self relationship

I have this relational table on my db:
id, is referenced to: "attivitaSost" (and attivitaSpostata).
The relathionship is "optional" so the foreignkey is nullable.
But since the problem is the same, I will try to solve the first relationship first.
My model "cciActivities" have this 2 methods:
public function attOrig()
{
return $this->hasOne(CcieActivity::class,'id', 'attivitaSost');
}
public function attSpost(){
return $this->belongsTo(CcieActivity::class,'attivitaSost','id');
If I set the inverse:
public function attOrig()
{
return $this->hasOne(CcieActivity::class,'attivitaSost','id');
}
not works, and goes in a infinite loop thats goes in 500.
are well written? who needs to carry the foreign key? the children or the parent? there is a standard or I make work as was thinking:
save the new model,
pick up the id,
save it on the parent model,
The code:
$ccieActPadre= CcieActivity::where('id',$ccieActivityId)->first();
$ccieActivityNew = CcieActivity::create($data);
$ccieActPadre -> attivitaSost = $ccieActivityNew->id;
$ccieActPadre->save();
I am asking this, because when i try to apply methods filters like
$ccieActivities = CcieActivity::doesntHave('attOrig')
->get();
are returned not what i am expected.
When I am trying to render the resource activities, im using an api Resource like:
return [
'id' => $this->id,
'project' =>new ProjectResource($this->project) , //id, nomeEnte, name, email, ruolo
'catAttivita' => $this->catAttivita,
'nomeAttivita' => $this->nomeAttivita,
'descrizione' => $this->descrizione,
'dataInizioPrevista' => $this->dataInizioPrevista,
'dataFinePrevista'=> $this->dataFinePrevista,
'numNegoziAderentiPrevisti'=> $this->numNegoziAderentiPrevisti,
'numAziendeCoinvoltePreviste'=> $this->numAziendeCoinvoltePreviste,
'numInfluencerPartecipantiPrevisti'=> $this->numInfluencerPartecipantiPrevisti,
'numBuyerPrevistiB2B'=> $this->numBuyerPrevistiB2B,
'budgetTotalePrevisto'=> $this->budgetTotalePrevisto,
'modalitaRealizzazionePrevista'=> $this->modalitaRealizzazionePrevista,
'attivitaSpostata' => new CcieActivityResource($this->attOrigSpost),
'attivitaSostituitaaaaa' => new CcieActivityResource($this->attOrig),
];
this part
'attivitaSostituita' => new CcieActivityResource($this->attOrig),
never works! whatever method I apply!
So I need to understand which is the right convention to menage a 1:1 optional self relationship over a laravel model, thanks.
The second parameters for hasOne and belongsTo are not the same.
belongsTo is for the related model and hasOne is for the local model
$this->hasOne(Phone::class, 'foreign_key', 'local_key');
$this->belongsTo(User::class, 'foreign_key', 'owner_key');
In your case, the hasOne has the wrong parameters. change it to
public function attOrig()
{
return $this->hasOne(CcieActivity::class, 'attivitaSost', 'id');
}
EDIT:
Never eager load by default the parent in the child model and the child in the parent model even if they are seperate Classes. It will lead to an infinite loop.

Laravel Coding Practice / Most Optimised Method to Store

Laravel documentation says one should store as follows:
public function store(Request $request)
{
// Validate the request...
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
}
However, why not just as follows:
public function store(Request $request)
{
Flight::create($request->all());
}
The above example is quite easy, since it only has one field. But I imagine its rather tedious to do something with many fields and have to assign each one as opposed to just passing the whole $request as in the second example?
First option gives you better control as to what goes into new model. If you store everything from the request then user might inject fields that you don't want to be stored for a new model in your store method.
For example, your flight has column is_top_priority that is declared as fillable in your Flight model, but when creating new flight you want to set only name for you flight (and leave is_top_priority as null or maybe it has default value of 0 in your table). If you write Flight::create($request->all()); then user can inject <input name="is_top_priority" value="1"> and get advantage of your code.
That is why it is not recommended to use fill($request->all()). Use $request->only(...) or assign each needed field manually as provided in your first example.
For example your model have some fields like name, email, password,status and etc.
Request validate name, email and password and if you do this:
Flight::create($request->all());
Client can send with other fields status, but you change status manually. I do this:
Flight::create([
'name' => $request->get('name'),
'email' => $request->get('email'),
'password' => $request->get('password'),
'status' =>config('params.flight.status.not_active'),
]);

Laravel 5.4 storing mass assignment model and relationship

I'm unsure of the best practice when inserting mass assignment relationships within Laravel 5.4 - I'm new to the framework. The below code is working correctly, however can you tell me is there a way to simply into one line (inserting relationships)?
I've tried to look at 'save()'and 'push()' but it's not working as expected. Would this have an impact if transactions would scale up?
I have a Listing model, with a hasOne relationship:
public function garage()
{
return $this->hasOne('App\Garage', 'post_id');
}
First of all I have some validation, then I use the following to store, which I want to simplify to one one line of code:
public function store(Request $request)
{
// Validation has passed, insert data into database
$listing = Listing::create($request->all());
$listing->Garage()->create($request->all());
}
Also if I wanted to return the data inserted, how would I do this as the following is only returning the Listing model and not the Garage relationship? Yes I know that I wouldn't do this in a real world application.
return \Response::json([
'message' => 'Post Created Succesfully',
'data' => $listing
]);
Any help is muchly appreciated
Method chaining
Your store method looks good. You could chain methods though, if you don't need the listing object itself:
public function store(Request $request)
{
// Validation has passed, insert data into database
$garage = Listing::create($request->all())
->garage()->create($request->all();
}
But if you need the listing object, it's fine as you did it before!
Returning relation models
If you want to return the garage relation model too, you can simply do that by accessing it like a normal class propery:
return \Response::json([
'message' => 'Post Created Succesfully',
'data' => [$listing, $listing->garage]
//Access collection of garage relation ship
]);

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.

Resources