Десятичный разделитель запятая

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
asp2403
Сообщения: 9
Зарегистрирован: 2018.08.28, 11:36

Десятичный разделитель запятая

Сообщение asp2403 »

Добрый день!
Хочется глобально для всех полей типа number установить русский формат десятичного разделителя (,).
Прописал в конфиге

Код: Выделить всё

'i18n' => [
            'translations' => [
                '*' => [
                    'sourceLanguage' => 'ru-RU',
                    'class' => 'yii\i18n\PhpMessageSource',
                    'basePath' => '@app/messages',
                    'fileMap' => [
                        'app' => 'app.php',
                        'app/error' => 'error.php',
                    ],
                ],
            ],
        ],
        'formatter' => [
            'class' => 'yii\i18n\Formatter',
            'nullDisplay' => '',
            'dateFormat' => 'dd.MM.yyyy',
            'locale' => 'ru',
            'decimalSeparator' => ',',
            'thousandSeparator' => ' ',
        ],
        'container' => [
        'definitions' => [
            yii\validators\NumberValidator::className() => [
                'numberPattern' => '/^\s*[-+]?[0-9]*\,?[0-9]+([eE][-+]?[0-9]+)?\s*$/'
            ]
        ]
    ]
Валидация отрабатывает нормально, но значения постятся в БД в обрезанном виде (без дробной части). Посмотрел в отладчике, проблема происходит в методе QueryBuilder->prepareUpdateSets($table, $columns, $params = []):

Код: Выделить всё

....
foreach ($columns as $name => $value) {

            $value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
            ...
            }            
Изначально в $value нормальное значение (2,9) и тип поля определяется по схеме правильно (double) но после dbTypecast($value) возвращается 2. В чем может быть дело?
Аватара пользователя
proctoleha
Сообщения: 298
Зарегистрирован: 2016.07.10, 19:00

Re: Десятичный разделитель запятая

Сообщение proctoleha »

А как БД воспринимает число 1,789?

БД представления не имеет о том, что происходит снаружи. Если тип поля float, то разделителем десятичных знаков должна быть точка. Без вариантов. Yii2 тут абсолютно не при чем. И нет никаких проблем в никаком методе.

Попробуйте в php скрипте написать $a = 1,1 + 1,1;
Вот за что я не люблю линукс, так это за свои кривые, временами, руки
asp2403
Сообщения: 9
Зарегистрирован: 2018.08.28, 11:36

Re: Десятичный разделитель запятая

Сообщение asp2403 »

proctoleha писал(а): 2019.02.07, 14:34 БД представления не имеет о том, что происходит снаружи. Если тип поля float, то разделителем десятичных знаков должна быть точка. Без вариантов. Yii2 тут абсолютно не при чем. И нет никаких проблем в никаком методе.
Ну, ок, проблема не в БД и Yii, а в (float)$value, а делать-то что? :) Я пробовал устанавливать локаль

Код: Выделить всё

setlocale(LC_ALL, 'russian');
безрезультатно. И серверная валидация формата с запятой в этом случае отъезжает.

Это мой первый проект на php вообще и Yii в частности, поэтому я могу спрашивать какие-то очевидные вещи. Спасибо.
Аватара пользователя
Alexum
Сообщения: 683
Зарегистрирован: 2016.09.26, 10:00

Re: Десятичный разделитель запятая

Сообщение Alexum »

Как вариант в моделях форм фильтрами приводить данные к нужному виду (при валидации).
asp2403
Сообщения: 9
Зарегистрирован: 2018.08.28, 11:36

Re: Десятичный разделитель запятая

Сообщение asp2403 »

Alexum писал(а): 2019.02.08, 14:25 Как вариант в моделях форм фильтрами приводить данные к нужному виду (при валидации).
То есть в каждой модели для полей типа number писать фильтр? А нет более простого и глобального решения? Задача-то типичная.
asp2403
Сообщения: 9
Зарегистрирован: 2018.08.28, 11:36

Re: Десятичный разделитель запятая

Сообщение asp2403 »

В общем, вышел из положения следующим образом:
1. В конфиге

Код: Выделить всё

'container' => [
        'definitions' => [
            yii\validators\NumberValidator::className() => [
                'numberPattern' => '/^\s*[-+]?[0-9]*[.,]?[0-9]+([eE][-+]?[0-9]+)?\s*$/'
            ]
        ]
    ]
- принимаем и точку и запятую как разделитель.

2. Устанавливаем локаль в index.php

Код: Выделить всё

setlocale(LC_ALL, 'russian');
3. Для каждой модели, где есть вещественные поля переопределяем beforeSave, типа

Код: Выделить всё

public function beforeSave($insert)
    {
        if (parent::beforeSave($insert)) {

            $this->square = str_replace(",", ".", $this->square);

            return true;
        } else {

            return false;
        }
    }
Но, честно говоря, все это похоже на костыль. Во первых, нельзя строго потребовать запятую при вводе (хотя для наших пользователей возможность вводить и точку и запятую скорее фича, чем бага). А во вторых, для каждой модели переопределять beforeSave несколько напрягает. Может, есть какие-нибудь best practice?
Аватара пользователя
proctoleha
Сообщения: 298
Зарегистрирован: 2016.07.10, 19:00

Re: Десятичный разделитель запятая

Сообщение proctoleha »

Как вариант написать свой валидатор, в котором подменять запятую на точку и проверять на число, например:

Код: Выделить всё

<?php
namespace app\models;

use yii\validators\Validator;

class RuNumberValidator extends Validator
{
    public function validateAttribute($model, $attribute)
    {
        $model->$attribute = str_replace(',', '.', $model->$attribute);
        if (!is_numeric($model->$attribute)) {
            $this->addError($model, $attribute, 'Field ' . $attribute . ' is not number');
        }
    }
}
И в нужном месте

Код: Выделить всё

    public function rules()
    {
        return [
           ...
            ['my-field', RuNumberValidator::class]
          ...  
        ];
    }
Вот за что я не люблю линукс, так это за свои кривые, временами, руки
asp2403
Сообщения: 9
Зарегистрирован: 2018.08.28, 11:36

Re: Десятичный разделитель запятая

Сообщение asp2403 »

В итоге, написал поведение:

Код: Выделить всё

class RuNumberBehavior extends Behavior {

    public $attributes;

    public function events()
    {
        return [
            ActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
            ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave',
        ];
    }
    
    public function beforeSave()
    {
        foreach ($this->attributes as $attribute) {
            $this->owner[$attribute] = str_replace(',', '.', $this->owner[$attribute]);
        }
    }
}
Использование:

Код: Выделить всё

public function behaviors()
    {
        return [
            [
                'class' => RuNumberBehavior::className(),
                'attributes' => ['square'],
            ]
        ];
    }
Ответить