Calculate difference between two dates with timestamps in Laravel - sql

I have in my table a timestamps with created_at and updated_at
$table->timestamps();
But I want to add another column that calculates the difference between created_at and updated_at in days
Should I do that in SQL or in my controller?

You can do that from within your Eloquent model.
Let's assume your model has the name User.
You can create a computed field by defining an Accessor.
class User extends Model
{
$dates = [
'updated_at',
'created_at'
];
$appends = ['diffInDays'];
public function getDiffInDaysAttribute()
{
if (!empty($this->created_at) && !empty($this->updated_at)) {
return $this->updated_at->diffInDays($this->created_at);
}
}
}
Some explanation
By adding created_at and updated_at to the $dates array, Laravel automatically casts your date values to Carbon.
Now, if you do something like $user->created_at, you don't get the string, but a Carbon instance of that date. This allows you to make some nice date calculations, like the one above.
By adding an Accessor with the getDiffInDaysAttribute function, you can call the days difference via $user->diffInDays like a normal attribute, although it is not on the model.
But if you would now do something like $user->toArray(), the diffInDays attribute will not be available.
To always add the difference in days when you retrieve User data, you can add the field to the $appends array.
That way, the field will always be returned when you retrieve User data via Eloquent.

To have it auto save this value on every update to that model, then you can put this in the model.
public static function boot()
{
parent::boot();
static::updating(function($model) {
$diffInDays = $model->updated_at->diffInDays($model->created_at);
$model->timestamp_difference = $diffInDays;
});
}
timestamp_difference is the name of the DB column that I used, this can be whatever you want it to be.

Use Carbon for count date difference in days.
$to = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $created_at);
$from = \Carbon\Carbon::createFromFormat('Y-m-d H:i:s', $updated_at);
$diff_in_days = $to->diffInDays($from);

Related

Laravel relationship with custom method

Table project:
project_id (int)
requestor_id (uuid)
Table requestor:
requestor_id (uuid)
Model Project:
public function requestor() {
return $this->hasOne('App\Models\Requestor', 'requestor_id', 'requestor_id');
}
Model Requestor has one method:
// this method return object users info from ldap
public function getLdapAttribute() {
$ldapWrapper = new LdapWrapper();
return $ldapWrapper->checkIfUuidExists($this->requestor_id, true);
}
Select all projects with requestor relationship:
$query = (new Project)->newQuery()->with(['requestor'])->get();
And Question is:
How can I select all projects with requestor relationship and on every requestor object call method getLdapAttribute and return all as one object?
Thank you very much :)
You can put the name of that "Ldap" attribute in the $appends array of the App\Requestor Class and Eloquent will automatically append a property named ldap with the value ,of the returned for the getLdapAttribute method.
Link to the Official Laravel Documentation for this Eloquent feature !
As SQL query of getLdapAttribute method is not specified, we can fetch projects first, and then iterate over and get that attribute.
If SQL query of getLdapAttribute is given then we can get all the data in one query (here we get attributes after the first query of fetching projects).
$projects = Project::with(['requestor'])
->get()
->each(function ($project) {
$project->requestor->getLdapAttribute();
});

Yii2 REST API relational data return

I've set up Yii2 REST API with custom actions and everything is working just fine. However, what I'm trying to do is return some data from the API which would include database relations set by foreign keys. The relations are there and they are actually working correctly. Here's an example query in one of the controllers:
$result = \app\models\Person::find()->joinWith('fKCountry', true)
->where(..some condition..)->one();
Still in the controller, I can, for example, call something like this:
$result->fKCountry->name
and it would display the appropriate name as the relation is working. So far so good, but as soon as I return the result return $result; which is received from the API clients, the fkCountry is gone and I have no way to access the name mentioned above. The only thing that remains is the value of the foreign key that points to the country table.
I can provide more code and information but I think that's enough to describe the issue. How can I encode the information from the joined data in the return so that the API clients have access to it as well?
Set it up like this
public function actionYourAction() {
return new ActiveDataProvider([
'query' => Person::find()->with('fKCountry'), // and the where() part, etc.
]);
}
Make sure that in your Person model the extraFields function includes fKCountry. If you haven't implemented the extraFields function yet, add it:
public function extraFields() {
return ['fKCountry'];
}
and then when you call the url make sure you add the expand param to tell the action you want to include the fkCountry data. So something like:
/yourcontroller/your-action?expand=fKCountry
I managed to solve the above problem.
Using ActiveDataProvider, I have 3 changes in my code to make it work.
This goes to the controller:
Model::find()
->leftJoin('table_to_join', 'table1.id = table_to_join.table1_id')
->select('table1.*, table_to_join.field_name as field_alias');
In the model, I introduced a new property with the same name as the above alias:
public $field_alias;
Still in the model class, I modified the fields() method:
public function fields()
{
$fields = array_merge(parent::fields(), ['field_alias']);
return $fields;
}
This way my API provides me the data from the joined field.
use with for Eager loading
$result = \app\models\Person::find()->with('fKCountry')
->where(..some condition..)->all();
and then add the attribute 'fkCountry' to fields array
public function fields()
{
$fields= parent::fields();
$fields[]='fkCountry';
return $fields;
}
So $result now will return a json array of person, and each person will have attribute fkCountry:{...}

Yii 1.x how to change model name in POST (CActiveForm)

Its posible change model name in post/get?
I have model with large names, like "VerLargeModelName" and many parameters.
It does not fit in GET (query string limit).
Update:
i need just change generated inputs from CActiveForm (change LongModelName[a] to short[a])
You can just change the name. You can do this like this echo $form->textFieldBlock($model,'name',array('name' => 'x["name"]') or whatever you want. You could also create a class (widget) with does this for your.
class MyActiveForm extends CActiveForm {
public function hiddenField($model, $attribute, $htmlOptions = array()) {
if(isset($htmlOptions['shortName'])) {
$htmlOptions['name'] = $htmlOptions['shortName'] . "[".$attribute."]";
unset($htmlOptions['shortName']);
}
return parent::hiddenField($model, $attribute, $htmlOptions);
}
}
You change CActiveFrom from the widget to MyActiveForm. Then use $form->textFieldBlock($model,'name',array('shortName' => 'x'). You could also change the above code to always change to a shortname without the htmlOptions. So that it is always x. However you could not have two form at once in this case. Benifit is that you would not need to add array('shortName' => 'x') to all of them, but just change CActiveFrom to MyActiveForm. So that would save you time, but cost your flexibility (with you might need later on maybe).
You have to create a function offcourse for every input field you want to use from ActiveRecord. The name of the element would become x['name']
In the controller you could simply do $model->attributes = $_POST['x'].

Handling uuid pk column in yii

I'm using UUID's as PK in my tables. They're stored in a BINARY(16) MySQL column. I find that they're being mapped to string type in YII. The CRUD code I generate breaks down though, because these binary column types are being HTML encoded in the views. Example:
<?php echo
CHtml::link(CHtml::encode($data->usr_uuid), /* This is my binary uuid field */
array('view', 'id'=>$data->usr_uuid)); ?>
To work around this problem, I overrode afterFind() and beforeSave() in my model where I convert the values to/from hex using bin2hex and hex2bin respectively. See this for more details.
This takes care of the view problems.
However, now the search on PK when accessing a url of the form:
http://myhost.com/mysite/user/ec12ef8ebf90460487abd77b3f534404
results in User::loadModel($id) being called which in turn calls:
User::model()->findByPk($id);
This doesn't work since the SQL is being generated (on account of it being mapped to php string type) is
select ... where usr_uuid='EC12EF8EBF90460487ABD77B3F534404'
What would have worked is if I could, for these uuid fields change the condition to:
select ... where usr_uuid=unhex('EC12EF8EBF90460487ABD77B3F534404')
I was wondering how I take care of this problem cleanly. I see one possiblity - extend CMysqlColumnSchema and override the necessary methods to special case and handle binary(16) columns as uuid type.
This doesn't seem neat as there's no support for uuid natively either in php (where it is treated as string) or in mysql (where I have it as binary(16) column).
Does anyone have any recommendation?
If you plan using it within your own code then I'd create my own base AR class:
class ActiveRecord extends CActiveRecord
{
// ...
public function findByUUID($uuid)
{
return $this->find('usr_uuid=unhex(:uuid)', array('uuid' => $uuid));
}
}
If it's about using generated code etc. then customizing schema a bit may be a good idea.
I used the following method to make working with uuid (binary(16)) columns using Yii/MySQL possible and efficient. I mention efficient, because I could have just made the column a CHAR(32) or (36) with dashes, but that would really chuck efficient out of the window.
I extended CActiveRecord and added a virtual attribute uuid to it. Also overloaded two of the base class methods getPrimaryKey and setPrimaryKey. With these changes most of Yii is happy.
class UUIDActiveRecord extends CActiveRecord
{
public function getUuid()
{
$pkColumn = $this->primaryKeyColumn;
return UUIDUtils::bin2hex($this->$pkColumn);
}
public function setUuid($value)
{
$pkColumn = $this->primaryKeyColumn;
$this->$pkColumn = UUIDUtils::hex2bin($value);
}
public function getPrimaryKey()
{
return $this->uuid;
}
public function setPrimaryKey($value)
{
$this->uuid = $value;
}
abstract public function getPrimaryKeyColumn();
}
Now I get/set UUID using this virtual attribute:
$model->uuid = generateUUID(); // return UUID as 32 char string without the dashes (-)
The last bit, is about how I search. That is accomplished using:
$criteria = new CDbCriteria();
$criteria->addCondition('bkt_user = unhex(:value)');
$criteria->params = array(':value'=>Yii::app()->user->getId()); //Yii::app()->user->getId() returns id as hex string
$buckets = Bucket::model()->findAll($criteria);
A final note though, parameter logging i.e. the following line in main.php:
'db'=>array(
...
'enableParamLogging' => true,
);
Still doesn't work, as once again, Yii will try to html encode binary data (not a good idea). I haven't found a workaround for it so I have disabled it in my config file.

Apache Wicket DateTextField clear

I have a problem in my DateTextField in Wicket. I want to set it by default to nothing, null, so date can be choosed after someone picks it. But by default it sets value to current day. How to "clear" it so nothing is in this datetextfield? Here is my Datefield code:
DateField date_insert_date_from = new DateField("insert_date_from", new PropertyModel(this, "date")) {
/**
* Format date to yyyy-MM-dd pattern.
*/
#Override
protected DateTextField newDateTextField(String id, PropertyModel dateFieldModel) {
return DateTextField.forDatePattern(id, dateFieldModel, "yyyy-MM-dd");
}
};
form.add(date_insert_date_from);
As #kan says, you need to make sure the modelobject for the datefield is null.
Simple way to do so is:
DateField date_insert_date_from = new DateField("insert_date_from", new Model<Date>(null));
Now, if anyone enters a date into the datefield and submits the form, the Model will contain the chosen date and you can retrieve it by writing:
Date chosenDate = date_insert_date_from.getModelObject();
If you want to use a propertymodel, as you do, you need to make sure the object on which the propertymodel acts (this in your case) has a field capable of holding a date and getter/setter methods for that field.
In your case, this.date should be initialized with null and this should have
public Date getDate()
and
public void setDate(Date date)
methods.