Search code examples
phpcsvactiverecordmodelyii2

Yii2: How to map a CSV string in an attribute to CheckboxList in a form?


I have a model with an attribute that holds a CSV string.

(The model is actually an ActiveRecord object but I guess this is not important. Correct me if I'm wrong.)

/**
 * @property string $colors Can be something like "red" or "red,green,blue" or ""
 */
class Product extends Model {        
}

And I have a form in which I'd like to display this attribute as a checkboxList so that the user can select the possible values with simple clicks instead of typing into a textInput.

Theoretically, it should look similar to this:

<?php $availableColors = ['red' => 'Red', 'green' => 'Green', 'blue' => 'Blue']; ?>

<?php $form = ActiveForm::begin([]); ?>
    <?= $form->field($model, 'colors')->checkboxList($availableColors) ?>
<?php ActiveForm::end(); ?>

This does obviously not work since the field colors would need to be an array. But in my model it is a string.

What would be a good way to achieve that? With JS or pseudo attributes? The colors attribute must not be changed since it is already used in other contexts that shouldn't be modified.


Solution

  • Now I solved it with an extra model for the form. This seems to me a proper solution.

    /**
     * @property string $colors Can be something like "red" or "red,green,blue" or ""
     */
    class Product extends Model {
    }
    
    /**
     * @property string[] $colorsAsArray
     */
    class ProductForm extends Product {
    
        public function rules() {
            return array_merge(parent::rules(), [
                ['colorsAsArray', 'safe'] // just to make it possible to use load()
            ]);
        }
    
        public function getColorsAsArray() {
            return explode(',', $this->colors);
        }
    
        public function setColorsAsArray($value) {
            $this->colors = self::implode($value);
        }
    
        protected static function implode($value) {
            if ($value == 'none-value') return '';
            return implode(',', $value);
        }
    
        /* - - - - - - - - - - optional - - - - - - - - - - */
    
        public function attributeLabels() {
            $attributeLabels = parent::attributeLabels();
            return array_merge($attributeLabels, [
                'colorsAsArray' => $attributeLabels['colors'],
            ]);
        }
    }
    

    With this I can use the form that way:

    <?php $availableColors = ['red' => 'Red', 'green' => 'Green', 'blue' => 'Blue']; ?>
    
    <?php $form = ActiveForm::begin([]); ?>
        <?= $form->field($model, 'colorsAsArray')
                 ->checkboxList($availableColors, ['unselect' => 'none-value']) ?>
    <?php ActiveForm::end(); ?>
    

    Of course, now the controller has to use the inherited model class.

    The solution deals also with the issue if no checkbox is selected. That is why 'none-value' is introduced.