How to customize date mutators in Laravel? - php

I've created a few datetime fields in my database, and as is described in Laravel documentation, I can "customize which fields are automatically mutated". However there's no example showing how it can be done, nor is there any search result. What should I do to make certain fields auto mutate?
For example, I created a table called "people" in migration, one of the fields is defined as this:
class CreatePeopleTable extends Migration {
public function up(){
Schema::create("bookings",function($table){
...
$table->dateTime("birthday");
...
}
}
}
And I defined a model for "people" in models:
class People extends Eloquent{
//nothing here
}
If I refer to the birthday of a People instance, it'll be string, instead of DateTime
$one=People::find(1);
var_dump($one->birthday);
//String
The date mutator should be able to convert it directly to Carbon object, but the documentation doesn't say much about how it should be implemented.

In your People model just add this array:
protected $dates = array('birthday');
Laravel's Model.php internaly merges your fields with the default ones like this:
/**
* Get the attributes that should be converted to dates.
*
* #return array
*/
public function getDates()
{
$defaults = array(static::CREATED_AT, static::UPDATED_AT, static::DELETED_AT);
return array_merge($this->dates, $defaults);
}

According to this doc, you can use model member function getDates() to customize which fileds are automatically mutated, so the following example will return Carbon instance instead of String:
$one = People::find(1);
var_dump($one->created_at);//created_at is a field mutated by default
//Carbon, which is a subclass of Datetime
But it doesn't say clearly how to add your own fields. I found out that the getDates() method returns an array of strings:
$one = People::find(1);
echo $one->getDates();
//["created_at","modified_at"]
So what you can do is appending field names to the return value of this method:
class People extends Eloquent{
public function getDates(){
$res=parent::getDates();
array_push($res,"birthday");
return $res;
}
}
Now birthday field will be returned as a Carbon instance whenever you call it:
$one = People::find(1);
var_dump($one->birthday);
//Carbon

What do you mean by: automatically mutated?
If you mean mutated after being retrieved from DB use Accessors and Mutators (Laravel docs).
Add this to your model:
public function getDateAttribute( $date )
{
// modify $date as you want, example
// $date = new \Carbon\Carbon($date);
// $date->addDay()
// return (string)$date
}

As Sasa Tokic says, add protected $dates = array('birthday'); to your People model like so:
class People extends Eloquent{
protected $dates = array('birthday');
}
You can then use Carbon to do clever things to this value, like so:
$people->birthday->format('jS F Y')
PHP's date() function docs (http://uk3.php.net/manual/en/function.date.php) and Carbon's docs (https://github.com/briannesbitt/Carbon) will help here:

Related

Extending DB facade Laravel

I would like to convert a timestamp and have some other values related to it. My question is how I can introduce my own method like DB::raw() that appends everything to the current select values.
So, for instance, for something like this
$user = DB::table('users')
->select('*', DB::timestamp('timestamp_column', 'convert_timezone', 'called_as'))
->where('id', 1)->first();
Let's assume that I am trying to get the value for created_at column and it's called as converted_created_at and it should return something like below.
{
id: 1,
name:'John Doe',
converted_created_at: {
'utc_time': 'created_at value as that is in utc by default',
'converted_time': 'timestamp converted into user timezone',
'diff': '10 hours ago' // difference between created_at and current timestamp
}
}
So, how do I introduce my own method that does this?
You can take example of any SQL database as you wish.
I know I can do that with Model but I wanted to see how to approach this problem using a facade.
Thank you in advance for your help.
First look here: https://stackoverflow.com/a/40615078/860099 - Try this Extend DB facade:
namespace App\Facades;
use Illuminate\Support\Facades\DB as DBBase;
class DB extends DBBase {...}
and in config/app.php change
'DB' => Illuminate\Support\Facades\DB::class,
to
'DB' => App\Facades\DB::class,`
(i write code from head)
Alternative:
You can easily create helper class eg. DBTools witch static methods and inside that methods you will use DB and construct proper query. And use it like that DBTools::yourMethod(...)
As argument to that method you can give... QUERY here is example of calling this method
DBTools::yourMethod(User::query())->first();
and inside you can easyily manipulate that query and return updated version.
ALTERNATIVE: If your goal is to add some new filed in Model (json) that not exist in db but is generated then you can use $appends (look: mutators and appends)
class User extends Model
{
protected $appends = ['converted_created_at'];
...
public function getConvertedCreatedAtAttribute() {
return ...; // return generated value from other fields/sources
}
Thanks to #kamil for showing me the way.
I am writing an answer in case anyone in the future finds this helpful.
I have come up with my own method that helps to convert timezone easily without writing too much code inside select query for DB facade for PostgreSQL.
I have created a file like this now.
<?php
namespace App\Custom\Facade;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class DBTools extends DB
{
/**
* Convert a timestamp
* #param $timestamp - timestamp to be converted
* #param bool $insideRaw - if this helper method is getting used inside DB::raw() method
* #param null $timezone
* #param null $format - time format
* #param null $calledAs - column to called as
* #return \Illuminate\Database\Query\Expression|string
*/
public static function convertTime($timestamp, $insideRaw = false, $timezone = null, $format = null, $calledAs = null)
{
if (Auth::check()) {
if (!$timezone)
$timezone = Auth::user()->timezone;
if (!$format)
$format = Auth::user()->time_format;
}
$query = "to_char($timestamp at time zone '$timezone', '$format')" . ($calledAs ? " as $calledAs" : '');
if (!$insideRaw) {
return DB::raw($query);
}
return $query;
}
}
Now this can be easily be called inside select for DB facade or inside DB::raw() in case you're handling much more complicated query.
Hope this helps someone.

Change automatically the format of date fields (Laravel)

I'm looking for a DateTime Mutator that change the format of dates, I'm working with Oracle DB and the admitted Format is (DD/MM/YYYY) and the input type "date" stores dates in (YYYY,MM,DD) format.
I found the $date function and a trait by Torzer, but I have to indicate the fields that I want to convert the format.
is there some trait or function that detect all date fields and convert them automatically in a format (DD/MM/YYYY)? this without indicate the field.
nowadays I use protected $date in my model:
protected $dates = [ 'fecha_nac', 'fecha_nac1', 'fecha_nac2', ];
By default laravel uses date formate 'Y-m-d H:i:s' if you want to use a different format you can customize it in your model in the following way.
protected $dateFormat = 'your date formate';
in your case it will be.
protected $dateFormat = 'd-m-Y';
You can override the getDates method on HasAttributes trait.
/**
* Get the attributes that should be converted to dates.
*
* #return array
*/
public function getDates()
{
$defaults = [static::CREATED_AT, static::UPDATED_AT];
return $this->usesTimestamps()
? array_unique(array_merge($this->dates, $defaults))
: $this->dates;
}
On your model:
public function getDates()
{
$dates = parent::getDates();
// add your dynamic logic here
return $dates;
}
I would really go for explicitly defining which fields should be converted as these dynamic operations can be expensive if you are working with the model quite a lot.

Creating dynamically named mutators in Laravel Eloquent models

I have a list of date fields, and all of them have the same logic in their mutators. I would like to extract this functionality to a trait so that in the future all I would need is to create an array of date fields in the model and use the trait.
Something like this:
foreach( $dates as $date ) {
$dateCamelCase = $this->dashesToUpperCase($date);
$setDateFunctionName ='set'.$dateCamelCase.'Attribute';
$this->{$setDateFunctionName} = function() use($date) {
$this->attributes[$date] = date( 'Y-m-d', strtotime( $date ));
};
}
Before answering your specific question, let's first see how Eloquent mutators work.
How eloquent mutators work
All Eloquent Model-derived classes have their __set() and offsetSet() methods to call the setAttribute method which takes care of setting the attribute value and mutating it, if needed.
Before setting the value, it checks for:
Custom mutator methods
Date fields
JSON castables and fields
Tapping into the process
By understanding this, we can simply tap into the process and overload it with our own custom logic. Here's an implementation:
<?php
namespace App\Models\Concerns;
use Illuminate\Database\Eloquent\Concerns\HasAttributes;
trait MutatesDatesOrWhatever
{
public function setAttribute($key, $value)
{
// Custom mutation logic goes here before falling back to framework's
// implementation.
//
// In your case, you need to check for date fields and mutate them
// as you wish. I assume you have put your date field names in the
// `$dates` model property and so we can utilize Laravel's own
// `isDateAttribute()` method here.
//
if ($value && $this->isDateAttribute($key)) {
$value = date('Y-m-d', strtotime($value));
}
// Handover the rest to Laravel's own setAttribute(), so that other
// mutators will remain intact...
return parent::setAttribute($key, $value);
}
}
Needless to say that your models require to use this trait to enable the functionality.
You ain't gonna need it
If mutating dates is the only usecase you need to have "dynamically named mutators", that's not required at all. As you might have already noticed, Eloquent's date fields can be reformatted by Laravel itself:
class Whatever extends Model
{
protected $dates = [
'date_field_1',
'date_field_2',
// ...
];
protected $dateFormat = 'Y-m-d';
}
All fields listed there will be formatted as per $dateFormat. Let's not reinvent the wheel then.

How do I change the date format Laravel outputs to JSON?

I've built an application in Laravel and eloquent returns dates in this format: 2015-04-17 00:00:00. I'm sending one particular query to JSON so I can make a graph with D3, and I think I would like the dates in ISO8601 ('1995-12-17T03:24:00') or some other format that plays nice with the javascript Date() constructor.
Is there a way to change the date format being output to JSON on the Laravel end? I'm not sure using a mutator is the best approach because it would affect the date in other parts of my application.
Or would it be better to leave the JSON output as is, and use some javascript string methods to manipulate the date format before passing it to the Date() constructor? Which approach is more efficient?
Here is my model:
class Issue extends Model {
protected $fillable = [
'client_id',
'do',
'issue_advocate',
'service_number',
'issue_location',
'issue_description',
'level_of_service',
'outcome',
'referral_id',
'file_stale_date',
'date_closed',
'issue_note',
'staff_hours'
];
protected $dates = [
'do',
'date_closed',
'file_stale_date'
];
public function setDoAttribute($value)
{
$this->attributes['do'] = Carbon::createFromFormat('F j, Y', $value)->toDateString();
}
}
Here is my query:
$issues = Issue::with('issuetypes')
->select(['do','level_of_service','outcome','id'])
->whereBetween('do',[$lastyear,$now])
->get()->toJson();
And the JSON I get back:
[{"do":"2014-12-23 00:00:00","level_of_service":1,"outcome":1,"id":18995,"issuetypes":[{"id":9,"issuetype":"Non Liberty","pivot":{"issue_id":18995,"issuetype_id":9}}]}]
I know it's an old question, but there is still no good answer to that.
Changing protected $dateFormat will affect database, instead method serializeDate() must be overriden
class MyModel extends Eloquent {
protected function serializeDate(\DateTimeInterface $date) {
return $date->getTimestamp();
}
}
Or myself I chose to create trait
trait UnixTimestampSerializable
{
protected function serializeDate(\DateTimeInterface $date)
{
return $date->getTimestamp();
}
}
and then add
class SomeClassWithDates extends Model {
use UnixTimestampSerializable;
...
}
Expanding on umbrel's answer a bit I've created a trait that turns the DateTimeInstance into a Carbon instance so that I can easily make use of it's common formats.
In my particular case I wanted to serialize all dates according to ISO-8601.
The trait is as follows...
use DateTimeInterface;
use Carbon\Carbon;
trait Iso8601Serialization
{
/**
* Prepare a date for array / JSON serialization.
*
* #param \DateTimeInterface $date
* #return string
*/
protected function serializeDate(DateTimeInterface $date)
{
return Carbon::instance($date)->toIso8601String();
}
}
and from here I can simply use it on the relevant models...
class ApiObject extends Model
{
use Iso8601Serialization;
}
Obviously you could name the trait more appropriately if you're using a different format but the point is that you can use any of Carbon's common formats simply by replacing toIso8601String() with the format you need.
I strongly suggest you use the Carbon class to handle all your dates and datetimes variables, it already comes with Laravel 5 so you can start using whenever you want.
Check it out on Carbon Repo to see what you can do with it.
As an example, you can format dates from your model like this
Carbon::parse($model->created_at)->format('d-m-Y')
As for a good approach, I would suggest to use the Repository Pattern along with Presenters and Transformers. By using it you can define how you want your json to be displayed/mounted and opt to skip the presenter whenever you want in order to still get you Eloquent model returned when you make your queries.
use this function in any Model
protected function serializeDate(DateTimeInterface $date){
return $date->format('Y-m-d h:i:s');
}
Result
You can easily change the format that used to convert date/time to string when your models are serialized as JSON by setting $dateFormat property of your model to the format you need, e.g.:
class MyModel extends Eloquent {
protected $dateFormat = 'Y-m-d';
}
You can find docs on different placeholders you can use in the format string here: http://php.net/manual/en/datetime.createfromformat.php
If you use usuals techniques as
protected $dateFormat = 'Y-m-d';
or
protected function serializeDate(DateTimeInterface $date) { ... }
or
protected $casts = [ "myDate" => "date:Y-m-d" ];
It'll only works when laravel will serialize itself objects. And you will anyway to put those code inside all models, for all properties.
So my solution, you have to (too) put this code in all models for all date properties by at last, it works in ALL cases :
public function getMyDateAttribute()
{
return substr($this->attributes['my_date'], 0, 10);
}

Symfony2 Map Entity column to another class

I'm new to symfony2. I'm having the following problem:
I have an Entity named Birthday (I've simplified the entity of course)
class Birthday{
private $date;
//#return \DateTime object
public function getDate(){
return $this->date;
}
public function __toString(){
return $this->date->format('IM AWARE IS NOT A GOOD IDEA: the format should be taken from parameters.yml');
}
}
I'd like to get an object of ANOTHER type instead of \DateTime.
How could I do this?
I'd like to have a __toString method in the BirthDay Entity that would display the date in a format taken from parameters.yml.
Again, I'm aware it is absolutely not a good idea to pass a service container into an Entity since entities are POPO's .
I'm planning on doing a new Class that would take in account the date time format that I specify in parameters.yml
add format property to the Birthday class and call it in __toString method..
example:
Entity:
public function __toString()
{
return $this->date->format($this->format);
}
Controller:
$format = $this->container->getParameter('FORMAT_FROM_PARAMETERS')
...
$birthday->setFormat($format);
...
print $birthday;
die;

Categories