An error occurred while rendering the form field if an array was received in the form instead of a string
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 |
Array to string conversion in app/vendor/yiisoft/yii2/helpers/BaseHtml.php
What Line of Code?
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()
The problem in request /test/test/?TestForm[email][][email protected]
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
How about adding form error that says that arrays aren't allowed?
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
@samdark But how to render the field itself, if there is an array there?
As empty.
@Kolyunya The configuration is OK. Look closely. Validation is fine. Problem in rendering field that contains an array
@rodion-k the configuration is indeed correct, that was my mistake, sorry.
@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.
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.
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?