Yii 1.1: Yii configuration: Dynamic & User dependent

2 followers

It is difficult to perform user dependent theming through a general configuration in 'main.php'. This article proposes a method to do so through a Factory that dynamically generates classes to allow delayed resolution of actual user values. This means that you can refer to values that are stored in the database through the CActiveRecord before Yii is actually loaded. The values will be fetched from the database when actually used for the first time, which is after Yii is loaded. Actually, you can use pretty much any method that relies on Yii with this method.

Why you may need this

Personnally, I wanted the theme directory and the jquery ui theme to be dependent on the user's context.

How it works

It works by assigning a "property object" to a configuration field. This "property object" will return a string value when used in a string context. (I also had to make it delegate calls to a child object in order to be compatible with theming.)

When you read/use the code below, you have to know that my User class has some properties providing the User's theme, jquery-ui theme, and other properties that may influence some application component behaviors.

Side effects

When passed in CJavaScript::encode(), the object is encoded as an object, not as a string. Making it a CJavaScriptExpression would not be ok either. So one needs to make sure the expression is used a string in certain locations by quoting the parameter ("$value") or concatenating the parameter ("".$value or $anothervalue.$value).

Example code and UserProperty class

It allows you to have a 'main.php' configuration like this:

require(__DIR__.'/../components/UserProperty.php');
 
  $juitheme=UserProperty::getProperty('juitheme','"myjuitheme"');
  $juitheme = array(
    'themeUrl' => $themeurl.'/css/jqueryui',
    'theme' => $juitheme,
    'htmlOptions'=>array('class' => $juitheme),
  );
 
  return array(
     'theme'=>UserProperty::getProperty('themeobject','Yii::app()->getThemeManager()->getTheme("mytheme")'),
     'components'=>array(
          'widgetFactory' => array(
                'widgets' => array(
                    'CJuiAutoComplete' =>$juitheme,
                    'CJuiDialog' => $juitheme,
                    //...
                 ),
            ),
      )
  );

The above sets the juitheme and general theme path from the User properties by using the class below (located in the components directory in my case).

/**
 * User Property factory.
 *
 * Generates an instance for a User Property that is computed when the value is first used.
 * This class allows setting up Yii configuration with User properties that are known only when
 * Yii is actually set up.
 *
 * Example for configuration array:
 * $juitheme=UserProperty::getProperty('juitheme','"myjuitheme"');
 * $juitheme = array(
 *   'themeUrl' => $themeurl.'/css/jqueryui',
 *   'theme' => $juitheme,
 *   'htmlOptions'=>array('class' => $juitheme),
 * );
 *
 * return array(
 *    'theme'=>UserProperty::getProperty('themeobject','Yii::app()->getThemeManager()->getTheme("mytheme")'),
 *    'components'=>array(
 *         'widgetFactory' => array(
 *               'widgets' => array(
 *                   'CJuiAutoComplete' =>$juitheme,
 *                   'CJuiDialog' => $juitheme,
 *                   //...
 *                ),
 *           ),
 *     )
 * );
 *
 * @license MIT
 */
class UserProperty {
    /**
     * Object pool of instances.
     * @var Object[]
     */
    static public $inst;
 
    /**
     * Provides an instance providing a user property when called.
     *
     * @param string $property Same as second parameter in CHtml::value
     * @param string $default  Default value, evaluated as PHP code (inside string, so null must be written as "null").
     * @param string $template If provided '{val}' in the template is replaced with the User property.
     * @return _UserProperty Property returned by User model or default if null or empty string.
     */
    public static function getProperty($property,$default=null,$template=null) {
        $propertyclass= "_User_".$property.base_convert(crc32(" ".$default.$template),10,36);
        if(!isset(self::$inst[$propertyclass])) {
            if($default) {
                // Test for object - if value is an object, do not handle it as a string//do not use default value.
                $defaultexpression='if(!is_object($this->val) && "{$this->val}"==="") $this->val='.$default.';';
            } else {
                $defaultexpression='';
            }
            $val="CHtml::value(AppUtils::getRealUser(),'$property')";
            if($template!==null) {
                $val2="if(\"{\$this->val}\"!=='') \$this->val=strtr('$template',array('{val}'=>\$this->val));";
            } else {
                $val2="";
            }
            $code="class $propertyclass extends _UserProperty{
                    protected function _val() {
                        if(!isset(\$this->val)) {
                          \$this->val=$val;$val2 $defaultexpression
                        }
                        return \$this->val;
                    }
                }";
            //Yii::trace($code);
            //if($property==='propertytocheck') throw new CException($code);
            eval($code);
            self::$inst[$propertyclass]=new $propertyclass;
        }
        return self::$inst[$propertyclass];
    }
}
 
/**
 * Abstract class for User Property.
 */
abstract class _UserProperty {
    protected $val;
 
    abstract protected function _val();
 
    public function __toString() {
        $r=$this->_val();
        return $r;
    }
    public function __call($name,$parameters) {
        return call_user_func_array(array($this->_val(),$name),$parameters);
    }
    public function __get($name) {
        return $this->_val()->$name;
    }
}

Be the first person to leave a comment

Please to leave your comment.

Write new article