Kevin Loquencio
Kevin Loquencio

Reputation: 411

Laravel Model Dynamic Attribute

I would like to ask how it's possible 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"
   }
]

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?

Upvotes: 6

Views: 26987

Answers (3)

pankaj
pankaj

Reputation: 1914

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..

Upvotes: 0

Jonathon
Jonathon

Reputation: 16333

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.

Upvotes: 8

Egretos
Egretos

Reputation: 1175

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);
}

Upvotes: 0

Related Questions