【问题标题】:Laravel Model Dynamic AttributeLaravel 模型动态属性
【发布时间】:2019-03-04 05:10:47
【问题描述】:

请问如何在模型类上创建动态属性。假设我有一个类似下面代码的表结构。

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

我的表结构中有一个名为“dynamic_fields”的列,它将保存字段的 JSON 字符串。下面是 JSON 结构的示例。

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

所以我想从我的动态字段中访问一个字段,例如“COLOR WAY”。 在我的模型中,我想像这样访问动态字段上的 COLOR WAY 字段

$material->color_way;

谁能告诉我怎么做? 对不起我的英语。

【问题讨论】:

  • 嗨凯文。首先你可以使用$table->json,如果你使用较新版本的mysql并在其中存储json,不需要是文本。然后根据您的示例,您尝试从 json 访问值,而不是键。 Eloquent 模型中的访问器函数可以写成getColorWayAttribute(),这样你就可以使用$material->color_way
  • 你总是提前知道动态属性是什么吗?他们的数量很少吗?如果是这样,您可以为它们全部定义accessor methods,这将允许您以您想要的方式访问它们。如果没有,那么您可以采取另一种方法。

标签: php laravel dynamic attributes field


【解决方案1】:

尝试在您的模型中使用此代码:

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

【讨论】:

    【解决方案2】:

    如果您提前知道只有某些动态字段,您可以选择为它们创建访问器方法。例如,您可以将其添加到您的模型中:

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

    这将允许您这样做:

    $colorWay = $material->color_way;
    

    或者,如果您的dynamic_fields 的组合不受限制,可能会有大量组合,或者您希望能够更灵活地添加更多组合并使其可访问,您可以覆盖getAttribute Laravel 模型类的方法。

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

    这种方法调用 Laravel 的 getAttribute 实现,它首先检查您是否定义了实际属性,或者您是否为该属性定义了访问器(就像我的第一个建议一样),然后检查是否存在具有该名称的方法在基础模型类上,然后如果您定义了一个关系,最后会尝试加载一个关系。

    当这些方法中的每一个都失败时(返回null),我们然后检查模型中是否有dynamic_fields 属性。如果有,我们遍历每个动态字段(假设您的dynamic_fields 被转换为array),然后我们将定义的动态字段的名称转换为小写并用下划线替换空格。然后我们最后检查我们刚刚派生的名称是否与提供的键匹配,如果匹配,我们返回值。如果没有,则返回原始的$attribute,即null

    这将允许您获取任何动态字段,就好像它们被定义为类中的属性一样。

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

    请注意:我尚未测试此代码,可能存在一两个问题。试一试,看看它是否适合你。另请注意,这仅适用于获取动态字段,设置它们将需要覆盖另一个方法。

    【讨论】:

      【解决方案3】:

      在本例中,您可以从动态模型获得动态列。以及它的模型关系

      1) 首先你要在Model中定义一个表Scope。

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

      使用它你也可以从 Rational Model 中获取数据...

      来自控制器

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

      【讨论】: