📜 ⬆️ ⬇️

All about namespaces in yii1

Introduction


Hello everyone, this article has been in my drafts for a year now. I kept postponing it for later, when I had more time, but due to the imminent release of yii2, I decided to finalize it and put it on display for the reader.

For 3 years now I have been working on one very large project in megaflowers. And, at some point in the development, when there were too many classes, and their names became like ContentDiscount , ItemDiscount , I realized that I needed to do something about it, and decided to introduce namespasys into our project. Well, as they say, to walk so to walk if to enter, then everywhere at once, and not there a little bit there, and in other places there.

So let's consider how to “prepare” the main types of classes in an application.

The basics


Since I decided to use namespaces everywhere, I chose the root namespace app (well, the application is too long). However, yii does not understand it, so I had to define it in the config (you can also in index.php), but since the config was connected along the path to it, and at the time of initialization it could not use Yii::setPathOfAlias (maybe the situation has changed now? ), then I had to modify index.php.
')
This is how my index.php began to look
 $yii=dirname(__FILE__).'/yii/framework/yii.php'; $config=dirname(__FILE__).'/protected/config/main.php'; // remove the following lines when in production mode defined('YII_DEBUG') or define('YII_DEBUG',true); // specify how many levels of call stack should be shown in each log message defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3); //   Yii,      require_once($yii); // ,  ,       $config=require($config); Yii::createWebApplication($config)->run(); 


And respectively config
 // -   yii,     Yii::getPathOfAlias Yii::setPathOfAlias('app', dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR); //       define(NS_SEPARATOR,NAMESPACE_SEPARATOR); //   return array( // .... ); 



Controllers


It would seem that everything should be simple - I specified the controllerNamespace in the config file, for example, our alias app , and everything works well. And no! For the time being, and all this falls in the case when the controller lies in a folder, for example, test and in the namespace app\test . Yii searches for it in the test folder, but in the namespace app . Since we need to work, and there was no time to write a bug report and do not need a pull request (but you can do it), I decided to write my decision. For this, I inherited from CWepApplication and redefined the createController method. It turned out not quite beautiful, because I had to duplicate a lot of code, but I still had to block this method to solve the internal tasks of the project.

WepApplication
  class WebApplication extends CWebApplication { //         public $controllerNamespace='app'; public function createController($route,$owner=null) { if($owner===null) $owner=$this; if(($route=trim($route,'/'))==='') $route=$owner->defaultController; $caseSensitive=$this->getUrlManager()->caseSensitive; $route.='/'; while(($pos=strpos($route,'/'))!==false) { $id=substr($route,0,$pos); if(!preg_match('/^\w+$/',$id)) return null; if(!$caseSensitive) $id=strtolower($id); $route=(string)substr($route,$pos+1); if(!isset($basePath)) // first segment { if(isset($owner->controllerMap[$id])) { return array( \Yii::createComponent($owner->controllerMap[$id],$id,$owner===$this?null:$owner), $this->parseActionParams($route), ); } /** @var $module \base\BaseModule */ if(($module=$owner->getModule($id))!==null){ return $this->createController($route,$module); } $basePath=$owner->getControllerPath(); $controllerID=''; } else $controllerID.='/'; $className=ucfirst($id).'Controller'; $classFile=$basePath.DIRECTORY_SEPARATOR.$className.'.php'; //     if($owner->controllerNamespace!==null) $className=$owner->controllerNamespace.NS_SEPARATOR.str_replace('/',NS_SEPARATOR,$controllerID).$className; if(is_file($classFile)) { if(!class_exists($className,false)) require($classFile); if(class_exists($className,false) && is_subclass_of($className,'CController')) { $id[0]=strtolower($id[0]); return array( new $className($controllerID.$id,$owner===$this?null:$owner), $this->parseActionParams($route), ); } return null; } $controllerID.=$id; $basePath.=DIRECTORY_SEPARATOR.$id; } } } 


And this is how our index.php began to look like:
 // change the following paths if necessary $yii=dirname(__FILE__).'/yii/framework/yii.php'; $config=dirname(__FILE__).'/protected/config/main.php'; // remove the following lines when in production mode defined('YII_DEBUG') or define('YII_DEBUG',true); // specify how many levels of call stack should be shown in each log message defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL',3); //   Yii,      require_once($yii); // ,   $config=require($config); // ,   $app=new app\components\WebApplication($config); $app->run(); 



Modules


We already have controllers, we can write further, but what to do if we want to put them into modules? The module is defined by the config for Yii::createComponent , that is, it can be used by manually specifying the class name.

config
 array( 'modules'=>array( 'front'=>array( 'class'=>'front\FrontModule' ) ) ) 


This method will not work, because yii knows nothing about the front alias. You can, by the same principle as for the alias app , register it in the config, but I didn’t like this method because of the redundancy of scribbling code (I wanted to write only the module names), so I acted easier and changed my descendant CWebApplication .

Webapplication
 class WebApplication extends CWebApplication { // .... /** *        (,  ) * @param array $modules */ public function setModules($modules) { $modulesConfig=array(); foreach($modules as $id=>$module){ if(is_int($id)) { $id=$module; $module=array(); } if(!isset($module['class'])) { //   \Yii::setPathOfAlias($id,$this->getModulePath().DIRECTORY_SEPARATOR.$id); $module['class']=NS_SEPARATOR.$id.NS_SEPARATOR.ucfirst($id).'Module'; } $modulesConfig[$id]=$module; } parent::setModules($modulesConfig); } } 


The solution is not perfect, and even a bug-report would be compiled (why, specifying the module class name, yii cannot find it? You have to write it as app.modules.ModuleClass ). Now, I think, to do all this change and touch CWebApplication less, for example, put the alias setting in the folder with the module and connect it to the main configuration.

We dealt with the modules, but as soon as it comes to the submodules, we will face the same problem. Yes, and for the correct operation of the controllers in the module, you need to manually specify the controllerNamespace for each module. We fix this by defining a base class for all modules.

Basemodule
 class BaseModule extends \CWebModule { /** *    +  */ protected function init() { parent::init(); //   ,      $namespace=implode(NS_SEPARATOR, array_slice(explode(NS_SEPARATOR,get_class($this)),0,-1)); $this->controllerNamespace=$namespace.NS_SEPARATOR.'controllers'; } /** *        (,  ) * @param array $modules */ public function setModules($modules) { $modulesConfig=array(); foreach($modules as $id=>$module){ if(is_int($id)) { $id=$module; $module=array(); } if(!isset($module['class'])) { \Yii::setPathOfAlias($id,$this->getModulePath().DIRECTORY_SEPARATOR.$id); $module['class']=NS_SEPARATOR.$id.NS_SEPARATOR.ucfirst($id).'Module'; } $modulesConfig[$id]=$module; } parent::setModules($modulesConfig); } } 


Part of the code can be traded out, but I will leave it to you.

Console commands


From the first time I didn’t manage to run the namespace command, I didn’t find anything similar to commandNampespace either in `CConsoleApplication` or in` CConsoleCommandRunner` (maybe it’s worth writing a feature request?). I began to dig in the direction of commandMap , but even here I was disappointed.

config console.php
 //   ,       Yii::setPathOfAlias('app',dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR); //... 'commandMap'=>array( 'import'=>'\app\commands\ImportCommand', ), 


The code fell cursing at the fact that it could not find the class ImportCom .

By trial and error, a working solution was found.

console.php
:
 //   ,       Yii::setPathOfAlias('app',dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR); //... 'commandMap'=>array( //   'import'=>array( 'class'=>'\app\commands\ImportCommand', ) ), 


From the minuses of this method it is possible to note the need to specify in the config absolute name for all commands.

To date, this is the only solution to the problem, I could not find others.

Models


So we got to the models. It would seem that everything should just be there, because models can be used in namespaces anyway, but when I saw how the relations method began to look, I decided to fix it. First, I defined a constant with the name of the class in each model: const CLASS_NAME=__CLASS_NAME__; .

Then I decided to do it easier by defining the base model (the solution was overlooked in yii2).

Base model NamespaceRecord
 class NamespaceRecord extends CActiveRecord { public static function className() { return get_called_class(); } } 



After these actions, our models have become simpler and more beautiful.

It was:
  public function relations(){ return array( 'country'=>'app\location\Country', ) } 


It became:
  public function relations(){ return array( 'country'=>Country::className(), ) } 


There were more problems with the forms, but, in yii, they already fixed this.

Widgets


For a long time, I wrote $this->widget(' \ ') in my views $this->widget(' \ ') , however, with the release of yii2, I made my widgets more similar to yii2. For this, I defined a base class for all widgets.

NSWidget Basic Widget
 class NSWidget extends \CWidget{ /** * @param array $options * @return \CWidget */ public static function begin($options=array()) { return \Yii::app()->controller->beginWidget(get_called_class(),$options); } /** * @return \CWidget */ public static function end() { return \Yii::app()->controller->endWidget(); } /** * @param array $options * @return string widget content */ public static function runWidget($options=array()) { return \Yii::app()->controller->widget(get_called_class(),$options,true); } } 



Everything, now we can write in
 echo MyWidgetNS\MyWidget::begin($options); echo MyWidgetNS\MyWidget::end(); //... echo MyWidget2NS\MyWidget2::runWidget($options); 


That's all, if you have any comments go suggestions on the article - write, correct.
UPD. Today we fixed a bug with controllers in a subfolder. No need to override CWebApplication. For alias, it is also possible (already necessary, since the application was fixed) to use the option in the config file:
config
 'aliases'=>array( 'app'=>'application' ), 


Through the same option, you can throw out the crutches for modules.

Source: https://habr.com/ru/post/209526/


All Articles