diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 2f06923..e0534e5 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -11,6 +11,7 @@ Yii Framework 2 Change Log - Bug #15194: Fixed `yii\db\QueryBuilder::insert()` to preserve passed params when building a `INSERT INTO ... SELECT` query for MSSQL, PostgreSQL and SQLite (sergeymakinen) - Bug #15229: Fixed `yii\console\widgets\Table` default value for `getScreenWidth()`, when `Console::getScreenSize()` can't determine screen size (webleaf) - Bug #15234: Fixed `\yii\widgets\LinkPager` removed `tag` from `disabledListItemSubTagOptions` (SDKiller) +- Enh #7988: Added `\yii\helpers\Console::errorSummary()` and `\yii\helpers\Json::errorSummary()` (developeruz) - Bug #15249: Controllers in subdirectories were not visible in commands list (IceJOKER) - Bug #15270: Resolved potential race conditions when writing generated php-files (kalessil) - Bug #15301: Fixed `ArrayHelper::filter()` to work properly with `0` in values (hhniao) diff --git a/framework/base/Model.php b/framework/base/Model.php index af97a86..5e1ddc7 100644 --- a/framework/base/Model.php +++ b/framework/base/Model.php @@ -611,6 +611,25 @@ class Model extends Component implements StaticInstanceInterface, IteratorAggreg } /** + * Returns the errors for all attributes as a one-dimensional array. + * @param bool $showAllErrors boolean, if set to true every error message for each attribute will be shown otherwise + * only the first error message for each attribute will be shown. + * @return array errors for all attributes as a one-dimensional array. Empty array is returned if no error. + * @see getErrors() + * @see getFirstErrors() + * @since 2.0.14 + */ + public function getErrorSummary($showAllErrors) + { + $lines = []; + $errors = $showAllErrors ? $this->getErrors() : $this->getFirstErrors(); + foreach ($errors as $es) { + $lines = array_merge((array)$es, $lines); + } + return $lines; + } + + /** * Adds a new error to the specified attribute. * @param string $attribute attribute name * @param string $error new error message diff --git a/framework/helpers/BaseConsole.php b/framework/helpers/BaseConsole.php index 28a63ba..8d90af7 100644 --- a/framework/helpers/BaseConsole.php +++ b/framework/helpers/BaseConsole.php @@ -8,6 +8,7 @@ namespace yii\helpers; use yii\console\Markdown as ConsoleMarkdown; +use yii\base\Model; /** * BaseConsole provides concrete implementation for [[Console]]. @@ -1051,4 +1052,45 @@ class BaseConsole self::$_progressEtaLastDone = 0; self::$_progressEtaLastUpdate = null; } + + /** + * Generates a summary of the validation errors. + * @param Model|Model[] $models the model(s) whose validation errors are to be displayed. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - showAllErrors: boolean, if set to true every error message for each attribute will be shown otherwise + * only the first error message for each attribute will be shown. Defaults to `false`. + * + * @return string the generated error summary + * @since 2.0.14 + */ + public static function errorSummary($models, $options = []) + { + $showAllErrors = ArrayHelper::remove($options, 'showAllErrors', false); + $lines = self::collectErrors($models, $showAllErrors); + + return implode(PHP_EOL, $lines); + } + + /** + * Return array of the validation errors + * @param Model|Model[] $models the model(s) whose validation errors are to be displayed. + * @param $showAllErrors boolean, if set to true every error message for each attribute will be shown otherwise + * only the first error message for each attribute will be shown. + * @return array of the validation errors + * @since 2.0.14 + */ + private static function collectErrors($models, $showAllErrors) + { + $lines = []; + if (!is_array($models)) { + $models = [$models]; + } + + foreach ($models as $model) { + $lines = array_unique(array_merge($lines, $model->getErrorSummary($showAllErrors))); + } + + return $lines; + } } diff --git a/framework/helpers/BaseHtml.php b/framework/helpers/BaseHtml.php index 4cea3d8..2ff7ca6 100644 --- a/framework/helpers/BaseHtml.php +++ b/framework/helpers/BaseHtml.php @@ -1216,35 +1216,45 @@ class BaseHtml $encode = ArrayHelper::remove($options, 'encode', true); $showAllErrors = ArrayHelper::remove($options, 'showAllErrors', false); unset($options['header']); + $lines = self::collectErrors($models, $encode, $showAllErrors); + if (empty($lines)) { + // still render the placeholder for client-side validation use + $content = ''; + $options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none'; + } else { + $content = ''; + } + return Html::tag('div', $header . $content . $footer, $options); + } + + /** + * Return array of the validation errors + * @param Model|Model[] $models the model(s) whose validation errors are to be displayed. + * @param $encode boolean, if set to false then the error messages won't be encoded. + * @param $showAllErrors boolean, if set to true every error message for each attribute will be shown otherwise + * only the first error message for each attribute will be shown. + * @return array of the validation errors + * @since 2.0.14 + */ + private static function collectErrors($models, $encode, $showAllErrors) + { $lines = []; if (!is_array($models)) { $models = [$models]; } + foreach ($models as $model) { - /* @var $model Model */ - foreach ($model->getErrors() as $errors) { - foreach ($errors as $error) { - $line = $encode ? Html::encode($error) : $error; - if (!in_array($line, $lines, true)) { - $lines[] = $line; - } - if (!$showAllErrors) { - break; - } - } - } + $lines = array_unique(array_merge($lines, $model->getErrorSummary($showAllErrors))); } - if (empty($lines)) { - // still render the placeholder for client-side validation use - $content = ''; - $options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none'; - } else { - $content = ''; + if ($encode) { + for ($i = 0; $i < count($lines); $i++) { + $lines[$i] = Html::encode($lines[$i]); + } } - return Html::tag('div', $header . $content . $footer, $options); + return $lines; } /** diff --git a/framework/helpers/BaseJson.php b/framework/helpers/BaseJson.php index 5ac9394..32a0c2b 100644 --- a/framework/helpers/BaseJson.php +++ b/framework/helpers/BaseJson.php @@ -10,6 +10,7 @@ namespace yii\helpers; use yii\base\Arrayable; use yii\base\InvalidParamException; use yii\web\JsExpression; +use yii\base\Model; /** * BaseJson provides concrete implementation for [[Json]]. @@ -179,4 +180,45 @@ class BaseJson return $data; } + + /** + * Generates a summary of the validation errors. + * @param Model|Model[] $models the model(s) whose validation errors are to be displayed. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - showAllErrors: boolean, if set to true every error message for each attribute will be shown otherwise + * only the first error message for each attribute will be shown. Defaults to `false`. + * + * @return string the generated error summary + * @since 2.0.14 + */ + public static function errorSummary($models, $options = []) + { + $showAllErrors = ArrayHelper::remove($options, 'showAllErrors', false); + $lines = self::collectErrors($models, $showAllErrors); + + return json_encode($lines); + } + + /** + * Return array of the validation errors + * @param Model|Model[] $models the model(s) whose validation errors are to be displayed. + * @param $showAllErrors boolean, if set to true every error message for each attribute will be shown otherwise + * only the first error message for each attribute will be shown. + * @return array of the validation errors + * @since 2.0.14 + */ + private static function collectErrors($models, $showAllErrors) + { + $lines = []; + if (!is_array($models)) { + $models = [$models]; + } + + foreach ($models as $model) { + $lines = array_unique(array_merge($lines, $model->getErrorSummary($showAllErrors))); + } + + return $lines; + } } diff --git a/tests/framework/base/ModelTest.php b/tests/framework/base/ModelTest.php index 303fc48..ca7f1c8 100644 --- a/tests/framework/base/ModelTest.php +++ b/tests/framework/base/ModelTest.php @@ -310,6 +310,9 @@ class ModelTest extends TestCase 'lastName' => ['Another one!'], ], $speaker->getErrors()); + $this->assertEquals(['Another one!', 'Something is wrong!', 'Totally wrong!'], $speaker->getErrorSummary(true)); + $this->assertEquals(['Another one!', 'Something is wrong!'], $speaker->getErrorSummary(false)); + $speaker->clearErrors('firstName'); $this->assertEquals([ 'lastName' => ['Another one!'], diff --git a/tests/framework/helpers/ConsoleTest.php b/tests/framework/helpers/ConsoleTest.php index bd9767f..88cac6d 100644 --- a/tests/framework/helpers/ConsoleTest.php +++ b/tests/framework/helpers/ConsoleTest.php @@ -10,6 +10,7 @@ namespace yiiunit\framework\helpers; use Yii; use yii\helpers\Console; use yiiunit\TestCase; +use yii\base\DynamicModel; /** * @group helpers @@ -204,6 +205,39 @@ class ConsoleTest extends TestCase $this->assertEquals($html, Console::ansiToHtml($ansi)); } + public function testErrorSummary() + { + $model = new TestConsoleModel(); + $model->name = 'not_an_integer'; + $model->addError('name', 'Error message. Here are some chars: < >'); + $model->addError('name', 'Error message. Here are even more chars: ""'); + $model->validate(null, false); + $options = ['showAllErrors' => true]; + $expectedHtml = "Error message. Here are some chars: < >\nError message. Here are even more chars: \"\""; + $this->assertEquals($expectedHtml, Console::errorSummary($model, $options)); + } +} + +/** + * @property string name + * @property array types + * @property string description + */ +class TestConsoleModel extends DynamicModel +{ + public function rules() + { + return [ + ['name', 'required'], + ['name', 'string', 'max' => 100] + ]; + } + + public function init() + { + $this->defineAttribute('name'); + } + /** * @covers \yii\helpers\BaseConsole::input() */ diff --git a/tests/framework/helpers/JsonTest.php b/tests/framework/helpers/JsonTest.php index 82fefaf..3952788 100644 --- a/tests/framework/helpers/JsonTest.php +++ b/tests/framework/helpers/JsonTest.php @@ -7,7 +7,7 @@ namespace yiiunit\framework\helpers; -use yii\base\Model; +use yii\base\DynamicModel; use yii\helpers\BaseJson; use yii\helpers\Json; use yii\web\JsExpression; @@ -208,9 +208,21 @@ class JsonTest extends TestCase } } } + + public function testErrorSummary() + { + $model = new JsonModel(); + $model->name = 'not_an_integer'; + $model->addError('name', 'Error message. Here are some chars: < >'); + $model->addError('name', 'Error message. Here are even more chars: ""'); + $model->validate(null, false); + $options = ['showAllErrors' => true]; + $expectedHtml = '["Error message. Here are some chars: < >","Error message. Here are even more chars: \"\""]'; + $this->assertEquals($expectedHtml, Json::errorSummary($model, $options)); + } } -class JsonModel extends Model implements \JsonSerializable +class JsonModel extends DynamicModel implements \JsonSerializable { public $data = ['json' => 'serializable']; @@ -218,4 +230,17 @@ class JsonModel extends Model implements \JsonSerializable { return $this->data; } + + public function rules() + { + return [ + ['name', 'required'], + ['name', 'string', 'max' => 100] + ]; + } + + public function init() + { + $this->defineAttribute('name'); + } }