Laravel not retrieving milliseconds in DateTime columns - laravel

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',
];

Related

Right way to save timestamps to database in 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/

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

Laravel Eloquent Save doesn't save

I am trying to save survey answers in my db, because of some to me unknown reason the ->save() method is not working, ->update() is working however.
I keep getting the error Array to string conversion every time I try to save.
I have used dd/return/var_dump/print_r whatever would work, to show that it was working up to that step. So now I know it works up to the ->save() method.
My controller:
$array = json_decode($request->getContent(), true);
foreach ($array as $survey) {
$objAns = new Survey_Answer();
$objAns->name = $survey['surveyName'];
$objAns->answers = $survey['answersPerQuestion'];
if($survey['complete'] === true) {
$objAns['complete'] = 1;
} else if($survey['complete'] === false) {
$objAns->complete = 0;
}
$objAns->save();
}
return;
My model:
class Survey_Answer extends Model
{
protected $fillable = ['name', 'answers', 'complete'];
}
My migration:
public function up()
{
Schema::create('survey__answers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('survey_id')->unsigned()->nullable()->index();
$table->foreign('survey_id')->references('id')->on('surveys')->onDelete('cascade');
$table->string('name');
$table->json('answers');
$table->boolean('complete');
$table->timestamps();
});
}
I expect the code to save everything that I send along with the $request. This only results in a error: Array to string conversion.
Thank you very much for your help
You need to store your $survey['answersPerQuestion'] in json format not as an array.
$objAns->answers = json_encode($survey['answersPerQuestion']);
Although as #Lucas Arbex pointed out in the comments, there's certainly a better way to store it.
I would suspect that $survey['answersPerQuestion'] is an array but you are trying to store it in a json column.
You can use Laravel's Array & JSON Casting to cast the array to a json string.
The array cast type is particularly useful when working with columns
that are stored as serialized JSON. For example, if your database has
a JSON or TEXT field type that contains serialized JSON, adding the
array cast to that attribute will automatically deserialize the
attribute to a PHP array when you access it on your Eloquent model:
Your Model:
class Survey_Answer extends Model
{
protected $fillable = ['name', 'answers', 'complete'];
protected $casts = [
'answers' => 'array',
];
}
Once the cast is defined, you may access the options attribute and it
will automatically be deserialized from JSON into a PHP array. When
you set the value of the options attribute, the given array will
automatically be serialized back into JSON for storage:
$user = App\User::find(1);
$options = $user->options;
$options['key'] = 'value';
$user->options = $options;
$user->save();

laravel 5.6 Eloquent : eloquent relationship model creation issue

in my controller i create an Eloquent Model Instance passign throug a relation. The model is loaded on controller's __construct, that's why is present a $this->store and not a $store.
public function index()
{
if (is_null($this->store->gallery)) {
$this->store->gallery()->create([
'title' => 'gallery_title,
'description' => 'gallery_description',
]);
}
$gallery = $this->store->gallery;
dd($gallery);
return view('modules.galleries.index', compact('gallery'));
}
Simply if a store's gallery is not present yet, let's create it.
The first time i print out my dd() is ALWAYS null, if i reload the page the dd() show correctly my gallery model.
The things is weird for me, seems like the first time the creation is done but not ready... I can work around but why this code doesn't work the first time?
Help is very appreciate.
Relationship codes: on gallery ....
public function store()
{
return $this->belongsTo(Store::class);
}
on store...
public function gallery()
{
return $this->hasOne(Gallery::class);
}
When using the $this->store->gallery()->create() method, the original method is not hydrated with the new value, you can simply do a
$gallery = $this->store->refresh()->gallery;
OR
$gallery = $this->store->load('gallery')->gallery;
if you want to make your code cleanner you can do that in your Store Model:
public function addGallery($gallery){
$this->gallery()->create($gallery);
return $this->load('gallery')->gallery;
}
And that in your controller:
$gallery = $this->store->addGallery([
'title' => 'gallery_title',
'description' => 'gallery_description',
]);
and voila ! You have your gallery ready to be used :)
It's the lazy load part of Eloquent. basicly, when you tested for it with is_null($this->store->gallery) it sets it to that value.
when you tried to recover it again, it did not do the DB query, it just loaded the value already present (null).
after creation you need to force reload the relation:
$this->store->load('gallery');
or
unset($this->store->gallery);
or
$gallery = $this->store->gallery()->get();

exclude certain records from laravel scout/algolia

I am using laravel scout to upload records for searching in algolia. I have added the searchable trait to my model, and everything is working fine.
There is a case now where I don't want to add certain records to my index if they have set status I.E UNPUBLISHED.
is there away I can evaluate the status field and decide if I want the model to be uploaded to the index?
Just use $model_name->unsearchable() to remove it from your Algolia index.
See "Removing Records" in the documentation for more details: https://laravel.com/docs/5.3/scout#removing-records
You can use method toSearchableData() and in case the status is Unpublished, just return empty array and the record will be skipped.
Otherwise just return $this->toArray().
It will do the trick.
Say we have a Post model, with a boolean published attribute, and a model factory to seed our table as follows:
$factory->define(App\Post::class, function (Faker\Generator $faker) {
$tile = $faker->realText(50);
$date = $faker->dateTime;
return [
'title' => $tile,
'body' => $faker->realText(500),
'published' => $faker->boolean(80),
'created_at' => $date,
'updated_at' => $date
];
});
Let's say we will seed 10 records.
public function run()
{
factory(App\Article::class, 10)->create();
}
If we tried to exclude unpublished records within the toSearchableArray() method, as suggested:
public function toSearchableArray()
{
if (! $this->published) {
return[];
}
// ...
}
When seeding the posts table, instead of ignoring unpublished records by returning an empty array, scout will keep asking the model factory for a published model.
For example, if two of the seeded records were randomly unpublished, scout would index all 10 records (instead of 8) anyway, replacing the unpublished ones by a new model factory (with a published set attribute). Thus causing to have two inexistent (on our table) records in the algolia index. Quite confusing.
The "neatest" way around this I could come up with, was to listen to the saved/updated events (saving/updating won't cut it) in the model's boot method.
protected static function boot()
{
static::saved(function ($model) {
if (! $model->published) {
$model->unsearchable();
}
});
static::updated(function ($model) {
if (! $model->published) {
$model->unsearchable();
}
});
parent::boot();
}
Check out this question. This problem has been solved in the new version of Scout
Adding Index to Laravel Scout Conditionally (Algolia)

Resources