Laravel replicate including relationships but maintain original timestamps - php

I am trying to replicate a record and its relationships in Laravel 8. But I want to keep the original records timestamps. I am able to do this on the main record, but the timestamps on all the relationships get set to the datetime that the record is replicated. I also have some observers set up and I dont want them firing when the replication takes place.
$order = App\Models\Order::with('comments', 'tracking_details')->find(4096);
$clone = $order->replicate()->fill([
'tracking_number' => null,
'created_at' => $order->created_at,
'updated_at' => $order->updated_at,
]);
$clone->saveQuietly();
foreach ($order->comments as $comment) {
App\Models\Comments::withoutEvents(function() use ($clone, $comment) {
$clone->comments()->create($comment->toArray());
});
}
foreach ($order->tracking_details as $details) {
App\Models\TrackingDetails::withoutEvents(function() use ($clone, $details) {
$clone->tracking_details()->create($details->toArray());
});
}
Any help would be greatly appreciated.

For relationships, you can use query builder instead.
// main record
$order = Order::find(4096);
$clone = $order->replicate()->fill([
'tracking_number' => null,
'created_at' => $order->created_at,
'updated_at' => $order->updated_at
]);
$clone->saveQuietly();
// comments relationship
$comments = Comments::toBase()->where('order_id', $order->id)->orderBy('id')->get()->map(function ($item) use ($clone) {
unset($item->id);
$item->order_id = $clone->id;
return (array) $item;
});
Comments::insert($comments->toArray());
// tracking_details relationship
$trackingDetails = TrackingDetails::toBase()->where('order_id', $order->id)->orderBy('id')->get()->map(function ($item) use ($clone) {
unset($item->id);
$item->order_id = $clone->id;
return (array) $item;
});
TrackingDetails::insert($trackingDetails->toArray());
In this case you don't work with eloquent, which means that no events will be fired and you will keep the original records timestamps.

Not sure if this will work, but have you tried disabling timestamps in your model?
public $timestamps = FALSE;

If you want your models to be able to save your timestamps via create() method, you should add created_at and updated_at properties to $fillable on your models. Reference: Making properties mass assignable

The created_at and updated_at columns are default timestamps which Laravel provides out of the box, create a cloned_at column default to null in the database and in your database model, add it to a fillable property, then you can simple insert data in the database where created_at and updated_at values will be parsed as a comma separated value, when retrieving the data, you can split and modify the string from CSV and parse it as a Carbon format, it's a dirty but a possible solution.

Related

Laravel pluck nested collection to root collection

I have a data structure like you can see in the picture above.
I want some fields from user field in the root collection, but I don't know how can I can get that.
public function index (Request $request) {
$draw = $request->get('draw');
$start = $request->get('start');
$length = $request->get('length');
$order = Order::with('user');
$total_order = $order->count();
$orders = Order::with('user')->offset($start)->limit($length)->get();
/*$draw = ceil($total_order/$length);*/
return response()->json([
'draw' => $draw,
'recordsTotal' => $total_order,
'recordsFiltered' => $total_order,
'data' => $orders,
]);
}
The flatmap approach seems weird, you have two models Order and Users, either you have to add extra properties to orders on the fly, which is weird in Laravel if you want to save the model again. Or you have to convert it to arrays and you loose the Model functionality.
Instead of trying to flatten the user object onto the order. You are already using with(), so your user is eager loaded and can rely on that. Instead use Eloquent Getters and map your user fields to the order.
class Order extends Model
{
public function getUserNameAttribute() {
return $this->user->name;
}
}
Eloquent getters function name has to be in the format getPropertyAttribute. Now you would be able to access it like so, which also can be done in blade etc.
foreach ($orders as $order) {
$userName = $order->userName;
}
If you truly want the related values in the root collection, you could do a left join on the table:
$orders = Order::leftJoin('users', 'users.id', '=', 'orders.user_id')
(Based on the model names, these would be the correct table and column names).
However, like Mrhn just pointed out, it is not very Eloquent-like to try and have data of related tables in the root collection of the main model.
P.S. If you do use the left join, be sure to only select the data you need, otherwise it will overwrite Order data (such as id)

Eloquent Belongs-to-Many relation using custom intermediate Table Models without 'updated_at' field

I'm using Laravel 8 and in my application, I have Belongs to Many relations with a custom model, and I want to remove the 'updated_at' field.
Relation
public function tracks()
{
return $this->belongsToMany(Track::class)
->using(CollectionTrack::class)
->withPivot('sort' , 'created_at' , 'id');
}
Custom model
class CollectionTrack extends Pivot
{
use Sortable;
public const UPDATED_AT = null;
public $incrementing = true;
public static function enableAutoSort () {
return false;
}
}
The issue is that when I want to sync, it tries to fill the updated_at field.
Column not found: 1054 Unknown column 'updated_at' in 'field list'
However, I removed the updated_at from the Model using the following line.
public const UPDATED_AT = null;
And also, only get the created_at in withPivot.
When I remove the created_at from withPivot, the issue goes away, but in that case, when I retrieve the data created_at won't be in the fields.
Note: my goal is to disable the updated_at timestamp and only have created_at so when I attach a new record, the created_at set and when I retrieve it, the model has these pivot fields 'sort', 'created_at', 'id.'
I think you can remove $table->timestamps() from your migration and just add a field created_at having default value current time stamp.
$table->timestamp('created_at')->default(DB::raw('CURRENT_TIMESTAMP'));
should work I guess.
There is another answer you can refer.
You either have to declare public $timestamps = false; in every model, or create a BaseModel, define it there, and have all your models extend it instead of eloquent. Just bare in mind pivot tables MUST have timestamps if you're using Eloquent.
Update: Note that timestamps are no longer REQUIRED in pivot tables after Laravel v3.
Update: You can also disable timestamps by removing $table->timestamps() from your migration
ORIGINAL ANSWER: https://stackoverflow.com/a/59171175/14290461
In your model add these two lines:
public $timestamps = ["created_at"]; //only want to used created_at column
const UPDATED_AT = null; //and updated by default null set
second way:
public $timestamps = false; //by default timestamp false
add function like this:
public function setCreatedAtAttribute($value) {
$this->attributes['created_at'] = \Carbon\Carbon::now();
}
for more info about laravel timestamp see

Eloquent updateOrCreate hide columns while updating

I am using the updateOrCreate() function to update the record if a record is already available in the table based on some condition. However, I do not want to update one of my columns that contains a unique value.
I want to perform the same action to this column like created_at is working
while creating the records value is adding to the created_at column but while updating it contains the same.
To prevent this issue I removed that column name from $fillable in my model. But, in this case, it is not adding any value to this column.
protected $fillable = ['uid', 'channel', 'updated_at'];
$data = [
'uid' => Helper::generateUid(),
'channel' => $info['channel'],
'created_at' => time(),
'updated_at' => time(),
];
$cond = ['channel' => $info['channel']];
$isAdded = Templates::updateOrCreate($cond, $data);
Expected result
I don't want to update uid column if details already there.
Actual result
If details are not there it adds value to uid column and other columns also and if details are available then also it is updating uid column.
You can't do that with the updateOrCreate method. You'll need to be slightly more explicit. You can use the firstOrNew method to retrieve or instantiate a new object. And then you can use the exists property to determine if it is an existing or new object.
$template = Templates::firstOrNew(['channel' => $info['channel']]);
if (!$template->exists) {
$template->uid = Helper::generateUid();
}
$template->save();
I left out the created_at and updated_at fields because they are automatically handled by Laravel.

Laravel always return date column with carbon

Is there a way to always make the dates from a column return using carbon?
Let say I have a Challenges model/table with date_end column.
When I do this call in my ChallengesController it returns all the challenges in json for my app:
public function index()
{
return response()->json([
'status'=>'success',
'challenges' => Auth::user()->challenges,
]);
}
But the date is still MySQL format. I know I can do :
public function index()
{
$challenges = Auth::user()->challenges;
foreach ( $challenges as $challenge){
$challenge['humandate'] = $challenge->date_end->diffForHumans();
}
return response()->json([
'status'=>'success',
'challenges' => $challenges,
]);
}
then get the date via challenge.humandate But is there a cleaner way?
In your model you can declare which columns should be converted to Carbon instances by adding them to the protected $dates array:
protected $dates = ['created_at', 'updated_at', 'date_end'];
Of course the first two are only necessary if you're using the ->timestamps() method in your migrations.
As #Castis points out, you can use attribute mutators and accessors to modify the data is it goes to the model and as it comes from the model. In your case define getCreatedAtAttribute method, for example, and return from it Carbon and the created_at field parsed into it.

Laravel Eloquent ORM - save() returns column not found (MySQL error)

The issue I'm running into is that I want to save the model, but I have a method call which writes to the instance and for some reason Laravel is trying to update that column.
Club Model (relevant code):
use Illuminate\Database\Eloquent\SoftDeletingTrait;
class Club extends Eloquent
{
use SoftDeletingTrait;
protected $table = 'clubs';
protected $fillable = array('name', 'address', 'city', 'state', 'zip', 'contact_name', 'contact_email', 'contact_phone', 'contact_photo', 'club_code', 'logo');
public function getCurrentCampaign($id = 0)
{
if (!$id)
{
if ($this->currentCampaign)
{
return $this->currentCampaign;
}
$id = $this->id;
}
$now = date('Y-m-d H:i:s');
$this->currentCampaign = DB::table('campaigns')
->where('club_id', $id)
->where('start_date', '<=', $now)
->where('end_date', '>=', $now)
->pluck('id');
return $this->currentCampaign;
}
}
The issue exists on the "club settings" page where a user can edit some stuff - I have a few updates that run on different tables, and then later I use $club->save(). I found that even if I call it directly after getCurrentCampaign, it throws the error.
$club = Club::findOrFail($clubID);
$club->getCurrentCampaign();
$club->save(); // Error
Error message:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'currentCampaign' in 'field list' (SQL: updateclubssetupdated_at= 2014-07-08 12:49:17,currentCampaign= 27 whereid= 23)
Considering that currentCampaign isn't in the $fillable array, I don't know what's happening. Am I misunderstanding how that works?
Thanks
Edit: For clarity, there are a few different things loaded into $club, not just the campaigns. I'm simply giving one for illustrative purposes.
Anything that you save on the Model object that is NOT a property would be treated as an attribute/table column. To avoid this you can simply declare those properties on the model:
// Club model
public $currentCampaign;
Then using your code won't cause the error you experience now.
Anyway, probably you should consider #watcher's suggestion on working with relations instead, but that depends on your app.
And about the fillable array - it has nothing to do with saving data but rather with filling object with an array of data (mass assignement):
$model->fill($someArray);
this is called when you __construct new object, save or update providing an array etc.
I don't think you're specifying your campaign relationship in 'the laravel way'. The underlying issue is that you're adding a property to your model, and, during saving, Eloquent believes that extra non-standard property represents a column in your database so it is trying to save it as such.
This is the path I'd take to solve it:
public function campaigns()
{
// 'hasMany' may not be what you need, just guessing
// from what I see so far...
return $this->hasMany('Campaigns');
}
public function currentCampaigns($now)
{
return $this->campaigns
->where('start_date', '>', $now)
->where('end_date', '<', $now);
}
Now, given an instance of a 'club', you can get its current campaigns like this:
$club->currentCampaigns(date('Y-m-d H:i:s'));
The relationship above is determined by how you structure the relationship in the database.
If your club table has a 'campaign_id' field, then the relationship in the club model to the campaign model would be belongsTo.
If there is a third table that has both campaign ids and club ids in it and thats how you determine what clubs are related to what campaigns, that relationship is hasMany
I belive also if you have a campaign_id in your clubs table, it will need to be present in the fillable array in order to actually be able to set it.

Categories