Yii2 : To exclude an attribute in afterFind() function - php

I have created_at and updated_at column in DB and I am filling it with timestamp.
To show the created_at and updated_at date in index.php(To the user in GridView) I have converted the timestamp to YYYY-MM-DD format using afterFind function in Model.
From the GridView if an edit button is clicked, I need to update a status in the row. So my code in the controller is
$existingRow = Project::findOne($id) // From the parameter
$existingRow->status = 2
$existingRow->save()
On executing the above command, the created_at field is saving with "YYYY-MM-DD" format which is converted from "afterFind()" function in the model.
How can I get the non-converted timestamp value to be saved ?

To handle created_at and updated_at you should use a TimestampBehavior. In your corresponding model add:
use yii\behaviors\TimestampBehavior;
public function behaviors()
{
return [
TimestampBehavior::className(),
];
}
Then it will automatically fill your fields on create/update your model.
To display it properly in GridView, define your column like:
<?= GridView::widget([
'dataProvider' => $dataProvider,
'columns' => [
'id',
'name',
'created_at:datetime', // or 'created_at:date' for just date
// ...
],
]) ?>
afterFind() is not best solution to such things.

Just like you overrode afterFind(), you can override the beforeSave() method in your model, to reformat the attributes to the required format before the Save operation is executed.
protected function beforeSave()
{
$parentResult = parent::beforeSave();
// pseudo code
// Change any attributes values as you require
// return true if successful ($result)
return $parentResult && $result; // Save operation will run if this returns true only
}

It may not be the expected answer, but this function got me what I want. Using $model->getOldAttribute($attributeName) gives the original data which is in the database (which is the original data not converted by afterFind() function).
$existingRow = ProjectResourceAssign::findOne($id);
$existingRow->status = 2;
// This piece of line will get you the original value of the attribute
$existingRow->created_at = $existingRow->getOldAttribute('created_at');
$existingRow->save(false);

Related

Laravel replicate including relationships but maintain original timestamps

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.

How to store one more field based on input with Laravel saving event?

I have a column in MySQL which has some integer stored and what I need is when I am saving (creating or updating) the model, calculate some modified data and store it in another column. I want to use saving event for that, but don't know how to do that.
I have tried to add the following code to the AppServiceProvider but it does not do anything.
Report::saving(function($report){
if(!is_null($report->year_month)){
$date = Carbon::parse($report->year_month . "-1");
$report->first_date = $date;
}
});
Check out the model events in laravel. Throw something like this in the model:
protected static function boot(){
parent::boot();
static::saving(function($thisModel){
$thisModel->otherField = 'your value';
}
}

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.

Get specific columns using with() in laravel with appends fields in model

In my User model, I have an appends fields:
protected $appends = [
'is_admin'
];
It will appends is_admin field in every request by using with() eager loading. However, in some scenario I don't want to return is_admin field, I am trying to use the follows:
$this->belongsTo('App\Models\User')
->select(['id', 'username', 'first_name', 'last_name']);
But it doesn't work.
Is the appends field always be appended even I use custome select field ?
appends is used when the model is serialized; it 'appends' attributes to the output.
If you have a single model and you want to not have the appends accessors added to the serialized data, you can set them hidden.
Example:
$test = new class extends Illuminate\Database\Eloquent\Model {
protected $appends = ['is_admin'];
public function getIsAdminAttribute() {
return rand(0, 1);
}
};
dump($test->toArray()); // will contain 'is_admin'
$test->makeHidden('is_admin');
dump($test->toArray()); // will not contain 'is_admin'
// This can be applied to Eloquent Collections.
$model->relation->makeHidden('is_admin');
One way of doing it.
Simply put this result to a variable
$data = $this->belongsTo('App\Models\User')->select(['id', 'username', 'first_name', 'last_name']);
and use directly
$data->makeHidden("is_admin");
This will work for you

Yii2 - How to get field value from relation

I have a table which has different types of text data from different sources, identified by type, lang, sourceId and stored in field text. Is there anyway to return by active record relation not object of table but only value of field text in oher words scalar query via relation?
Example: For now I have:
$modelName->RelationName->text) //field name storing expexted data, returns string.
Desired way is:
$modelName->RelationName//text field value returned only.
Yes But using lazy loading approach :
Update Your Relation as
public function getRelationName(){
//Related model Class Name
//related column name as select
return $this->hasOne(RelationClass::className() ,['id' => 'id'])->select('name')->scalar();
}
then get relation value as : -
$modelName->relationName//text field value returned only.
Extend your model with one or more getter for retrive the value you need using a relation eg:
In the model you need retrive the related data you can build a funtion for define the raltion (in this case an hasOne)
*/
public function getRelationName()
{
return $this->hasOne(ModelOfTheRelation::className(), ['column1' => 'column1', 'EvColumn2' => 'Evcolumn2']);
}
Then you can use getter function for the data
/* Getter f */
public function getRelatioName_field()
{
return $this->relationName->field;
}
In view you can easy obtain the data using
echo $model->relationName_field
or in gridview (dataprovider)
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'relationName_field',
this link could be useful http://www.yiiframework.com/wiki/621/filter-sort-by-calculated-related-fields-in-gridview-yii-2-0/

Categories