I want to convert created_at dates to Persian date. So I implemented getCreatedAtAttribute function to do that. Because I just want to convert dates in special situations, I declared $convert_dates property in the model with default value as false.
class Posts extends Model {
public $convert_dates = false;
/**
* Always capitalize the first name when we retrieve it
*/
public function getCreatedAtAttribute($value) {
return $this->convert_dates? convert_date($value): $value;
}
}
$Model = new Posts;
$Model->convert_dates = true;
$post = $Model->first();
echo $post->created_at; // Isn't converted because $convert_dates is false
As you see in the codes above, it seems the model properties will be re-initial in mutators so the value of $convert_dates is always false.
Is there any other trick or solution to solve this problem?
This way you can set the constructor.
public function __construct($value = null, array $attributes = array())
{
$this->convert_dates = $value;
parent::__construct($attributes);
}
Now you can access this value in your mutator.
public function getCreatedAtAttribute($value)
{
return $this->convert_dates ? convert_date($value) : $value;
}
OR
Fill the protected fillable array like this:
class DataModel extends Eloquent
{
protected $fillable = array('convert_dates');
}
Then initialize the Model as:
$dataModel = new DataModel(array(
'convert_dates' => true
));
Related
I have a CodeIgniter 4 Model.
When I return data from it with find()/findAll(), it returns all the attributes that are present in the model.
$userModel->findAll();
I'd like to hide some of them, for example, I don't want to return the dates (created_at, updated_at).
I tried to create an Entity object and return it, but it also returns everything.
Basically what I'd like to do is to have functionality like in Laravel, where you have a $hidden protected array in the Model.
I've been experimenting with the afterFind callback, but I don't like the solution. Mainly because there is a difference in the $data array when you use find() vs findAll()
protected $afterFind = ['prepareOutput'];
protected function prepareOutput(array $data) {
return $data;
}
$data['data'] is the actual data when using find()
$data['data'] is an array of arrays when using findAll();
Which kinda makes sense, but then I have to be sure to modify the data according to the method used.
Is there something I am missing in the documentation? Can this be done in a simpler way?
So I came up with this, but I'm still open to better and nicer solutions.
First I created my own Model and added the logic to hide some attributes:
<?php
namespace App\Models;
use CodeIgniter\Model;
class MyBaseModel extends Model {
protected $hidden = [];
public function prepareOutput(array $data) {
// if the hidden array is empty, we just return the original dta
if (sizeof($this->hidden) == 0) return $data;
// if no data was found we return the original data to ensure the right structure
if (!$data['data']) return $data;
$resultData = [];
// We want the found data to be an array, so we can loop through it.
// find() and first() return only one data item, not an array
if (($data['method'] == 'find') || ($data['method'] == 'first')) {
$data['data'] = [$data['data']];
}
if ($data['data']) {
foreach ($data['data'] as $dataItem) {
foreach ($this->hidden as $attributeToHide) {
// here we hide the unwanted attributes, but we need to check if the return type of the model is an array or an object/entity
if (is_array($dataItem)) {
unset($dataItem[$attributeToHide]);
} else {
unset($dataItem->{$attributeToHide});
}
}
array_push($resultData, $dataItem);
}
}
// return the right data structure depending on the method used
if (($data['method'] == 'find') || ($data['method'] == 'first')) {
return ['data' => $resultData[0]];
} else {
return ['data' => $resultData];
}
}
}
Now I can extend my existing models and fill in which attributes I want to hide:
<?php namespace App\Models;
use App\Entities\User;
class UserModel extends MyBaseModel {
protected $table = 'users';
protected $primaryKey = 'id';
protected $returnType = User::class;
protected $useSoftDeletes = false;
protected $allowedFields = ['email', 'password', 'first_name', 'last_name'];
protected $useTimestamps = true;
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
protected $allowCallbacks = true;
protected $afterFind = ['prepareOutput'];
protected $hidden = ['created_at', 'updated_at'];
}
How to assign value to my custom property ? I am comming from Yii2 background and there is pretty straight forward. Here I made this:
protected $appends = ['sites'];
public function getSitesAttribute()
{
return $this->sites;
}
public function setSitesAttribute($value)
{
$this->attributes['sites'] = $value;
}
Trying to assign like mymodel->sites = ['1', '2', '3'] but without success. After trying to get the mymodel->sites value error is thrown: undefined property 'sites'.
Your accessor is not quite correct, note that you're not returning $this->attributes['sites']; but instead $this->sites.
class MyModel extends Model
{
public function getSitesAttribute()
{
return $this->attributes['sites'];
}
public function setSitesAttribute($value)
{
$this->attributes['sites'] = $value;
}
}
Then;
$my_model->sites = [1,2,3];
$my_model->sites; // [1,2,3]
You might want to set an initial default value for $this->attributes['sites'] in the model constructor.
The problem lies with the $this->sites. It is correct that it will throw an exception that you class instance does not have a property $sites.
Take a look at the __get method of Model. You can see here that a getAttribute($key) call is being made. Diving even deeper you would find out that at some point the following code is being executed in the transformModelValue method:
if ($this->hasGetMutator($key)) {
return $this->mutateAttribute($key, $value);
}
This piece of code calls your getSitesAttribute method.
Going back a little bit and you will see that it tries to retrieve the value for the $key (sites) from the attributes array by calling the getAttributeFromArray($key). This will return the value stored in the attributes array which is where your setSitesAttribute mutator stores the value.
The following options I could come up with:
Remove the accessor to let the model retrieve the value from the $attributes array.
protected $appends = ['sites'];
public function setSitesAttribute($value)
{
$this->attributes['sites'] = $value;
}
Store value in and retrieve from $sites property. Not sure if $appends still works.
protected $appends = ['sites'];
private $sites = [];
public function getSitesAttribute()
{
return $this->sites;
}
public function setSitesAttribute($value)
{
$this->sites = $value;
}
I use in my model code to get a relation
class User extends Authenticatable
{
// ...
public function extensions()
{
return $this->belongsToMany(Extension::class, 'v_extension_users', 'user_uuid', 'extension_uuid');
}
// ...
}
The Extension has field password hidden.
class Extension extends Model
{
// ...
protected $hidden = [
'password',
];
// ...
}
Under some circumstances I want to makeVisible the password field.
How can I achieve this?
->makeVisible([...]) should work:
$model = \Model::first();
$model->makeVisible(['password']);
$models = \Model::get();
$models = $models->each(function ($i, $k) {
$i->makeVisible(['password']);
});
// belongs to many / has many
$related = $parent->relation->each(function ($i, $k) {
$i->makeVisible(['password']);
});
// belongs to many / has many - with loading
$related = $parent->relation()->get()->each(function ($i, $k) {
$i->makeVisible(['password']);
});
Well, I got the idea from https://stackoverflow.com/a/38297876/518704
Since my relation model Extension::class is called by name in my code return $this->belongsToMany(Extension::class,... I cannot even pass parameter to it's constructor.
So to pass something to the constructor I may use static class variables.
So in my Extension model I add static variables and run makeVisible method.
Later I destruct the variables to be sure next calls and instances use default model settings.
I moved this to a trait, but here I show at my model example.
class Extension extends Model
{
public static $staticMakeVisible;
public function __construct($attributes = array())
{
parent::__construct($attributes);
if (isset(self::$staticMakeVisible)){
$this->makeVisible(self::$staticMakeVisible);
}
}
.....
public function __destruct()
{
self::$staticMakeVisible = null;
}
}
And in my relation I use something like this
class User extends Authenticatable
{
...
public function extensions()
{
$class = Extension::class;
$class::$staticMakeVisible = ['password'];
return $this->belongsToMany(Extension::class, 'v_extension_users', 'user_uuid', 'extension_uuid');
}
...
}
The highest voted answer didn't seem to work for me (the relations attribute seems to be a protected array now so can't be used as a collection in #DevK's answer), I instead used:
$parent->setRelation('child', $parent->child->first()->setVisible(['id']));
I've used setNotification method of model by initial data from Controller within a variable $data as array. I have used self:: in this method instead of used table or Notification::save() or $obj->save(). by this way I don't know how to get Id which the last id after insert was done in laravel because I used $this->attributes that it is the protected variable in Model.
class Notification extends Model
{
protected $table = 'notification';
public $timestamps = true;
private $_data = false;
public function setNotification($data)
{
if (is_array($data)) {
$this->attributes = $data;
self::save();
}
}
}
Try something like $this->attributes[id] after save() is executed.
I suggest You to use create method instead and return created object, so then You can access id property.
public function setNotification($data)
{
if (is_array($data)) {
return $this->create($data);
}
return null;
}
I'm trying to strip non-numeric characters on an attribute before saving, but the new value is not saved.
My model looks like this:
class Model extends \Eloquent {
public function setColumnAttribute($value) {
$value = (int)preg_replace('/[^0-9]/', '', $value);
return $value;
}
}
That method do run, if I add a dd($value) in it, that dumps.
I update the model with this:
Model::find($id)->update($attributes);
Why doesn't it save the value given from the setColumnAttribute method? What am I missing?
By searching on eloquent mutator I find out you had to insert it in the $this->attributes array instead of returning the value.
class Model extends \Eloquent {
public function setColumnAttribute($value) {
$value = (int)preg_replace('/[^0-9]/', '', $value);
$this->attributes['column'] = $value;
}
}