yii2 icon indicating copy to clipboard operation
yii2 copied to clipboard

An error occurred while rendering the form field if an array was received in the form instead of a string

Open rodion-k opened this issue 8 years ago • 13 comments

What steps will reproduce the problem?

class TestForm extends \yii\base\Model
{
    public $email;

    public function rules()
    {
         return [['email', 'email']];
    }
}

class TestController extends \yii\web\Controller
{
    public function actionTest()
    {
        $model = new TestForm;
        if ($model->load($_GET) && $model->validate()) {
            // do something
        }
        return $this->render('test', ['model' => $model]);
    }
}

test.php

$form = ActiveForm::begin(['method' => 'get']);
echo $form->field($model, 'email')->textInput();
echo Html::submitButton('Send');
$form->end();

curl -g -k -I https://example.local/test/test/?TestForm[email][][email protected]

What is the expected result?

Validation error

What do you get instead?

HTTP/1.1 500 Internal Server Error yii\base\ErrorException Array to string conversion in app/vendor/yiisoft/yii2/helpers/BaseHtml.php

Additional info

Such requests can be sent by spam bots, for example. I think we need to clear the attribute if the types do not match to avoid the 500 error

Q A
Yii version 2.0.12
PHP version 7.1
Operating system

rodion-k avatar Dec 22 '17 09:12 rodion-k

Array to string conversion in app/vendor/yiisoft/yii2/helpers/BaseHtml.php

What Line of Code?

SilverFire avatar Dec 22 '17 09:12 SilverFire

yii\base\ErrorException: Array to string conversion in /app/vendor/yiisoft/yii2/helpers/BaseHtml.php:556 Stack trace: #0 /app/vendor/yiisoft/yii2/helpers/BaseHtml.php(1336): yii\helpers\BaseHtml::activeInput() #1 /app/vendor/yiisoft/yii2/widgets/ActiveField.php(395): yii\helpers\BaseHtml::activeTextInput() #2 /app/frontend/themes/dartc/views/test/test.php(7): yii\widgets\ActiveField->textInput() #3 /app/vendor/yiisoft/yii2/base/View.php(330): frontend\components\View->unknown() #4 /app/vendor/yiisoft/yii2/base/View.php(250): frontend\components\View->renderPhpFile() #5 /app/vendor/yiisoft/yii2/base/View.php(152): frontend\components\View->renderFile() #6 /app/vendor/yiisoft/yii2/base/Controller.php(381): frontend\components\View->render() #7 /app/frontend/controllers/TestController.php(23): frontend\controllers\TestController->render() #8 /app/vendor/yiisoft/yii2/base/InlineAction.php(57): frontend\controllers\TestController->actionTest() #9 /app/vendor/yiisoft/yii2/base/InlineAction.php(57): ::call_user_func_array:{/ap p/vendor/yiisoft/yii2/base/InlineAction.php:57}() #10 /app/vendor/yiisoft/yii2/base/Controller.php(156): yii\base\InlineAction->runWithParams() #11 /app/vendor/yiisoft/yii2/base/Module.php(523): frontend\controllers\TestController->runAction() #12 /app/vendor/yiisoft/yii2/web/Application.php(102): frontend\components\Application->runAction() #13 /app/vendor/yiisoft/yii2/base/Application.php(380): frontend\components\Application->handleRequest() #14 /app/frontend/components/Application.php(150): frontend\components\Application->run() #15 /app/frontend/web/index.php(26): frontend\components\Application->run()

rodion-k avatar Dec 22 '17 09:12 rodion-k

The problem in request /test/test/?TestForm[email][][email protected]

rodion-k avatar Dec 22 '17 09:12 rodion-k

Such requests can be sent by spam bots, for example. I think we need to clear the attribute if the types do not match to avoid the 500 error

Well, as far as I know, bots don't get upset when see 500 error :) If you get disturbed with error notifications, I think you should be disturbed with a lot of 404 notifications as well.

The best I can suggest is the following

diff --git a/framework/helpers/BaseHtml.php b/framework/helpers/BaseHtml.php
index 4cea3d8a9..318dce4d6 100644
--- a/framework/helpers/BaseHtml.php
+++ b/framework/helpers/BaseHtml.php
@@ -559,7 +559,13 @@ class BaseHtml
             $options['type'] = $type;
         }
         $options['name'] = $name;
-        $options['value'] = $value === null ? null : (string) $value;
+        if ($value === null) {
+            $options['value'] = null;
+        } elseif (is_scalar($value)) {
+            $options['value'] = (string) $value;
+        } else {
+            $options['value'] = '';
+        }
         return static::tag('input', '', $options);
     }

But I still don't know, whether we should to it

SilverFire avatar Dec 22 '17 10:12 SilverFire

How about adding form error that says that arrays aren't allowed?

samdark avatar Dec 22 '17 10:12 samdark

If you get disturbed with error notifications, I think you should be disturbed with a lot of 404 notifications as well

404 notifications can be routed to file, for example. 500 - to email

rodion-k avatar Dec 22 '17 10:12 rodion-k

@samdark But how to render the field itself, if there is an array there?

rodion-k avatar Dec 22 '17 10:12 rodion-k

As empty.

samdark avatar Dec 22 '17 10:12 samdark

@Kolyunya The configuration is OK. Look closely. Validation is fine. Problem in rendering field that contains an array

rodion-k avatar Jan 16 '18 09:01 rodion-k

@rodion-k the configuration is indeed correct, that was my mistake, sorry.

Kolyunya avatar Jan 16 '18 09:01 Kolyunya

@SilverFire could you please elaborate on why would you use an empty string in the last case instead of null?

Another approach is to try to cast to a string catching this notice. I'm not sure if it's a good approach though since framework users can replace Yii error handler with a custom implementation which doesn't catch errors.

Kolyunya avatar Jan 19 '18 11:01 Kolyunya

This should not be fixed in BaseHtml. You just need a validator, that casts user input to string/array. Personally I'm using something like

	public function rules() {
		return [
			['some_string_attribute', StringFilterValidator::class],
			['some_array_attribute', ArrayFilterValidator::class],
			// rest of rules
		];
	}

for every rules definition in frontend models. It casts attributes to proper type at the beginning of validation, so you don't need to worry that you get array when you expect string.

rob006 avatar Jan 20 '18 00:01 rob006

Should be some build-in way to avoid this, because currently, it looks like, we can get error 500 on any yii form. For example i tried yiiframework.com/login form, just need to add [] to any field in form e.g. LoginForm[password][] instead LoginForm[password] producing 500, very annoying bug if you have a lot of forms you want to protect of this.

Im currently solved it with custom validator as in suggestion above, validator code:

<?php
namespace common\validators;

use yii\validators\Validator;

class StringFilterValidator extends Validator
{
    public $message = 'Invalid value.';

    public function validateAttribute($model, $attribute): void
    {
        $value = $model->$attribute;
        if (!is_string($value)) {
            $model->$attribute = null;
            $this->addError($model, $attribute, $this->message);
        }
    }
}

but build-in StringValidator already have such functionality, just need to reset string on error, maybe add some parameter like resetOnError which will do $model->$attribute = null; in case of error? What do you think?

wa1kb0y avatar Sep 26 '23 07:09 wa1kb0y