3
votes

I would like to ask if how to create a dynamic attribute on the model class. Let's suppose I have a table structure like below code.

   Schema::create('materials', function (Blueprint $table) {
        $table->increments('id');
        $table->string('sp_number');
        $table->string('factory');
        $table->text('dynamic_fields')->comment('All description of the material will saved as json');
        $table->timestamps();
    });

I have a column in my table structure named "dynamic_fields" that will hold a JSON string for the fields. An example of JSON structure below.

[  
   {  
      "name":"COLOR WAY",
      "value":"ASDFF12"
   },
   {  
      "name":"DESCRIPTION",
      "value":"agg2sd12"
   },
   {  
      "name":"REF NUM",
      "value":"121312"
   }
]

So i want to access a field from my dynamic fields like for example "COLOR WAY". In my model i want to access the COLOR WAY field on the dynamic field like this way

$material->color_way;

Can anybody show me how to do it? sorry for my English.

3
Hi Kevin. First of all you can use $table->json if you use newer version of mysql and store json in it, does not need to be text. Then based on your example you are trying to access a value from your json, not a key. Accessor function in the Eloquent model can be written as getColorWayAttribute(), this will let you use $material->color_way.nakov
Do you always know what the dynamic attributes will be ahead of time? Are there a small number of them? If so you can define accessor methods for them all, which would allow you to access them in the way that you want. If not then there's another approach you could take.Jonathon

3 Answers

2
votes

If you know that there will only be certain dynamic fields ahead of time, you could opt to create accessor methods for them. For example, you could add this to your model:

// Dynamic fields must be cast as an array to iterate through them as shown below
protected $casts = [
    'dynamic_fields' => 'array'
];

// ...

public function getColorWayAttribute()
{
    foreach ($this->dynamic_fields as $field) {
        if ($field['name'] === 'COLOR WAY') {
            return $field['value'];
        }
    }

    return null;
}

This will allow you to do:

$colorWay = $material->color_way;

Alternatively, if the combinations your dynamic_fields are not limited, there could be a large number of them or you want there to be more flexibility to be able to add more and have them accessible, you could override the getAttribute method of Laravel's model class.

// Dynamic fields must be cast as an array to iterate through them as shown below
protected $casts = [
    'dynamic_fields' => 'array'
];

// ...

public function getAttribute($key)
{
    $attribute = parent::getAttribute($key);

    if ($attribute === null && array_key_exists('dynamic_fields', $this->attributes)) {
        foreach ($this->dynamic_fields as $dynamicField) {
            $name = $dynamicField['name'];
            if (str_replace(' ', '_', mb_strtolower($name)) === $key) {
                return $dynamicField['value'];
            }
        }
    }

    return $attribute;
}

This approach calls Laravel's implementation of getAttribute which first checks if you have an actual attribute defined, or if you have an accessor defined for the attribute (like in my first suggestion), then checks if a method exists with that name on the base model class and then finally attempts to load a relation if you have one defined.

When each of those approaches fails (null is returned), we then check to see if there's a dynamic_fields attribute in the model. If there is, we loop through each of the dynamic fields (assuming your dynamic_fields is cast as an array), we then convert the name of the defined dynamic field to lowercase and replace spaces with underscores. We then finally check to see if the name we have just derived matches the key provided and if it does, we return the value. If it doesn't, the original $attribute will be returned, which will be null.

This would allow you to get any of your dynamic fields as if they were defined as attributes in the class.

$colorWay = $material->color_way;
$description = $material->description;
$refNum = $material->ref_num;

Please note: I have not tested this code, there could well be an issue or two present. Give it a try and see if it works for you. Also note that this will only work for getting dynamic fields, setting them will require overriding another method.

0
votes

Try to use this code in your model:

protected $casts = [
    'dynamic_fields' => 'array',
];

public function setAttribute($key, $value)
{
    if (!$this->getOriginal($key)) {
        $this->dynamic_fields[$key] = $value;
    }

    parent::setAttribute($key, $value);
}

public function getAttribute($key)
{
    if (!$this->getOriginal($key)) {
        return $this->dynamic_fields[$key]
    }

    parent::getAttribute($key);
}
0
votes

In this example, you can get Dynamic Column form Dynamic Model. as well as its Models Relation too

1) first you have to define a table Scope in Model.

  private  $dynamicTable='';

  public function scopeDefineTable($query,$tableName)
{
    if( $tableName )
    {
        $this->dynamicTable= $tableName;
    }
    else
    {
        $this->dynamicTable= "deviceLogs_".date('n')."_".date('Y');
    }
    $query->from( $this->dynamicTable );

   $this->table=$this->dynamicTable; # give dynamic table nam to this model.
}


  public function  scopeCustomSelect( $query ,$items=[])
{
    $stu_class_col=['id as stu_class_id','std_id']; // Required else retional model will not retun data. here id and std_id is primary key and foreign key.
    $stu_doc_col=['id as stu_doc_id','std_id'];// Required else retional model will not retun data. here id and std_id is primary key and foreign key.

    foreach ( $items as $col)
    {
        if(  Schema::hasColumn('student_information', $col ))
        {
          $stu_info_col[]= $col ;
        }
        elseif (  Schema::hasColumn('student_class',$col))
        {
            $stu_class_col[]= $col ;
        }
        elseif (  Schema::hasColumn('student_image',$col))
        {
            $stu_doc_col[]= $col ;
        }
    }

   // converting array to string for bind column into with relation...
    $stu_class_col_string =  implode(',',$stu_class_col);
    $stu_doc_col_string =  implode(',',$stu_doc_col); 

    return $colQuery = $query->select($stu_info_col)
                    ->with(["student_class:$stu_class_col_string", "studentImage:$stu_doc_col_string"]);
}

using this you can get data from Rational Model too...

from Controller

    $studentInfo =  Student::whereHas("student_class",function($q) use($req){
                                   $q->where("std_session",$req->session_code);
                                   $q ->where("std_class",$req->class_code);
                                   $q ->where("std_section",$req->std_section); })
                           ->customSelect($fields['dataList'])
                           ->get();



here I am not using dynamic Model Scope. only Dynamic SustomSelect scope..