Open DeryabinSergey opened 4 years ago
Without making changes to Yii source code, you should solve adding a new rule based on default validator:
['population', 'default', 'value' => 0],
to be used only with NOT NULL field but with a DEFAULT value.
Next, Gii generator could be updated to produces this rule.
Ок, one more case. If field UNSIGNED
`population` int unsigned NOT NULL DEFAULT '0'
by default generator no rule fo this. I can put -1 in form and have error
SQLSTATE[22003]: Numeric value out of range: 1264 Out of range value for column 'population' at row 1
The SQL being executed was: INSERT INTO `country` (`code`, `name`, `population`) VALUES ('44', '44', -10)
I have quickly read yii2 gii source code and found where rules are created:
and there is not specific code for unsigned
type.
I'll make a PR updating this part to support default and unsgined values.
Generate advanced rules:
public function generateRules($table)
{
$columns = [];
foreach ($table->columns as $index => $column) {
$isBlameableCol = ($column->name === $this->createdByColumn || $column->name === $this->updatedByColumn);
$isTimestampCol = ($column->name === $this->createdAtColumn || $column->name === $this->updatedAtColumn);
$removeCol = ($this->useBlameableBehavior && $isBlameableCol)
|| ($this->useTimestampBehavior && $isTimestampCol);
if ($removeCol) {
$columns[$index] = $column;
unset($table->columns[$index]);
}
}
$rules = [];
//for enum fields create rules "in range" for all enum values
$enum = $this->getEnum($table->columns);
foreach ($enum as $field_name => $field_details) {
$ea = array();
foreach ($field_details['values'] as $field_enum_values) {
$ea[] = 'self::'.$field_enum_values['const_name'];
}
$rules['enum-' . $field_name] = "['".$field_name."', 'in', 'range' => [\n ".implode(
",\n ",
$ea
).",\n ]\n ]";
}
$rules = array_merge($rules, $this->rulesIntegerMinMax($table));
// inject namespace for targetClass
$modTable = clone $table;
$intColumn = 'nonecolumn';
foreach($modTable->columns as $key => $column){
switch ($column->type) {
case Schema::TYPE_SMALLINT:
case Schema::TYPE_INTEGER:
case Schema::TYPE_BIGINT:
case Schema::TYPE_TINYINT:
case 'mediumint':
$modTable->columns[$key]->type = Schema::TYPE_INTEGER;
$intColumn = $column->name;
}
}
$parentRules = parent::generateRules($modTable);
$ns = "\\{$this->ns}\\";
$match = "'targetClass' => ";
$replace = $match.$ns;
foreach ($parentRules as $k => $parentRule) {
if(preg_match('#\'' . $intColumn . '\',.*\'string\', \'max\' => 5#',$parentRule)){
unset($parentRules[$k]);
continue;
}
if(preg_match('#\'integer\']$#',$parentRule)){
unset($parentRules[$k]);
continue;
}
$parentRules[$k] = str_replace($match, $replace, $parentRule);
}
$rules = array_merge($rules,$parentRules);
$table->columns = array_merge($table->columns, $columns);
return $rules;
}
/**
* Generates validation min max rules.
*
* @param TableSchema $table the table schema
*
* @return array the generated validation rules
*/
public function rulesIntegerMinMax($table): array
{
$UNSIGNED = 'Unsigned';
$SIGNED = 'Signed';
$rules = [
Schema::TYPE_TINYINT . ' ' . $UNSIGNED => [
[],
'integer',
'min' => 0,
'max' => 255,
],
Schema::TYPE_TINYINT . ' ' . $SIGNED => [
[],
'integer',
'min' => -128,
'max' => 127,
],
Schema::TYPE_SMALLINT . ' ' . $UNSIGNED => [
[],
'integer',
'min' => 0,
'max' => 65535,
],
Schema::TYPE_SMALLINT . ' ' . $SIGNED => [
[],
'integer',
'min' => -32768,
'max' => 32767,
],
'mediumint' . ' ' . $UNSIGNED => [
[],
'integer',
'min' => 0,
'max' => 16777215,
],
'mediumint' . ' ' . $SIGNED => [
[],
'integer',
'min' => -8388608,
'max' => 8388607,
],
Schema::TYPE_INTEGER . ' ' . $UNSIGNED => [
[],
'integer',
'min' => 0,
'max' => 4294967295,
],
Schema::TYPE_INTEGER . ' ' . $SIGNED => [
[],
'integer',
'min' => -2147483648,
'max' => 2147483647,
],
Schema::TYPE_BIGINT . ' ' . $UNSIGNED => [
[],
'integer',
'min' => 0,
'max' => 0xFFFFFFFFFFFFFFFF,
],
Schema::TYPE_BIGINT . ' ' . $SIGNED => [
[],
'integer',
'min' => -0xFFFFFFFFFFFFFFF,
'max' => 0xFFFFFFFFFFFFFFE,
],
];
foreach ($table->columns as $column) {
$key = $column->type . ' ' . ($column->unsigned? $UNSIGNED : $SIGNED);
if(!isset($rules[$key])){
continue;
}
$rules[$key][0][] = $column->name;
}
/**
* remove empty rules
*/
foreach($rules as $ruleName => $rule){
if(!$rule[0]){
unset($rules[$ruleName]);
continue;
}
$rules[$ruleName] = '['
. '[\'' . implode('\',\'',$rule[0]) . '\']'
. ',\'integer\''
. ' ,\'min\' => ' . $rule['min']
. ' ,\'max\' => ' . $rule['max']
. ']';
}
return $rules;
}
Generate advanced rules:
public function generateRules($table)
Is this code available in some branch (to be tested) ?
Generate advanced rules:
public function generateRules($table)
Is this code available in some branch (to be tested) ?
It is in my private repository. Can give access.
It is in my private repository. Can give access.
Great, so you could create a PR starting from your local branch.
Have you tried to launch the tests suite?
It is in my private repository. Can give access.
Great, so you could create a PR starting from your local branch.
Have you tried to launch the tests suite?
Check email. You are free to use the code to top up your Gii. I don't have time now.
@FabrizioCaldarelli maybe mediumint
shoud add to Schema
constants if use @uldisn case
Additionaly can add enum:
generator.ph
'enum' => $this->getEnum($tableSchema->columns),
//..........................
/**
* prepare ENUM field values.
*
* @param array $columns
*
* @return array
*/
public function getEnum($columns)
{
$enum = [];
foreach ($columns as $column) {
if (!$this->isEnum($column)) {
continue;
}
$column_camel_name = str_replace(' ', '', ucwords(implode(' ', explode('_', $column->name))));
$enum[$column->name]['func_opts_name'] = 'opts'.$column_camel_name;
$enum[$column->name]['func_get_label_name'] = 'get'.$column_camel_name.'ValueLabel';
$enum[$column->name]['isFunctionPrefix'] = 'is'.$column_camel_name;
$enum[$column->name]['columnName'] = $column->name;
$enum[$column->name]['values'] = [];
$enum_values = explode(',', substr($column->dbType, 4, strlen($column->dbType) - 1));
foreach ($enum_values as $value) {
$value = trim($value, "()'");
$const_name = strtoupper($column->name.'_'.$value);
$const_name = preg_replace('/\s+/', '_', $const_name);
$const_name = str_replace(['-', '_', ' '], '_', $const_name);
$const_name = preg_replace('/[^A-Z0-9_]/', '', $const_name);
$label = Inflector::camel2words($value);
$enum[$column->name]['values'][] = [
'value' => $value,
'const_name' => $const_name,
'label' => $label,
'isFunctionSuffix' => str_replace(' ', '', ucwords(implode(' ', explode('_', $value))))
];
}
}
return $enum;
}
/**
* validate is ENUM.
*
* @param $column table column
*
* @return type
*/
public function isEnum($column)
{
return substr(strtoupper($column->dbType), 0, 4) == 'ENUM';
}
template
<?php
if(!empty($enum)){
?>
/**
* ENUM field values
*/
<?php
foreach($enum as $column_name => $column_data){
foreach ($column_data['values'] as $enum_value){
echo ' public const ' . $enum_value['const_name'] . ' = \'' . $enum_value['value'] . '\';' . PHP_EOL;
}
}
}
//................
foreach($enum as $column_name => $column_data){
?>
/**
* get column <?php echo $column_name?> enum value label
* @param string $value
* @return string
*/
public static function <?php echo $column_data['func_get_label_name']?>($value): string
{
if(!$value){
return '';
}
$labels = self::<?php echo $column_data['func_opts_name']?>();
return $labels[$value] ?? $value;
}
/**
* column <?php echo $column_name?> ENUM value labels
* @return array
*/
public static function <?php echo $column_data['func_opts_name']?>(): array
{
return [
<?php
foreach($column_data['values'] as $k => $value){
if ($generator->enableI18N) {
echo ' '.'self::' . $value['const_name'] . ' => Yii::t(\'' . $generator->messageCategory . '\', \'' . $value['value'] . "'),\n";
} else {
echo ' '.'self::' . $value['const_name'] . ' => \'' . $column_data['columnName'] . "',\n";
}
}
?>
];
}
<?php
}
if(!empty($enum)){
?>
/**
* ENUM field values
*/
<?php
foreach($enum as $column_name => $column_data){
foreach ($column_data['values'] as $enum_value){
?>
/**
* @return bool
*/
public function <?=$column_data['isFunctionPrefix'].$enum_value['isFunctionSuffix']?>(): bool
{
return $this-><?=$column_name?> === self::<?=$enum_value['const_name']?>;
}
<?php
}
}
}
What steps will reproduce the problem?
At documentation Getting Started for example next SQL
Look at last column
population
- it`s not null and has default value.Next in start gii generating Model
Country
with next rulespopulation
is not required and has no default value.Next step in CRUD make editor. In Add object form - population is not required filed, but when it`s blank I have SQL Error
What is the expected result?
For this case good SQL is without empty form field
INSERT INTO `country` (`code`, `name`) VALUES ('44', '44')
or with default value from scheme
INSERT INTO `country` (`code`, `name`, `population`) VALUES ('44', '44', '0')
Additional info