yiisoft / yii

Yii PHP Framework 1.1.x
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
4.84k stars 2.28k forks source link

Undefined property: CWebApplication::$lang #3677

Closed fazliddin closed 9 years ago

fazliddin commented 9 years ago

Error:

01 <?php
02 
03 /**
04 * Custom url manager which adds language code to url
05 */
06 class UrlManager extends CUrlManager
07 {
08     public function createUrl($route,$params=array(),$ampersand='&')
09     {
10         if(isset($params['lang']) && ($params['lang']==Yii::app()->lang->default))
11             unset($params['lang']);
12         elseif(!isset($params['lang']) && Yii::app()->lang->isDifferent())
13             $params['lang'] = Yii::app()->lang->current;
14 
15     return parent::createUrl($route,$params,$ampersand);
16     }
17 }

Code that causes problem:

01 <?php
02 
03 /**
04 * Custom url manager which adds language code to url
05 */
06 class UrlManager extends CUrlManager
07 {
08     public function createUrl($route,$params=array(),$ampersand='&')
09     {
10         if(isset($params['lang']) && ($params['lang']==Yii::app()->lang->default))
11             unset($params['lang']);
12         elseif(!isset($params['lang']) && Yii::app()->lang->isDifferent())
13             $params['lang'] = Yii::app()->lang->current;
14 
15     return parent::createUrl($route,$params,$ampersand);
16     }
17 }

class LanguageSwitcherWidget extends CWidget
{
    public $template='link'; //select
    public function init()
    {
        if(count(Yii::app()->lang->data)<2) return;

        $links = array();
        $params = $_GET;
        foreach(Yii::app()->lang->data as $key => $value)
        {
            $params['lang']=$key;
            $links[$key] = $this->owner->createUrl('', $params);
        }
        $this->render($this->template, compact('links'));
    }
}

class UrlManager extends CUrlManager
{
    public function createUrl($route,$params=array(),$ampersand='&')
    {
        if(isset($params['lang']) && ($params['lang']==Yii::app()->lang->default))
            unset($params['lang']);
        elseif(!isset($params['lang']) && Yii::app()->lang->isDifferent())
            $params['lang'] = Yii::app()->lang->current;

    return parent::createUrl($route,$params,$ampersand);
    }
}
klimov-paul commented 9 years ago

Indeed there is no lang field or property in CWebApplication. There is language instead.

fazliddin commented 9 years ago

Look the line which causes problem 10 if(isset($params['lang']) && ($params['lang']==Yii::app()->lang->default))

fazliddin commented 9 years ago

if there is no field lang then error must be on this line:

if(count(Yii::app()->lang->data)<2) return;
klimov-paul commented 9 years ago

In any of the case framework does NOT have CApplication::lang, so it is all your code.

fazliddin commented 9 years ago

here is config file:

'lang' => [
                'class' => 'core.components.LanguageComponent',
            ],
fazliddin commented 9 years ago

if I add lang to preload everything works fine, It is problem of framework!

cebe commented 9 years ago

you have to put lang into component section of config.

fazliddin commented 9 years ago

Yes, of course, it is there:

'components' => [
            'session' => [
                'class' => 'CDbHttpSession',
                'connectionID' => 'db',
                'cookieParams' => [
                    'domain' => '.domain.loc', // NEED TO CHANGE DYNAMIC VALUE
                ],
            ],

            'user' => [
                'allowAutoLogin' => true,
                'identityCookie' => [
                    'domain' => '.domain.loc', // NEED TO CHANGE DYNAMIC VALUE
                ],
            ],

            'sitecookie' => [
                'class' => 'SiteCookie',
                'timeOut' => 2592000, //30*24*60*60 ~ 30 days
            ],

            'lang' => [
                'class' => 'core.components.LanguageComponent',
            ],

            'request' => [
                'enableCsrfValidation' => true,
                'enableCookieValidation' => true,
                'csrfTokenName' => 'session-token',
            ],

            'db' => [
                'schemaCachingDuration' => PRODUCTION_MODE ? 86400000 : 0, // 1000 days
                'enableParamLogging' => !PRODUCTION_MODE,
                'charset' => 'utf8',
                'emulatePrepare' => true,
                'enableProfiling' => false,
            ],

            'cache' => [
                'class' => 'CFileCache',
            ],
        ],

Problem is that I can't understand why lang can be used normally in foreach(Yii::app()->lang->data as $key => $value) before the error, but $links[$key] = $this->owner->createUrl('', $params); causes problem

cebe commented 9 years ago

can you show the complete error including stack trace?

fazliddin commented 9 years ago

I put (>) sign to indicate bold lines

PHP notice

Undefined property: CWebApplication::$lang

/var/www/vhost/myproject/core/components/UrlManager.php(10)

01 <?php
02 
03 /**
04 * Custom url manager which adds language code to url
05 */
06 class UrlManager extends CUrlManager
07 {
08     public function createUrl($route,$params=array(),$ampersand='&')
09     {
>10         if(isset($params['lang']) && ($params['lang']==Yii::app()->lang->default))
11             unset($params['lang']);
12         elseif(!isset($params['lang']) && Yii::app()->lang->isDifferent())
13             $params['lang'] = Yii::app()->lang->current;
14 
15     return parent::createUrl($route,$params,$ampersand);
16     }
17 }
Stack Trace
#0  
–  /var/www/vhost/yii-framework/base/CApplication.php(543): UrlManager->createUrl("", array("lang" => "uz"), "&")
538      * @param string $ampersand the token separating name-value pairs in the URL.
539      * @return string the constructed URL
540      */
541     public function createUrl($route,$params=array(),$ampersand='&')
542     {
>543         return $this->getUrlManager()->createUrl($route,$params,$ampersand);
544     }
545 
546     /**
547      * Creates an absolute URL based on the given controller and action information.
548      * @param string $route the URL route. This should be in the format of 'ControllerID/ActionID'.
#1  
–  /var/www/vhost/myproject/core/components/LanguageComponent.php(50): CApplication->createUrl("", array("lang" => "uz"))
45       
46         // if user visits default home page, but he chose other language before
47         // we redirect him to his prefered language
48         if(empty($_GET) && $cookLang && ($cookLang != $this->default))
49         {
>50             Yii::app()->request->redirect(Yii::app()->createUrl('', ['lang' => $cookLang]));
51         }
52         
53         // If user chose language by select option
54         elseif(isset($_POST['lang']) && $this->exist($_POST['lang']))
55         {
#2  
–  /var/www/vhost/yii-framework/base/CModule.php(387): LanguageComponent->init()
382             if(!isset($config['enabled']) || $config['enabled'])
383             {
384                 Yii::trace("Loading \"$id\" application component",'system.CModule');
385                 unset($config['enabled']);
386                 $component=Yii::createComponent($config);
>387                 $component->init();
388                 return $this->_components[$id]=$component;
389             }
390         }
391     }
392 
#3  
–  /var/www/vhost/yii-framework/base/CModule.php(103): CModule->getComponent("lang")
098      * @return mixed the named property value
099      */
100     public function __get($name)
101     {
102         if($this->hasComponent($name))
>103             return $this->getComponent($name);
104         else
105             return parent::__get($name);
106     }
107 
108     /**
#4  
–  /var/www/vhost/myproject/application/extensions/language/LanguageSwitcherWidget.php(11): CModule->__get("lang")
06 class LanguageSwitcherWidget extends CWidget
07 {
08     public $template='link'; //select
09     public function init()
10     {
>11         if(count(Yii::app()->lang->data)<2) return;
12 
13         $links = array();
14         $params = $_GET;
15         foreach(Yii::app()->lang->data as $key => $value)
16         {
#5  
–  /var/www/vhost/yii-framework/web/CBaseController.php(147): LanguageSwitcherWidget->init()
142      * @return CWidget the fully initialized widget instance.
143      */
144     public function createWidget($className,$properties=array())
145     {
146         $widget=Yii::app()->getWidgetFactory()->createWidget($this,$className,$properties);
>147         $widget->init();
148         return $widget;
149     }
150 
151     /**
152      * Creates a widget and executes it.
#6  
–  /var/www/vhost/yii-framework/web/CBaseController.php(172): CBaseController->createWidget("ext.language.LanguageSwitcherWidget", array("template" => "link"))
167             $widget->run();
168             return ob_get_clean();
169         }
170         else
171         {
>172             $widget=$this->createWidget($className,$properties);
173             $widget->run();
174             return $widget;
175         }
176     }
177 
#7  
–  /var/www/vhost/myproject/application/views/site/index.php(22): CBaseController->widget("ext.language.LanguageSwitcherWidget", array("template" => "link"))
17 <p>For more details on how to further develop this application, please read
18 the <a href="http://www.yiiframework.com/doc/">documentation</a>.
19 Feel free to ask in the <a href="http://www.yiiframework.com/forum/">forum</a>,
20 should you have any questions.</p>
21 
>22 <?php $this->widget('ext.language.LanguageSwitcherWidget',['template' => 'link']); ?>
#8  
–  /var/www/vhost/yii-framework/web/CBaseController.php(126): require("/var/www/vhost/myproject/application/views/site/index.php")
121             $data=$_data_;
122         if($_return_)
123         {
124             ob_start();
125             ob_implicit_flush(false);
126             require($_viewFile_);
127             return ob_get_clean();
128         }
129         else
130             require($_viewFile_);
131     }
#9  
–  /var/www/vhost/yii-framework/web/CBaseController.php(95): CBaseController->renderInternal("/var/www/vhost/myproject/application/views/site/index.php", null, true)
090     {
091         $widgetCount=count($this->_widgetStack);
092         if(($renderer=Yii::app()->getViewRenderer())!==null && $renderer->fileExtension==='.'.CFileHelper::getExtension($viewFile))
093             $content=$renderer->renderFile($this,$viewFile,$data,$return);
094         else
>095             $content=$this->renderInternal($viewFile,$data,$return);
096         if(count($this->_widgetStack)===$widgetCount)
097             return $content;
098         else
099         {
100             $widget=end($this->_widgetStack);
#10 
–  /var/www/vhost/yii-framework/web/CController.php(869): CBaseController->renderFile("/var/www/vhost/myproject/application/views/site/index.php", null, true)
864      */
865     public function renderPartial($view,$data=null,$return=false,$processOutput=false)
866     {
867         if(($viewFile=$this->getViewFile($view))!==false)
868         {
>869             $output=$this->renderFile($viewFile,$data,true);
870             if($processOutput)
871                 $output=$this->processOutput($output);
872             if($return)
873                 return $output;
874             else
#11 
–  /var/www/vhost/yii-framework/web/CController.php(782): CController->renderPartial("index", null, true)
777      */
778     public function render($view,$data=null,$return=false)
779     {
780         if($this->beforeRender($view))
781         {
>782             $output=$this->renderPartial($view,$data,true);
783             if(($layoutFile=$this->getLayoutFile($this->layout))!==false)
784                 $output=$this->renderFile($layoutFile,array('content'=>$output),true);
785 
786             $this->afterRender($view,$output);
787 
#12 
–  /var/www/vhost/myproject/application/controllers/SiteController.php(38): CController->render("index")
33         System::out($l,false);
34 
35         $l1 = LanguageModel::model();
36         //$l->init();
37         System::out($l1);*/
>38         $this->render('index');
39     }
40 
41     /**
42      * This is the action to handle external exceptions.
43      */
#13 
–  /var/www/vhost/yii-framework/web/actions/CInlineAction.php(49): SiteController->actionIndex()
44         $controller=$this->getController();
45         $method=new ReflectionMethod($controller, $methodName);
46         if($method->getNumberOfParameters()>0)
47             return $this->runWithParamsInternal($controller, $method, $params);
48         else
>49             return $controller->$methodName();
50     }
51 
52 }
#14 
–  /var/www/vhost/yii-framework/web/CController.php(308): CInlineAction->runWithParams(array())
303     {
304         $priorAction=$this->_action;
305         $this->_action=$action;
306         if($this->beforeAction($action))
307         {
>308             if($action->runWithParams($this->getActionParams())===false)
309                 $this->invalidActionParams($action);
310             else
311                 $this->afterAction($action);
312         }
313         $this->_action=$priorAction;
#15 
–  /var/www/vhost/yii-framework/web/CController.php(286): CController->runAction(CInlineAction)
281      * @see runAction
282      */
283     public function runActionWithFilters($action,$filters)
284     {
285         if(empty($filters))
>286             $this->runAction($action);
287         else
288         {
289             $priorAction=$this->_action;
290             $this->_action=$action;
291             CFilterChain::create($this,$action,$filters)->run();
#16 
–  /var/www/vhost/yii-framework/web/CController.php(265): CController->runActionWithFilters(CInlineAction, array())
260         {
261             if(($parent=$this->getModule())===null)
262                 $parent=Yii::app();
263             if($parent->beforeControllerAction($this,$action))
264             {
>265                 $this->runActionWithFilters($action,$this->filters());
266                 $parent->afterControllerAction($this,$action);
267             }
268         }
269         else
270             $this->missingAction($actionID);
#17 
–  /var/www/vhost/yii-framework/web/CWebApplication.php(282): CController->run("")
277         {
278             list($controller,$actionID)=$ca;
279             $oldController=$this->_controller;
280             $this->_controller=$controller;
281             $controller->init();
>282             $controller->run($actionID);
283             $this->_controller=$oldController;
284         }
285         else
286             throw new CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".',
287                 array('{route}'=>$route===''?$this->defaultController:$route)));
#18 
–  /var/www/vhost/yii-framework/web/CWebApplication.php(141): CWebApplication->runController("")
136             foreach(array_splice($this->catchAllRequest,1) as $name=>$value)
137                 $_GET[$name]=$value;
138         }
139         else
140             $route=$this->getUrlManager()->parseUrl($this->getRequest());
>141         $this->runController($route);
142     }
143 
144     /**
145      * Registers the core application components.
146      * This method overrides the parent implementation by registering additional core components.
#19 
–  /var/www/vhost/yii-framework/base/CApplication.php(180): CWebApplication->processRequest()
175     public function run()
176     {
177         if($this->hasEventHandler('onBeginRequest'))
178             $this->onBeginRequest(new CEvent($this));
179         register_shutdown_function(array($this,'end'),0,false);
>180         $this->processRequest();
181         if($this->hasEventHandler('onEndRequest'))
182             $this->onEndRequest(new CEvent($this));
183     }
184 
185     /**
#20 
–  /var/www/vhost/myproject/public/frontend/index.php(20): CApplication->run()
15     require(ROOT_DIR . '/application/config/main-shared.php'),
16     require(ROOT_DIR . '/application/config/main.php'),
17     require(ROOT_DIR . '/application/config/main-local.php')
18 );
19 
>20 Yii::createWebApplication($config)->run();
21 
22 if(!PRODUCTION_MODE)
23     require ROOT_DIR . '/application/config/debug-info.php';
24 
2014-12-28 05:29:11 Apache/2.4.7 (Ubuntu) Yii Framework/1.1.15
Application Log
Timestamp   Level   Category    Message
05:29:11.581417 trace   system.db.CDbCommand    
Querying SQL: SELECT *
FROM `cms_languages`
in /var/www/vhost/myproject/application/models/LanguageModel.php (84)
in /var/www/vhost/myproject/application/models/LanguageModel.php (93)
in /var/www/vhost/myproject/core/components/LanguageComponent.php (33)

here is lang component:

<?php

/**
* Automatically initializes languages
*/
class LanguageComponent extends CApplicationComponent
{
    public $data = array();    // array of key-values: en=>English, ...
    public $default = null;        // index: [en|ru]
    public $current = null;        // index: [en|ru]

    /**
     * [getIndices description]
     * @return [type] [description]
     */
    public function getIndices()
    {
        return array_keys($this->data);
    }

    public function exist($code)
    {
        return array_key_exists($code, $this->data);
    }

    public function isDifferent()
    {
        return ($this->default != $this->current) ? true : false;
    }

    public function initData()
    {
        $data = LanguageModel::model()->getProcessedData();
        if (empty($data))
            throw new CHttpException(404, Yii::t('system', 'No language found'));

        $this->default = $data['default'];
        $this->data = $data['data'];
    }

    public function init()
    {
        $this->initData();        
        $cookLang = Yii::app()->sitecookie->get('lang');

        // if user visits default home page, but he chose other language before
        // we redirect him to his prefered language
        if(empty($_GET) && $cookLang && ($cookLang != $this->default))
        {
            Yii::app()->request->redirect(Yii::app()->createUrl('', ['lang' => $cookLang]));
        }

        // If user chose language by select option
        elseif(isset($_POST['lang']) && $this->exist($_POST['lang']))
        {
            // we do nothing if he rechose his current language
            if($cookLang && ($cookLang == $_POST['lang']))
                $this->current = $_POST['lang'];
            else
                // we redirect him to chosen language url
                Yii::app()->request->redirect($_POST[$_POST['lang']]);
        }

        // if language code is already provided by GET
        elseif(isset($_GET['lang']) && $this->exist($_GET['lang']))
        {
            $this->current = $_GET['lang'];
        }
        else
            // we neither POST, nor GET and we are not in homepage
            $this->current = $this->default;

        Yii::app()->language = $this->current;
        if($this->current != $cookLang)
            Yii::app()->sitecookie->set('lang', $this->current);

    return parent::init();
    }
}
fazliddin commented 9 years ago

Huh, I found problem. It is partially my fault, and partially frameworks fault. LanguageComponent calls UrlManager::createUrl and, in turn, createUrl calls LanguageComponent::default which results infinite loop. Framework must have internal stack and should detect such situations and give understandable error.

Actually lang component must be preload, but it uses results of UrlManager through $_GET array. If I put lang in preload, parsed data of UrlManager is not available. How to make preload of lang after request has been parsed?

cebe commented 9 years ago

it may be better to move some of the things you do in init() to a beforeAction event that you add by attaching a behavior to the application.