《Yii 1.1应用程序开发实例》

第十二章:使用第三方代码

本章中,我们将涵括以下内容:

  • 在Yii中使用Zend Framework
  • 自定义Yii的自动加载器
  • 在Yii中使用Kohana
  • 在Yii中使用PEAR

介绍

很显然一个框架不可能为我们的 Web 应用实现的所有需求。比如发送 Email 的功能,在有些框架,我们需要一个功能非常丰富的类库才能实现,而有些,则可能仅需要我 们实现一些 API 接口即可。没有一个框架能够覆盖到所有开发者的需求。这也是为什么Yii 实现了大部分通用的功能,而留下了一小部分给开发者和外部的类库来实现.

这个章节中,我们将学习,在Yii框架下如何使用非Yii的代码来实现需求,设计到的 框架包括 Zend Framework,Kohana,和 Pear。

在Yii中使用Zend Framework

Yii为应用的构建提供了很多优秀的解决方案。当然,你可能需要更多优秀的方案。Zend Framework就是一个非常好的学习榜样之一。它具有很多高质量的任务解决方式,比如使用Google的API,发送Email等。

在本例中,我们主要学习下如何使用Zend_Mail包从Yii应用中发送Email。接下来,我们又两个简单的处理方式实现需求: 引入整个框架,或者,自定义一个自动加载类,这样我们可以只加载Zend_Mail模块以及相关的依赖。

准备工作

  • 使用命令yiic webapp创建Web应用
  • 从以下地址下载Zend Framework框架: http://framework.zend.com/download/current/ 本例中使用 1.11.6 版本
  • 解压下载的压缩包中的library/Zend到protected/vendors/Zend

怎么做...

进行以下步骤:

  1. 创建一个发送Email的Controller文件protected/controllers/MailtestController.php,代码如下:
<?php
class MailtestController extends CController
{
    public function actionIndex()
    {
        $mail = new Zend_Mail('utf-8');
        $mail->setHeaderEncoding(Zend_Mime::ENCODING_QUOTEDPRINTABLE);
        $mail->addTo("alexander@example.com", "Alexander Makarov");
        $mail->setFrom("robot@example.com", "Robot");
        $mail->setSubject("Test email");
        $mail->setBodyText("Hello, world!");
        $mail->setBodyHtml("Hello, <strong>world</strong>!");
        $mail->send();
        echo "OK";
    }
}
  1. 现在运行mailtest/index,你将看到以下错误:
  1. 它的意思是Yii的自动加载器在加载Zend_Mail类时失败了。这个错误是意料之中的,毕竟Yii并不知道Zend Framework的命名规范。所以逻辑上,我们有两个解决方案:
    • 明确地引入类文件
    • 创建我们自己的类加载器
  1. 首先,我们从引入类文件开始。所有Zend Framework的类都含有require_once表达式来解决依赖关系。这些表达式主要用于添加包含路径的PHP文件,样例如下:
require_once 'Zend/Mail/Transport/Abstract.php';
  1. 在Yii中,我们使用Yii::import来导入路径,这其实等同于在PHP的包含路径中添加我们需要的类目录。所以,我们可以采取以下方式解决:
class MailtestController extends CController
{
    public function actionIndex()
    {
        Yii::import('application.vendors.*');
        require "Zend/Mail.php";
        $mail = new Zend_Mail('utf-8');
        $mail->setHeaderEncoding(Zend_Mime::ENCODING_QUOTEDPRINTABLE);
        $mail->addTo("alexander@example.com", "Alexander Makarov");
        $mail->setFrom("robot@example.com", "Robot");
        $mail->setSubject("Test email");
        $mail->setBodyText("Hello, world!");
        $mail->setBodyHtml("Hello, <strong>world</strong>!");
        $mail->send();
        echo "OK";
    }
}
  1. 现在发送Email应该不会有什么错误了。如果你没有使用Zend的其他类库,那么这个方法应该是可行的。但是,如果你使用了更多的Zend相关类,那么就有很多不必要的麻烦事儿了。
  1. 现在让我们使用Zend_Loader_Autoloader来解决这个问题。首先,添加自动加载器的最好地方肯定是index.php这个引导文件了。以下方法可以实现动态加载类库:
// change the following paths if necessary
$yii=dirname(__FILE__).'/../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);
       
require_once($yii);
$app = Yii::createWebApplication($config);
 
// adding Zend Framework autoloader
Yii::import('application.vendors.*');
require "Zend/Loader/Autoloader.php";
Yii::registerAutoloader(array('Zend_Loader_Autoloader', 'autoload'), true);
 
$app->run();
  1. 现在移除以下代码
Yii::import('application.vendors.*');
require "Zend/Mail.php";

在MailtestController中使用,没有出现任何错误并且运行良好说明,Zend Framework已经自动加载,正常工作了。

它是如何工作的...

现在,让我们再来回顾下第一种方法中,它是怎么运行的。首先,我们使用了Yii::import。Yii::import('path.alias.*'),它的作用其实类似于PHP的在本类中引入其他PHP文件。虽然Zend Framework本身是没有自动加载器的, 但是它使用了require_once来包含所有的类文件。因此,如果你使用类似Zend_Mail这样简单的组件,根本不需要添加任何多余的require_once。

第二个方法并不强制使用require表达式。由于Yii允许使用多个自动加载器,并且,在Zend Framework的最后一个版本中,也加入了自己的自动加载器,所以,我们可以在我们的应用中使用它。而加载它最合适的时间就是在应用的引导刚被加载,但应用还没有运行的时候。为了完成这个功能,我们把index.php的Yii::createWebApplication($config)->run()拆分成两个子句 ,然后在这两个子句间加入了一个自动加载器的初始化:

Yii::import('application.vendors.*');
require "Zend/Loader/Autoloader.php";
Yii::registerAutoloader(array('Zend_Loader_Autoloader', 'autoload'), true);

当然,我们仍然需要Yii::import('application.vendors.*'),因为Zend Framework的类库将要继续使用require_once。然后,我们通过设定Yii::registerAutoloader的第二个参数为true,来请求一个自动加载类,并且将其在PHP的自动加载栈的最后边。

还有更多...

有兴趣学习更多关于Yii导入,自动加载和Zend Framework 的朋友,可以参照以下网址进一步学习:

另请参阅

自定义Yii的自动加载器

Yii使用了一个命名转换器和一个自动加载器来完成加载唯一的类文件。这种设计是非常优秀的,它避免了文件的多次引入。正如其他的框架可以使用不同的命名转换器,Yii提供了自定义自动加载的规则的类。在本章的“在Yii中使用Zend Framework”小节中,我们没有特意的去引入Zend Framework的类文件,而是,使用了Zend_Loader_Autoloader来激活他们。如果我们仅需要Zend Framework的核心类,那么它自身的自动加载器就过于庞大,并且,在每一个Zend Framework的类中都得使用require_once子句。在这个小节中,我们将要创建一个非常简单且快速的自动加载器,它可以完成同样的需求,但效率更高。

准备工作

  • 使用命令yiic webapp创建Web应用
  • 从以下地址下载Zend Framework框架 http://framework.zend.com/download/current/ 本例中使用 1.11.6 版本
  • 解压下载的压缩包中的library/Zend到protected/vendors/Zend
  • 创建protected/controllers/MailtestController.php,代码如下:
<?php
class MailtestController extends CController
{
    public function actionIndex()
    {
        $mail = new Zend_Mail('utf-8');
        $mail->setHeaderEncoding(Zend_Mime::ENCODING_QUOTEDPRINTABLE);
        $mail->addTo("alexander@example.com", "Alexander Makarov");
        $mail->setFrom("robot@example.com", "Robot");
        $mail->setSubject("Test email");
        $mail->setBodyText("Hello, world!");
        $mail->setBodyHtml("Hello, <strong>world</strong>!");
        $mail->send();
        echo "OK";
    }
}

怎么做...

进行以下步骤:

  1. 创建 protected/components/EZendAutoloader.php代码如下:
<?php
class EZendAutoloader
{
    /**
     * @var array class prefixes
     */
    static $prefixes = array(
        'Zend'
    );
    
    /**
     * @var string path to where Zend classes root is located
     */
    static $basePath = null;
    
    /**
     * Class autoload loader.
      *
     * @static
     * @param string $className
     * @return boolean
     */
    static function loadClass($className)
    {
        foreach(self::$prefixes as $prefix)
        {
            if(strpos($className, $prefix.'_')!==false)
            {
                if(!self::$basePath) self::$basePath =
                    Yii::getPathOfAlias("application.vendors").'/';
                include self::$basePath.str_replace('_','/',$className).'.php';
                return class_exists($className, false) ||
                interface_exists($className, false);
            }
        }
        return false;
    }
}
  1. 现在修改index.php。替换
Yii::createWebApplication($config)->run();

$app = Yii::createWebApplication($config);
 
// adding custom Zend Framework autoloader
Yii::import("application.vendors.*");
Yii::import("application.components.EZendAutoloader", true);
Yii::registerAutoloader(array('EZendAutoloader','loadClass'), true);
 
$app->run();
  1. 运行mailtest/index。它应该发送了一条Email,并且输出“OK”字样。这意味着自动加载工作正常。当然,我们引入vendors路径来显式的加载所有Zend Framework中的类文件,以此来适应require_once命令。修复它的唯一方法是,移除所有Zend Framework下的require_once。如果你在Linux环境下,可以使用:
% cd path/to/ZendFramework/library
% find . -name '*.php' -not -wholename '*/Loader/Autoloader.php' \
  -not -wholename '*/Application.php' -print0 | \
  xargs -0 sed --regexp-extended --in-place 's/(require_once)/\/\/ \1/g'

在Mac上:

% cd path/to/ZendFramework/library
% find . -name '*.php' | grep -v './Loader/Autoloader.php' | \
xargs sed -E -i~ 's/(require_once)/\/\/ \1/g'
% find . -name '*.php~' | xargs rmf

你也可以简单的使用IDE,或者其他工具替换require_once为//require_once。

  1. 完成了上步操作后,你就可以移除index.php文件中的Yii::import("application.vendors.*"),然后再加载mailtest/index。它应该会再发一条Email,然后输出“OK”。这说明Zend Framework,已经在没有require_once的情况下,成功运行了。

它是如何工作的...

框架和类库使用的autoloading依赖于PHP SPL的autoload。当你调用一个没有被引入的类时,它会触发PHP抛出错误。使用spl_autoload_register后,你可以注册多个类加载的回调。如此第一个失败了,还会有另外一个尝试并加载类文件。Yii不是个例外,它实现了YiiBase::autoload来使用自己的加载器。我们可以使用index.php中的Yii::registerAutoloader来添加加载器。这个方法的实现如下:

public static function registerAutoloader($callback, $append=false)
{
    if($append)
    {
        self::$enableIncludePath=false;
        spl_autoload_register($callback);
    }
    else {
        spl_autoload_unregister(array('YiiBase','autoload'));
        spl_autoload_register($callback);
        spl_autoload_register(array('YiiBase','autoload'));
    }
}

所以,本质上讲,它还是一个SPL自动加载器,而registerAutoloader方法添加了另一个回调,并且确保它在Yii自己的加载器注册之前注册。如果我们使用true作为第二个参数,那么这个自定义的加载器,就会在Yii内部加载器注册之后注册。自定义的加载器不允许触发加载Yii框架内部类的。

现在,让我们继续自定义我们的自动加载器。所有SPL自动加载的回调函数,仅接受一个参数。这个参数就是需要被加载的类的名称。给定名称之后,加载器会尝试引入包含这个名称的类。为了更加灵活性,我们定义以下两个属性:

  1. prefixes定义了一个需要在类调用前被自动加载的列表。
  2. basePath指向Zend的路径,默认是protected/vendors。

对于每个prefix属性,我们会检测这个需要加载的类是否是以它开头以及是否需要使用自动加载器。如果是以它开头,就替换类名中的“_”为“/”,然后使用改好后的类名作为完整路径引入。

在最后一步,去掉require_once语句,这样就仅加载我们真正需要的类了。

还有更多...

正如应用运行后,类的加载一直进行,我们使用了一个必须能够尽快被调用的外部类库。这意味着自动加载的方法,应尽可能的简单高效。其他需要注意的事情如下:

  • require_once比require慢
  • 使用file_exists或者is_file方法,会降低加载速度
  • 尽可能使用绝对路径, 取代相对路径. 来确保当 apc.stat=0 时, APC 高效执行 (它允许不去检测文件是否被改变,在生产服务器上获得更多的性能)
进一步阅读

为了学习更多关于Yii的自动加载和API,参考以下网址:

另请参阅

在Yii中使用Kohana

有时候写一个自动加载器,你需要深入到另一个框架的源码。其中一例子就是Kohana框架。在这个小节中,我们将要学习使用Kohana的类来修改图片的尺寸。

准备工作

  • 使用命令yiic webapp创建Web应用
  • 从以下地址下载Kohana http://kohanaframework.org/download 本例中使用3.1版本。
  • 解压system和modules两个文件夹到protected/vendors/Kohana目录下。

怎么做...

进行以下步骤:

  1. 首先,我们需要需要一段代码来执行图片调整和显示图片。创建protected/controllers/ImageController.php,代码如下:
<?php
class ImageController extends CController
{
    public function actionIndex()
    {
        $image = new Image_GD(Yii::getPathOfAlias("system")."/yii-powered.png");
        $image->resize(80, 80);
        Yii::app()->request->sendFile("image.png", $image->render());
    }
}
  1. 运行image/index,得到以下错误:
  1. 它的意思是,Yii不能找到Kohana的类文件。为了解决这个问题,我们需要自定义一个自动加载器。创建protected/components/EKohanaAutoloader.php,代码如下:
<?php
class EKohanaAutoloader
{
    /**
     * @var list of paths to search for classes.
     * Add full paths to modules here.
     */
    static $paths = array();
         
    /**
     * Class autoload loader.
     *
     * @static
     * @param string $className
     * @return boolean
     */
    static function loadClass($className)
    {
        if(!defined("SYSPATH"))
            define("SYSPATH", Yii::getPathOfAlias("application.vendors.Kohana.system"));
        if(empty(self::$paths))
            self::$paths = array(Yii::getPathOfAlias("application.vendors.Kohana.system"));
        $path = 'classes/'.str_replace('_', '/', strtolower($className)).'.php';
        foreach (self::$paths as $dir)
        {
            if (is_file($dir."/".$path))
                require $dir."/".$path;
        }
        return false;
    }
}
  1. 为了成功运行它,我们需要修改index.php。替换。
Yii::createWebApplication($config)->run();

为如下代码:

$app = Yii::createWebApplication($config);
 
// adding custom Kohana autoloader
Yii::import("application.components.EKohanaAutoloader", true);
EKohanaAutoloader::$paths = array(Yii::getPathOfAlias
    ("application.vendors.Kohana.modules.image"));
Yii::registerAutoloader(array('EKohanaAutoloader','loadClass'), true);
 
$app->run();
  1. 再次运行image/index,你会看到类似如下截图的内容:

这意味着Kohana的类已经成功加载了。

注意Kohana的类加载器在代码执行方面不是最优的,并且它不能再正式的项目中使用。

它是如何工作的...

Kohana 3依赖于自动加载,它有一个非常特别的命名转换。从结果来看,直接调用它的类文件有太多的工作要做,而创建自动加载器是在不修改Kohana类的前提下,实现它的最合理方式。

下面让我们来看一下Kohana自动加载器,它的位置如下:

protected/vendors/Kohana/system/classes/kohana/core.php

这个方法的名是auto_load:

public static function auto_load($class)
{
    try {
        // Transform the class name into a path
        $file = str_replace('_', '/', strtolower($class));
        if ($path = Kohana::find_file('classes', $file))
        {
            // Load the class file
            require $path;
         
            // Class has been found
            return TRUE;
        }
        // Class is not in the filesystem
        return FALSE;
    }
    catch (Exception $e)
    {
        Kohana_Exception::handler($e);
        die;
    }
}

从这一部分,我们可以看到它使用了一个类来构建相关路径,而这些路径是用来在classes路径下进行查找文件的:

$file = str_replace('_', '/', strtolower($class));

现在让我们深入研究一下find_file:

public static function find_file($dir, $file, $ext = NULL, $array = FALSE)
{
    if ($ext === NULL)
    {
        // Use the default extension
        $ext = EXT;
    }
    elseif ($ext)
    {
        // Prefix the extension with a period
        $ext = ".{$ext}";
    }
    else {
        // Use no extension
        $ext = '';
    }
    
    // Create a partial path of the filename
    $path = $dir.DIRECTORY_SEPARATOR.$file.$ext;
    
    if (Kohana::$caching === TRUE AND isset
        (Kohana::$_files[$path.($array ? '_array' : '_path')]))
    {
        // This path has been cached
        return Kohana::$_files[$path.($array ? '_array' : '_path')];
    }
    
    if (Kohana::$profiling === TRUE AND class_exists
        ('Profiler', FALSE))
    {
        // Start a new benchmark
        $benchmark = Profiler::start('Kohana', __FUNCTION__);
    }
    
    if ($array OR $dir === 'config' OR $dir === 'i18n'
        OR $dir === 'messages')
    {
        // Include paths must be searched in reverse
        $paths = array_reverse(Kohana::$_paths);
        
        // Array of files that have been found
        $found = array();
        
        foreach ($paths as $dir)
        {
            if (is_file($dir.$path))
            {
                // This path has a file, add it to the list
                $found[] = $dir.$path;
            }
        }
    }
    else {
        // The file has not been found yet
        $found = FALSE;
        
        foreach (Kohana::$_paths as $dir)
        {
            if (is_file($dir.$path))
            {
                // A path has been found
                $found = $dir.$path;
                
                // Stop searching
                break;
            }
        }
    }
    
    if (Kohana::$caching === TRUE)
    {
        // Add the path to the cache
        Kohana::$_files[$path.($array ? '_array' : '_path')] = $found;
        
        // Files have been changed
        Kohana::$_files_changed = TRUE;
    }
    
    if (isset($benchmark))
    {
        // Stop the benchmark
        Profiler::stop($benchmark);
    }
    
    return $found;
}

正如我们所知道的,文件的扩展名永远是.php,路径永远是classes,而正如当下我们不关心缓存,结构等等,最有用的部分如下:

$path = $dir.DIRECTORY_SEPARATOR.$file.$ext;
foreach (Kohana::$_paths as $dir)
{
    if (is_file($dir.$path))
    {
        // A path has been found
        $found = $dir.$path;
        
        // Stop searching
        break;
    }
}

现在,我们已经很接近真相了。最后剩下的东西就是Kohana::$_paths:

    /**
     * @var  array   Include paths that are used to find files
     */
    protected static $_paths = array(APPPATH, SYSPATH);

我们并不关心应用程序,所以,可以忽略 APPPATH 部分。此外SYSPATH是system的路径,Kohana大部分的类全在这儿,把它作为一个默认是非常合适的。

当自动加载器类就绪后,我们使用index.php中的Yii::registerAutoloader来注册它。在Yii的默认加载器注册后,这步是非常重要的。 所以, 设置Yii::registerAutoloader的第二个参数为true。图片类并不是核心类,它在image模块中,因此我们采用以下方式设定图片模型的路径:

EKohanaAutoloader::$paths = array(Yii::getPathOfAlias
    ("application.vendors.Kohana.modules.image"));

还有更多...

图片修整是一项非常通用的任务,最好从复用性及执行预期两方面考虑,将其与其他程序分离,然后创建一个分离的PHP脚本来处理图片修整。例如,可以采用如下方式使用:

<img src="/image.php?src=avatar.png&size=s" />

它的意思是以avatar.png作为源图片,重设置大小为 100×100像素,可能image.php脚本将采取的以下步骤:

  • 如果存在要处理的图像,则处理
  • 如果没有图像,读取源图像,调整其大小,并处理保存

为了获得更好的效果,你可以配置WEB服务器来直接处理已存在的图片,避免使用PHP脚本来处理,然后重指向那些不存在的图片到处理脚本。

进一步阅读

为了学习更多的关于Yii的自动加载和Kohana,参考如下地址:

另请参阅

在Yii中使用PEAR

另外一个常用的PHP类库是PEAR,它拥有自己独特的命名转换机制,所以为了使用PEAR,我们也需要实现一个自动加载器来引入文件路径。在本小节中,我们使用PEAR的Text_Password类来生成随机的密码。

准备工作

怎么做...

我们想要使用的PEAR包的页面是http://pear.php.net/package/Text_Password。重要的东西是 "Easy Install" 和文件。

  1. 我们首先安装包文件,打开终端窗口,然后输入“Easy Install”部分的建议:
pear install Text_Password
  1. 它应该有如下反馈:
downloading Text_Password-1.1.1.tgz ...
Starting to download Text_Password-1.1.1.tgz (4,357 bytes)
.....        done: 4,357 bytes
install ok: channel://pear.php.net/Text_Password-1.1.1
  1. 现在来使用它,我们将生成10个长度为8的密码。创建protected/controllers/PasswordController.php,代码如下:
class PasswordController extends CController
{
    public function actionIndex()
    {
        require "Text/Password.php";
        $textPassword = new Text_Password();
        $passwords = $textPassword->createMultiple(10, 8);
        echo "<ul>";
        foreach($passwords as $password)
        {
            echo "<li>".$password."</li>";
        }
        echo "</ul>";
    }
}

它是如何工作的...

在Yii中使用PEAR包是非常容易的。不需要配置Yii或者向提供PEAR包的引导文件中添加任何多余的代码。

还有更多...

学习更多的 PEAR 相关知识,可参考以下地址:

另请参阅

评论 X

      友荐云推荐
      Copyright 2011-2014. YiiBook.com