Right way to save timestamps to database in laravel? - laravel

As part of a standard laravel application with a vuejs and axios front-end, when I try to save an ISO8601 value to the action_at field, I get an exception.
class Thing extends Model {
protected $table = 'things';
// timestamp columns in postgres
protected $dates = ['action_at', 'created_at', 'updated_at'];
protected $fillable = ['action_at'];
}
class ThingController extends Controller {
public function store(Request $request) {
$data = $request->validate([
'action_at' => 'nullable',
]);
// throws \Carbon\Exceptions\InvalidFormatException(code: 0): Unexpected data found.
$thing = Thing::create($data);
}
}
My primary requirement is that the database saves exactly what time the client thinks it saved. If another process decides to act on the "action_at" column, it should not be a few hours off because of timezones.
I can change the laravel code or I can pick a different time format to send to Laravel. What's the correct laravel way to solve this?

The default created_at and updated_at should work fine.
You should always set your timezone in your config/app.php to UTC
Add a timezone column or whichever you prefer in your users table
Do the time-offsets in your frontend or api response
Here's a sample code to do the time offset in backend
$foo = new Foo;
$foo->created_at->setTimezone('America/Los_Angeles');
or frontend using momentjs
moment(1650037709).utcOffset(60).format('YYYY-MM-DD HH:mm')
or using moment-timezone
moment(1650037709).tz('America/Los_Angeles').format('YYYY-MM-DD HH:mm')

class Thing extends Model {
protected $table = 'things';
// timestamp columns in postgres
protected $dates = ['action_at', 'created_at', 'updated_at'];
protected $fillable = ['action_at'];
}
class ThingController extends Controller {
public function store(Request $request) {
$data = $request->validate([
'action_at' => 'nullable',
]);
// convert ISO8601 value, if not null
if ($data['action_at'] ?? null && is_string($data['action_at'])) {
// note that if the user passes something not in IS08601
// it is possible that Carbon will accept it
// but it might not be what the user expected.
$action_at = \Carbon\Carbon::parse($data['action_at'])
// default value from config files: 'UTC'
->setTimezone(config('app.timezone'));
// Postgres timestamp column format
$data['action_at'] = $action_at->format('Y-m-d H:i:s');
}
$thing = Thing::create($data);
}
}
Other tips: don't use the postgres timestamptz column if you want to use it with protected $dates, laravel doesn't know how to give it to carbon the way postgres returns the extra timezone data.
See the carbon docs for other things you can do with the $action_at instance of \Carbon\Carbon, such as making sure the date is not too far in the future or too far in the past. https://carbon.nesbot.com/docs/

Related

Laravel - How to change response format for specific fields

I've "Product" model.
And need to change some value formats for only responses.
For example;
I've "price" on database as decimal (11,2).
I want this as "1.000.000,00" format on response.
Or created_at field to "Carbon::parse($this->created_at)->toDayDatetimeString()"
Or I want to add 3 specific columns with my user attribute, on response. (is_allowed etc.)
How can this be possible on model?
How can I response like that?
You can use Mutator and Accessor to set format :
https://laravel.com/docs/8.x/eloquent-mutators#accessors-and-mutators
public function setDateAttribute($date) {
$this->attributes['date'] = Carbon::createFromFormat('Y-m-d', $date);
}
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
As a best practice in Laravel you can use Eloquent Resources: Eloquent Resources
It's basically a "transformer" between models data and API/Responses Output.
The only one thing to notice is that in the Resource files yout must specify all fields and relations (if needed) of the Model manually.
In the toArray() function you can modify the type of all data of your model as you prefer.
If not, you can access the new field by $model->my_custom_field (Laravel can resolve the name of the getter function automatically).
public function toArray($request)
{
$editedFieldValue = doSomething();
return [
'my_field' => $editedFieldValue,
'other_field' => '',
];
}
If you want to do that in Model, you can create customs fields:
class MuModel extends Model
{
protected $appends = ['my_custom_field'];
public function getMyCustomFiledAttribute(){
$newData = doSomething($this->existent_field);
return $newData;
}
}
The $appends variable add the new fields to all responses generated from the Model, as a normal database field.
P.S.: You can create a getAttribute() function for existent database attribute and return the value as you want!
For example: getCreatedAtAttribute()

How to use Model in function parameter to reduce code in laravel?

This is how i am trying to update record in my laravel function which doesn't work
public function completePacking(SaleOrder $saleOrder)
{
$saleOrder->update(['status' => 'Draft']);
}
it is working
public function completePacking($id)
{
$saleOrder = SaleOrder::findOrFail($id);
$saleOrder->status = 'Dispatched';
$saleOrder->save();
}
i want to use first method because it is less code but that is not working
Add 'status' to your $fillable attribute in your SaleOrder model.
Or remove 'status' from $guarded attribute in SaleOrder model.
After doing any of the following, you would be able to use your desired version to update status.
Read more on https://laravel.com/docs/5.7/eloquent#mass-assignment
$saleOrder = SaleOrder::where('id', $id)->update(['status' => 'Draft']);

Adding two created_at on one insert

I want to add another created_at column on the database columns for reference.
I want to have a created_at and created_at1 at the same time.
This is what I have in my Model:
const CREATED_AT = 'created_at1';
protected $dates = [created_at, created_at1];
But I'm receiving this error: Field 'created_at' doesn't have a default value.
You do not need to add casting to the created_at since Laravel has already done it for you.
You need to add inside the string like
protected $dates = ['created_at1']
If you want to set the created_at1 when a new model is created, you can add Model Events.
Inside your model,
protected static function boot(){
parent::boot();
static::creating(function($model){
$model->created_at1 = Carbon::now();
});
}
Inside controller
$model = Model::create([
...
]);
Now it will set created_at and created_at1
For the insert, you have to manually save the value to the created_at1 because it will not reflect the model event.
Model::insert([
...
'created_at1' => Carbon::now()
]);
You might not be passing value to created_at column while inserting data. Please do check. If this is not the case please do provide more information on your problem.

Laravel not retrieving milliseconds in DateTime columns

In Laravel, I have some columns with milliseconds. Note that some other columns do not have milliseconds (e.g. created_at, updated_at).
Here is my migration:
Schema::create('brands', function (Blueprint $table) {
$table->increments('id');
$table->dateTime('sync_date_time', 3);
$table->timestamps();
});
My model is simply:
class Brand extends Model {}
Yet when I have a record with milliseconds (e.g. 2018-12-19 01:40:46.512) , and execute:
$brand->sync_date_time;
It always returns a string without the milliseconds portion (e.g. 2018-12-19 01:40:46).
Why is this happening, and how can I fix this issue?
Note that this is not a Carbon issue, as I am not using Carbon for this field at all.
I saw a similar question here:
Laravel timestamps to show milliseconds
that claims this is in fact a PDO issue and suggest using a string or timestamp. But it's quite an old answer, so I was hoping there is a better answer.
This issue has nothing to do with Laravel, but due to a known bug in PDO with newer versions of MySQL.
This issue exists in PHP 7.2 and has been fixed in PHP 7.3.
So to fix this issue, just upgrade to PHP 7.3.
If you want to auto-cast to a Carbon date, I found the following does NOT work when it comes to setting, saving and toArray:
protected $casts = [
'sync_date_time' => 'datetime:Y-m-d H:i:s.v',
];
Instead I found that you must have nothing in casts for the column, and add the following methods to the model:
public function getSyncDateTimeAttribute(string $value): Carbon
{
return Carbon::createFromFormat('Y-m-d H:i:s.v', $value);
}
public function setSyncDateTimeAttribute(Carbon $value): void
{
$this->attributes['fidel_created'] = $value->format('Y-m-d H:i:s.v');
}
But remember you still need to upgrade to PHP 7.3!
Put this at the top of your model:
protected $dates = ['sync_date_time'];
Laravel will automatically cast this to a Carbon instance which you can then format simply with:
$brand->sync_date_time->format('Y-m-d H:i:s.u')
You can also try setting the protected $dateFormat variable at the top of your model too.
I had the same bug with Lumen 8.
I've change this code in my model class :
protected $casts = [
'last_activity_at' => 'datetime',
];
to
public function getLastActivityAtAttribute(?string $value): ?Carbon
{
return empty($value) ? null : Carbon::createFromFormat('Y-m-d H:i:s.v', $value);
}
public function setLastActivityAtAttribute(Carbon $value): void
{
$this->attributes['last_activity_at'] = $value->format('Y-m-d H:i:s.v');
}
Milliseconds save well now.
I tried this code too but it doesn't work :
protected $casts = [
'last_activity_at' => 'datetime:Y-m-d H:i:s.v',
];

How does laravel/lumen dateformat 'U' actually works?

I'm not able get the timestamps with dateformat 'U' working in lumen.
In migration:
$table->timestamps();
In Model:
protected $dateFormat = 'U';
protected $dates = [
'created_at',
'updated_at',
'deleted_at'
];
public function getDateFormat()
{
return 'U';
}
Insert row from controller:
$model = new ApiKey;
$model->random= rand();
$model->name = $name;
$model->scope = $scope;
$model->save();
It does insert the row in the database but with 0000-00-00 00:00:00 values for created_at and updated_at columns.
Also, while retrieving a model via toArray or toJson it thrown exception:
I want lumen to autoupdate the timestamps and retrive timestamps as unixtimestamp format i.e. number of seconds from 1st Jan 1970.
Also, $table->timestamps() didn't create deleted_at column. What do I need to do get this column created via laravel.
Is there any other option than $table->timestamp('deleted_at');?
I've found a solution bay changing timestamps columns to int. But I want the things to be done in laravel way.
Unix timestamps are integers, not compatible with SQL datetime/timestamp fields. If you want to use unix timestamps, use integer field types for storage.
The timestamps() method only creates created_at and updated_at, soft deletes are not enabled by default. This method should not be used if you want integer storage.
If you only want to change the output format when serializing data, use casting:
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'created_at' => 'datetime:U',
];
This is likely caused by the fact that Laravel/Lumen created the date/time columns as the type timestamp not int so you're trying to save wrong type of data in the field, causing the 0000-00-00 00:00:00.
This would also cause the carbon issue as you are trying to createFromFormat with the wrong format compared to the content.
You can use $table->integer('deleted_at'); in your migration to create a deleted_at column.
TL;DR:
Manually create your date time columns with $table->integer('updated_at').
<?php
namespace App\Traits;
use Illuminate\Support\Carbon;
trait sqlServerDateFormat
{
public function fromDateTime($value)
{
return Carbon::parse(parent::fromDateTime($value))->format('d-m-Y H:i:s');
}
}

Resources