Eloquent 5.4 - HasMany Object Mass Update Timestamps - laravel

really need your help here. ( I don't know what I want is possible on Eloquent )
Lets pretend this Relationship: One user can have many Childs
Note: Ignore problems in the code, this is just an example.
Now lets add some code into it.
// Return HasMany Object Instance from Eloquent.
$hasMany = $user->childs()
// Perform Mass Update.
$hasMany->update(['born_at' => Carbon::now])
So far nothing wrong with it, the first line returns an HasMany Object ( Documentation )
The problem is that Mass Updating touches my Model's timestamps ( created_at, updated_at ) and specially for this update I don't want it to do that.
Disabling it on the Model is not an option for me I do use the timestamp touch normally but I don't want to use in this case.
Neither I want to iterate over the Collection ( $user->childs ) because I have many rows to update and its an overhead to generate one query for each Model to update.
What I expect for an answer to this question: Simple, I just want an way to turn off the timestamps to do the mass updating or something like that.
( Normally on a single Model you can disable it like this: $model->timestamps = false, but this will not work here because hasMany instance does not have this attribute. )

You could set the property default of the model to false. So in you're model class you will have:
public $timestamps = false;
But this will always disable the timestamps until je use:
$model->timestamps = true;

In case someone finds this through Google:
One possible solution is to Fallback to the base QueryBuilder:
(new Child)
->newQuery()
->toBase()
->where('parent_id', $model->id)
->update([
'born_at' => Carbon::now,
]);
Of course, one could just use here something like DB::table(...)...

Related

How to dissociate elements from a HasMany relation?

There's the save and saveMany methods on the HasMany relation class, but where are the dissociate(Many)/detach(Many) methods? There's also no built-in way to get the inverse relationship method, so what's the best way to dissociate an array of id's/models from a HasMany relationship object.
Currently I'm using:
$hasMany = $parent->theRelationship(); // Get the relationship object.
$child = $hasMany->getRelated(); // Get an empty related model.
$key = $hasMany->getForeignKeyName(); // Get the name of the column on the child to set to NULL.
$child->findMany($IDs)->each(function($model) use ($key) {
$model->$key = NULL;
$model->save();
});
This could be alot shorter with something like:
$hasMany = $parent->theRelationship();
$hasMany->dissociate($IDs);
Bonus points if you have any official answers from Taylor as to why he hasn't implemented this, I've seen him close feature requests of this kind on GitHub.
I am not sure why there isn't a function, but to be more performant than your example, you could use the DB class like:
\DB::table('child_table')->where('parent_id', $parent->id)->update(['parent_id' => null]);
You could use detach like so;
$parent->theRelationship()->detach([1,2,3])
Where you pass an array of IDs.
From Laravel documentation:
"For convenience, attach and detach also accept arrays of IDs as input"
The performatic way (1 db update):
$partent->theRelationship()->update(['parent_id' => null]);
The readable way (multiple db updates):
$parent->theRelationship->each->parentRelationship()->dissociate();

Laravel Eloquent increment without updating timestamps

I have an Eloquent model on which I would like to increment a single attribute. So far I've been using the following line of code to achieve this:
Thread::where('id', $threadId)->increment('like_count');
This however has the unwanted side-effect of updating the updated_at timestamp. I've found the following way of updating a record without altering the timestamp:
$thread = Thread::where('id', $threadId)->first();
$thread->timestamps = false;
$thread->like_count++;
$thread->save();
But that suddenly looks a lot less concise. Therefore, I would like to know of there's a way to use the increment method without updating timestamps.
If you do not need timestamps at all, you can disable it once for all for that particular model using :
public $timestamps = false; inside your model. This will add additional step that whenever you want the timestamps to be updated, you need to assign them value manually like $object->created_at = Carbon::now()
Secondly, if you want those disabled for particular query, then as you mentioned in your question is one way.
Another way is using query builder. Now timestamps is the functionality associated with Eloquent. However, if you update using simple query builder, it does not update timestamps on its own.
So you can do :
DB::table('threads')
->where('id', $threadId)
->update([ 'votes' => DB::raw('votes + 1') ]);
However, I will personally prefer using Eloquent way of doing this if given a choice.
Update
You can now pass additional parameter to increment function to specify what other columns you would like to update.
So this will become :
$thread = Thread::find($threadId);
$thread->increment('votes', 1, [
'updated_at' => $thread->updated_at
]);
old thread but with laravel 7 and php7.4 you can do
Thread::where('id', $threadId)
->where(fn($q) => $q->getModel()->timestamps = false)
->increment('like_count');
older versions of php:
Thread::where('id', $threadId)
->where(function($q) {$q->getModel()->timestamps = false;})
->increment('like_count');
You could encapsulate the whole process into one method of the model.

Can not Soft delete multiple rows using Query Builder

My model is something like this:
namespace App;
use Illuminate\Database\Eloquent\SoftDeletes;
class Photo extends Model {
use SoftDeletes;
protected $dates = ['deleted_at'];
}
_ I can soft delete using:
$\App\Photo::find(1)->delete();
_ It does not work when I try to use soft delete on multiple rows:
\App\Photo::whereIn('id', [1,2,3])->delete();
Does any one know why?
No, you can't soft delete multiple rows.
The only Laravel way is the DB facade in this case.
Here is how I would soft delete multiple rows.
DB::table('table_name')->whereIn('id', [array of ids])
->update([
'deleted_at' => now()
]);
or
ModelName::whereIn('id', [array of ids])
->update(['deleted_at' => now()]);
Instead of whereIn you can put in any where condition like you usually put and can update the deleted_at key. Soft delete is nothing but marking the column as deleted.
This is also a very efficient solution rather than running soft delete for each model inside a loop which can crash the system if there're too many items in the array.
Hope this helps.
The soft delete functionality only works on an instance of the Eloquent model itself. When you are doing this:
\App\Photo::find(1)->delete();
You are actually first retrieving the Photo with an ID of 1 from the database which is then prepared and made available as an instance of the Eloquent model (which can then use soft delete).
However, when you do this:
\App\Photo::whereIn('id', [1,2,3])->delete();
You are not actually retrieving anything from the database, you are basically just preparing DELETE SQL in a more convenient way. This effectively runs something like:
DELETE FROM `photos` WHERE `id` IN (1,2,3);
This is different from something like:
foreach (\App\Photo::whereIn('id', [1,2,3])->get() as $photo) {
$photo->delete(); # $photo is an eloquent model and can soft-delete
}
Notice the ->get() which is actually grabbing data from the database first and will make it available as a collection of Eloquent models (which then can soft delete).
I don't think you can soft-delete a batch. In my foreach example using ->get() I imagine multiple queries are executed - something like:
UPDATE `photos` SET `deleted_at` = NOW() WHERE `id` = 1;
UPDATE `photos` SET `deleted_at` = NOW() WHERE `id` = 2;
UPDATE `photos` SET `deleted_at` = NOW() WHERE `id` = 3;
Hope that makes sense.
Try this as well:
\App\Photo::whereIn('id', [1,2,3])
->get()
->map(function($photo) {
$photo->delete();
});

Saving a model with multiple foreign keys in Laravel 4

I understand that in order to save a foreign key, one should use the related model and the associate() function, but is it really worth the trouble of going through this
$user = new User([
'name' => Input::get('name'),
'email' => Input::get('email')
]);
$language = Language::find(Input::get('language_id');
$gender = Gender::find(Input::get('gender_id');
$city = City::find(Input::get('city_id');
$user->language()->associate($language);
$user->gender()->associate($gender);
$user->city()->associate($city);
$user->save();
when one can simply do this?
User::create(Input::all());
I feel like I'm missing something here, maybe there's an even simpler and cleaner way to handle foreign keys in controllers (and views)?
You can use push() method instead which would allow you to push to related models.
This link should answer your query.
Eloquent push() and save() difference
I really don't see anything wrong at all with doing User::create(Input::all());.
Obviously you'd want some validation, but it's doing the same thing.
I think the associate() method is more useful for the inverse of your situation.
For example, say you had a form which a user could fill out to add their city to your app, and upon doing so, they should automatically be assigned to that city.
$city = City::create(Input::all()); would only achieve the first half of your requirements because the user has not yet been attached as city does not have a user_id column.
You'd then need to do something like $city->user()->associate(User::find(Auth::user()->id));

Doctrine toarray does not convert relations

I followed doctrine documnetation to get started. Here is the documentation.
My code is
$User = Doctrine_Core::getTable("User")->find(1);
when I access relations by $User->Phonenumbers, it works. When I convert User object to array by using toArray() method, it does not convert relations to array. It simply display $User data.
Am I missing something?
By using the find method you've only retrieved the User data which is why the return of toArray is limited to that data. You need to specify the additional data to load, and the best place to do this is usually in the original query. From the example you linked to, add the select portion:
$q = Doctrine_Query::create()
->select('u.*, e.*, p.*') // Example only, select what you need, not *
->from('User u')
->leftJoin('u.Email e')
->leftJoin('u.Phonenumbers p')
->where('u.id = ?', 1);
Then when toArray'ing the results from that, you should see the associated email and phonenumber data as well.
I also noticed an anomaly with this where if you call the relationship first then call the ToArray, the relationship somehow gets included. what i mean is that, taking your own eg,
$User = Doctrine_Core::getTable("User")->find(1);
$num= $User->Phonenumbers->office; // assumed a field 'office' in your phone num table
$userArray = $user->toArray(true);
In the above case, $userArray somehow contains the whole relationship. if we remove the $num assignment it doesn't.
am guessing this is due to doctrine only fetching the one record first, and it's only when you try to access foreign key values that it fetches the other related tables

Resources