sublime text 编辑器插件安装

没有评论

2013 年 02 月 22 日 at 下午 3:08分类:乱七八糟

1.ctags的配置:
使用package control 搜索ctags 进行安装(安装ctags插件就可以了, 还有一个 CTags for PHP 插件没什么用),
注意安装好插件后要需要安装ctags命令。window 下载 ctags.exe http://vdisk.weibo.com/s/7QZd7 。
将ctags.exe文件放在一个环境变量能访问到的地方。然后在sublime项目文件夹右键, 会出现Ctag:Rebuild Tags 的菜单。
点击它,然后会生成.tags的文件。注意这里的项目就是你的代码的根目录,这个.ctags会生成在根目录中。
然后在你代码中, 光标放在某个函数上, 点击ctrl+shift+鼠标左键 就可以跳转到函数声明的地方。

2.成对匹配的增强。 像这些符号是成对的:花括号{}, 中括号[],括号:() ,引号“” 等。
这些符号当我们鼠标放在开始符号的位置的时候, 希望能明显看到结尾符号在哪儿sublime默认是下划线,很不明显,
想要明显一点,可以安装插件 BracketHighlighter。

3.function name display。 这个插件可以在状态栏显示出当前光标处于哪个函数中。

4.goto document。 这个插件能帮助我们快速查看手册。
比如我们在写php代码时, 突然忘记了某个函数怎么用了,将鼠标放在这个函数上,然后按F1,它能快速打开PHP手册中说明这个函数用法的地方。
安装好 goto document插件后我们再配置快捷键F1 跳转到文档。
打开sublime的菜单栏Preferences->key bindings -User 设置快捷键:
[

{ “keys”: ["f1"], “command”: “goto_documentation” }

]

5.语法提示。 我们需要在写代码的时候如果有语法错误,能立即提示我们, 可以安装sublimelint, sublimeint 需要系统有php命令。
所以需要设置好php的环境变量才行。

6.ZenCoding 这个,不解释了,还不知道ZenCoding的同学强烈推荐去看一下:《Zen Coding: 一种快速编写HTML/CSS代码的方法》。

7.JS Format,一个JS代码格式化插件。安装好之后按快捷键:ctrl+alt+f

8.代码注释格式化。additional PHP snippet插件能提示phpdocument格式的代码,安装DocBlockr 插件,能形成注释块。
不用每次敲注释的斜杠或星号。

YII的runController执行过程分析

没有评论

2013 年 02 月 21 日 at 下午 5:40分类:YII

Yii应用的入口脚本最后一句启动了WebApplication
Yii::createWebApplication($config)->run();
CApplication:

public function run()
{
   $this->onBeginRequest(new CEvent($this));
   $this->processRequest();
   $this->onEndRequest(new CEvent($this));
}

processRequest()开始处理请求,由CWebApplication实现:

public function processRequest()
{
   if(is_array($this->catchAllRequest) && isset($this->catchAllRequest[0]))
   {
    $route=$this->catchAllRequest[0];
    foreach(array_splice($this->catchAllRequest,1) as $name=>$value)
     $_GET[$name]=$value;
   }
   else
    $route=$this->getUrlManager()->parseUrl($this->getRequest());
   $this->runController($route);
}

urlManager应用组件的parseUrl() 创建了$route (形式为controllerID/actionID的字符串),runController()创建Controller对象开始处理http请求。
$route 的值可能存在以下几种情况:
- 为空: 用 defaultController 值代替;
- “moduleID/controllerID/actionID”: module下的
- “controllerID/actionID” : 最常见的形式
- “folder1/folder2/controllerID/actionID” 多级目录下的控制器
runController首先调用createController()创建控制器对象

public function runController($route)  
    {  
        //根据route创建Controller对象数组  
        if(($ca=$this->createController($route))!==null)  
        {  
            //包含controller对象和actionID  
            list($controller,$actionID)=$ca;  
            //TODO::这里是干什么用的  
            $oldController=$this->_controller;  
            $this->_controller=$controller;  
            //调用controller对象的初始化方法  
            $controller->init();  
            //使用actionID运行这个Controller  
            $controller->run($actionID);  
            $this->_controller=$oldController;  
        }  
        Else  
            //如果没有找到对应的Controller,跳转到404页面  
            throw new CHttpException(404,Yii::t('yii','Unable to resolve the request "{route}".',  
                array('{route}'=>$route===''?$this->defaultController:$route)));  
    }

其实真正的核心处理是在createController,对于createController,我们着重需要了解的是下面的这段注释:

/** 
* …… 
* 这个方法以下面的顺序创建一个控制器 
* 1. 如果第一个字段在controllerMap(初始配置)中,则使用对应的控制器配置来创建控制器 
* 2.如果第一个字段是一个模块(module)ID,则使用相应的模块来创建控制器 
* 3.如果通过上面两项均无法创建控制器,将会搜索controllerPath(根目录对应的controller文件夹)来创建对应的控制器。  
* …… 
*/  
public function createController($route,$owner=null)  
{  
// $owner为空则设置为$this,即 $_app对象
   if($owner===null)
    $owner=$this;
   // $route为空设置为defaultController,在$config里配置
   if(($route=trim($route,'/'))==='')
    $route=$owner->defaultController;
   $caseSensitive=$this->getUrlManager()->caseSensitive;

   $route.='/';
   // 逐一取出 $route 按 ‘/’分割后的第一段进行处理
   while(($pos=strpos($route,'/'))!==false)
   {
    // $id 里存放的是 $route 第一个 ‘/’前的部分
    $id=substr($route,0,$pos);
    if(!preg_match('/^\w+$/',$id))
     return null;
    if(!$caseSensitive)
     $id=strtolower($id);
    // $route 存放’/’后面部分
    $route=(string)substr($route,$pos+1);
    if(!isset($basePath)) // 完整$route的第一段
    {
     // 如果$id在controllerMap[]里做了映射
     // 直接根据$id创建controller对象
     if(isset($owner->controllerMap[$id]))
     {
      return array(
       Yii::createComponent($owner->controllerMap[$id],$id,$owner===$this?null:$owner),
       $this->parseActionParams($route),
      );
     }

     // $id 是系统已定义的 module,根据$id取得module对象作为$owner参数来createController
     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';
    // 控制器类文件存在,则require并创建控制器对象&返回
    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;
   }
}  

也就是说,对于一个aaaa/bbbb/cccc的路由,yii首先从config/main.php中定义的controllerMap去寻找是否有名为aaaa的controller,如果有,那么就已aaaa为controller进行创建,否则再去寻找是否有名为aaaa的模块,如果有,那么就使用aaaa模块的名为bbbb的controller进行创建,否则在protected/controllers下寻找是否有名为aaaa的controller。
createController() 返回一个创建好的控制器对象和actionID, runController()调用控制器的init()方法和run($actionID)来运行控制器:
$controller->init()里没有动作,因此我们可以在自己的控制器中重写这个方法来实现初始化的时候处理数据。
run()方法:

public function run($actionID)
{
   if(($action=$this->createAction($actionID))!==null)
   {
    if(($parent=$this->getModule())===null)
     $parent=Yii::app();
    if($parent->beforeControllerAction($this,$action))
    {
     $this->runActionWithFilters($action,$this->filters());
     $parent->afterControllerAction($this,$action);
    }
   }
   else
    $this->missingAction($actionID);
}

$controller->run($actionID)里首先创建了Action对象:

public function createAction($actionID)
{
   // 为空设置为defaultAction
   if($actionID==='')
    $actionID=$this->defaultAction;
   // 控制器里存在 'action'.$actionID 的方法,创建CInlineAction对象
   if(method_exists($this,'action'.$actionID) && strcasecmp($actionID,'s')) // we have actions method
    return new CInlineAction($this,$actionID);
   // 否则根据actions映射来创建Action对象(就是我们自己创建的继承自CAtion的对象)
   else
    return $this->createActionFromMap($this->actions(),$actionID,$actionID);
}

这里可以看到控制器并不是直接调用了action方法,而是需要一个Action对象来运行控制器动作,这样就统一了控制器方法和actions映射的action对象对action的处理,即两种形式的action处理都统一为IAction接口的run()调用。
IAction接口要求实现run(),getId(),getController () 三个方法,Yii提供的CAction类要求构造函数提供Controller和Id并实现了getId()和getController ()的处理,Action类从CAction继承即可。
这里其实可以分为两种action,上面的注释也写了,第一种就是我们在控制器中定义了相关的方法的比如:actionIndex这种,这种的话系统是调用CInlineAction对象来处理的。还有一种呢就是我们没有定义
实际的方法的而是定在actions方法中的,也就是我们自定义了一个类的那种,这种的话就是直接调用我们的这个class来处理了。不管是哪一种,都必须要实现一个run方法,因为这个方法是来具体实现
业务的,如系统的run方法就调用了我们控制器的方法。其实在CInlineAction类中还有个方法就是runWithParams方法,这个方法其实是重写了CAtion类中的一个方法,其实框架默认调用的就是这个方法
而不是run方法,这个方法会判断run方法的参数情况然后再来处理,但是不管参数情况如何都会调用这个run方法,因此我们在我们自定义的ACTION类中也可以实现这个runWithParams方法的。
CInlineAction在web/action下,run()是很简单的处理过程,调用了Controller的action方法:

class CInlineAction extends CAction
{
public function run()
{
   $method='action'.$this->getId();
   $this->getController()->$method();
}
public function runWithParams($params)
{
	$methodName='action'.$this->getId();
	$controller=$this->getController();
	$method=new ReflectionMethod($controller, $methodName);
	//如果run方法有参数的话,那么就用反射来处理,最后调用run方法
	if($method->getNumberOfParameters()>0)
		return $this->runWithParamsInternal($controller, $method, $params);
	//直接调用我们action
	else
		return $controller->$methodName();
}
}

这是CAtion中的runWithParams方法,上面的CInlineAction重写CAtion中的此方法

public function runWithParams($params)
{
	$method=new ReflectionMethod($this, 'run');
	//如果run方法有参数的话,那么就用反射来处理,最后调用run方法
	if($method->getNumberOfParameters()>0)
		return $this->runWithParamsInternal($this, $method, $params);
	//直接调用CInlineAction的run方法
	else
		return $this->run();
}

回到 $controller->run($actionID)

public function run($actionID)
{
   if(($action=$this->createAction($actionID))!==null)
   {
    if(($parent=$this->getModule())===null)
     $parent=Yii::app();
    if($parent->beforeControllerAction($this,$action))
    {
     $this->runActionWithFilters($action,$this->filters());
     $parent->afterControllerAction($this,$action);
    }
   }
   else
    $this->missingAction($actionID);

Yii::app()->beforeControllerAction() 实际是固定返回true的,所以action对象实际是通过控制器的runActionWithFilters()被run的

public function runActionWithFilters($action,$filters)
{
   // 控制器里没有设置过滤器
   if(empty($filters))
    $this->runAction($action);
   // 控制器里设置过滤器
   else
   {
    $priorAction=$this->_action;
    $this->_action=$action;
     // 创建过滤器链对象并运行,其实这一步的执行很简单,就是按照过滤规则先过滤一下,然后再来执行runAction($action)方法
    CFilterChain::create($this,$action,$filters)->run();
    $this->_action=$priorAction;
   }
}

没有过滤器,runAction()就是最终要调用前面创建的action对象的runWithParams(),然后在这个方法中决定是访问run还是$methodName还是其他,因为这个方法可以被用户重写。

public function runAction($action)
	{
		$priorAction=$this->_action;
		$this->_action=$action;
		if($this->beforeAction($action))
		{
			if($action->runWithParams($this->getActionParams())===false)
				$this->invalidActionParams($action);
			else
				$this->afterAction($action);
		}
		$this->_action=$priorAction;
	}

在runWithParams()中调用了我们真正的方法:

public function runWithParams($params)
	{
		$methodName='action'.$this->getId();
		$controller=$this->getController();
		$method=new ReflectionMethod($controller, $methodName);
		if($method->getNumberOfParameters()>0)
			return $this->runWithParamsInternal($controller, $method, $params);
		else
			return $controller->$methodName();
	}

其实在CController类中的run方法并没有加上final关键字,意味着我们这控制器中可以重写他,然后来实现我们自己从控制器到方法的这么一段流程,当然YII已经实现的很perfect了。

这样子,然后联系我们上一篇讲的,我们的程序就已经讲解到了我们自己的控制器了,下一篇将分析执行控制器过程中的过滤和视图这一块的功能。

Yii启动分析

没有评论

2013 年 02 月 05 日 at 上午 11:23分类:PHP | YII

1.启动

$yii=dirname(__FILE__).'/../framework/yii.php';  
$config=dirname(__FILE__).'/protected/config/main.php';  
     
// remove the following line when in production mode  
defined('YII_DEBUG') or define('YII_DEBUG',true);  
     
require_once($yii);  
Yii::createWebApplication($config)->run()

上面的require_once($yii) 引用出了后面要用到的全局类Yii,Yii类是YiiBase类的完全继承:

class Yii extends YiiBase{}

系统的全局访问都是通过Yii类(即YiiBase类)来实现的,Yii类的成员和方法都是static类型。

2.类的加载

Yii利用PHP5提供的spl库来完成类的自动加载。在YiiBase.php 文件结尾处

spl_autoload_register(array('YiiBase','autoload'));

将YiiBase类的静态方法autoload 注册为类加载器。 PHP autoload 的简单原理就是执行 new 创建对象或通过类名访问静态成员时,系统将类名传递给被注册的类加载器函数,类加载器函数根据类名自行找到对应的类文件并include 。
下面是YiiBase类的autoload方法:

public static function autoload($className)  
{  
   // use include so that the error PHP file may appear  
   if(isset(self::$_coreClasses[$className]))  
    include(YII_PATH.self::$_coreClasses[$className]);  
   else if(isset(self::$_classes[$className]))  
    include(self::$_classes[$className]);  
   else
    include($className.'.php');  
}

可以看到YiiBase的静态成员$_coreClasses 数组里预先存放着Yii系统自身用到的类对应的文件路径:

private static $_coreClasses=array(  
   'CApplication' => '/base/CApplication.php',  
   'CBehavior' => '/base/CBehavior.php',  
   'CComponent' => '/base/CComponent.php',  
   ...  
)

非 coreClasse 的类注册在YiiBase的$_classes 数组中:

private static $_classes=array();

其他的类需要用Yii::import()讲类路径导入PHP include paths 中,直接

include($className.'.php')

3. CWebApplication的创建

回到前面的程序入口的 Yii::createWebApplication($config)->run();

public static function createWebApplication($config=null)  
{  
   return new CWebApplication($config);  
}

现在autoload机制开始工作了。
当系统 执行 new CWebApplication() 的时候,会自动

include(YII_PATH.'/base/CApplication.php')

$config先被传递给CApplication的构造函数

public function __construct($config=null)  
{  
   Yii::setApplication($this);  
     
   // set basePath at early as possible to avoid trouble  
   if(is_string($config))  
    $config=require($config);  
   if(isset($config['basePath']))  
   {  
    $this->setBasePath($config['basePath']);  
    unset($config['basePath']);  
   }  
   else
    $this->setBasePath('protected');  
   Yii::setPathOfAlias('application',$this->getBasePath());  
   Yii::setPathOfAlias('webroot',dirname($_SERVER['SCRIPT_FILENAME']));  
     
   $this->preinit();  
     
   $this->initSystemHandlers();  
   $this->registerCoreComponents();  
     
   $this->configure($config);  
   $this->attachBehaviors($this->behaviors);  
   $this->preloadComponents();  
     
   $this->init();  
}

Yii::setApplication($this); 将自身的实例对象赋给Yii的静态成员$_app,以后可以通过 Yii::app() 来取得。
后面一段是设置CApplication 对象的_basePath ,指向 proteced 目录。

Yii::setPathOfAlias('application',$this->getBasePath());  
Yii::setPathOfAlias('webroot',dirname($_SERVER['SCRIPT_FILENAME']));

设置了两个系统路径别名 application 和 webroot,后面再import的时候可以用别名来代替实际的完整路径。别名配置存放在YiiBase的 $_aliases 数组中。

$this->preinit();

预初始化。preinit()是在 CModule 类里定义的,没有任何动作。

$this->initSystemHandlers() 方法内容:

/** 
* Initializes the class autoloader and error handlers. 
*/
protected function initSystemHandlers()  
{  
   if(YII_ENABLE_EXCEPTION_HANDLER)  
    set_exception_handler(array($this,'handleException'));  
   if(YII_ENABLE_ERROR_HANDLER)  
    set_error_handler(array($this,'handleError'),error_reporting());    
}

设置系统exception_handler和 error_handler,指向对象自身提供的两个方法。

4. 注册核心组件

$this->registerCoreComponents();

代码如下:

protected function registerCoreComponents()  
{  
   parent::registerCoreComponents();  
     
   $components=array(  
    'urlManager'=>array(  
     'class'=>'CUrlManager',  
    ),  
    'request'=>array(  
     'class'=>'CHttpRequest',  
    ),  
    'session'=>array(  
     'class'=>'CHttpSession',  
    ),  
    'assetManager'=>array(  
     'class'=>'CAssetManager',  
    ),  
    'user'=>array(  
     'class'=>'CWebUser',  
    ),  
    'themeManager'=>array(  
     'class'=>'CThemeManager',  
    ),  
    'authManager'=>array(  
     'class'=>'CPhpAuthManager',  
    ),  
    'clientScript'=>array(  
     'class'=>'CClientScript',  
    ),  
   );  
     
   $this->setComponents($components);  
}

注册了几个系统组件(Components)。
Components 是在 CModule 里定义和管理的,主要包括两个数组

private $_components=array();  
private $_componentConfig=array();

每个 Component 都是 IApplicationComponent接口的实例,Componemt的实例存放在$_components 数组里,相关的配置信息存放在$_componentConfig数组里。配置信息包括Component 的类名和属性设置。

CWebApplication 对象注册了以下几个Component:urlManager, request,session,assetManager,user,themeManager,authManager,clientScript。 CWebApplication的parent 注册了以下几个 Component:coreMessages,db,messages,errorHandler,securityManager,statePersister。

Component 在YiiPHP里是个非常重要的东西,它的特征是可以通过 CModule 的 __get() 和 __set() 方法来访问。 Component 注册的时候并不会创建对象实例,而是在程序里被第一次访问到的时候,由CModule 来负责(实际上就是 Yii::app())创建

5. 处理 $config 配置

继续, $this->configure($config);
configure() 还是在CModule 里:

public function configure($config)  
{  
   if(is_array($config))  
   {  
    foreach($config as $key=>$value)  
     $this->$key=$value;  
   }  
}

实际上是把$config数组里的每一项传给 CModule 的 父类 CComponent __set() 方法。

public function __set($name,$value)  
{  
   $setter='set'.$name;  
   if(method_exists($this,$setter))  
    $this->$setter($value);  
   else if(strncasecmp($name,'on',2)===0  
               && method_exists($this,$name))  
   {  
    //duplicating getEventHandlers() here for performance  
    $name=strtolower($name);  
    if(!isset($this->_e[$name]))  
     $this->_e[$name]=new CList;  
     $this->_e[$name]->add($value);  
    }  
    else if(method_exists($this,'get'.$name))  
     throw new CException(Yii::t('yii','Property "{class}.{property}" is read only.',  
     array('{class}'=>get_class($this), '{property}'=>$name)));  
    else
     throw new CException(Yii::t('yii','Property "{class}.{property}" is not defined.',  
     array('{class}'=>get_class($this), '{property}'=>$name)));  
   }  
}

我们来看看:
if(method_exists($this,$setter))
根据这个条件,$config 数组里的basePath, params, modules, import, components 都被传递给相应的 setBasePath(), setParams() 等方法里进行处理。

6、$config 之 import

其中 import 被传递给 CModule 的 setImport:

public function setImport($aliases)  
{  
   foreach($aliases as $alias)  
    Yii::import($alias);  
}

Yii::import($alias)里的处理:

public static function import($alias,$forceInclude=false)  
{  
   // 先判断$alias是否存在于YiiBase::$_imports[] 中,已存在的直接return, 避免重复import。  
   if(isset(self::$_imports[$alias])) // previously imported  
    return self::$_imports[$alias];  
     
   // $alias类已定义,记入$_imports[],直接返回  
   if(class_exists($alias,false))  
    return self::$_imports[$alias]=$alias;  
     
   // 类似 urlManager 这样的已定义于$_coreClasses[]的类,或不含.的直接类名,记入$_imports[],直接返回  
   if(isset(self::$_coreClasses[$alias]) || ($pos=strrpos($alias,'.'))===false) // a simple class name  
   {  
    self::$_imports[$alias]=$alias;  
    if($forceInclude)  
    {  
     if(isset(self::$_coreClasses[$alias])) // a core class  
      require(YII_PATH.self::$_coreClasses[$alias]);  
     else
      require($alias.'.php');  
    }  
    return $alias;  
   }  
     
   // 产生一个变量 $className,为$alias最后一个.后面的部分  
   // 这样的:'x.y.ClassNamer'  
   // $className不等于 '*', 并且ClassNamer类已定义的,      ClassNamer' 记入 $_imports[],直接返回  
   if(($className=(string)substr($alias,$pos+1))!=='*' && class_exists($className,false))  
    return self::$_imports[$alias]=$className;  
     
   // 取得 $alias 里真实的路径部分并且路径有效  
   if(($path=self::getPathOfAlias($alias))!==false)  
   {  
    // $className!=='*',$className 记入 $_imports[]  
    if($className!=='*')  
    {  
     self::$_imports[$alias]=$className;  
     if($forceInclude)  
      require($path.'.php');  
     else
      self::$_classes[$className]=$path.'.php';  
     return $className;  
    }  
    // $alias是'system.web.*'这样的已*结尾的路径,将路径加到include_path中  
    else // a directory  
    {  
     set_include_path(get_include_path().PATH_SEPARATOR.$path);  
     return self::$_imports[$alias]=$path;  
    }  
   }  
   else
    throw new CException(Yii::t('yii','Alias "{alias}" is invalid. Make sure it points to an existing directory or file.',  
     array('{alias}'=>$alias)));  
}

7. $config 之 components

$config 数组里的 $components 被传递给CModule 的setComponents($components)

public function setComponents($components)  
{  
   foreach($components as $id=>$component)  
   {  
    if($component instanceof IApplicationComponent)  
     $this->setComponent($id,$component);  
    else if(isset($this->_componentConfig[$id]))  
     $this->_componentConfig[$id]=CMap::mergeArray($this->_componentConfig[$id],$component);  
    else
     $this->_componentConfig[$id]=$component;  
   }  
}

$componen是IApplicationComponen的实例的时候,直接赋值:

$this->setComponent($id,$component),  
     
public function setComponent($id,$component)  
{  
   $this->_components[$id]=$component;  
   if(!$component->getIsInitialized())  
    $component->init();  
}

如果$id已存在于_componentConfig[]中(前面注册的coreComponent),将$component 属性加进入。
其他的component将component属性存入_componentConfig[]中。

8. $config 之 params

public function setParams($value)  
{  
   $params=$this->getParams();  
   foreach($value as $k=>$v)  
    $params->add($k,$v);  
}

configure 完毕!

9. attachBehaviors

$this->attachBehaviors($this->behaviors);

空的,没动作

预创建组件对象

$this->preloadComponents();  
     
protected function preloadComponents()  
{  
   foreach($this->preload as $id)  
    $this->getComponent($id);  
}

getComponent() 判断_components[] 数组里是否有 $id的实例,如果没有,就根据_componentConfig[$id]里的配置来创建组件对象,调用组件的init()方法,然后存入_components[$id]中。

10. init()

$this->init();

11. run()

public function run()  
{  
   $this->onBeginRequest(new CEvent($this));  
   $this->processRequest();  
   $this->onEndRequest(new CEvent($this));  
}

下次接着这个run继续分析。

附上YII社区中提供的一张YII流程图:yii

本文来自:http://www.yiibase.com/yii/view/213.html

YII中模块化modules的使用

没有评论

2013 年 02 月 04 日 at 下午 10:55分类:PHP | YII

一个相对来说大的项目。如果按照yii生成的webapp进行开发。所有的controller放到controllers文件夹下,所有的model放到models文件夹下面,如果你有n多个controller和n多的model,是不是就显得这种组织结构过于繁琐,冗余了。还好YII支持Modules结构。你的项目可以分成n多的Module,然后每一个Module有自己的controllers和models,其实这种结构叫做HMVC。这样的组织结构,无论是开发,管理都方便简洁多了。看看YII的Modules的是组织方式和使用方法。module是对相同业务逻辑的app中的内容模块化,比如博客前台系统可以模块化成blog,博客后台系统可以模块化成admin,模块化便于对应用的管理扩展。
我们可以手动创建一个modules,也可以使用yiic命令行来创建。建议还是 用手工来创建吧,这样子自己对整个目录结构都更加清晰和了解了。建好的目录结构如下:

│   ├── models
│   │   ├── ContactForm.php
│   │   ├── LoginForm.php
│   │   └── User.php................................................................
│   ├── modules模块的存放目录
│   │   └── timeline一个模块,模块的名字对应是目录的名字,唯一。也是路由中的moduleid
│   │       ├── components模块用到的组件
│   │       ├── controllers包含控制器
│   │       │   └── DefaultController.php默认控制器
│   │       ├── messages国际化
│   │       ├── models模型类文件
│   │       ├── TimelineModule.php模块的类文件
│   │       └── views试图文件
│   │           ├── default默认视图
│   │           │   ├── index.php视图文件
│   │           └── layouts包含布局文件
|   |           │   ├── main.php布局文件
│   ├── runtime....................................................................
│   │   └── application.log
│   ├── tests
│   │   ├── bootstrap.php
│   │   ├── fixtures
│   │   │   └── tbl_user.php

上面我建立了一个名字叫timeline的模块。
要使用模块功能,我们需要在模块里面创建一个模块的类文件,即上面的TimelineModule.php这个文件里面的内容如下:

<?php
class TimelineModule extends CWebModule
{
        // 这个方法是在此模块被创建的时候调用的,我们可以在这里做一些自定义的事情
	public function init()
	{
                // 导入模块下面的模型文件和组件文件
		$this->setImport(array(
			'timeline.models.*',
			'timeline.components.*',
		));
	}
        //这个方法实在所有模块控制器被执行之前调用的。
	public function beforeControllerAction($controller, $action)
	{
		if(parent::beforeControllerAction($controller, $action))
		{
			return true;
		}else{
			return false;
                }
	}
}

模块必须继承CWebModule(->CModule->CComponent)。类名是模块名首字母大写,后缀是Module。
init()
初始化模块,通过代码可以看到,可以用来导入其他模块的组件。主要完成模块的初始工作
beforeControllerAction()
afterControllerAction()
用于在模块内的Controller,Action执行之前和执行之后进行相关的操作
模块的配置,使用方法
(1).配置文件/yii/webapp/protected/config/main.php
‘modules’=>array(‘timeline’,),
配置文件中也可以及添加对模块中属性初始化的参数例如:
‘modules’=>array(‘timeline’=>array(‘param’=>’param1′),
对应的访问方式是:
Yii::app()->controller->module->param;
(2).要想禁用一个模块,很简单:
‘modules’=>array(‘timeline’=>array(‘enabled’=>’false’),即可。

(3).YII中的模块是非常灵活的,一个模块可以包含子模块。理论上,模块可以是无限嵌套。
模块中的控制器:
模块中的控制器和外边的控制器一样,都是需要继承CController的,其他的和外边一样,

布局:
模块中过的布局可以使用自己模块中的布局文件来进行,
模块的布局文件存放在modules/modulesName/views/layouts/xxx.php,
要使用某个布局文件只要是控制器中定义$layout的值就可以了。

class DefaultController extends Controller{
	
	public $layout = 'test';//使用布局
	
	public function actionIndex(){
		$this->render('test');
	}
	
}

他的视图文件同样的是存放在modules/modulesName/views/ControllerName/xxx.php中。

访问该模块控制器的方式:
1:http://xxx.xxx.com/webapp/timeline/default/index
2:http://xxx.xxx.com/webapp/index.php?r=/timeline/default/index

YII中控制器执行类CAction的使用

没有评论

2013 年 02 月 04 日 at 下午 4:17分类:PHP | YII

Yii控制器基本的执行单位为action,通常情况下,在Controller类中定义一个actionTest的函数,那么当访问test这个action时,会自动执行actionTest方法。在实际的项目中,如果Controller有多个action,那么如果把所有的action处理逻辑都写在Controller中,那么这个Controller类会异常的大,不利于后期维护,我们可以通过覆盖actions方法,配置action map把不通action分散到各个类中去处理:

public function actions(){
	return array(
		‘action1’=>array(
                    ‘class’=>’path.to.actionclass1’,
                    ‘property’=>’’,
                 ),
                ‘action2’=>array(
                    ‘class’=>’path.to.actionclass2’,
                    ‘property’=>’’,
                ),
	);
}

定义了上述配置数组之后,对于一个名为’deal’的action,Controller会首先去找是否有actionDeal这个方法,如果没有再去判断actions返回值数组是否有key为deal的值,进而用配置的类来处理,这个action类至少要有一个run方法(不一定要继承CAction类),来执行相应的处理逻辑,否则会报fatal error。虽然这个action类只要有run方法就可以,不一定要继承CAction类,但是还是推荐大家使用CAction类,一方面保持框架的完整性,一方面不能访问调用他的Controller。
看个实例:

public function actions()
	{
		return array(
			'test'=>array(
                                 //声明类的路径
				'class'=>'application.actions.TestAction',
                                //设置默认的模版
				'defaultView'=>'test',
                                 //设置默认的布局
				'layout'=>'test',
                                 //设置一个behaviour
				'onTest'=>array($this,'testActionHandler'),
			),
		);
	}

在application.actions下建立TestAction.php文件

<?php
class TestAction extends CAction{
	
	public $controlObject = null;
	
	public $viewPath = null;
	
	public $viewParam = 'v';
	
	public $view = null;
	
	public $actionPath = 'test';
	
	public $defaultView = 'index';
	
	public $layout = '';
	
	public function onTest($event){
		$this->raiseEvent('onTest', $event);
	}

	public function getRequestedView()
	{
		if($this->viewPath===null)
		{
			if(!empty($_GET[$this->viewParam])){
				$this->viewPath = $_GET[$this->viewParam];
			}else{
				$this->viewPath = $this->defaultView;
			}
		}
		return $this->viewPath;
	}
	
	protected function resolveView($viewPath)
	{
		if(preg_match('/^\w[\w\.\-]*$/',$viewPath))
		{
			$view = strtr($viewPath,'.','/');
			if(!empty($this->actionPath)){
				$view = $this->actionPath.'/'.$view;
			}
			if($this->getController()->getViewFile($view)!==false)
			{
				$this->view = $view;
				return;
			}
		}
		throw new CHttpException(404, Yii::t('yii', 'The requested view "{name}" was not found.', array('{name}'=>$viewPath)));
	}
	
	public function run(){
		//出发事件
		$this->onTest(new CEvent($this));
		//解析模版
		$this->resolveView($this->getRequestedView());
		//获取调用的控制器对象
		$controller = $this->getController();
		//如果要指定特殊的布局的话,就这么写,
		$controller->layout = $this->layout;
		//调用控制器的render方法渲染一个视图
		$controller->render($this->view, array('data'=>$this));
		
	}
}

这里面要说下的是,我们在控制器中要是想渲染一个模版,直接调用$this->render方法就可以了,但是在CAction中并没有方法来渲染模版,我们需要使用控制器中的这个方法来处理这个业务。还有个就是布局,我们在控制器默认的布局都是在control中定义的,但是在这个CAction中的布局也就继承了那个布局,如果我们要在这个CAction中改变布局的话,也是需要借助控制器中layout方法来处理。还有属性,我们可以任意给这个CAction定义属性,在控制器中可以给相关的属性定义值,还可以绑定事件,事件的触发时间是你人为自定定义的。他的这个模版目录是可以自定义的,这个其实跟控制器中一样的!

YII自定义验证规则

没有评论

2013 年 02 月 04 日 at 下午 3:12分类:PHP | YII

1、简单的方法:在 model 内部定义规则
比方说,你要检查用户的密码是否足够安全.
首先在模型(model)中添加两个常量

const WEAK = 0;
const STRONG = 1;

然后在模型(model)的 rules 方法中设置:

/**
 * @为模型属性返回数组类型的验证规则
 */
public function rules()
{
    return array(
       array('password', 'passwordStrength', 'strength'=>self::STRONG),
    );
}

确保你写的规则不是一个已经存在的规则,否则将会报错哦.
现在要做的是在该模型(model)中创建一个名称为上面填写的规则的方法(即 passwordStrength)。

/**
 * check if the user password is strong enough
 * check the password against the pattern requested
 * by the strength parameter
 * This is the 'passwordStrength' validator as declared in rules().
 */
public function passwordStrength($attribute, $params)
{
    if ($params['strength'] === self::WEAK){
        $pattern = '/^(?=.*[a-zA-Z0-9]).{5,}$/';  
    }elseif ($params['strength'] === self::STRONG){
        $pattern = '/^(?=.*\d(?=.*\d))(?=.*[a-zA-Z](?=.*[a-zA-Z])).{5,}$/';  
    }  
    if(!preg_match($pattern, $this->$attribute)){
      $this->addError($attribute, 'your password is not strong enough!');
    }
}

刚才创建的方法需要两个参数:* $attribute 需要验证的属性* $params 在规则中自定义的参数
在模型的 rules 方法中我们验证的是 password 属性,所以在验证规则中需要验证的属性值应该是 password.
在 rules 方法中我们还设置了自定义的参数 strength,它的值将会放到 $params 数组中.
你会发现在方法中我们使用了 CModel::addError().
添加错误接受两个参数:第一个参数是在表单中显示错误的属性名,第二个参数时显示的错误信息 。
这样子我们一个普通的自定义验证规则就搞定了。这样子的例子的话 在 系统提供的blog中就有应用。

2、完整的方法:继承 CValidator 类
如果你对YII的框架系统文件有所了解的话,你应该知道在system.validators目录下都是一些验证的class他们都是继承自CValidators类,那么整理成这么一个类的话 我们在任何模型中都可以使用了,而上面的方法只能在一个模型中使用,太过于局限。下面我们就来写一个类似的验证类。
首先要做的是创建类文件.最好的方法是类的文件名和类名相同,可以使用 yii 的延迟加载(lazy loading)功能。
让我们在应用(application)的扩展(extensiions)目录(在 protected 文件夹下)下新建一个文件夹.
将目录命名为: validators
然后创建文件: passwordStrength.php
在文件中创建我们的验证方法:

class passwordStrength extends CValidator
{
    const WEAK = 0;
    const STRONG = 1;
    public $strength;
    private $weak_pattern = '/^(?=.*[a-zA-Z0-9]).{5,}$/';
    private $strong_pattern = '/^(?=.*\d(?=.*\d))(?=.*[a-zA-Z](?=.*[a-zA-Z])).{5,}$/';
        ...
}

在类中创建属性,此属性为在验证规则中使用的参数.
CValidator 会自动根据参数来填充这些属性.
我们也创建了两个其他的属性,它们为 preg_match 函数使用的正则表达式.
现在我们应该重写父类的抽象方法(abstract method) validateAttribute(如果不知道怎么写,可以去system.validators目录下随便找个文件看看,人家是怎么写的)

/**
 * Validates the attribute of the object.
 * If there is any error, the error message is added to the object.
 * @param CModel $object the object being validated
 * @param string $attribute the attribute being validated
 */
protected function validateAttribute($object,$attribute)
{
    // check the strength parameter used in the validation rule of our model
    if ($this->strength == self::WEAK){
      $pattern = $this->weak_pattern;
    }elseif ($this->strength == self::STRONG){
      $pattern = $this->strong_pattern;
    }
    // extract the attribute value from it's model object
    $value=$object->$attribute;
    if(!preg_match($pattern, $value))
    {
        $this->addError($object,$attribute,'your password is too weak!');
    }
}

如果要实现客户端验证还需要重写类中的方法 clientValidateAttribute.

/**
 * Returns the JavaScript needed for performing client-side validation.
 * @param CModel $object the data object being validated
 * @param string $attribute the name of the attribute to be validated.
 * @return string the client-side validation script.
 * @see CActiveForm::enableClientValidation
 */
public function clientValidateAttribute($object,$attribute)
{
        
    // check the strength parameter used in the validation rule of our model
    if ($this->strength == self::WEAK){
      $pattern = $this->weak_pattern;
    }elseif ($this->strength == self::STRONG){
      $pattern = $this->strong_pattern;     
    }
    $condition="!value.match({$pattern})";
        
    return "
if(".$condition.") {
    messages.push(".CJSON::encode('your password is too weak, you fool!').");
}
";
}

正如你看到的此方法简单的返回了一个在验证中将使用到的 javascript.和system.validators下所有验证规则返回的结果都一样。
那么建好了这个class,那么如何使用呢?

你可以在返回 规则数组(ruels array)前 使用 Yii::import 方法,或使用Yii的符号方式:

/**
 * @return array validation rules for model attributes.
 */
public function rules()
{
    return array(
       array('password', 'ext.validators.passwordStrength', 'strength'=>self::STRONG),
    );
}

YII中创建widget

没有评论

2013 年 02 月 04 日 at 下午 12:17分类:PHP | YII

YII中要创建一个widget必须要继承系统的CWidget类,这样子才能使用其提供的一些功能,此外还要重写父类的run方法,这样子你的widget才能正常工作。

class TestWidget extends CWidget{
	
	public $var = 'test';
	
        //初始化时会被调用,会被 CController::beginWidget() 调用 
	public function init(){
		echo 'starting...';
	}
	
        //运行此widget的入口方法,会被 CController::endWidget() 调用 
	public function run(){
                //渲染一个test模块
		$this->render('test');
	}
	
        //供视图或者是其他使用的方法
	public function get(){
		echo "Just Test Data";
	}
}

把这个文件命名为TestWidget.php存储到protected\widget\TestWidget.php,当然 你也可以把此文件存储到protected\components\TestWidget.php中,这些都没关系,可以喜欢单独一个目录,看起来更清晰点。

对应的view文件的内容如下:

<h1>TestWidget</h1>
<?php $this->get();?>
<?php var_dump($this->var);?>

命名为test.php存储到protected\widget\views\test.php中,当然,你要是把TestWidget.php存储在components中的话,那么你的模板文件test.php存储到这里protected\components\views\test.php就行了。
这里说明下:视图文件中的$this指的是整个TestWidget对象,这里面可以随意的调用TestWidget里面的公共方法和属性。
这样子我们一个简单的widget就创建好了,那么如何使用呢?
使用创建好的widget:
在模板中如下:

<?php $this->widget('application.widget.TestWidget', array(
		'var'=>$_SERVER,)//这里可以给控制器传递参数滴
);?>

YII用户登录体系

没有评论

2013 年 02 月 04 日 at 上午 11:55分类:PHP | YII

YII的登陆可以分为Yii::app()->user对象, 登录验证, 持久化三个方面加以说明。
一、Yii::app()->user
这是Yii底层调用用户组件,是Cwebuser的一个实例,如果没有在main.php文件里配置的话yii默认会调用Cwebuser,当然也可以自己去扩展Cwebuser这个类
先说一下所有组件类的基类:CComponent,CComponent实现了定义,使用属性和事件的协议,属性是通过getter方法和setter方法定义。
访问属性就像访问普通的对象变量。读取或写入属性将调用相对应的getter或setter方法
例如:

    $a = $component->text;     // 相当于 $a=$component->getText();
    $component->text='abc';  // 相当于 to $component->setText('abc');

getter 和 setter 方法的格式如下:

    // getter, 定义读取属性 'text'
    public function getText() { ... }
    // setter, 定义写入属性 'text' 将 $value 的值赋给这个属性
    public function setText($value) { ... }

由此可以看出,在调用CComponent对象属性的时候,可以调用$this->getUser()方法也可以直接用$this->user这个属性。
下面来看一下登录流程:
用户的登录页面是site/login,actionLogin中首先实例化了一个LoginForm对象,当用户登录数据提交后系统调
用LoginForm的login方法在此方法中调用了CUserIdentity类的一个子类(application.components.UserIdentity)的authenticate方法对用户名,密码进行验证。验证通过后,调用一下CWebuser的login方法,在此方法中会调用创建session的方法,从而保证了Cwebuser的持久化。

具体的概述就是:
CWebUser 代表一个Web应用程序的持久状态.
CWebUser 作为ID为user的一个应用程序组件. 因此, 在任何地方都能访问用户状态 Yii::app()->user.
CWebUser 应该和一个 identity 一起使用 identity 实现了实际的验证算法.

一个典型的使用CWebUser的身份验证过程如下:
用户提供所需的信息进行身份验证.
一个 identity instance 是用户提供的信息创建的.
调用 IUserIdentity::authenticate 来检查identity是否有效.
如果有效, 调用 CWebUser::login 登陆用户, 然后用户浏览器重定向到 returnUrl.
如果无效, 从identity instance中检索错误代码或错误信息然后显示

二、登录验证
yii提供了CUserIdentity类,这个类一般用于验证用户名和密码的类.
继承后我们需要重写其中的authenticate()方法来实现我们自己的验证方法.具体代码如下:

class UserIdentity extends CUserIdentity  
{  
    private $_id;  
    public function authenticate()  
    {  
        $record=User::model()->findByAttributes(array('username'=>$this->username));  
        if($record===null)  
            $this->errorCode=self::ERROR_USERNAME_INVALID;  
        else if($record->password!==md5($this->password))  
            $this->errorCode=self::ERROR_PASSWORD_INVALID;  
        else  
        {  
            $this->_id=$record->id;  
            $this->setState('title', $record->title);  
            $this->errorCode=self::ERROR_NONE;  
        }  
        return !$this->errorCode;  
    }  
 
    public function getId()  
    {  
        return $this->_id;  
    }  
}

这里可以和上篇文章中讲到的来存储一些信息。
在用户登陆时则调用如下代码:

    $identity=new UserIdentity($username,$password);
    if($identity->authenticate())  
        Yii::app()->user->login($identity);  
    else  
        echo $identity->errorMessage;

在用户退出是调用了: Yii::app()->user->logout();

三、CWebuser记录session的值
在验证用户名和密码成功后yii调用Cwebuser的login方法

    public function login($identity,$duration=0)
    {
        $id=$identity->getId();
        //获取用户自定义的一些数据
        $states=$identity->getPersistentStates();
        if($this->beforeLogin($id,$states,false))
        {
            $this->changeIdentity($id,$identity->getName(),$states);
            if($duration>0)
            {
                if($this->allowAutoLogin)
                    $this->saveToCookie($duration);
                else
                    throw new CException(Yii::t('yii','{class}.allowAutoLogin must be set true in order to use cookie-based authentication.',
                        array('{class}'=>get_class($this))));
            }

            $this->afterLogin(false);
        }
    }

在changeIdentity方法中调用了:

    Yii::app()->getSession()->regenerateID(true);
    $this->setId($id);--
    $this->setName($name);--
    //分别将__id和__name保存到session中
    public function setState($key,$value,$defaultValue=null)
    {
        $key=$this->getStateKeyPrefix().$key;//获取app的编号
        if($value===$defaultValue)
            unset($_SESSION[$key]);
        else
            $_SESSION[$key]=$value;
    }
    //将自定义的数据写入session
    $this->loadIdentityStates($states);

   protected function loadIdentityStates($states)
	{
		$names=array();
		if(is_array($states))
		{
			foreach($states as $name=>$value)
			{
				$this->setState($name,$value);
				$names[$name]=true;
			}
		}
		$this->setState(self::STATES_VAR,$names);
	}

YII 存放登录信息的类

没有评论

2013 年 02 月 01 日 at 下午 1:39分类:PHP | YII

如果在用户登录后想额外调用除 user,id之外的数据库变量,可以这样设置:
在登陆验证时候增加额外项:Yii::app()->user->last_login_time
在UserIdentity.php中

class UserIdentity extends CUserIdentity
{
      public function authenticate()
      {
        //xxxxxxxxxx
	$this->setState('last_login_time',$user->last_login_time);
        //xxxxxxxxxx
      }
}

如此,在应用程序的任何地方,这个属性可以通过如下获取:Yii::app()->user->last_login_time;
将其他的信息存放在session中:
重写父类的方法

public function setState($key, $value, $defaultValue = null) {
    $key = $this->getStateKeyPrefix() . $key;
    if ($value === $defaultValue)
        unset($_SESSION[$key]);
    else
        $_SESSION[$key] = $value;
}

其中的user是yii的一个components.需要在protected/config/main.php中定义

'user'=>array(
        // enable cookie-based authentication
        'allowAutoLogin'=>true,
        'loginUrl' => array('site/login'),
),

通过扩展CWebUser添加信息到Yii:app()->user
步骤:1、添加$user属性到UserIdentity类。 添加getUser()方法-getter上面这个属性。加setUser($user)方法-setter上面这个属性,它可以赋值给user的信息通过$user这个属性。
我的UserIdentity类例子:

<?php
class UserIdentity extends CUserIdentity
{
    /**
    * User's attributes
    * @var array
    */
    public $user;

    public function authenticate()
    {
        $this->errorCode=self::ERROR_PASSWORD_INVALID;
        $user=User::model()->findByAttributes(array('email'=>CHtml::encode($this->username)));
        if ($user)
        {
            if ($user->password === md5($user->salt.$this->password)) {
                $this->errorCode=self::ERROR_NONE;
                $this->setUser($user);
            }
        }
        unset($user);
        return !$this->errorCode;
    }

    public function getUser()
    {
        return $this->user;
    }

    public function setUser(CActiveRecord $user)
    {
        $this->user=$user->attributes;
    }
}
?> 

现在用户的属性已经设置,创建WebUser类并把它放在/protected/components

<?php
class WebUser extends CWebUser
{
    public function __get($name)
    {
        if ($this->hasState('__userInfo')) {
            $user=$this->getState('__userInfo',array());
            if (isset($user[$name])) {
                return $user[$name];
            }
        }

        return parent::__get($name);
    }

    public function login($identity, $duration) {
         $this->setState('__userInfo', $identity->getUser());
        parent::login($identity, $duration);
    }
    public function getIsGuest()
    {
        $customer = Yii::app()->session->get('customer');
        return  $customer===null||$customer['id']===null;
    }

    public function getCustomerId()
    {
       if(!$this->getIsGuest()){
            $customer = Yii::app()->session->get('customer');
            $this->customerId = isset($customer['id']) ? $customer['id'] : 0;
        }
        return $this->customerId;
    }
}
?> 

别忘了修改配置文件中的配置

<?php
'components'=>array(
    'user'=>array(
        'class'=>'WebUser',
    )
)
?> 

调用方法:

调用方法
Yii::app()->user->customerId
Yii::app()->user->getIsGuest();

我们要是想在登陆的时候存放一些自己的信息到user组件中 那么我们可以这样子:

public function authenticate()
	{
		$user=User::model()->find('LOWER(username)=?',array(strtolower($this->username)));
		$_tmp = array();
		foreach($user as $key=>$val){

			$_tmp[$key] = $val;

		}
		$_tmp['fbbin'] = $_SERVER;
		//存放自己的信息
		$this->setPersistentStates($_tmp);
		$this->setState('yanlin', 225500);
		return $this->errorCode==self::ERROR_NONE;
	}

那么这样子以来 我们可以再任何地方通过Yii::app()->user->yanlin; 来获取你设置的值