Difference between #4 and #3 of How to handle decimal separators (e.g comma instead of dot) for l18n

unchanged
Title
How to handle decimal separators (e.g comma instead of dot) for l18n
unchanged
Category
How-tos
unchanged
Tags
changed
Content
Yii i18n does not cover decimal format. Some languages like Spanish (I live in
Argentina) uses comma ',' instead of dot '.' as decimal separator.

A localized web app should:

   1- Display decimal values with the local decimal separator.

   2- Allow entering decimal values with the local decimal separator.

Yii does not provides a way to do it, there is an extesion to solve this
situation (decimali18nbehavior), but it has a couple of severe issues: User
input is transformed to DB format using 'beforeSave', that's after rules
validation. Every number validation will fail because Yii will try to validate
as a number with dot separator a value with comma separator. By the other hand,
decimali18nbehavior formats the decimal values before displaying usign the
afterFind event. That's prevents to be able to make any math operation on the
values. In other word, un version is made too late, and the other conversion is
made too early.

This article proposes a workaround to this issue.

1st: Convert user input (comma) to db/php format (dot). 

It should be done using the beforeValidate event of the CActiveRecord. You
should create a new class, XActiveRecord for instance, and override the
beforeValidate method:

This is the content of the XActiveRecord.php file:
~~~
[php]
abstract class XActiveRecord extends CActiveRecord {
	protected function beforeValidate()
	{
            foreach($this->owner->getTableSchema()->columns as $name
=> $column)
            {
                if (preg_match('/^decimal\(\d+,(\d+)\)/',$column->dbType,$m)
&& ($value=$this->$name)!==null)
                    $this->$name=str_replace(',','.',$this->$name);
            }
	    return parent::beforeValidate();
	}
}
~~~

then just inherit this class instead of CActiveRecord in your model classes.

If you are using GIIX maybe you are already extending the GxActiveRecord. If it
is the case, you can simply extend this class instead of CActiveRecord
~~~
[php]
abstract class XActiveRecord extends GxActiveRecord {
~~~

That's all regarding the used inut handling.

Now lets deal with the decimal value output. Making the conversion too early
would obstruct making any math operation over the numerical values. For that
reason I discarded the afterFind event. To make the replacement in the last
stage of the output we will work on two methos of the CHtml class: value and
resolveValue.

Good news: Now with Yii 1.1.9 we can override core classes without touching the
/framework files!
Bad news: We can only make a full override replacing the whole class, not just a
method or two.

To override the CHtml class edit your index.php and put:
~~~
[php]
Yii::$classMap=array('CHtml' =>
'/protected/components/web/helpers/CHtml.php');
~~~
just before 
~~~
[php]
Yii::createWebApplication($config)->run();
~~~

the make a full copy of /framework/web/helpers/CHtml.php to
/protected/components/web/helpers/CHtml.php

and last but not least, replace the 'value' and 'resolveValue' methods with:




~~~
[php]
	public static function value($model,$attribute,$defaultValue=null)
	{
		foreach(explode('.',$attribute) as $name)
		{
			if(is_object($model)){
				if(strstr($model->getTableSchema()->columns[$attribute]->dbType,'decimal'))
				     $model = str_replace('.',',',$model->$name);
				else $model=$model->$name;
				//$model=$model->$name;
			}
			else if(is_array($model) && isset($model[$name])){
				if(strstr($model->getTableSchema()->columns[$attribute]->dbType,'decimal'))
				     $model = str_replace('.',',',$model[$name]);
				else $model=$model[$name];
				//$model=$model[$name];
			     }
			else
				return $defaultValue;
		}
		return $model;
	}
~~~



~~~
[php]
	public static function resolveValue($model,$attribute)
	{
		if(($pos=strpos($attribute,'['))!==false)
		{
			if($pos===0)  // [a]name[b][c], should ignore [a]
			{
				if(preg_match('/\](\w+)/',$attribute,$matches))
					$attribute=$matches[1];
				if(($pos=strpos($attribute,'['))===false)
					return $model->$attribute;
			}
			$name=substr($attribute,0,$pos);
			$value=$model->$name;
			foreach(explode('][',rtrim(substr($attribute,$pos+1),']')) as $id)
			{
				if(is_array($value) && isset($value[$id]))
					$value=$value[$id];
				else
					return null;
			}
			return $value;
		}
		else

			try {
			if(strstr($model->getTableSchema()->columns[$attribute]->dbType,'decimal'))
			     return str_replace('.',',',$model->$attribute);
			else return $model->$attribute;
			}
			catch ( Exception $e){ 
				return $model->$attribute;
			}
	}
~~~


and you are done.

I know that it's not an elegant solution, but as far as I know there is no way
to override a core class reusing the original class code. If anyone has any idea
to improve this approach just post a message.

Please add your comments, suggestions, etc, in the forum. [Forum
post](http://www.yiiframework.com/forum/index.php?/topic/28048-handling-decimal-separators-wiki-article/
"Forum post")




Write new article
  • Written by: jpablo
  • Category: How-tos
  • Yii Version: 1.1
  • Votes: +1
  • Viewed: 10,910 times
  • Created on: Jan 22, 2012
  • Last updated: Jan 23, 2012