ukula 发表于 2017-4-5 12:12:53

PHP框架Yii系列教程(二):功能简介

1 MVC架构

1.1处理流程
  一个Web请求在Yii内部的执行流程如下图所示:



1.2组件角色



  组件名


  角色与责任




  index.php


  入口脚本。创建Application的单例对象。




  application


  前端控制器。分析用户请求并将其分派到合适的控制器中以作进一步处理。它同时作为服务中心,维护应用级别的配置。




  request


  解析用户的请求。




  urlManager


  帮助application决定请求的控制器和动作。




  controller


  执行所请求的动作,动作通常会引入所必要的模型并渲染相应的视图,动作完成视图渲染并将其呈现给用户。




  model


  读取数据库或进行其他业务操作。




  view


  视图渲染出最终的页面。




1.3最佳实践
  假设有这样一个包含几个子应用的Web应用,例如:
  Ø Front-end:面向外部用户的Web站点
  Ø Back-end:为管理员提供管理功能的Web站点
  Ø Console:在终端中作为定时作业执行
  Ø Web API:为第三方提供集成的API

1.3.1 Model
  Ø 包含代表特定数据的属性
  Ø 包含业务逻辑、验证规则
  Ø 包含操纵数据的代码
  Ø 不应使用$_GET,$_POST等与界面类型绑定的变量
  Ø 避免嵌入HTML等展示层代码
  有时遵从第三条“包含操纵数据的代码”会让Model层变得很胖,因此可以为不同的子应用提供不同的Model子类。例如定义一个NewsBase类包含跨子应用共享的代码,为每个子应用实现自己的Model类,只包含针对当前子应用的代码。

1.3.2 View
  Ø 包含展示代码,例如HTML和简单的遍历、格式化、渲染PHP代码
  Ø 避免包含显示访问数据的代码
  Ø 避免直接访问$_GET,$POST等变量
  视图层的代码重用方式:
  Ø 布局:公共展示区域(如页眉、页脚)可以放在一个布局视图中
  Ø 部分视图:非布局代码的代码片段可以通过部分视图重用,例如表单代码
  Ø 控件:如果部分视图中包含大量逻辑,就可以提取为控件类来重用。
  Ø 助手类:完成细小任务的代码可以用助手类来重用,例如格式化数据,产生HTML标签

1.3.3 Controller
  Ø 可以访问$_GET,$_POST等PHP变量来获得用户请求
  Ø 创建Model实例,并管理其生命周期
  Ø 避免嵌入SQL语句
  Ø 避免包含HTML等展示层代码
  在设计良好的MVC应用程序中,控制器通过非常瘦,只包含几十行代码。然而模型层通常会很胖,因为它包含了不同领域的业务逻辑,满足特定的需求。
  


2应用组件扩展
  Yii 预定义了一系列核心应用组件,提供常见Web 应用中所用的功能。像【1.1组件角色】中的urlManager,【3数据访问】中将用到的CDbConnection,以及对Memcached支持等等都是通过可扩展的应用组件的形式加入到Yii中的。

2.1内置应用组件
  下面我们列出了由CWebApplication预定义的核心组件。
  ·assetManager:CAssetManager-管理私有资源文件的发布。
  ·authManager:CAuthManager-管理基于角色的访问控制(RBAC).
  ·cache:CCache-提供数据缓存功能。注意,你必须指定实际的类(例如CMemCache,CDbCache)。否则,当你访问此组件时将返回
NULL。
  ·clientScript:CClientScript-管理客户端脚本(javascripts
和CSS).
  ·coreMessages:CPhpMessageSource-提供
Yii 框架用到的核心信息的翻译。
  ·db:CDbConnection-提供数据库连接。注意,使用此组件你必须配置其connectionString属性。
  ·errorHandler:CErrorHandler-处理未捕获的
PHP 错误和异常。
  ·format:CFormatter-格式化数值显示。此功能从版本1.1.0
起开始提供。
  ·messages:CPhpMessageSource-提供Yii应用中使用的信息翻译。
  ·request:CHttpRequest-提供关于用户请求的信息。
  ·securityManager:CSecurityManager-提供安全相关的服务,例如散列,加密。
  ·session:CHttpSession-提供session相关的功能。
  ·statePersister:CStatePersister-提供全局状态持久方法。
  ·urlManager:CUrlManager-提供
URL 解析和创建相关功能
  ·user:CWebUser-提供当前用户的识别信息。
  ·themeManager:CThemeManager-管理主题。

2.2激活应用组件
  要激活应用组件,我们需要定制Application。方式一是提供一个配置文件以创建Application实例时初始化其属性值;另一种方式是继承CWebApplication。
  我们通常采用方式一,在一个单独的PHP脚本(例如protected/config/main.php)中保存这些配置。要激活某个应用组件,我们需要在这个PHP脚本中配置Application的components属性。例如下面激活memcached组件实现缓存功能:
  array(
  ......
   'components'=>array(
  ......
  'cache'=>array(
  'class'=>'CMemCache',
  'servers'=>array(
   array('host'=>'server1',
'port'=>11211,
'weight'=>60),
   array('host'=>'server2',
'port'=>11211,
'weight'=>40),
  ),
  ),
   ),
  )


  然后在入口脚本index.php中,将配置文件的名字作为参数传递给应用构造器:
  $app=Yii::createWebApplication($configFile);
  现在就可以访问应用组件了。使用Yii::app()->ComponentID,其中的ComponentID是指组件的ID(例如Yii::app()->cache)。

3数据访问方式
  Yii数据访问建立在PHP的PDO扩展上,通过一个统一的接口访问不同的数据库。在此基础上,Yii提供了三种数据库编程方式。

3.1直接使用核心类
  Yii DAO 主要包含如下四个类:
  CDbConnection:代表一个数据库连接。
  CDbCommand:代表一条通过数据库执行的SQL 语句。
  CDbDataReader:代表一个只向前移动的,来自一个查询结果集中的行的流。
  CDbTransaction:代表一个数据库事务。
  使用方法与Java的JDBC很像,例如:

$connection=Yii::app()->db;
$command=$connection->createCommand($sql);

// 查询
$dataReader=$command->query();
while(($row=$dataReader->read())!==false){ ... }
// 绑定参数
$sql="INSERT INTO tbl_user (username, email) VALUES(:username,:email)";
$command=$connection->createCommand($sql);
// 用实际的用户名替换占位符 ":username"
$command->bindParam(":username",$username,PDO::PARAM_STR);
// 用实际的 Email 替换占位符 ":email"
$command->bindParam(":email",$email,PDO::PARAM_STR);
$command->execute();


3.2查询构造器
  Yii的查询构造器(Query Builder)提供了面向对象方式编写SQL语句。例如:

$user = Yii::app()->db->createCommand()
->select('id, username, profile')
->from('tbl_user u')
->join('tbl_profile p', 'u.id=p.user_id')
->where('id=:id', array(':id'=>$id))
->queryRow();

  对于比较简单的SQL语句来说,使用这种方式编程的优点有:
  Ø 提高代码可读性
  Ø 参数绑定避免SQL注入攻击
  Ø 在PDO基础上的进一步数据库抽象,简化数据迁移

3.3活动对象模式
  虽然前两种方式已经可以处理几乎任何数据库相关的任务,但很可能我们会花费90%的时间以编写一些执行普通CRUD操作的SQL语句。而且我们代码中混杂的SQL语句也会使代码变得难以维护。要解决这些问题,我们可以使用活动对象模式(Active Record,简称AR)。
  AR是一个流行的对象-关系映射(ORM)技术。每个AR类代表一个数据表,数据表的列在 AR 类中体现为类的属性,一个AR实例则表示表中的一行。常见的CRUD操作作为AR的方法实现。因此,我们可以以一种更加面向对象的方式访问数据。

classPostextendsCActiveRecord
{
publicstaticfunctionmodel($className=__CLASS__)
{
returnparent::model($className);
}
publicfunctiontableName()
{
return'tbl_post';
}
}

$post=newPost;
$post->title='sample post';
$post->content='post body content';
$post->save();

  具体例子见【6.2模型层实例】。

4数据缓存支持
  Yii 提供了不同的缓存组件,可以将缓存数据存储到不同的媒介中。例如, CMemCache 组件封装了 PHP 的 memcache 扩展并使用内存作为缓存存储媒介。 CApcCache 组件封装了 PHP APC 扩展; 而 CDbCache 组件会将缓存的数据存入数据库。下面是一个可用缓存组件的列表:
  ·CMemCache:使用
PHPmemcache
扩展.
  ·CApcCache:使用
PHPAPC
扩展.
  ·CXCache:使用 PHPXCache
扩展。注意,这个是从 1.0.1
版本开始支持的。
  ·CEAcceleratorCache:使用
PHPEAccelerator
扩展.
  ·CDbCache:使用一个数据表存储缓存数据。默认情况下,它将创建并使用在runtime
目录下的一个SQLite3
数据库。你也可以通过设置其connectionID属性指定一个给它使用的数据库。
  ·CZendDataCache:使用
ZendData Cache 作为后台缓存媒介。注意,这个是从1.0.4
版本开始支持的。
  ·CFileCache:使用文件存储缓存数据。这个特别适合用于存储大块数据(例如页面)。注意,这个是从1.0.6
版本开始支持的。
  ·CDummyCache:目前
dummy 缓存并不实现缓存功能。此组件的目的是用于简化那些需要检查缓存可用性的代码。例如,在开发阶段或者服务器尚未支持实际的缓存功能,我们可以使用此缓存组件。当启用了实际的缓存支持后,我们可以切换到使用相应的缓存组件。在这两种情况中,我们可以使用同样的代码Yii::app()->cache->get($key)获取数据片段而不需要担心Yii::app()->cache可能会是null。此组件从
1.0.5 版开始支持。

4代码生成工具
  (TODO 介绍Yii和Gii,可以用来快速开发系统原型)

5单元测试框架
  (TODO 介绍基于PHPUnit的单元测试)

6一个典型例子
  在《Yii入门实例.docx》的基础上增加下面三个实例。
  演示程序的整体目录结构如下:
  app/
  |-- index.php
  `-- protected
  |-- config
  | `-- main.php
  |-- controllers
  | |-- CacheController.php
  | |-- HelloController.php
  | |-- PostController.php
  | |-- route
  | ||-- CreateAction.php
  | ||-- DeleteAction.php
  | |`-- UpdateAction.php
  | `-- RouteController.php
  |-- extensions
  | `-- redis
  | |-- CRedisCache_old.php
  | |-- CRedisCache.php
  | `-- Predis.php
  |-- filters
  | `-- PerfFilter.php
  |-- models
  | `-- Post.php
  `-- views
  |-- cache
  | `-- result.php
  |-- hello
  | `-- result.php
  |-- post
  | `-- result.php
  `-- route
  `-- result.php

6.1控制器层实例
  此控制器层实例主要演示三方面:
  Ø 请求参数自动绑定到方法形参
  Ø 独立的Action类替代action方法
  Ø 使用过滤器实现关注点分离
  1.helloyii/app/protected/controllers/RouteController.php
  ===============================================================================
  classRouteController extends CController
  {
  publicfunction actions()
  {
  $path= 'application.controllers.route.';
  returnarray(
  'create'=>$path.'CreateAction',
   'update'=>$path.'UpdateAction',
   'delete'=>$path.'DeleteAction',
  );
  }
  publicfunction filters()
  {
  returnarray(
  array('application.filters.PerfFilter'),
  );
  }
  }
  2.helloyii/app/protected/controllers/route/DeleteAction.php
  ===============================================================================
  class DeleteAction extends CAction //2.独立的Action类
  {
  public function run($id) //1.$_GET请求中的参数自动绑定到方法形参
  {
  $data = "This isDeleteAction served you, id:$id";
  Yii::app()->getController()->render('result',array('data'=>$data));
  }
  }
  现在访问http://helloyii.com/app/index.php?r=route/delete&id=1就能看到效果。
  注:CreateAction.php和UpdateAction.php的代码与之类似,就不列举了。
  3.helloyii/app/protected/filters/PerfFilter.php
  ===============================================================================
  classPerfFilter extends CFilter //3.实现过滤器抽象类,对Action进行前后处理
  {
  protectedfunction preFilter($filterChain)
  {
  echo'PerfFilter pre action<br/>';
  returntrue;
  }
  protectedfunction postFilter($filterChain)
  {
  echo'<br/>PerFilter post action';
  returntrue;
  }
  }
  4.helloyii/app/protected/views/route/result.php
  ===============================================================================
  <?php
  echo$data;
  ?>

6.2模型层实例
  首先创建数据库helloyii,在其中创建一张新表用来演示。

CREATETABLEtbl_post(
idINTEGERNOTNULLPRIMARYKEYAUTO_INCREMENT,
titleVARCHAR(128)NOTNULL,
contentTEXTNOTNULL,
create_timeINTEGERNOTNULL
);

  1.helloyii/app/protected/config/main.php
  ===============================================================================
  return array(
  'components' => array(
  'db' => array(
  'connectionString' =>'mysql:host=localhost;port=3306;dbname=helloyii;',
  'emulatePrepare' => true,
  'username'=> 'root',
  'password'=> '',
  'charset' =>'utf8',
  ),
  ),
  'import'=>array(
  'application.models.*',
  ),
  );
  注意:使用main.php对应用进行配置的话,要修改入口脚本index.php:
  $config=dirname(__FILE__).'/protected/config/main.php';
  Yii::createWebApplication($config)->run();
  2.helloyii/app/protected/controllers/RouteController.php
  ===============================================================================
  class PostController extends CController
  {
  public function actionQuery()
  {
  // Insert one row totable
  $post = new Post;
  $post->title ='sample post';
  $post->content ='content for sample post';
  $post->create_time =time();
  $post->save();
  // Query all rows intable
  $data =Post::model()->findAll();
  //Delegate viewer torender
  Yii::app()->getController()->render('result',array('data'=>$data));
  }
  }
  3.helloyii/app/protected/models/Post.php
  ===============================================================================
  class Post extends CActiveRecord
  {
  public static functionmodel($className=__CLASS__)
  {
  returnparent::model($className);
  }
  public function tableName()
  {
  return 'tbl_post'; //对应数据库中的表名
  }
  }
  4.helloyii/app/protected/views/post/result.php
  ===============================================================================
  <html>
  <head></head>
  <body>
  <table border="1">
  <tr>
  <th>ID</th>
  <th>Title</th>
  <th>Content</th>
  <th>CreateTime</th>
  </tr>
  <?php
  foreach ($data as $row)
  {
  echo "<tr>";
  echo"<td>".$row->id."</td>";
  echo"<td>".$row->title."</td>";
  echo"<td>".$row->content."</td>";
  echo"<td>".$row->create_time."</td>";
  echo "</tr>";
  }
  ?>
  </table>
  </body>
  </html>
  现在访问http://helloyii.com/app/index.php?r=post/query,效果如下图:


6.3数据缓存实例
  详见《Yii集成Redis》。

参考资料
  1 Yii的控制器官方介绍
  http://www.yiiframework.com/doc/guide/1.1/zh_cn/basics.controller
  2 MVC最佳实践官方介绍
  http://www.yiiframework.com/doc/guide/1.1/zh_cn/basics.best-practices
页: [1]
查看完整版本: PHP框架Yii系列教程(二):功能简介