Updating laravel relationship data - laravel

When we inserting or updating the data via Eloquent relationship model, which is the best approach to use?
Example
$user->profile->update(['salary' => 5000]);
vs
$user->profile()->update(['salary' => 5000]);
I understand that
$user->profile() will return the relationship class such as Illuminate/Database/Eloquent/Relations/HasOne
$user->profile will return the actual UserProfile model class
I somehow remember I saw someone recommended to use $user->profile->update() instead of $user->profile()->update() but I couldn't find the article or reference link anymore
However, I found that if $user->profile is null, then it might caused an error such as
Call to a member function update() on null
So will it be easier to use relationship function update all the time?
$user->profile()->create()
$user->profile()->update()
$user->profile()->save()
$user->profile()->delete()
Is there any situation we should use the $user->profile->save() instead?
Or should one use it when it is in the multiple nested relationship?
$user->profile->bank()->create()
$user->profile()->bank()->create()
Update
reference link (for my own understanding)
https://github.com/laravel/framework/issues/13568
https://github.com/laravel/framework/issues/2536
Eloquent attach/detach/sync fires any event?
Conclusion
For now, will use code below in application, both will trigger events
if ($user->bank === null) {
$user->bank()->save(new UserBankAccount($input)); // trigger created event
// $user->bank()->create($input);// trigger created event
} else {
$user->bank->update($input); // trigger updated event
// $user->bank()->update($input); // will NOT trigger updated event
}

You can use forceFill()
Example:
$user->bank->forceFill($data)->save();

Related

Column change that affected other columns

I have following table:
$table->boolean('prop_enabled');
$table->boolean('prop1_enabled')->nullable();
$table->boolean('prop2_enabled')->nullable();
$table->boolean('prop3_enabled')->nullable();
My requirement is when prop_enabled is false, then prop1_enabled, prop2_enabled and prop3_enabled values shall be set to null during row insertion or update.
Is my table design is appropriate for solving the problem?
If my table is good to solve the problem, which approach is better to handle my requirement? I have options to create trigger on database, or to override Create and save method from Model class.
I believe the best approach is doing the Observer solution. Using an mutator, requires a lot of mutators to secure your data structure and / or business logic that can ignore it. The observer is using events and there for you can only circumvent it from triggering by avoiding using eloquent.
You can hook into the saving event on Models. This will look at the model before saving it, then alter it, if your case is true and thereby creating the optimal 1 query insertion. No matter how your business logic is, this will trigger before the creation or updating.
class YourModelObserver {
public function saving(YourModel $model) {
if ($model->prop_enabled === false) {
$model->prop1_enabled = false;
$model->prop2_enabled = false;
$model->prop3_enabled = false;
}
}
}
In a provider you have to register your listener.
public function boot()
{
YourModel::observe(YourModelObserver::class);
}
My biggest problem with your design is a common code smell i have seen before. Naming variables after numbers, when you have multiple just seems wrong for me. What happens if you get the 4. prop and how will you handle showing them and updating em? An alternative approach would be to have em in a more generic fashion in another table when they had a name or key instead of being prop1 or prop2 and having logic associated with creating them or updating em. If you are certain the prop1, prop2 and prop3 are not gonna change the design is good for now.

Laravel - why is a Model's relations not being loaded?

I have a Laravel 5.3 site and I think maybe I have some weird things going on due to some actions happening in API controllers and some happening in the regular controllers.
Or maybe an issue where at some times I am dealing with a Model, and sometimes with an Eloquent Collection.
The issue is, I am trying to retrieve relations on a Model and am getting null.
For instance, I have course Model that relates to week Model.
In course Model I get week items as
public function weeks()
{
return $this->hasMany(Week::class, 'course_id');
}
In backend, these relations get sent in this way:
$course->load('weeks')
All is good.
But when course item gets deleted and I try and take action in the week controller as
static::deleting(function($course) {
$course->weeks->delete();
});
$course->weeks is null. At that time I see in the database that all is good and this course items does indeed have a week item related, but $course shows 0 relations.
So something odd is happening where $course->webinars is not grabbing the week items related.
Is there something that I am fundamentally doing wrong? Maybe it is because in the models I have these sorts of statements:
protected $table = 'Week';
Are these preventing the relations from being pulled? I always thought that is I had some function in a model that returns relations that those relations would always be available when I use syntax $course->weeks.
Ideas?
Thanks again,
You can simply setup migrations to automatically delete from weeks if you delete a course, provided you have foreign key relationship.
If you have a column course_id in weeks table then add this into your migration
$table->foreign('course_id')
->references('id')->on('courses')
->onDelete('cascade')
I think you can use Observers. In your AppServiceProvider, first register the observer.
public function boot()
{
Course::observe(CourseObserver::class);
}
Now, add an Observer class.
class CourseObserver
{
public function deleting(Course $course)
{
$course->weeks()->delete();
}
}

Overriding Laravel get and first methods

I need to override above mentioned methods to skip some database records. Using where is not an option since I would have to use it every single time as there are records in database that I do not need most of the time and I am not allowed to delete them from DB. Here is my attempt of doing this:
class SomeTable extends BaseModel {
public static function first() {
$query = static::query();
$data = $query->first();
if($data && $data->type == 'migration_type') return null;
return $data;
}
public static function get() {
$query = static::query();
$data = $query->get();
foreach($data as $key => $item) {
if($item->type == 'migration_type') unset($data[$key]);
}
return $data;
}
}
The problem with this code is that it works only when direct called on model. If I am using some other functions, like where, before get or first methods, it just skips my overridden method.
What would be the right way to do this and should I put this code within model?
My question is not duplicate as in the answer from mentioned question it is said:
all queries made from Models extending your CustomModel will get this new methods
And I need to override those two functions only for specific model, not for each one in application as not all tables have type column. That's the reason why I have written them within model class.
I need to override above mentioned methods to skip some database records.
Consider a global query scope on the model.
https://laravel.com/docs/5.8/eloquent#global-scopes
Global scopes allow you to add constraints to all queries for a given model. Laravel's own soft delete functionality utilizes global scopes to only pull "non-deleted" models from the database. Writing your own global scopes can provide a convenient, easy way to make sure every query for a given model receives certain constraints.
The issue here is that the where() method on the model returns a QueryBuilder instance where get() will return a Collection instance.
You should be able to override collection's default methods by adding a macro in it's place and can be done like so...
Collection::macro('toUpper', function () {
return $this->map(function ($value) {
return Str::upper($value);
});
});
Extending the query builder instance is not so easy but a good tutorial exists here and involves overriding the application's default connection class, which is not great when it comes to future upgrades.
Because after calling where you're dealing with the database builder and theses methods inside your model aren't being called .. about the issue you might overcome it by using select instead of first directly so will deal with the builder ..
example:
SomeTable::select('col1','col2')->take(1)->get();
another thing overriding these kind of methods is not a good idea if you're working with other developer on the same project.
good luck

Laravel Backpack : Storing Belongs To Many relationships using custom view

I have a flight class and this flight has a custom view field like so:
This represents a belongs to many relationship which stores website_id / flight_id and pricing as pivot data in a pivot table.
The custom view uses JS to send this data back to the controller in this format:
{"1":{"price_adult":"434","price_child":"545"},"2":{"price_adult":"323","price_child":"324"},"3":{"price_adult":"434","price_child":"43"}}
Trying to send this data with the request doesn't create the relations fields, and because I do not have a flight ID at the point of creating this within the controller I can not loop this JSON to make the relations manually.
Can anyone point out what the best course of action is or if there is support for this? I took a look at the docs but they are woefully short and patchy in terms of being much help.
EDIT:
I should have said I can probably make this work using a custom name attribute on the model for the relation, then add a set mutator to loop this data and update the prices relation but I don't want to go down this route if there is support for this I am missing out of the box in backpack.
EDIT2:
Someone asked about the relation:
$this->belongsToMany(Website::class, 'website_pricing')->withPivot('price_adult', 'price_child');
This is working fine its not a problem with the relation working its how can I get backpack to store the data as a relation when the flight has no ID yet, or how can I pass the data I posted above in such a way that the backpack crud controller can handle it?
You may need to create a flight first, if no flight id is being provided. Can you explain the database relational structure more?
Basically thought I should post what I did because no one could provide an answer to this.
So basically you have to copy the store / update functions from the parent, changing a few lines.
$this->crud->hasAccessOrFail('create');
// fallback to global request instance
if (is_null($request)) {
$request = \Request::instance();
}
// replace empty values with NULL, so that it will work with MySQL strict mode on
foreach ($request->input() as $key => $value) {
if (empty($value) && $value !== '0') {
$request->request->set($key, null);
}
}
// insert item in the db
$item = $this->crud->create($request->except(['save_action', '_token', '_method']));
$this->data['entry'] = $this->crud->entry = $item;
// show a success message
\Alert::success(trans('backpack::crud.insert_success'))->flash();
// save the redirect choice for next time
parent::setSaveAction();
return parent::performSaveAction($item->getKey());
Basically any line which references a function in the parent class using $this->method needs to be changed to parent::
This line is what I used to submit the relations JSON string passed to the controller as relations $item->prices()->sync(json_decode($request->input('prices'), true));
This is done after the line containing $item = $this->crud->create as the item id that just got stored will be available at that point.

Laravel model events for attach/detach

I have 3 tables, products, taxonomies and product_taxonomy, as you can tell the 3rd table is a pivoting table. In taxonomies table, I hold a field called num_products which keeps track of the quantity of products that belongs to this taxonomy. Now how can I trigger a model event every time a product is attached to or detached from a taxonomy? I want to update that num_products value in the model event.
Laravel models have events that you can hook into. You have the following options:
creating
created
updating
updated
saving
saved
deleting
deleted
restoring
restored
You'd code it like this:
User::creating(function($user)
{
if ( ! $user->isValid()) return false;
});
Docs: http://laravel.com/docs/4.2/eloquent#model-events
Or you could use Model Observers that are baked in. You have the following options:
creating
updating
saving
You would write a method on your model:
class UserObserver {
public function saving($model)
{
//
}
public function saved($model)
{
//
}
}
You can then register then register the observer:
User::observe(new UserObserver);
Docs: http://laravel.com/docs/4.2/eloquent#model-observers
Hope it helps.

Resources