分类目录归档:Yaf

Zend\Authentication 应用实例(Yaf框架)

#用户表SQL
SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(64) NOT NULL,
  `email` varchar(128) NOT NULL,
  `password` varchar(1024) NOT NULL,
  `active` tinyint(1) DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `email_idx` (`email`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'vfeelit', 'vfeelit@qq.com', '4cbfa3a5874c68e0593c7a7c5ec7d4fc6c823235b71fc7fb96db51eceb2073d5db55e20b0a22098c8440b665c462141cc7dcfe1b25ff7d2be717aacaf8578d882b869ea8d7cba9ab2f82', '0');
INSERT INTO `user` VALUES ('2', 'ifeeline', 'ifeeline@qq.com', '4cbfa3a5874c68e0593c7a7c5ec7d4fc6c823235b71fc7fb96db51eceb2073d5db55e20b0a22098c8440b665c462141cc7dcfe1b25ff7d2be717aacaf8578d882b869ea8d7cba9ab2f82', '1');


################################
<?php
use Ifeeline\Registry;
use Ifeeline\Password;

use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Storage\Session as SessionStorage;
use Zend\Authentication\Result;
use Zend\Authentication\Adapter\DbTable\CredentialTreatmentAdapter;

use Table\UserModel;

class AuthPlugin extends Yaf\Plugin_Abstract 
{
    public function routerStartup(Yaf\Request_Abstract $request, Yaf\Response_Abstract $response)
    {
    }
     
    // 命令行方式不会经过这里
    public function routerShutdown(Yaf\Request_Abstract $request, Yaf\Response_Abstract $response )
    {
        // 路由之后才能获取这三个值
        $module = strtolower($request->getModuleName());
        $controller = strtolower($request->getControllerName());
        $action = strtolower($request->getActionName());
         
        $default = new Zend\Session\Container();
        if(!$request->isPost()){
            $default->offsetSet('securityToken',md5(uniqid(rand(),true)));
        }
         
        // 可以传入Zend\Authentication\Storage\Session对象,实际关联一个SESSION容器
        $auth = new AuthenticationService();
        if($auth->hasIdentity()) {
            $storage = $auth->getStorage();
            $storageData = $storage->read();
             
            $access_time = 0;
            if(!empty($storageData->access_time)) {
                $access_time = (int)$storageData->access_time;
            }
             
            // 已经半小时没有活动了 实际SESSION可能并没有清除
            if((time() - $access_time) > 1800) {
                $auth->clearIdentity();
                $response->clearBody()->setRedirect("/login/login");
                exit;
            } else {
                $storageData->access_time = time();
                $storage->write($storageData);
            }
             
            if(($controller === "login")) {
                if($action === "logout") {
                    $auth->clearIdentity();
                    $response->clearBody()->setRedirect("/login/login");
                    exit;
                }
                if($action === "login") {
                    $response->clearBody()->setRedirect("/");
                    exit;
                }
            }
        } else if($request->isPost()) {
            // 验证token
            if(!isset($_POST['securityToken']) || ($_POST['securityToken'] !== $default->offsetGet('securityToken'))) {
                $response->clearBody()->setRedirect("/login/login");
                exit;
            }
            
            // 需要验证的数据
            $email = trim($_POST['email']);
            $password = trim($_POST['password']);
            if(empty($email) || empty($password)) {
                $default->offsetSet("freshMessage", "邮件地址或密码不能为空");
                $response->clearBody()->setRedirect("/login/login");
                exit;
            }
            
            // 匹配邮件地址 和 密码
            $user = new Table\UserModel();
            $userRow = $user->getUserByEmail($email);
            if(!empty($userRow)) {
                // 查看是否已经被禁用
                if((int)$userRow['active'] < 1) {
                    $default->offsetSet("freshMessage", "账户已经禁用.");
                    $response->clearBody()->setRedirect("/login/login");
                    exit;
                }
                
                $hashPassword = trim($userRow['password']);
                $salt = Ifeeline\Password::getPasswordSaltByHash($hashPassword);
                $nowPassword = Ifeeline\Password::getPasswordHash($salt, $password);
                
                if($nowPassword !== $hashPassword) {
                    $default->offsetSet("freshMessage", "密码不正确");
                    $response->clearBody()->setRedirect("/login/login");
                    exit;
                }
            } else {
                $default->offsetSet("freshMessage", "邮件地址不存在");
                $response->clearBody()->setRedirect("/login/login");
                exit;
            }            
            
            // 实际上,以上的密码比较已经结束  这里使用它的会话持久化功能
            $dbAdapter = Registry::get('db');
            $authAdapter = new CredentialTreatmentAdapter($dbAdapter);
            $authAdapter
                ->setTableName('user')
                ->setIdentityColumn('email')
                ->setCredentialColumn('password');
         
            // 这里应该使用自定义的密码哈希算法,然后再传递进行比较
            $authAdapter
                ->setIdentity($email)
                ->setCredential($nowPassword);
              
            $result = $auth->authenticate($authAdapter);
         
            // 这个IF应该永不会进入
            if (!$result->isValid()) {
                switch ($result->getCode()) {
                    case Result::FAILURE_IDENTITY_NOT_FOUND:
                        //break;
                    case Result::FAILURE_CREDENTIAL_INVALID:
                        //break;
                    //case Result::SUCCESS:
                    //    break;
                    default:
                        //$result->getMessages()
                        $default->offsetSet("freshMessage", "用户名或密码不正确.");
                        break;
                }
                 
                $response->clearBody()->setRedirect("/login/login");
                exit;
            } else {                
                $row = $authAdapter->getResultRowObject(null, array('password'));
                // 账户被禁用(这不会执行)
                if((int)$row->active < 1) {
                    // 清楚认证信息
                    $auth->clearIdentity();
                     
                    $default->offsetSet("freshMessage", "用户名已经被禁用.");
                     
                    $response->clearBody()->setRedirect("/login/login");
                    exit;
                } else {
                    $row->access_time = time();
                     
                    $storage = $auth->getStorage();
                    $storage->write($row);
                     
                    // 成功登录
                    $response->clearBody()->setRedirect("/");
                    exit;
                }
            }
        } else {
            if(($controller !== "login") || (($controller === "login") && ($action !== "login"))) {
                $response->clearBody()->setRedirect("/login/login");
                exit;
            }
        }
    }
     
    public function preDispatch(Yaf\Request_Abstract $request, Yaf\Response_Abstract $response)
    {
    }
     
    public function postDispatch(Yaf\Request_Abstract $request, Yaf\Response_Abstract $response)
    {
    }
}

##对应控制器 模型 和 模板
<?php
use Ifeeline\Registry;
use Ifeeline\BaseController;
 
class LoginController extends BaseController
{
    public function loginAction()
    {
        // 取回登录失败信息
        $default = new Zend\Session\Container();
        if($default->offsetExists("freshMessage")){
            $this->_view->freshMessage = $default->offsetGet("freshMessage");
            $default->offsetUnset("freshMessage");
        }
        $this->_view->securityToken = $default->offsetGet("securityToken");
         
        $this->render("login/login.phtml");
    }
     
    public function logoutAction()
    {
        return false;
    }
}
###################################
<?php
namespace Table;

use Ifeeline\Registry;
use Exception;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Sql\Sql;
use Zend\Db\Sql\Select;

class UserModel extends TableGateway {
	protected $table = 'user';
	public function __construct(AdapterInterface $adapter = null, $features = null, ResultSetInterface $resultSetPrototype = null, Sql $sql = null){
		if($adapter instanceof Adapter){
			parent::__construct($this->table, $adapter, $features, $resultSetPrototype, $sql);
		}else{
			$adapter = Registry::get('db');
			if($adapter instanceof Adapter){
				parent::__construct($this->table, $adapter);
			}else{
				throw new Exception("Need an Zend\Db\Adapter object.");
			}
		}
	}
	
	// 根据邮件地址返回一行
	public function getUserByEmail($email=null)
	{
	    if(!empty($email) && is_string($email)) {
	        $current = $this->select(array('email'=>$email))->current();
	        if(!empty($current)) {
	            return $current->getArrayCopy();
	        }
	    }
	    return array();
	}
}
###################################
<div>
<?php 
if($this->freshMessage){
    print_r($this->freshMessage);
}
?>
</div>
<form action="/login/login" method="post">
<table style="width:500px;">
    <tr>
        <td style="width:150px; text-align:right">邮件地址</td>
        <td><input type="input" name="email" value="" /></td>
    </tr>
    <tr>
        <td style="width:150px; text-align:right">密码</td>
        <td><input type="password" name="password" value="" /></td>
    </tr>
    </tr>
        <td>
        <input type="hidden" name="securityToken" value="<?php echo $this->securityToken;?>" />
        </td>
        <td><input type="submit" value="提交" /></td>
    </tr>
</table>
</form>

// 同时贴上Bootstrap配置
<?php
use Yaf\Application;
use Yaf\Bootstrap_Abstract as BootstrapAbstract;
use Yaf\Dispatcher;
use Yaf\Route\Regex;

use Ifeeline\Registry;

use Zend\Db\Adapter\Adapter;
use Zend\Mail\Transport\Smtp as SmtpTransport;
use Zend\Mail\Transport\SmtpOptions;
use Zend\Session\Config\SessionConfig;
use Zend\Session\SessionManager;
use Zend\Session\Validator\HttpUserAgent;

class Bootstrap extends BootstrapAbstract {
	private $_config;

	public function _init(Dispatcher $dispatcher) {
	    // 引入Composer,Yaf扩展的配置项yaf.use_spl_autoload务必设置为1
	    if(file_exists(ROOT_PATH.'/vendor/autoload.php')){
	        $loader = include ROOT_PATH.'/vendor/autoload.php';
	        // 可以手工载入一批第三方库
	        // 明确指定命名空间对应的路径,有利于提升性能
	        $loader->add("",ROOT_PATH.'/library');
	        $loader->addPsr4("Zend\\",ROOT_PATH.'/library/Zend');
	        
	        Registry::set('loader', $loader);
	    }
	    
	    // 禁止自动渲染
	    $dispatcher->autoRender(FALSE);
	    
	    // 保存配置
		$this->_config = Application::app()->getConfig();
		Registry::set('config', $this->_config);

		// 报错设置
		if($this->_config->global->showError){
			error_reporting (-1);
			ini_set('display_errors','On');
		}
		
		// 命令行方式,跳过SESSION
		if(!defined("SKIP_SESSION")) {
		    // SESSION
		    $config = new SessionConfig();
		    
		    $sessionConfig = $this->_config->session->toArray();
		    if(isset($sessionConfig['save_path'])) {
		        @mkdir($sessionConfig['save_path'],0777,true);
		    }
		    
		    $config->setOptions($sessionConfig);
		    $manager = new SessionManager($config);
		    $manager->getValidatorChain()->attach('session.validate', array(new HttpUserAgent(), 'isValid'));
		    $manager->start();
		    if(!$manager->isValid()) {
		        $manager->destroy();
		        throw new \Exception("会话验证失败");
		    }
		    Registry::set('session', $manager);
		}

		// 数据库
		Registry::set('db',function(){
			$mysqlMasterConfig = $this->_config->mysql->master->toArray();
			$adapter = new Adapter($mysqlMasterConfig);
			return $adapter;
		});
		
        //
		Registry::set('job',function(){
            $jobConfig = $this->_config->mysql->job->toArray();
            
            //$jobConfig['driver'] = 'mysqli';
            // or
            unset($jobConfig['charset']);
            $jobConfig['driver'] = 'pdo_mysql';
            $jobConfig['driver_options']['1002'] = "SET NAMES UTF8;";

		    $jobAdapter = new Adapter($jobConfig);
		    return $jobAdapter;
        });
		
		// 邮件
		Registry::set('mail',function() {
		    $options   = new SmtpOptions($this->_config->smtp->toArray());
		    $mail = new SmtpTransport();
		    $mail->setOptions($options);
		    
		    return $mail;
		});
		
		// 日志
		Registry::set('logger', function() {
		    $logger = new Zend\Log\Logger;
		    $writer = new Zend\Log\Writer\Stream($this->_config->log->path.'/'.date("Ymd").".log");
		    
		    $logger->addWriter($writer);
		    return $logger;
		});
	}
	
	public function _initRoutes() {
		//Dispatcher::getInstance()->getRouter()->addRoute("xxx", new Regex(,,));
	}
	
	public function _initPlugin(Dispatcher $dispatcher) {
		$authPlugin = new AuthPlugin();
		$dispatcher->registerPlugin($authPlugin);
	}
}

Yaf实现简单布局 与 实例(分页)

在一个视图输出中,套入一个布局是很常见的。Yaf中提供了一个简单的视图实现,只要稍微封装,就可以实现一般的布局。

<?php
namespace Ifeeline;

use Yaf\Controller_Abstract as ControllerAbstract;

class BaseController extends ControllerAbstract
{
    // 实现简单布局
    public function render($tpl, array $parameters = NULL)
    {
        if(!empty($tpl) && is_string($tpl)) {
            if(!empty($parameters) && is_array($parameters)){
                $this->_view->assign($parameters);
            }
            $content = $this->_view->render($tpl);
            // 总是启用布局,除非明确禁止
            if($this->_view->layout !== false) {
                // 确定布局文件
                $layout = $this->_view->layoutTemplate;
                if(empty($layout) || !is_string($layout)) {
                    $layout = "main.phtml";
                }
                
                // 确定布局路径
                $layoutPath = '';
                $config = Registry::get('config');
                if(isset($config->global->layoutPath)) {
                    $layoutPath = $config->global->layoutPath;
                }
                if(empty($layoutPath)) {
                    if(defined('APPLICATION_PATH')) {
                        $layoutPath = APPLICATION_PATH."/layouts";
                    }
                }
                
                // 布局文件存在
                if(!empty($layoutPath) && file_exists($layoutPath."/".$layout)) {
                    $this->_view->setScriptPath(APPLICATION_PATH."/layouts");
                    $this->_view->assign("content", $content);
                    echo $this->_view->render($layout);
                    return;
                }
            }
            echo $content;
        }
    }
}

// 所有控制器类继承自Ifeeline\BaseController
<?php
use Ifeeline\BaseController;

class TestViewController extends BaseController
{
}

Yaf\Controller_Abstract本身实现了render()方法(实际是视图render()的封装),这里覆盖掉这个方法,把视图输出套入布局中。

Yaf中的View非常的简单,这个对象的数据可以通过$this->_view->xxxx=vvvv设置,也可以通过$this->_view->assign(array())设置,还可以在调用$this->_view->render(‘index.phtml’,array())传入,这些传入的数据都存储在_tpl_var中,所以多次渲染都会共用这个变量池,故而可以把子视图输出,套入布局中,在布局中进行输出,从而实现二步视图。

以下贴一个实例代码(分页实现):

#控制器
use Ifeeline\Registry;

use Ifeeline\BaseController;
use Zend\Db\Adapter\Adapter;
use Table\PlatformModel;
use Zend\Db\TableGateway\TableGateway;
use Zend\Paginator\Adapter\DbTableGateway;
use Zend\Paginator\Paginator;

class TestViewController extends BaseController
{   
    public function indexAction()
    {
        $dbAdapter = Registry::get('job');
        $tableGateway = new TableGateway("datatables_demo", $dbAdapter);
        $tableGatewayAdapter = new DbTableGateway($tableGateway,null,"id ASC");
        $paginator = new Paginator($tableGatewayAdapter);

        // 总页数
        $totalPage = count($paginator);
        
        // 获取当前页码
        $page = !empty($_GET['page'])?(int)$_GET['page']:1;
        if($page < 1) {
            $page = 1;
        } else if($page > $totalPage) {
            $page = $totalPage;
        }
        $paginator->setCurrentPageNumber($page);
        
        
        //
        $paginator->setPageRange(10);
        
        ///////////////////////////////////////
        // 视图输出
        $this->_view->assign(array(
            // 布局参数
            'title' => "页面标题设置",
            // 视图数据
            'paginator' => $paginator
        ));
        $this->render("testview/index.phtml");
    }
}

# 布局 layout.phtml
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title><?php echo isset($title)?$title:'默认标题';?></title>
</head>
<body>
<?php
// 页头
echo $this->render("header.phtml");

// 主体
if(isset($content)){
    echo $content;
}

// 页脚
echo $this->render("footer.phtml");
?>
</body>
</html>

# testview/index.phtml
<table style="width:500px;">
    <tr style="backgroup-color:#ccc">
        <td style="width:150px; text-align:left;">名称</td>
        <td style="width:150px; text-align:left;">值</td>
        <td>操作</td>
    </tr>
<?php
if(!empty($paginator)) {
    $datas = $paginator->getCurrentItems();
}
if(!empty($datas)) {
    foreach($datas as $row) {
?>
    <tr>
        <td style="text-align:left;"><?php echo $row['first_name'];?></td>
        <td style="text-align:left;"><?php echo $row['last_name'];?></td>
        <td style="text-align:left;">编辑 更新 删除</td>
    </tr>
<?php 
    }
}
?>
</table>

<br /><br />
<?php 
if(!empty($paginator)) {
    echo $this->render("paginator.phtml");
}

# paginator.phtml 分页
<?php 
// 取回请求的Uri
$request = \Yaf\Dispatcher::getInstance()->getRequest();
$baseURL = $request->getRequestUri();
// 分页控制
$pageCtrl = $paginator->getPages();
// 查询数据
$query = $_GET;
unset($query['x'],$query['y']);

if ($pageCtrl->pageCount): ?>
<div class="paginationControl">
    <?php echo $pageCtrl->firstItemNumber;?> - <?php echo $pageCtrl->lastItemNumber;?> of <?php echo $pageCtrl->totalItemCount;?>

    <!-- First page link -->
    <?php 
    if (isset($pageCtrl->previous)): 
        $query['page'] = $pageCtrl->first;
    ?>
    <a href="<?php echo $baseURL."?".http_build_query($query);?>">首页</a> |
    <?php else: ?>
    <span class="disabled">首页</span> |
    <?php endif; ?>

    <!-- Previous page link -->
    <?php if (isset($pageCtrl->previous)): 
        $query['page'] = $pageCtrl->previous;
    ?>
      <a href="<?php echo $baseURL."?".http_build_query($query);?>">
                     前一页
      </a> |
    <?php else: ?>
      <span class="disabled">前一页</span> |
    <?php endif; ?>

    <!-- Numbered page links -->
    <?php foreach ($pageCtrl->pagesInRange as $pageIndex): ?>
      <?php if ($pageIndex != $pageCtrl->current): 
            $query['page'] = $pageIndex;
      ?>
        <a href="<?php echo $baseURL."?".http_build_query($query);?>">
            <?php echo $pageIndex; ?>
        </a> |
      <?php else: ?>
        <?php echo $pageIndex; ?> |
      <?php endif; ?>
    <?php endforeach; ?>
    
    <!-- Next page link -->
    <?php if (isset($pageCtrl->next)): 
        $query['page'] = $pageCtrl->next;
    ?>
      <a href="<?php echo $baseURL."?".http_build_query($query);?>">
                     下一页
      </a> |
    <?php else: ?>
      <span class="disabled">下一页</span> |
    <?php endif; ?>
    
    <!-- Last page link -->
    <?php if (isset($pageCtrl->next)): 
        $query['page'] = $pageCtrl->last;
    ?>
      <a href="<?php echo $baseURL."?".http_build_query($query);?>">
                    末页
      </a>
    <?php else: ?>
      <span class="disabled">末页</span>
    <?php endif; ?>
</div>
<?php endif; ?>

yaf-layout

Yaf框架快速搭建

文件下载:Yaf样板

编译模块,添加:

[yaf]
;yaf.environ=product
;yaf.library=NULL
;yaf.cache_config=0
;yaf.name_suffix=1
;yaf.name_separator=""
;yaf.forward_limit=5
yaf.use_namespace=1
yaf.use_spl_autoload=1

建立public目录,建立两个文件:

//.htaccess
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.php

//index.php
<?php
define('ROOT_PATH', dirname(__DIR__));
define('APPLICATION_PATH', ROOT_PATH . "/application");

$application = new Yaf\Application(require ROOT_PATH . "/conf/global.php");
$application->bootstrap()->run();

建立conf目录,键一个文件:

// global.php
<?php
return array(
    'application' => array(
        'directory' => realpath(__DIR__.'/../application'), // 应用目录,可改名
        //'ext' => "php",
        //'bootstrap' => "Bootstrapplication.php",
        //'library' => application.directory + "/library",
        //'baseUri' => NULL,
        'dispatcher' => array(
            //'defaultModule' => "index",
            'throwException' => FALSE,              // 默认TRUE
            //'catchException' => False,
            //'defaultController' => "index",
            //'defaultAction' => "index",
        ),
        //'view' => array(
        //    'ext' => "phtml",
        //),
        //'modules' => 'Index',
        //'system' => array(),
    ),
    'mysql' => array(
        'master' => array(
            'driver' => "pdo_mysql",
            'driver_options' => array(
                "1002" => "SET NAMES UTF8;"
            ),
            'hostname' => "localhost",
            'database' => 'test',
            'username' => 'root',
            'password' => '',
            'port' => 3306
        ),
        'slave' => array(
            'driver' => "pdo_mysql",
            'driver_options' => array(
                "1002" => "SET NAMES UTF8;"
            ),
            'hostname' => "localhost",
            'database' => 'test',
            'username' => 'root',
            'password' => '',
            'port' => 3306
        ),
        'mysqli' => array(
            'driver' => "mysqli",
            'charset' => 'utf8',
            'host' => "localhost",
            'dbname' => 'test',
            'username' => 'root',
            'password' => '',
            'port' => 3306
        )
    )
);

建立application文件夹,在其下建立controllers layouts library models plugins views文件夹和Bootstrap.php文件:

<?php
use Yaf\Application;
use Yaf\Bootstrap_Abstract as BootstrapAbstract;
use Yaf\Dispatcher;
use Yaf\Route\Regex;
use Zend\Db\Adapter\Adapter;
use Ifeeline\Registry;

class Bootstrap extends BootstrapAbstract 
{
	private $_config;

	public function _init(Dispatcher $dispatcher) 
	{
	    // 配置
		$this->_config = Application::app()->getConfig();
		Registry::set('config', $this->_config);

		// 是否显示错误
		if($this->_config->application->showError){
			error_reporting (-1);
			ini_set('display_errors','On');
		}
		
        // 引入Composer,Yaf扩展的配置项yaf.use_spl_autoload务必设置为1
        if(file_exists(ROOT_PATH.'/vendor/autoload.php')){
	       $loader = include ROOT_PATH.'/vendor/autoload.php';
	       
	       // 可以手工载入一批第三方库
	       $loader->setPsr4("Zend\\",ROOT_PATH.'/library/Zend');
	       
	       Registry::set('loader', $loader);
		}
		
		// 禁止自动渲染视图
		$dispatcher->autoRender(FALSE);
		// 一样?
		//$dispatcher->disableView();
		
		// 数据适配器
		Registry::set('db',function(){
			$mysqlMasterConfig = $this->_config->mysql->master->toArray();
			$adapter = new Adapter($mysqlMasterConfig);
			return $adapter;
		});
	}
	
	public function _initRoutes(Dispatcher $dispatcher)
	{
		//Dispatcher::getInstance()->getRouter()->addRoute("xxx", new Regex(,,));
	}
	
	// 注册插件
	public function _initPlugin(Dispatcher $dispatcher) 
	{
		$authPlugin = new AuthPlugin();
		$dispatcher->registerPlugin($authPlugin);
	}
	
	// 初始化视图布局
	public function _initLayout() 
	{
	}
}

在application/library下建立:

##Ifeeline/Registry.php
<?php
namespace Ifeeline;

class Registry
{
	public static $instance = array();

	public static function set($name,$callBack)
	{
		if(!isset(self::$instance[$name])) {
			self::$instance[$name] = $callBack;
		}
	}

	public static function get($name)
	{
		if(isset(self::$instance[$name])) {
			if(is_callable(self::$instance[$name])) {
				$func = self::$instance[$name];
				self::$instance[$name] = $func();
			}
			return self::$instance[$name];
		}
		return false;
	}
	
	public static function has($name)
	{
		if(isset(self::$instance[$name]))
		{
			return true;
		}
		return false;
	}
	
	public static function del($name)
	{
		if(self::has($name)) {
			unset(self::$instance[$name]);
		}
	}
}

在application/plugins下建立一个插件类样板:

##Auth.php 文件名不带Plugin,但是类名要带
<?php

class AuthPlugin extends Yaf\Plugin_Abstract 
{

    public function preDispatch(Yaf\Request_Abstract $request, Yaf\Response_Abstract $response)
    {
//      $module = strtolower($request->getModuleName());
//     	$controller = strtolower($request->getControllerName());
//     	$action = strtolower($request->getActionName());
    }
    
    public function routerShutdown(Yaf\Request_Abstract $request, Yaf\Response_Abstract $response ){
//         print_r($request);
    }
}

在application的models目录下建立Table文件夹,在其下建立一个模型样板:

##Platform.php  类名需要包含Model后缀
<?php
namespace Table;

use Ifeeline\Registry;
use Exception;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\Adapter\Adapter;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Sql\Sql;

class PlatformModel extends TableGateway {
	protected $table = 'platform';
	public function __construct(AdapterInterface $adapter = null, $features = null, ResultSetInterface $resultSetPrototype = null, Sql $sql = null){
		if($adapter instanceof Adapter){
			parent::__construct($this->table, $adapter, $features, $resultSetPrototype, $sql);
		}else{
			$adapter = Registry::get('db');
			if($adapter instanceof Adapter){
				parent::__construct($this->table, $adapter);
			}else{
				throw new Exception("Need an Zend\Db\Adapter object.");
			}
		}
	}
	public function getAll(){
		return $this->select("id > 0")->toArray();
	}
}

在application的controllers中建立个控制器:

##Test.php  文件名不要带Controller,但是类名需要
<?php
use Yaf\Controller_Abstract as ControllerAbstract;
use Ifeeline\Registry;
use Zend\Db\Adapter\Adapter;

class TestController extends ControllerAbstract
{
    public function init()
    {
        $db = Registry::get('db');
        $platforms = $db->query("SELECT * FROM platform",Adapter::QUERY_MODE_EXECUTE);
        
        // 
        $this->_view->assign(array("platforms"=>$platforms->toArray()));
    }
    
    public function indexAction()
    {
        echo $this->_view->render("test/index.phtml");
    }

    public function helloAction() 
    {
        $db = Registry::get('db'); 
        //$db = new Zend\Db\Adapter\Adapter($driver);
        $platforms = $db->query("SELECT * FROM platform",Adapter::QUERY_MODE_EXECUTE);
        
        echo $this->_view->render("test/hello.phtml",array("platforms"=>$platforms->toArray()));
    }
    
    public function methodAction()
    {
        $request = $this->getRequest();
        
        // 请求类型
        if($request->isGet()) { 
        }      
        if($request->isPost()) {
        }
        if($request->isHead()) {
        }
        if($request->isPut()) {
        }
        if($request->isCli()) {
        }
        if($request->isXmlHttpRequest()) {
        }
        
        // 获取当前请求的类型, 可能的返回值为GET,POST,HEAD,PUT,CLI等
        // 用来批判的是否是命令行
        if ($request->getMethod() == "CLI") {
            echo "running in cli mode";
        }
        
        // 获取路由参数 不是获取$_GET
        // http://yaf.vfeelit.com/index/test/method/aaa/1111
        $v = $request->getParam("aaa");
        if($v) {
            echo 'aaa->'.$v;
        }
        $request->setParam("fire",'nono');
        $vs = $request->getParams();
        print_r($vs);
    }

    //使用模型
    public function modeAction()
    {
        $platform = new PlatformModel();
        print_r($platform->getAll());
    }
}

整个MVC齐全了。

PHP框架Yaf 之 运行流程

官方给的流程图:
yaf_sequence

Yaf是一个以PHP扩展方式添加的框架,它的内部运行过程无法像一般PHP框架一样可以打开源代码查看跟踪看个明白。但是它跟一般的的MVC框架的运行流程是大同小异的。特别是如果你熟悉Zend Framework框架的流程,你要熟悉Yaf的框架,基本上学习成本可以说是零。

Yaf借用了很多Zend Framework 1.x的概念。基本就是它的C语言实现版,不过它比Zend Framework的MVC实现简化了一些。下面总结一下运行流程。

首先从入口程序开始:

<?php
define('APPLICATION_PATH', dirname(__FILE__));

$application = new Yaf_Application( APPLICATION_PATH . "/conf/application.ini");
print_r($application);

$application->bootstrap();
$application->run();

Yaf_Application对象生成后,输出这个对象看看有啥东西:

Yaf_Application Object
(
    [config:protected] => Yaf_Config_Ini Object
        (
            [_config:protected] => ...
            [_readonly:protected] => 1
        )

    [dispatcher:protected] => Yaf_Dispatcher Object
        (
            [_router:protected] => Yaf_Router Object
                (
                    [_routes:protected] => Array
                        (
                            [_default] => Yaf_Route_Static Object
                                (
                                )

                        )

                    [_current:protected] => 
                )

            [_view:protected] => 
            [_request:protected] => Yaf_Request_Http Object

            [_plugins:protected] => Array
                (
                )

            [_auto_render:protected] => 1
            [_return_response:protected] => 
            [_instantly_flush:protected] => 
            [_default_module:protected] => Index
            [_default_controller:protected] => Index
            [_default_action:protected] => index
        )

    [_modules:protected] => Array
        (
            [0] => Index
        )

    [_running:protected] => 
    [_environ:protected] => product
    [_err_no:protected] => 0
    [_err_msg:protected] => 
)

看到Yaf_Application对象生成后,内部就生成了Yaf_Config_Ini 和 Yaf_Dispatcher,还有_modules(定义了哪些模块),_environ(决定使用哪个配置节),Yaf_Config_Ini对象保存了配置文件的信息,Yaf_Dispatcher叫分发器,玛尼这个就是一般意义上的前段控制器吧,它内部就丰富一些,有路由器,Yaf_Request_Http对象,插件数组,默认的模块默认的控制器和方法(Action),看起来,实例化的东西不算多吧。

第二部就是允许Yaf_Application对象的bootstrap()方法,这个实际就是Bootstrap类(可通过配置修改),这个类实际没有任何内容,不过在它里面可以定义_init开头的方法,这些方法, 都接受一个参数:Yaf_Dispatcher $dispatcher,它会被自动按照顺序执行:

class Bootstrap extends Yaf_Bootstrap_Abstract{
    	public function _initConfig() {
		//把配置保存起来
		$arrConfig = Yaf_Application::app()->getConfig();
		Yaf_Registry::set('config', $arrConfig);
	}
	public function _initPlugin(Yaf_Dispatcher $dispatcher) {
		//注册一个插件
		$objSamplePlugin = new SamplePlugin();
		$dispatcher->registerPlugin($objSamplePlugin);
	}
}

先看看这里自定义的_initConfig方法,把配置对象取出来,然后写入到Yaf_Registry中,这样在任何代码中都可以通过Yaf_Registry::get(‘config’)获得这个对象的引用,当要全局共享数据时,这个方法就非常有用。

接下来看看_initPlugin方法,它里面要注册插件,所以携带了Yaf_Dispatcher对象引用。这个引用跟前面输出的那个分发器是同一个对象(全局只有一个分发器或叫前端控制器),插件注册后就会写入到分发器的_plugins数组中。

一般在这个Bootstrap中对资源进行初始化。

这个bootstrap()完成之后,就开始run()方法。这个方法中首先要触发的routerStartup事件,所有绑定到这个事件的方法这个时候首先被执行。然后启动路由器匹配路由,把结果更新到Request对象(找出模块 控制器 和 方法),然后触发routerShutdown事件。我们可以在这个事件中绑定一个方法输出分发器对象看看:

//在注册了的插件中添加如下代码
Yaf_Dispatcher Object
(
    [_router:protected] => Yaf_Router Object
        (
            [_routes:protected] => Array
                (
                    [_default] => Yaf_Route_Static Object
                        ()
                )

            [_current:protected] => _default
        )

    [_view:protected] => 
    [_request:protected] => Yaf_Request_Http Object
        (
            [module] => Index
            [controller] => Vfeelit
            [action] => zend
            [method] => GET
            [params:protected] => Array
                (
                )

            [language:protected] => 
            [_exception:protected] => 
            [_base_uri:protected] => /sample
            [uri:protected] => /sample/vfeelit/zend
            [dispatched:protected] => 
            [routed:protected] => 1
        )

    [_plugins:protected] => Array
        (
            [0] => SamplePlugin Object
                (
                )

        )

    [_auto_render:protected] => 1
    [_return_response:protected] => 
    [_instantly_flush:protected] => 
    [_default_module:protected] => Index
    [_default_controller:protected] => Index
    [_default_action:protected] => index
)

注意看Yaf_Request_Http对象,它的module、controler、action都确定了(路由匹配的结果),它的routed对应的值是1,表示已经路由完成,但是dispatched还没有值,就是还没有进行分发。

接下来触发dispatchLoopStart时间,进入分发循环,在具体分发前,先触发preDispatch,然后是开始分发(具体逻辑不知,一般是路由器匹配路由后,由路由器分发),这里发生的逻辑应该是把控制器的类文件装载进来,开始控制器的初始化(自动调用init方法)然后执行对应的Action,注意,如果调用了Forward将会设置isDispatched为FALSE(Forward的调用会导致request对象发送变化),但是当前的Action不会停止,直到返回后判断isDispatched为FALSE后再次进入循环进行新的分发过程。Action调用完毕后,判断是否是autoRender,如果是就初始化Render(就是视图),这个时候$dispatch对象中的_view才被填充。返回后开始触发postDispatch。

接下来判断IsDispatched(请求对象中记录)来是否进行是否再次分发过程。如果被标记为已分发,接下来就是触发dispatchLoopShutdown。

然后判断是否是autoResponse,如果是就把响应返回给客户端。

Yaf MVC整体流程大概如此,总体上,路由器与路由匹配上,跟一般MVC实现不太一样,另外就是视图渲染上,没有涉及到布局的内容(可以自己实现),在Model上,没有任何体现。所以Yaf是轻型MVC。如果要追求速度,它应该是简单的,这样才能被可靠依赖。

把这个流程图记住,开发就会得心应手。

原创文章,转载务必保留出处。
永久链接:http://blog.ifeeline.com/1197.html

PHP框架Yaf 之 引入和使用Zend Framework组件

Yaf中没有提供所谓的ORM,实际上Yaf本身保持自己是一个高性能的MVC框架足够了,因为其它的东西完全可以重用别的类库,比如使用Zend Framework的组件,由于Zend Framework的每个组件是高度自治的以致于它可以独立使用,你或者觉得Zend Framework的MVC实现太笨重,没有问题的,你可以用Yaf来代替它或者自己来实现一个MVC,举例:Magento这个开源程序数据库层就基于Zend Framework的数据库封装,它重用了Zend Framework的组件,但是MVC是自己实现的。

以下是在Yaf中使用Zend Framework组件(以数据库组件为例)。
首先,我这里使用的是Zend Framework 1.x的组件,虽然Zend Framework 2.x已经发现很久了,由于对2.x没什么研究,所以…

Yaf中类的加载使用Yaf Autoloader来负责类的加载,文档原话:Yaf在自启动的时候, 会通过SPL注册一个自己的Autoloader, 出于性能的考虑, 对于框架相关的MVC类, Yaf Autoloader只以目录映射的方式尝试一次。在Yaf中当遇到一个类需要加载时,它扫描的目录是php.ini中设置的ap.library目录,这个目录存放全局类,没有找到就到配置文件的ap.library目录中寻找,这个目录中的类一般可以认为是所谓的本地类(本项目专用),为了跳过首先去加载全局类,可以调用Yaf_Loader的registerLocalNamespace方法, 来申明那些类前缀是本地类。明白这个就好了,我这里直接把Zend类库放入到项目的library中。

我这里打算使用Zend Framework的Zend_Db组件,在Bootstrap类中放入方法:

        public function _initDatabaseAdapter(){
                $dbConfig = Yaf_Application::app()->getConfig()->get("db");
                $dbAdapter = $dbConfig->get("adapter");
                $dbParams = $dbConfig->get("params")->toArray();

                $db = Zend_Db::factory($dbAdapter, $dbParams);
                Zend_Db_Table::setDefaultAdapter($db);

                Yaf_Registry::set('db', $db);
        }

获取数据库配置信息,然后调用Zend_Db::factory方法,把返回的适配器用Yaf_Registry来保存。

数据库配置信息如下(config/application.ini中):

db.adapter = 'pdo_mysql'
db.params.host = '127.0.0.1'
db.params.username = 'root'
db.params.password = 'root'
db.params.dbname = 'ai_manage'
db.params.charset = 'utf8'

然后开始运行,报告如下错误:

Warning: require_once(Zend/Db/Adapter/Pdo/Abstract.php): failed to open stream: No such file or directory in /usr/local/httpd-2.2.27/htdocs/vfeelit/application/library/Zend/Db/Adapter/Pdo/Mysql.php on line 27

查看Zend/Db/Adapter/Pdo/Mysql.php的27行:

require_once 'Zend/Db/Adapter/Pdo/Abstract.php';

原来Zend_Db_Adapter_Pdo_Mysql继承自Zend_Db_Adapter_Pdo_Mysql_Abstract,而这个类是首先要加载进来的。玛尼的,自动装载函数无法装载Zend_Db_Adapter_Pdo_Mysql_Abstract还是怎么的,非要用那么粗暴的方法?我既然能装载Zend_Db_Adapter_Pdo_Mysql,自然也能装载Zend_Db_Adapter_Pdo_Mysql_Abstract,有点不理解。不过Zend Framework 1.x中大量出现这个粗暴装载的方法,好吧,require_once自然是搜索include_path,当前include_path自然没有包含Zend类库,那么修改一下include_path好了:

//在Bootstrap中添加如下函数
        public function _initIncludePath(){
                $config = Yaf_Application::app()->getConfig();
                set_include_path(implode(PATH_SEPARATOR, array(
                      realpath($config->get("application")->directory .'/library'),
                      get_include_path(),
                )));
	}

再次运行,就没有报错了。这说明成功使用了Zend_Db组件。

下面从数据库中读一段数据出来:

//控制器方法
        public function zendAction(){
                $db = Yaf_Registry::get('db');
                $result = $db->query("select id,order_id from ai_order where id > 6000 order by rand() limit 10");
		foreach($result->fetchAll() as $row){
			echo "ID {$row['id']} -- {$row['order_id']}\n";
		}
		return false;
	}

代码执行成功。

以上代码直接编写SQL语句是不是有点过时的感觉?没有问题,换一种搞法,Zend_Db_Table实现了所谓ORM,这里来个简单的例子:

//从ai_order中获取数据
//先建立一个Model
AiOrder.php 
<?php
class AiOrderModel extends Zend_Db_Table_Abstract{
	protected $_name = "ai_order";
}

//然后在控制器中
$aiOrder = new AiOrderModel();
// SELECT * FROM ai_order WHERE id = "1"
$row = $aiOrder->fetchRow("id = 1");
$row->name = "Vfeelit";

$row->save();

以上把id=1的记录映射到$row中,然后修改字段,调用save()方法数据就更新到数据表了。这玛尼的就是最简单的ORM了。

ORM在遇到复杂的情况时就会比较复杂繁琐,ORM的理念就是让你忘记数据库,所有一切都是对象。实际情况是小项目直接写SQL好了。大一点的项目需要多人开发时最好采用一套ORM,有了它可以统一开发模式,不至于人人都写SQL代码。就我个人来说,不怎么喜欢ORM。

原创文章,转载务必保留出处。
永久链接:http://blog.ifeeline.com/1194.html