Laravel 5: How to use firstOrNew with additional relationship fields - laravel

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.

Related

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

Attach to all possible relations from many-to-many method on create?

I have two Model named Position and Event, they have many-to-many relationship with each other.
Event:
<?php
class Event extends Model
{
protected $fillable = [
'name', 'opta_id'
];
public function positions() {
return $this->belongsToMany(Position::class);
}
}
Position:
class Position extends Model
{
protected $fillable = [
'name', 'short_name'
];
public function events() {
return $this->belongsToMany(Event::class);
}
}
Each event should have a pivot entry for each position currently in the database, there are no exceptions. So every time a user creates a new event, I want to create a pivot entry for each existing position.
I am struggling to figure this out using the documentation and SO. I could use sync() or attach() to make the connections by explicitly naming the IDs of all the positions in the db. In the EventController's store method:
$data = $request->all();
$event = Event::create($data);
$event->positions()->sync([1, 22, 34, 167]);
But for this to work, I would first have to get all the entries from the positions table, format them into an array of IDs, and pass them to this method. Is there any built-in or canonical way to do this?
There is no built-in way, but the manual solution is quite short:
$event->positions()->attach(Position::pluck('id'));
attach() is more efficient than sync() because it inserts all pivot records in a single query.
I've gotten the other solution, So to may achieve your purpose you have
// the 1st sync will remove all the relationship to the position table
$event->positions()->sync([1, 22, 34, 167]);
// the 2nd sync is to extend the relationship from the 1st
$event->positions()->syncWithoutDetaching([1, 22, 34, 167]);

Laravel, how cast object to new Eloquent Model?

I get via Request a Json Object.
I clearly parse this object in order to check if it may fit the destination model.
Instead of assigning property by property. Is there a quick way to populate the model with the incoming object?
If you have an array of arrays, then you can use the hydrate() method to cast it to a collection of the specified model:
$records = json_decode($apiResult, true);
SomeModel::hydrate($records);
If you just have a single record, then you can just pass that array to the model’s constructor:
$model = new SomeModel($record);
Just pass your object casted to array as Model constructor argument
$model = new Model((array) $object);
Internally this uses fill() method, so you may first need to add incoming attributes to $fillable property or first create model and then use forceFill().
You should convert that object to array and use fill($attributes) method.
As method name says, it will fill object with provided values. Keep in mind that it will not persist to database, You have to fire save() method after that.
Or if You want to fill and persist in one method - there is create($attributes) which runs fill($attributes) and save() under the hood.
You can use Mass Assignment feature of Laravel,
You model would look like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['name', 'email', 'phone'];
}
And the process of populating the Model would be like this:
// This would be your received json data converted to array
// use 'json_decode($json, true)' to convert json data to array
$json_arr = [
'name' => 'User Name',
'email' => 'email#example.com',
'phone' => '9999999999'
];
$user = new \App\User($json_arr);
Hope this helps!
Castings may fail due to several reasons. A safe way is to add a static function to the model to generate from both array or object. feels like an extension to the model.
public static function generateFromObject($object)
{
$myModel = new MyModel();
foreach($object as $k => $v)
$myModel->{$k} = $v; //for arrays $myModel[$k] = $v;
return $myModel;
}
and you can use anywhere like,
$myModel = MyModel::generateFromObject($myObjectOrArray)->save();

Can you create a new Model instance without saving it to the database

I want to create a whole bunch of instances of a model object in Laravel, then pick the optimal instance and save it to the database. I know that I can create an instance with Model::create([]), but that saves to the database. If possible I'd like to create a bunch of models, then only "create" the one that is best.
Is this possible?
I am using Laravel 5.0
You create a new model simply by instantiating it:
$model = new Model;
You can then save it to the database at a later stage:
$model->save();
You can create instances with Model::make(). It works the same way as create but it doesn't save it.
Whether or not this is best practice is another matter entirely.
Yes, it is possible different ways: you can use the mass assignment without saving.
Please remember to set first the $fillable property in your model.
WAY #1: using the method fill
$model = new YourModel;
$model->fill([
'field' => 'value',
'another_field' => 'another_value'
]);
$model->save();
WAY #2: using the constructor
$model = new YourModel([
'field' => 'value',
'another_field' => 'another_value'
]);
$model->save();
In YourModel set the $fillable property with the fileds allowed for mass assignment:
class YourModel extends Model
{
protected $fillable = ['field', 'another_field'];
// ...
}
Laravel documentation: https://laravel.com/docs/9.x/eloquent#mass-assignment
there is also a method you can call it statically to get new instance:
$modelInstance = $modelName::newModelInstance();
it takes array $attributes = [] as a parameter

Return Eloquent model with different name from database tables

I want to return a JSON of an Eloquent model, but I'd like to change the array keys. By default they are set as the table field names, but I want to change them.
For example if I have a users table with two fields : id and user_name
When I return User::all(); I'll have a JSON with "[{"id" => 1, "user_name" => "bob}] etc.
I'd like to be able to change user_name to username. I haven't found the way to do it without an ugly foreach loop on the model.
I'm not sure why you would want to do this in the first place and would warn you first about the structure if your app/would it be better to make things uniform throughout.. but if you really want to do it.. you could do:
$user = User::find($id);
return Response::json(array('id' => $user->id, 'username' => $user->user_name));
That will return a JSON object with what you want.
You can also change the name of the key with:
$arr[$newkey] = $arr[$oldkey];
unset($arr[$oldkey]);
Just have a look at robclancy's presenter package, this ServiceProvider handles those things you want to achieve.
GITHUB LINK
Just set the $hidden static for you model to the keys you want to hide:
class User extends Eloquent
{
public static $hidden = 'id';
}
and name them the way you like with get and set functons.

Resources