How to store array/widget configuration to the database with config validation rules

  1. Requirement
  2. Design

Requirement

You may have scenarios, where you want to store a specific array configuration to the database. A classic example could be saving Yii widget configuration to the database and then retrieving it at runtime, with dynamic parameters set. This wiki discusses one of the approaches to do this. Let's consider you have something like the NavBar configuration below to store in the db:

NavBar::begin([
    'brandLabel' => 'My Company',
    'brandUrl' => Yii::$app->homeUrl,
    'options' => [
        'class' => 'navbar-inverse navbar-fixed-top',
    ],
]);
echo Nav::widget([
    'options' => ['class' => 'navbar-nav navbar-right'],
    'items' => $menuItems,
]);
NavBar::end();

Design

The design approach involves creating the data structure to store the serialized array configurations with tags as variables that will be replaced at runtime. The rules/validators for validating the widget configuration, will also be stored serialized in a rules column. While running the widget at runtime, the rules will be validated dynamically using the yii\base\DynamicModel.

1. Data Structure

Create a table tbl_widget_config in your database with the following minimum columns. Create a model called WidgetConfig for this table through gii.

id INT(11) primary key COMMENT 'Your widget config identifier'
class_name VARCHAR(180) COMMENT 'Fully namespaced class name for the widget'
config TEXT COMMENT 'Serialized widget configuration'
rules TEXT COMMENT 'Rules to be checked for validating the widget configuration'
2. Storing Config To Database

Convert your widget configuration in such a way that you can include tags for variables to be dynamically replaced at runtime. The following code can be used to populate your tbl_widget_config.

// creating a configuration
public function createConfig() {
    $model = new WidgetConfig;
    $model->class_name = 'yii\bootstrap\NavBar';
    $model->config = serialize([
        'brandLabel' => 'My Company',
        'brandUrl' => '{{homeUrl}}',
        'options' => [
            'class' => 'navbar-inverse navbar-fixed-top',
        ]
    ]);
    $model->rules = serialize([
        ['brandUrl', \common\validators\TagValidator::className()],    
        // this is a custom TagValidator but could be any validator
    ]);
    $model->save();
}

Note the tag {{homeUrl}} will be dynamically replaced at runtime.

3. Validating Config and running widget

You can use your own custom validator to parse any tags (as called in the example above but not yet described here) or simply use a 'parseTags' function such as in the following example ...

// Parse tags
public static function parseTags($config) {
    return unserialize(strtr($config, [
        '{{homeUrl}}' => Yii::$app->homeUrl,
        '{{otherTag1}}' => 'Value 1'
    ]));
}

// Retrieve the configuration
public function runWidget($configId) {
    $dbWidget = WidgetConfig::findOne($configId);
    $widgetConfig = \yii\base\DynamicModel::validateData(
        static::parseTags($dbWidget->config), 
        static::parseTags($dbWidget->rules)
    );
    // To run the widget call below
    $class = $dbWidget->class_name;
    $class::widget($widgetConfig->getAttributes());
}