月度归档:2012年09月

Zen-cart 事件触发(观察者模式实现)

在checkout_shipping或checkout_payment等的页面头中有很多诸如$zco_notifier->notify(‘NOTIFY_HEADER_START_CHECKOUT_SHIPPING’)之类的代码,比如这里就是指触发NOTIFY_HEADER_START_CHECKOUT_SHIPPING事件,那么绑定到这个事件的代码将被执行,看起来就像是一个钩子,把额外的代码勾进来,这样可以减少对内核的修改。

在includes/auto_loaders/config.core.php中:

  $autoLoadConfig[0][] = array('autoType'=>'class',
                                'loadFile'=>'class.notifier.php');
  $autoLoadConfig[0][] = array('autoType'=>'classInstantiate',
                                'className'=>'notifier',
                                'objectName'=>'zco_notifier');

这里预设生成了一个叫$zco_notifier的对象,查看includes/classes/class.notifier.php:

class notifier extends base {
}

这个所谓的通知类只是继承一下base类而已。从$zco_notifier->notify(‘NOTIFY_HEADER_START_CHECKOUT_SHIPPING’)可知,事件的触发只是调用notify()方法而已,那么这个事件所绑定的代码是如何绑定的呢? 查看这个class.base.php文件(定义了base类):

class base {
  function attach(&$observer, $eventIDArray) {
    foreach($eventIDArray as $eventID) {
      $nameHash = md5(get_class($observer).$eventID);
      base::setStaticObserver($nameHash, array('obs'=>&$observer, 'eventID'=>$eventID));
    }
  }
  function detach($observer, $eventIDArray) {
    foreach($eventIDArray as $eventID) {    
      $nameHash = md5(get_class($observer).$eventID);
      base::unsetStaticObserver($nameHash);
    }
  }
  function notify($eventID, $paramArray = array()) {
    $observers = & base::getStaticObserver();
    if (!is_null($observers))
    {
      foreach($observers as $key=>$obs) {
        if ($obs['eventID'] == $eventID) {
          $obs['obs']->update($this, $eventID, $paramArray);
        }
      }
    }
  }
  function & getStaticProperty($var)
  {
    static $staticProperty;
    return $staticProperty;
  }
  function & getStaticObserver() {
    return base::getStaticProperty('observer');
  }
  function & setStaticObserver($element, $value)
  {
    $observer =  & base::getStaticObserver();
    $observer[$element] = $value;
  }
  function & unsetStaticObserver($element)
  {
    $observer =  & base::getStaticObserver();
    unset($observer[$element]);
  }
}

仔细研究一下notify()方法,getStaticObserver()方法返回所有的静态的观察者,然后找到和事件对应的观察者(一个事件可能绑定多个观察者),然后调用它的update()方法,所以这个update()就是代码实现的地方。静态观察者列表存放格式:

Array(
  ['观察者类名和事件的哈希'] => Array('eventID'=>'事件','obs'=>'观察者对象')
)

同样,如果绑定某个观察者到某个事件,只要使用attach()方法把观察则对象和事件(或事件数组,一个观察者对象可以绑定到多个事件)传递入方法即可。我们完全可以定义一个独立的类,在它里面实现update()方法(主要的实现代码),实例化这个观察者类后,然后调用$zco_notifier的attach()添加这个观察者绑定到具体的事件,不过,我们完全可以在这个观察者类实例化时调用attach()把自身注册到某事件,所以为了让观察者对象有管理观察者对象列表的权利,那么让每个观察者都继承这个base类即可(这样每个观察者对象都可以对观察者列表进行管理),或者在构造函数中调用$zco_notifier的attach()方法。

实现一个观察者:

class MinOrderObserver extends base{
        function __construct() {
                $this->attach($this, array('NOTIFY_PRODUCT_VIEWS_HIT_INCREMENTOR'));
	}

	function update(&$class, $eventID, $paramsArray) {
		global $messageStack, $currencies, $current_page;
		if( defined('MIN_ORDER_NUMBER') && (trim(MIN_ORDER_NUMBER) != '') ){
			if( ($current_page == 'shopping_cart') && ((int)$_SESSION['cart']->count_contents() > 0) && (ceil($_SESSION['cart']->show_total()) < MIN_ORDER_NUMBER) ){
  				$messageStack->add('shopping_cart', sprintf(TEXT_MIN_ORDER,$currencies->format(MIN_ORDER_NUMBER)), 'caution');
			}
		}
	}
}

或者在构造函数中调用$zco_notifier对象的attach()方法了注册自身到某个事件。//zen-cart 150 之前的版本的做法

class MinOrderObserver extends base{
	function MinOrderObserver(){
		global $zco_notifier;
		$zco_notifier->attach($this, array('NOTIFY_HEADER_END_SHOPPING_CART'));
        }
        ....
}

接下来是要让观察者类加载都自动装入到系统,并且要进行实例化(对象名无所谓,因为在实例化时在构造函数中就完成了自身注册到事件),那么着就要依赖zen-cart的自动装载功能:

// 新建includes/auto_loaders/config.min_order.php
if (!defined('IS_ADMIN_FLAG')) {
 	die('Illegal Access');
} 

$autoLoadConfig[189][] = array('autoType'=>'class',
                                'loadFile'=>'observers/class.min_order.php');

$autoLoadConfig[189][] = array('autoType'=>'classInstantiate',
                              'className'=>'MinOrderObserver',
                              'objectName'=>'MinOrderObserver');

这样系统运行后,当触发NOTIFY_HEADER_END_SHOPPING_CART事件时,这个事件对应的对象的update()方法就会被执行,代码就是如此勾进去的。就相当于在NOTIFY_HEADER_END_SHOPPING_CART那行代码之后直接加入了update()方法里面的代码一样。

可以参考http://blog.ifeeline.com/178.html来制作采用这种方法实现的插件的一个例子。

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

Zend Framework中的插件(Plugins in Zend Framework)

Zend Framework makes heavy use of plugin architectures. ZF大量使用插件。 Plugins allow for easy extensibility and customization of the framework while keeping your code separate from Zend Framework’s code. 插件可以方便地扩展性和定制的框架,同时保持你的代码和Zend Framework的代码分开。

Typically, plugins in Zend Framework work as follows:
•Plugins are classes. The actual class definition will vary based on the component — you may need to extend an abstract class or implement an interface, but the fact remains that the plugin is itself a class. 实际的类定义将基于组件的不同而有所差异 – 你可能需要继续个抽象类或实现一个接口,但事实上插件本身是一个类。
•Related plugins will share a common class prefix. For instance, if you have created a number of view helpers, they might all share the class prefix “Foo_View_Helper_”. 共享相同的类前缀。
•Everything after the common prefix will be considered the plugin name or short name (versus the “long name”, which is the full classname). For example, if the plugin prefix is “Foo_View_Helper_”, and the class name is “Foo_View_Helper_Bar”, the plugin name will be simply “Bar”. 在公共的类前缀之后就是插件的名字,或者叫做短名。
•Plugin names are typically case sensitive. The one caveat is that the initial letter can often be either lower or uppercase; in our previous example, both “bar” and “Bar” would refer to the same plugin. 插件名称通常大小写敏感。但需要注意的是,插件的首个字母可大写也可以小写。

Using Plugins
Components that make use of plugins typically use Zend_Loader_PluginLoader to do their work. 使用插件的组件通常使用Zend_Loader_PluginLoader来实现。 This class has you register plugins by specifying one or more “prefix paths”.这个类让你通过指定一个活多个前缀路径来注册插件。 The component will then call the PluginLoader’s load() method, passing the plugin’s short name to it. 组件将传递插件的短名来调用PluginLoader的load方法。 The PluginLoader will then query each prefix path to see if a class matching that short name exists. PluginLoader将查询每个前缀路径去查看是否有短名存在并匹配。 Prefix paths are searched in LIFO (last in, first out) order, so it will match those prefix paths registered last first — allowing you to override existing plugins. 前缀路径搜索使用后进先出的顺序,所以它将匹配那些最后注册的前缀路径,这样可以覆盖存在的插件。
Some examples will make all of this more clear.

Basic Plugin Example: Adding a single prefix path
In this example, we will assume some validators have been written and placed in the directory foo/plugins/validators/, and that all these classes share the class prefix “Foo_Validate_”; these two bits of information form our “prefix path”. Furthermore, let’s assume we have two validators, one named “Even” (ensuring a number to be validated is even), and another named “Dozens” (ensuring the number is a multiple of 12). The tree might look like this: 假设在foo/plugins/validators/目录下有一些验证器,它们共享Foo_Validate_类前缀。

foo/
|-- plugins/
|   |-- validators/
|   |   |-- Even.php
|   |   |-- Dozens.php

Now, we’ll inform a Zend_Form_Element instance of this prefix path. Zend_Form_Element’s addPrefixPath() method expects a third argument that indicates the type of plugin for which the path is being registered; in this case, it’s a “validate” plugin.

$element->addPrefixPath(‘Foo_Validate’, ‘foo/plugins/validators/’, ‘validate’);
Now we can simply tell the element the short name of the validators we want to use. In the following example, we’re using a mix of standard validators (“NotEmpty”, “Int”) and custom validators (“Even”, “Dozens”):

$element->addValidator('NotEmpty')
        ->addValidator('Int')
        ->addValidator('Even')
        ->addValidator('Dozens');

When the element needs to validate, it will then request the plugin class from the PluginLoader. The first two validators will resolve to Zend_Validate_NotEmpty and Zend_Validate_Int, respectively; the next two will resolve to Foo_Validate_Even and Foo_Validate_Dozens, respectively.

What happens if a plugin is not found?
What happens if a plugin is requested, but the PluginLoader is unable to find a class matching it? For instance, in the above example, if we registered the plugin “Bar” with the element, what would happen?
The plugin loader will look through each prefix path, checking to see if a file matching the plugin name is found on that path. If the file is not found, it then moves on to the next prefix path. 找不到就继续寻找下一个前缀路径。
Once the stack of prefix paths has been exhausted, if no matching file has been found, it will throw a Zend_Loader_PluginLoader_Exception. 如果最终还是没有找到,抛出异常。

Intermediate Plugin Usage: Overriding existing plugins
One strength of the PluginLoader is that its use of a LIFO stack allows you to override existing plugins by creating your own versions locally with a different prefix path, and registering that prefix path later in the stack.
For example, let’s consider Zend_View_Helper_FormButton (view helpers are one form of plugin 视图助手类是一个表单插件). This view helper accepts three arguments, an element name (also used as the element’s DOM identifier), a value (used as the button label), and an optional array of attributes. The helper then generates HTML markup for a form input element. 这个助手创建表单输入元素的HTML标记。

Let’s say you want the helper to instead generate a true HTML button element; don’t want the helper to generate a DOM identifier, but instead use the value for a CSS class selector; and that you have no interest in handling arbitrary attributes. You could accomplish this in a couple of ways. In both cases, you’d create your own view helper class that implements the behavior you want; the difference is in how you would name and invoke them.

Our first example will be to name the element with a unique name: Foo_View_Helper_CssButton, which implies the plugin name “CssButton”. While this certainly is a viable approach, it poses several issues: if you’ve already used the Button view helper in your code, you now have to refactor; alternately, if another developer starts writing code for your application, they may inadvertently use the Button view helper instead of your new view helper.

So, the better example is to use the plugin name “Button”, giving us the class name Foo_View_Helper_Button. We then register the prefix path with the view: 还是命名为Button, 不过注册一个不同的前缀路径。

// Zend_View::addHelperPath() utilizes the PluginLoader; however, it inverts
// the arguments, as it provides a default value of "Zend_View_Helper" for the
// plugin prefix.
//
// The below assumes your class is in the directory 'foo/view/helpers/'.
$view->addHelperPath('foo/view/helpers', 'Foo_View_Helper');

Once done, anywhere you now use the “Button” helper will delegate to your custom Foo_View_Helper_Button class!
Foo_View_Helper_Button 覆盖了 Zend_View_Helper_Button

Zend_Application_Bootstrap_BootstrapAbstract类中有一个getPluginLoader()方法:

    public function getPluginLoader()
    {
        if ($this->_pluginLoader === null) {
            $options = array(
                'Zend_Application_Resource'  => 'Zend/Application/Resource',
                'ZendX_Application_Resource' => 'ZendX/Application/Resource'
            );

            $this->_pluginLoader = new Zend_Loader_PluginLoader($options);
        }

        return $this->_pluginLoader;
    }

这里初始化了这个插件装载器,资源放置在Zend/Application/Resource中。

在index.php中,$application->bootstrap()实际是调用了Bootstrap的bootstrap()方法,而bootstrap()实际是内部调用_bootstrap()方法:

    protected function _bootstrap($resource = null)
    {
        if (null === $resource) {
            foreach ($this->getClassResourceNames() as $resource) {
                $this->_executeResource($resource);
            }

            foreach ($this->getPluginResourceNames() as $resource) {
                $this->_executeResource($resource);
            }
        } elseif (is_string($resource)) {
            $this->_executeResource($resource);
        } elseif (is_array($resource)) {
            foreach ($resource as $r) {
                $this->_executeResource($r);
            }
        } else {
            throw new Zend_Application_Bootstrap_Exception('Invalid argument passed to ' . __METHOD__);
        }
    }

其中的getClassResourceNames() 和 getPluginResourceNames()分别对应了类资源 和 插件资源。在配置文件中的resources开头的配置,都是所谓的插件资源,它对应Zend_Appliction_Resource里面的资源。例如:

resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
resources.frontController.params.displayExceptions = 0
resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts"
resources.view[] =

resources.db.adapter = "PDO_MYSQL"
resources.db.params.host = "localhost"
resources.db.params.dbname = "guestbook"
resources.db.params.username = "root"
resources.db.params.password = "root"

通过插件初始化后,可以通过如下方法得到对象引用:
$this->getPluginResource(‘db’) $this->getPluginResource(‘ frontController ‘)
$this->getPluginResource(‘ layout ‘)

永久链接: http://blog.ifeeline.com/204.html

Zend Framework 资源装载器

Resource Autoloaders
Resource autoloaders are intended to manage namespaced library code that follow Zend Framework coding standard guidelines, but which do not have a 1:1 mapping between the class name and the directory structure. 资源装载器用来管理符合ZF编码标准的库代码的命名空间,但是它们的类名和目录结构不是1对1映射的。Their primary purpose is to facilitate autoloading application resource code, such as application-specific models, forms, and ACLs. 它们的主要目的是使应用程序的资源代码更容易装载,比如应用指定的的模型表单和ACL.

Resource autoloaders register with the autoloader on instantiation, with the namespace to which they are associated. 资源自动转载器在实例化时注册与它们相关联的命名空间的装载器。This allows you to easily namespace code in specific directories, and still reap the benefits of autoloading. 这允许把命名空间放入到指定目录,并且保留自动转载的好处。

Resource autoloader usage 资源转载器用法
Let’s consider the following directory structure:

path/to/some/directory/
    acls/
        Site.php
    forms/
        Login.php
    models/
        User.php

Within this directory, all code is prefixed with the namespace “My_”. 在这个目录中所有的代码使用My_这个命名空间前缀。 Within the “acls” subdirectory, the component prefix “Acl_” is added, giving a final class name of “My_Acl_Site”. 在acls子目录中,组件添加了前缀Acl_,最终的类名为My_Acl_Site。 Similarly, the “forms” subdirectory maps to “Form_”, giving “My_Form_Login”. forms子目录映射到Form_,命名为My_Form_Login. The “models” subdirectory has no component namespace, giving “My_User”. models子目录没有组件名空间,得到的是My_User类名。

You can use a resource autoloader to autoload these classes. 可以使用资源装载器自动装载这些类。 To instantiate the resource autoloader, you are required to pass at the minimum the base path and namespace for the resources it will be responsible for: 为了实例化资源装载器,你至少需要为资源传递基本路径和命名空间:

$resourceLoader = new Zend_Loader_Autoloader_Resource(array(
    'basePath'  => 'path/to/some/directory',
    'namespace' => 'My',
));

Base namespace
In Zend_Loader_Autoloader, you are expected to provide the trailing underscore (“_”) in your namespace if your autoloader will use it to match the namespace. Zend_Loader_Autoloader_Resource makes the assumption that all code you are autoloading will use an underscore separator between namespaces, components, and classes. As a result, you do not need to use the trailing underscore when registering a resource autoloader. Zend_Loader_Autoloader中你需要指定下划线作为分隔符。Zend_Loader_Autoloader_Resource中假定分隔符号都是下划线,所以指定名空间是不需要指定下划线分隔符。

Now that we have setup the base resource autoloader, we can add some components to it to autoload. 现在已经实例化了一个资源转载器,我们可以添加一些组件给它让其自动装载。This is done using the addResourceType() method, which accepts three arguments: a resource “type”, used internally as a reference name; the subdirectory path underneath the base path in which these resources live; and the component namespace to append to the base namespace. As an example, let’s add each of our resource types. 使用addResourceType()来完成这个操作,它接收三个参数,一个资源的类型,作为一个内部的引用名字;第二个参数指定了在基路径下的资源放置地的子目录;第三个参数指定了组件名空间,它会后加到基命空间之后。

$resourceLoader->addResourceType('acl', 'acls/', 'Acl')
               ->addResourceType('form', 'forms/', 'Form')
               ->addResourceType('model', 'models/');

Alternately, you could pass these as an array to addResourceTypes(); the following is equivalent to the above: 可以传递一个数组。

$resourceLoader->addResourceTypes(array(
    'acl' => array(
        'path'      => 'acls/',
        'namespace' => 'Acl',
    ),
    'form' => array(
        'path'      => 'forms/',
        'namespace' => 'Form',
    ),
    'model' => array(
        'path'      => 'models/',
    ),
));

Finally, you can specify all of this when instantiating the object, by simply specifying a “resourceTypes” key in the options passed and a structure like that above: 最后,可以在实例化这个对象是指定所有的这些东西,使用resourceTypes下标指定以上这些内容。

$resourceLoader = new Zend_Loader_Autoloader_Resource(array(
    'basePath'      => 'path/to/some/directory',
    'namespace'     => 'My',
    'resourceTypes' => array(
        'acl' => array(
            'path'      => 'acls/',
            'namespace' => 'Acl',
        ),
        'form' => array(
            'path'      => 'forms/',
            'namespace' => 'Form',
        ),
        'model' => array(
            'path'      => 'models/',
        ),
    ),
));

The Module Resource Autoloader
Zend Framework ships with a concrete implementation of Zend_Loader_Autoloader_Resource that contains resource type mappings that cover the default recommended directory structure for Zend Framework MVC applications. This loader, Zend_Application_Module_Autoloader, comes with the following mappings: Zend Framework带有一个的具体实施Zend_Loader_Autoloader_Resource,它包含资源类型映射,包括推荐的默认Zend Framework的MVC应用程序的目录结构。

forms/       => Form
models/      => Model
    DbTable/ => Model_DbTable
    mappers/ => Model_Mapper
plugins/     => Plugin
services/    => Service
views/
    helpers  => View_Helper
    filters  => View_Filter

As an example, if you have a module with the prefix of “Blog_”, and attempted to instantiate the class “Blog_Form_Entry”, it would look in the resource directory’s “forms/” subdirectory for a file named “Entry.php”. 例如,如果你有一个模块以Blog_为前缀,并且试图实例化Blog_Form_Entry,它将搜索forms/这个资源子目录去寻找一个命名为Entry.php的文件。

When using module bootstraps with Zend_Application, an instance of Zend_Application_Module_Autoloader will be created by default for each discrete module, allowing you to autoload module resources. 当使用Zend_Application模块初始化程序时,为每个离散的模块,一个Zend_Application_Module_Autoloader将默认被创建,它允许你去自动装载模块资源。

在基于ZF的项目中,这个资源装载器的初始化,是在Zend_Application_Bootstrap_Bootstrap的构造函数中完成的:

    public function __construct($application)
    {
        parent::__construct($application);

        if ($application->hasOption('resourceloader')) {
            $this->setOptions(array(
                'resourceloader' => $application->getOption('resourceloader')
            ));
        }
        $this->getResourceLoader();  //获取资源装载器

        if (!$this->hasPluginResource('FrontController')) {
            $this->registerPluginResource('FrontController');
        }
    }

    // 细节 Zend_Application_Module_Autoloader 继承 Zend_Loader_Autoloader_Resource
    public function getResourceLoader()
    {
        if ((null === $this->_resourceLoader)
            && (false !== ($namespace = $this->getAppNamespace()))
        ) {
            $r    = new ReflectionClass($this);
            $path = $r->getFileName();
            $this->setResourceLoader(new Zend_Application_Module_Autoloader(array(
                'namespace' => $namespace,
                'basePath'  => dirname($path),
            )));
        }
        return $this->_resourceLoader;
    }

Zend_Application_Module_Autoloader类依靠了Zend_Loader_Autoloader的装载,而这里初始化Zend_Application_Module_Autoloader后,当要使用在其中定义的资源时,就可以依靠它来初始化资源,比如Application_Form_login类。

去查看Zend_Application_Module_Autoloader类:

class Zend_Application_Module_Autoloader extends Zend_Loader_Autoloader_Resource
{
    /**
     * Constructor
     *
     * @param  array|Zend_Config $options
     * @return void
     */
    public function __construct($options)
    {
        parent::__construct($options);
        $this->initDefaultResourceTypes();
    }

    /**
     * Initialize default resource types for module resource classes
     *
     * @return void
     */
    public function initDefaultResourceTypes()
    {
        $basePath = $this->getBasePath();
        $this->addResourceTypes(array(
            'dbtable' => array(
                'namespace' => 'Model_DbTable',
                'path'      => 'models/DbTable',
            ),
            'mappers' => array(
                'namespace' => 'Model_Mapper',
                'path'      => 'models/mappers',
            ),
            'form'    => array(
                'namespace' => 'Form',
                'path'      => 'forms',
            ),
            'model'   => array(
                'namespace' => 'Model',
                'path'      => 'models',
            ),
            'plugin'  => array(
                'namespace' => 'Plugin',
                'path'      => 'plugins',
            ),
            'service' => array(
                'namespace' => 'Service',
                'path'      => 'services',
            ),
            'viewhelper' => array(
                'namespace' => 'View_Helper',
                'path'      => 'views/helpers',
            ),
            'viewfilter' => array(
                'namespace' => 'View_Filter',
                'path'      => 'views/filters',
            ),
        ));
        $this->setDefaultResourceType('model');
    }
}

发现其实就是多了initDefaultResourceTypes这个方法而已,这个方法规定了目录组织结构,然后资源装载器就知道如何装载对应的类。实际上,我们也可以模仿它实现自定义的目录结构,或者直接使用Zend_Loader_Autoloader来设置也可。

永久链接:http://blog.ifeeline.com/202.html

Zend Framework自动转载之Zend_Load_Autoloader

Zend Framework对于自动转载类提供了Zend_Loader_Autoloader类(对于资源加载,提供了Zend_Loader_Autoloader_Resource类),它实现单态模式,查看构造函数:

    protected function __construct()
    {
        spl_autoload_register(array(__CLASS__, 'autoload'));
        $this->_internalAutoloader = array($this, '_autoload');
    }

可以看到构造函数中使用spl_autoload_register()注册当前类的autoload函数作为自动装载函数(实际这里的代码是对SPL的自动装载进行了一层封装)。使用如下代码测试:

// Define path to application directory
defined('APPLICATION_PATH')
    || define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/../application'));

// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
    realpath(APPLICATION_PATH . '/../library'),
    get_include_path(),
)));

require_once 'Zend/Loader/Autoloader.php';
// 获取Zend_Loader_Autoloader类对象,然后打印它,看初始化的内容
$loader = Zend_Loader_Autoloader::getInstance();
print_r($loader);

//以下是对象的输出
Zend_Loader_Autoloader Object
(
    [_autoloaders:protected] => Array
        (
        )

    [_defaultAutoloader:protected] => Array
        (
            [0] => Zend_Loader
            [1] => loadClass
        )

    [_fallbackAutoloader:protected] => 
    [_internalAutoloader:protected] => Array
        (
            [0] => Zend_Loader_Autoloader Object
 *RECURSION*
            [1] => _autoload
        )

    [_namespaces:protected] => Array
        (
            [Zend_] => 1
            [ZendX_] => 1
        )

    [_namespaceAutoloaders:protected] => Array
        (
        )

    [_suppressNotFoundWarnings:protected] => 
    [_zfPath:protected] => 
)

可以看到,默认初始化了_namespaces为Array([Zend_]=>1, [ZendX_]=>1) 和 _defaultAutoloader 为Array([0]=>Zend_Loader,[1]=>loadClass) 和 _internalAutoloader为Array([0] => Zend_Loader_Autoloader Object [1] => _autoload)。_internalAutoloader就是默认的装载器,实际它最终还是调用_defaultAutoloader设置的函数装载类(在类未提供装载函数时)。

命名空间可以使用registerNamespace($namespace)、unregisterNamespace($namespace)、getRegisteredNamespaces()来管理命名空间,实际是在操作_namespaces字段。注意,并不是注册了名空间,名空间的类就能自动装载(符合ZF规范的类组织才能),默认如果没有为名空间指定装载器,则使用_internalAutoloader,有几个相关函数unshiftAutoloader($callback, $namespace = ”)、pushAutoloader($callback, $namespace = ”)、removeAutoloader($callback, $namespace = null),这几个函数如果指定了$namespace就可以为具体的名空间指定专用的装载器。这个方法操作_namespaceAutoloaders(同时也会操作_autoloaders,它保存全部的自定义函数),比如为”My_”命名空间指定自动装载函数(意思是My_这个命名空间的类使用myLoad函数装载,对于其它的名空间如果没有指定就使用默认的):

$loader = Zend_Loader_Autoloader::getInstance();
$loader->pushAutoloader("myLoad","My_");
print_r($loader->getNamespaceAutoloaders("My_"));
//以下是输出
Array([0]=> myLoad)

以上使用了getNamespaceAutoloaders($namespace)获取名空间的类装载器,如果返回空数组,则表示名空间没有指定装载函数(那么框架将使用默认装载器装载类)。

另外,setAutoloaders(array $autoloaders) 和 getAutoloaders()操作_autoloaders,_autoloaders保存了全部自定义的自动装载函数,它一般都和$_namespaceAutoloaders一致,除非明确调用以上函数操作_autoloaders。

例子,让My_命名空间的类自动装载:

//在libray目录下建立My目录,里面放入My_MyClass.php
class My_MyClass{
    public function __construct(){
		echo "My_Class object.";
	}
}

//在运行脚本中
$loader = Zend_Loader_Autoloader::getInstance();
$loader->registerNamespace("My_");
$myclass = new My_MyClass();
//输出 My_Class object.  说明类正确加载,对象生成。

如非明确指定装载器,否则都使用默认的实现,而默认的实现主要逻辑体系在autoload($class)方法的实现:

    public static function autoload($class)
    {
        $self = self::getInstance();
        foreach ($self->getClassAutoloaders($class) as $autoloader) {
            if ($autoloader instanceof Zend_Loader_Autoloader_Interface) {
                if ($autoloader->autoload($class)) {
                    return true;
                }
            } elseif (is_array($autoloader)) {
                if (call_user_func($autoloader, $class)) {
                    return true;
                }
            } elseif (is_string($autoloader) || is_callable($autoloader)) {
                if ($autoloader($class)) {
                    return true;
                }
            }
        }

        return false;
    }

getClassAutoloaders($class)返回自动转载器的列表,分三种情况:
1 如果这个自动装载器实现了Zend_Loader_Autoloader_Interface接口,这调用这个对象的autoload()函数装载类。
2 如果是数组,这个情况一般就是指Array([0] => Zend_Loader_Autoloader Object [1] => _autoload),它被传入call_use_func()函数,那么这个_autoload将被执行,它执行时实际调用Zend_Loader类的loadClass()函数(_defaultAutoloader指定的内容)。注意,可以为名空间设置一个或多个没有关联下标的装载器(一般都没有必要),这些装载器在当名空间没有匹配到装载器时会被使用,也可以不指定,则使用默认的。
3 直接指定的回调函数

在使用Zend Framework构建的MVC项目中,自动装载器是在什么地方初始化的呢。 每个应用都是从一个Application开始的:

// Zend_Application类的构造函数
    public function __construct($environment, $options = null)
    {
        $this->_environment = (string) $environment;

        require_once 'Zend/Loader/Autoloader.php';
        $this->_autoloader = Zend_Loader_Autoloader::getInstance();

        if (null !== $options) {
            if (is_string($options)) {
                $options = $this->_loadConfig($options);
            } elseif ($options instanceof Zend_Config) {
                $options = $options->toArray();
            } elseif (!is_array($options)) {
                throw new Zend_Application_Exception('Invalid options provided; must be location of config file, a config object, or an array');
            }

            $this->setOptions($options);
        }
    }

从这个构造函数中可以清楚的知道,Zend_Loader_Autoloader的实例赋值给了$this->_autoloader,可以通过getAutoloader()方法获取这个实例,然后操作这个对象。

更多内容可以参考源码 或 参考文档。

永久链接: http://blog.ifeeline.com/197.html

Zend Framework中的自动装载

Autoloading is a mechanism技巧 that eliminates排除 the need to manually require dependencies依赖关系 within your PHP code. Per the PHP autoload manual, once an autoloader has been defined, it “is automatically called in case you are trying to use a class or an interface which hasn’t been defined yet.” 一旦一个autoloader已经定义,当试图使用一个当前没有定义的类或者接口时它就会被自动调用。

Using autoloading, you do not need to worry about where a class exists in your project. 使用autoloading,你不需要担心一个类放在项目的什么地方。 With well-defined autoloaders, you do not need to worry about where a class file is relative to the current class file; you simply use the class, and the autoloader will perform the file lookup.

Additionally, autoloading, because it defers推迟 loading to the last possible moment and ensures that a match only has to occur once, can be a huge巨大的 performance boost提高 — particularly if you take the time to strip out require_once() calls before you move to deployment.

Zend Framework encourages鼓励 the use of autoloading, and provides several tools to provide autoloading of both library code as well as application code. This tutorial covers these tools, as well as how to use them effectively有效地.

Goals and Design 目标与设计
1 Class Naming Conventions类命名惯例
To understand autoloading in Zend Framework, first you need to understand the relationship between class names and class files. 要理解Zend Framework中的自动装载,首先需要理解类名和类文件之间的关系。

Zend Framework has borrowed an idea from PEAR, whereby class names have a 1:1 relationship with the filesystem. Zend Framework从PEAR那里借用了方法,类名和文件系统的关系是1比1的关系。 Simply put, the underscore character (“_”) is replaced by a directory separator in order to resolve the path to the file, and then the suffix “.php” is added. 简单的说,为了分解路径到文件,使用下划线替换了目录分隔符。 For example, the class “Foo_Bar_Baz” would correspond to “Foo/Bar/Baz.php” on the filesystem. The assumption is also that the classes may be resolved via PHP’s include_path setting, which allows both include() and require() to find the filename via a relative path相对路径 lookup on the include_path.

Additionally, per PEAR as well as the PHP project, we use and recommend using a vendor or project prefix for your code.在代码中使用和建议使用一个前缀。 What this means is that all classes you write will share a common class prefix; 这个意味着所有的类共享一个公共的类前缀;for example, all code in Zend Framework has the prefix “Zend_”. This naming convention helps prevent naming collisions.这个命名惯例避免命名冲突。 Within Zend Framework, we often refer to this as the “namespace” prefix; be careful not to confuse it with PHP’s native namespace implementation. 在Zend Framework中,通常把它作为命名空间,不过它跟PHP原生的命名空间是不一样的。

Zend Framework follows these simple rules internally, and our coding standards encourage that you do so as well for all library code.

2 Autoloader Conventions and Design自动转载惯例与设计
Zend Framework’s autoloading support, provided primarily via Zend_Loader_Autoloader, has the following goals and design elements:
Zend Framework自动转载的支持主要通过Zend_Loader_Autoloader提供,它有如下一些目标和设计元素:
•Provide namespace matching. If the class namespace prefix is not in a list of registered namespaces, return FALSE immediately. This allows for more optimistic matching, as well as fallback to other autoloaders. 提供命名空间匹配。如果一个类命名空间前缀没有在注册的命名空间列表中,马上返回FALSE。这样可以更快匹配,以及回退到其它的装载器。
•Allow the autoloader to act as a fallback autoloader. In the case where a team may be widely distributed广泛分布, or using an undetermined未确定的 set of namespace prefixes, the autoloader should still be configurable such that it will attempt to match any namespace prefix. It will be noted, however, that this practice is not recommended, as it can lead to unnecessary lookups. 允许自动转载器扮演一个回调自动转载器。在这种情况下,在一个团队可能广泛分布 或 使用一个未确定的命名空间前缀集合,自动装载器还是可配置,这样它就试图去匹配任意的命名空间前缀。要注意,然而,这种做法是不推荐,因为它可能会导致不必要的查找。
•Allow toggling切换 error suppression禁止. We feel — and the greater PHP community does as well — that error suppression is a bad idea. It’s expensive, and it masks very real application problems. So, by default, it should be off. However, if a developer insists坚持认为 that it be on, we allow toggling it on.
•Allow specifying custom callbacks for autoloading. Some developers don’t want to use Zend_Loader::loadClass() for autoloading, but still want to make use of Zend Framework’s mechanisms. Zend_Loader_Autoloader allows specyfing an alternate callback for autoloading. 允许为自动转载定制回调函数。
•Allow manipulation of the SPL autoload callback chain. The purpose of this is to allow specifying additional autoloaders — for instance, resource loaders for classes that don’t have a 1:1 mapping to the filesystem — to be registered before or after the primary Zend Framework autoloader. 运行维护SPL自动转载回调函数链。这个目的是允许指定另外的装载器,比如,类不是1:1映射到文件系统的资源加载器,在Zend Framework自动转载器之前或之后可以被注册。

Basic Autoloader Usage 基本自动转载器的使用

Now that we have an understanding of what autoloading is and the goals and design of Zend Framework’s autoloading solution, let’s look at how to use Zend_Loader_Autoloader.

In the simplest case, you would simply require the class, and then instantiate it. 要先加载这个类,然后实例化它。 Since Zend_Loader_Autoloader is a singleton (due to the fact that the SPL autoloader is a single resource由于SPL自动装载器是一个单态资源的这一事实), we use getInstance() to retrieve an instance.

require_once 'Zend/Loader/Autoloader.php';
Zend_Loader_Autoloader::getInstance();

By default, this will allow loading any classes with the class namespace prefixes of “Zend_” or “ZendX_”, as long as they are on your include_path.
默认,这将允许加载任意的以Zend_或ZendX_为命名空间前缀的类,只要它们在include_path中能找到。

What happens if you have other namespace prefixes you wish to use? 当希望使用其它命名空间前缀时将发生什么? The best, and simplest, way is to call the registerNamespace() method on the instance. 最好并且最简单的方法是调用实例的registerNamespace()方法。 You can pass a single namespace prefix, or an array of them: 可以传递一个命名空间前缀或者一个数组:

require_once 'Zend/Loader/Autoloader.php';
$loader = Zend_Loader_Autoloader::getInstance();
$loader->registerNamespace('Foo_'); //注册名空间
$loader->registerNamespace(array('Foo_', 'Bar_'));

Alternately, you can tell Zend_Loader_Autoloader to act as a “fallback” autoloader. This means that it will try to resolve any class regardless of namespace prefix. 可选的,你可以告诉Zend_Loader_Autoloader去扮演一个回调装载函数。 这意味它将试图去解析任意的类而不管命名空间前缀。

$loader->setFallbackAutoloader(true);

Do not use as a fallback autoloader
While it’s tempting to use Zend_Loader_Autoloader as a fallback autoloader, we do not recommend the practice.
Internally, Zend_Loader_Autoloader uses Zend_Loader::loadClass() to load classes. 在内部,Zend_Loader_Autoloader使用Zend_Loader::loadClass()加载类。 That method uses include() to attempt to load the given class file. 这个方法使用include()去试图加载给定的类文件。 include() will return a boolean FALSE if not successful — but also issues a PHP warning. include()如果不成功将返回FALSE,但是也发出一条PHP警告信息。 This latter fact can lead to some issues: 这一事实可能导致一些问题:
•If display_errors is enabled, the warning will be included in output. 如果display_errors是启用的,这个警告将包含在输出中。
•Depending on the error_reporting level you have chosen, it could also clutter混乱 your logs. 依赖error_reporting的级别,它可能让你的日志变得混乱。

You can suppress禁止 the error messages (the Zend_Loader_Autoloader documentation details this), but note that the suppression is only relevant有关联的 when display_errors is enabled; the error log will always display the messages. For these reasons, we recommend always configuring the namespace prefixes the autoloader should be aware of.

Note: Namespace Prefixes vs PHP Namespaces命名空间前缀与PHP内置的命名空间对比
At the time this is written, PHP 5.3 has been released. With that version, PHP now has official namespace support.
However, Zend Framework predates在它之前 PHP 5.3, and thus namespaces. Within Zend Framework, when we refer to “namespaces”, we are referring to a practice whereby classes are prefixed with a vender “namespace”. As an example, all Zend Framework class names are prefixed with “Zend_” — that is our vendor “namespace 厂商名空间”.

Zend Framework plans to offer native PHP namespace support to the autoloader in future revisions, and its own library will utilize使用 namespaces starting with version 2.0.0. 从2.0.0开始支持PHP原生名空间。

If you have a custom autoloader you wish to use with Zend Framework — perhaps an autoloader from a third-party library you are also using — you can manage it with Zend_Loader_Autoloader’s pushAutoloader() and unshiftAutoloader() methods. These methods will append or prepend, respectively分别地, autoloaders to a chain that is called prior优先的 to executing Zend Framework’s internal autoloading mechanism. This approach offers the following benefits:
•Each method takes an optional second argument, a class namespace prefix.每个方法提供一个可先的第二参数,它指定了类的命名空间前缀。 This can be used to indicate表明 that the given autoloader should only be used when looking up classes with that given class prefix. 这可以用来表明给定的自动装载器只能在当查找有给定类前缀的类时使用。 If the class being resolved does not have that prefix, the autoloader will be skipped — which can lead to performance improvements.
•If you need to manipulate spl_autoload()’s registry, any autoloaders that are callbacks pointing to instance methods can pose引起 issues, as spl_autoload_functions() does not return the exact精确的 same callbacks. Zend_Loader_Autoloader has no such limitation.
Autoloaders managed this way may be any valid PHP callback.

// Append function 'my_autoloader' to the stack,
// to manage classes with the prefix 'My_':
$loader->pushAutoloader('my_autoloader', 'My_');
 
// Prepend static method Foo_Loader::autoload() to the stack,
// to manage classes with the prefix 'Foo_':
$loader->unshiftAutoloader(array('Foo_Loader', 'autoload'), 'Foo_');

Resource Autoloading 资源自动加载
Often, when developing an application, it’s either difficult to package classes in the 1:1 classname:filename standard Zend Framework recommends, or it’s advantageous for purposes of packaging not to do so. However, this means you class files will not be found by the autoloader. 类和文件名不是1:1对应,开发中存在这些情况。

If you read through the design goals for the autoloader, the last point in that section indicated that the solution should cover this situation. Zend Framework does so with Zend_Loader_Autoloader_Resource. Zend Framework中使用Zend_Loader_Autoloader_Resource来实现资源自动加载。

A resource is just a name that corresponds to a component namespace (which is appended to the autoloader’s namespace) and a path (which is relative to the autoloader’s base path). 资源仅是一个对组件命名空间相关的名字(它被后加到自动转载器命名空间之后)和路径(它是相关的自动转载器的基路径)In action, you’d do something like this:

$loader = new Zend_Application_Module_Autoloader(array(
	    'namespace' => 'Blog',
	    'basePath'  => APPLICATION_PATH . '/modules/blog',
	));

(Zend_Application_Module_Autoloader 继承自Zend_Loader_Autoloader_Resource)

Once you have the loader in place, you then need to inform it of the various resource types it’s aware of. These resource types are simply pairs of subtree and prefix. 一旦你有了加载器,然后你需要通知它的各种它知道的资源类型。这些资源类型是简单的一对子树和前缀
As an example, consider the following tree:

path/to/some/resources/
	|-- forms/
	|   `-- Guestbook.php        // Foo_Form_Guestbook
	|-- models/
	|   |-- DbTable/
	|   |   `-- Guestbook.php    // Foo_Model_DbTable_Guestbook
	|   |-- Guestbook.php        // Foo_Model_Guestbook
	|   `-- GuestbookMapper.php  // Foo_Model_GuestbookMapper

Our first step is creating the resource loader: 

	$loader = new Zend_Loader_Autoloader_Resource(array(
	    'basePath'  => 'path/to/some/resources/',
	    'namespace' => 'Foo',
	));

Next, we need to define some resource types. 接下来需要定义一些资源类型。

Zend_Loader_Autoloader_Resourse::addResourceType() has three arguments: the “type” of resource (an arbitrary string), the path under the base path in which the resource type may be found, and the component prefix to use for the resource type. Zend_Loader_Autoloader_Resourse::addResourceType()有三个参数,资源的类型,资源类型可能查找的在基本路径下的路径, 和为资源类型给定的组件的前缀。 In the above tree, we have three resource types: form (in the subdirectory “forms”, with a component prefix of “Form”), model (in the subdirectory “models”, with a component prefix of “Model”), and dbtable (in the subdirectory “models/DbTable”, with a component prefix of “Model_DbTable”). 在上面给出的例子有三种资源类型,form(在子目录forms中,它的组件前缀时Form) model(在子目录models中,组件前缀时Model) 和dbtable(在子目录models/DbTable,组件前缀是Model_DbTable)。 We’d define them as follows:

$loader->addResourceType('form', 'forms', 'Form')
	       ->addResourceType('model', 'models', 'Model')
	       ->addResourceType('dbtable', 'models/DbTable', 'Model_DbTable');

Once defined, we can simply use these classes: 

	$form      = new Foo_Form_Guestbook();
	$guestbook = new Foo_Model_Guestbook();

Note: Module Resource Autoloading
Zend Framework’s MVC layer encourages the use of “modules”, which are self-contained applications within your site. Zend Framework’s MVC层鼓励使用modules,它在你的网站中是自我包含的应用程序。 Modules typically have a number of resource types by default, and Zend Framework even recommends a standard directory layout for modules(http://framework.zend.com/manual/1.12/en/project-structure.filesystem.html). 模块默认通常有一系列的资源类型,所以Zend Framework 甚至建议为模块建立一个标准的目录布局。Resource autoloaders are therefore quite useful in this paradigm — so useful that they are enabled by default when you create a bootstrap class for your module that extends Zend_Application_Module_Bootstrap. For more information, read the Zend_Loader_Autoloader_Module documentation. (说使用这个标准的好处)

永久链接:http://blog.ifeeline.com/188.html

Zen-cart插件 – Google工具(Google管理员工具 Google邮箱)认证

要使用Google管理员工具、Google邮箱前首先要认证你是域所有者,如何能认证你是域的所有值呢,Google给出三种认证方法:
1 上传一个它给定的文件
2 在你网站首页添加一行给定的META信息
3 添加一条text记录到你的域的域名解析服务器中

一般Google推荐使用上传文件的方法认证域所有者,但是如果能够通过网站后台输入给定的信息然后这个META信息就自定添加到首页,通过这样方式来进行认证看起来是非常方便的。在Zen-cart中,实现也非常简单:

// 插入配置表,分别对应认证Google管理员工具和Google邮箱时给定的字符串
INSERT INTO `configuration` (`configuration_title`, `configuration_key`, `configuration_value`, `configuration_description`, `configuration_group_id`, `sort_order`, `last_modified`, `date_added`) VALUES
('Google管理员工具Meta方式验证', 'GOOGLE_SITE_VERIFICATION_MASTER_TOOL', '', 'Google管理员工具Meta方式验证代码,如果不想使用Meta方式验证,请留空即可。', 1, 8, NULL, now());

INSERT INTO `configuration` (`configuration_title`, `configuration_key`, `configuration_value`, `configuration_description`, `configuration_group_id`, `sort_order`, `last_modified`, `date_added`) VALUES
('Google App Meta方式验证', 'GOOGLE_SITE_VERIFICATION_APP', '', 'Google App Meta方式验证代码,如果不想使用Meta方式验证,请留空即可。', 1, 8, NULL, now())

然后打开includes/includes/templates/模版名/common/html_header.php文件,在meta name=”generator”行的下一行添加:

// define('GOOGLE_SITE_VERIFICATION_MASTER_TOOL','');
if($this_is_home_page && defined('GOOGLE_SITE_VERIFICATION_MASTER_TOOL') && (GOOGLE_SITE_VERIFICATION_MASTER_TOOL != '')){
	echo '<meta name="google-site-verification" content="'.GOOGLE_SITE_VERIFICATION_MASTER_TOOL.'" />'."\n";
}
// define('GOOGLE_SITE_VERIFICATION_MASTER_APP','');
if($this_is_home_page && defined('GOOGLE_SITE_VERIFICATION_APP') && (GOOGLE_SITE_VERIFICATION_APP != '')){
	echo '<meta name="google-site-verification" content="'.GOOGLE_SITE_VERIFICATION_APP.'" />'."\n";
}

这样就可以通过后台点一下,输入给定信息,就可以完成Google工具的认证。非常方便。另外,Google分析也是网站常用配置,如果每一个新站都要粘贴一段几乎一样的代码,看起来非常不爽,根据以上的方式,也可以做到半自动配置:

INSERT INTO `configuration` (`configuration_title`, `configuration_key`, `configuration_value`, `configuration_description`, `configuration_group_id`, `sort_order`, `last_modified`, `date_added`) VALUES
('Google  Analytics账户ID', 'GOOGLE_ANALYTICS_ACOUNT', '', 'Google  Analytics账户ID,自动添加Google分析代码,如果不采用系统自动添加的方式,请留空', 1, 8, NULL, now());

然后打开includes/includes/templates/模版名/common/html_header.php文件,在脚本文件加载之前插入如下代码:

<?php
//define('GOOGLE_ANALYTICS_ACOUNT','');
if(defined('GOOGLE_ANALYTICS_ACOUNT') && GOOGLE_ANALYTICS_ACOUNT != ''){
?>
<script type="text/javascript">

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', '']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

</script>
<?php
}
?>

可以看到,这段JS只有一个变量是要动态写入的,这样配置Google分析也非常方便了。

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

Zen-cart插件 – 限制最小订单金额

在Zen-cart的页面的头部文件heade_php.php文件中,经常可以看到类似如下这些代码:

$zco_notifier->notify('NOTIFY_HEADER_START_CHECKOUT_SHIPPING');

这些代码其实实现了观察者模式,通过这个调用可以触发注册到这个事件的代码,类似于一个陷入或者说是一个钩子,虽然Zen-cart内核中并没有利用这个功能实现任何功能,但是我们却可以利用这个预留功能开发插件,做到不改动或很小改动Zen-cart的内核代码,这样的插件也比较好维护。

限制最小订单金额 这个插件就是利用了Zen-cart预留的功能,做到不需要改动任何的内核代码,连模版文件也不需要改动。

需求:
当客户购物车中的产品总金额低于某个预设值时,不允许客户下订单并提示需要超过某金额才能下单。

实现过程:
1 在数据库配置表中插入一个可以配置项,它表示客户下单的最小金额

INSERT INTO `configuration` (`configuration_title`, `configuration_key`, `configuration_value`, `configuration_description`, `configuration_group_id`, `sort_order`, `date_added`) VALUES
('Minimum Order Setup', 'MIN_ORDER_NUMBER', '', 'Setup the min order limit', 2, 20,now())

2 当客户进入到checkout_shipping、checkout_payment、checkout_confirmation这些页面时,如果不满足条件,跳回到购物车页面

// 新建includes/init_includes/init_min_order.php文件
if (!defined('IS_ADMIN_FLAG')) {
  	die('Illegal Access');
}

if( defined('MIN_ORDER_NUMBER') && (trim(MIN_ORDER_NUMBER) != '') ){		
	$main_page = array('checkout_shipping','checkout_payment','checkout_confirmation','quick_checkout');
	
	if( in_array(trim($current_page),$main_page) && ((int)$_SESSION['cart']->count_contents() > 0) && (ceil($_SESSION['cart']->show_total()) < MIN_ORDER_NUMBER) ){
		zen_redirect(zen_href_link("shopping_cart"));
	}
}

3 当进入到购物车页面时,如果不满足条件,发出提示信息,这里我们利用了向NOTIFY_HEADER_END_SHOPPING_CART事件绑定代码来实现

// 新建includes/classes/observers/class.min_order.php文件
class MinOrderObserver extends base{
	function MinOrderObserver(){
		global $zco_notifier;
		$zco_notifier->attach($this, array('NOTIFY_HEADER_END_SHOPPING_CART'));
	}

	function update(&$class, $eventID, $paramsArray) {
		global $messageStack, $currencies, $current_page;
		if( defined('MIN_ORDER_NUMBER') && (trim(MIN_ORDER_NUMBER) != '') ){
			if( ($current_page == 'shopping_cart') && ((int)$_SESSION['cart']->count_contents() > 0) && (ceil($_SESSION['cart']->show_total()) < MIN_ORDER_NUMBER) ){
  				$messageStack->add('shopping_cart', sprintf(TEXT_MIN_ORDER,$currencies->format(MIN_ORDER_NUMBER)), 'caution');
			}
		}
	}
}

// 新建语言文件includes/languages/extra_definitions/min_order.php
define('TEXT_MIN_ORDER', 'Free shipping for all order, but we only accept orders over %s.');

4 最后需要依靠系统的资源自动初始化功能把以上的资源加入初始化列表

// 新建includes/auto_loaders/config.min_order.php
if (!defined('IS_ADMIN_FLAG')) {
 	die('Illegal Access');
} 

$autoLoadConfig[189][] = array('autoType'=>'class',
                                'loadFile'=>'observers/class.min_order.php');

$autoLoadConfig[189][] = array('autoType'=>'classInstantiate',
                              'className'=>'MinOrderObserver',
                              'objectName'=>'MinOrderObserver');

$autoLoadConfig[189][] = array('autoType'=>'init_script',
                                 'loadFile'=> 'init_min_order.php');

在后台的最小值设置限制值:

当购物车的总价不满足条件时:

当跳到运费页时,如果不满足条件的,会重新跳回到购物车页面。 这样,这个功能在没有修改任何Zen-cart内核文件和模版文件的情况下完成了,感觉是非常干净。

测试案例地址: http://blog.ifeeline.com/zen-cart/z151,如果购物车订单金额低于100,将无法下订单。

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

PHP的call_user_func()函数

call_user_func()函数参考
-Description 描述
mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )

Calls the callback given by the first parameter and passes the remaining parameters as arguments.

-Parameters参数

callback
    The callable to be called.
parameter
    Zero or more parameters to be passed to the callback. 

第一个参数的类型是callable,那何为callable类型呢?(最新版本应该是改为叫callbacks了)
Callbacks can be denoted by callable type hint as of PHP 5.4. This documentation used callback type information for the same purpose.

Some functions like call_user_func() or usort() accept user-defined callback functions as a parameter. Callback functions can not only be simple functions, but also object methods, including static class methods. 回调函数既可以是一般的函数,也可以是对象的方法,包括静态类方法。

Passing 参数传递

A PHP function is passed by its name as a string. Any built-in or user-defined function can be used, except language constructs such as: array(), echo, empty(), eval(), exit(), isset(), list(), print or unset(). 说明哪些方法不能传递。

A method of an instantiated object is passed as an array containing an object at index 0 and the method name at index 1. 一个对象的方法是通过一个数组传递的,下标0指向对象,1指向对象的方法。

Static class methods can also be passed without instantiating an object of that class by passing the class name instead of an object at index 0. As of PHP 5.2.3, it is also possible to pass ‘ClassName::methodName’. 静态类方法可以直接传递类名给数组的0下标。PHP 5.2.3以后的版本也可以使用ClassName::methodName的格式。

Apart from common user-defined function, create_function() can also be used to create an anonymous callback function. As of PHP 5.3.0 it is possible to also pass a closure to a callback parameter.

综上,所谓的callbacks类型,只是一个格式要求而已。例子:

//直接调用一个函数
function user($var){
	echo $var;
}
call_user_func("user","Say Hello.");

//调用一个对象的方法
class Obj{
	public function inObj($a,$b){
		echo $a.'--'.$b;
	}
}
$o = new Obj();
call_user_func(array($o, inObj),"Hello", "World");
//将输出 Hello--World

如上代码call_user_func(array($o, inObj),”Hello”, “World”) 换成 call_user_func(array(Obj, inObj),”Hello”, “World”)结果也一样,不过这样有什么区别呢? 当调用的这个方法依赖对象的数据时,就必须是调用对象的方法,如果类只是作为方法的集合,换句话说就是类的方法是独立的,就不需要传递对象进去,直接给个类名即可。如果调用的是静态方法,只要直接给ClassName::methodName的格式到第一参数既可。

除了call_user_func()函数,还有一个call_user_func_arrar()函数,用法类似,只是参数传递的时候要放入一个数组。比如:
call_user_func_array(array($o, inObj),array(“Hello”, “World”));

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

PHP 的自动装载

PHP5中提供了__autoload()函数作为自动装载的实现。当加载PHP类时,如果类所在文件没有被包含进来,或者类名出错,Zend引擎会自动调用__autoload 函数。此函数需要用户自己实现__autoload函数。在PHP5.1.2版本后,可以使用spl_autoload_register函数自定义自动加载处理函数。当没有调用此函数,默认情况下会使用SPL自定义的spl_autoload函数。

spl_autoload_register参考:(PHP 5 >= 5.1.2)
-描述:
bool spl_autoload_register ([ callable $autoload_function [, bool $throw = true [, bool $prepend = false ]]] )

Register a function with the spl provided __autoload stack. If the stack is not yet activated it will be activated. 注册一个函数到spl提供的__autoload堆栈。如果堆栈当前没有激活,注册了函数后将被激活。

If your code has an existing __autoload() function then this function must be explicitly registered on the __autoload stack. 

This is because spl_autoload_register() will effectively replace the engine cache for the __autoload() function by either spl_autoload() or spl_autoload_call(). 如果你的代码已经存在了__autoload()函数,这个函数必须明确注册到__autoload堆栈中。 这是因为针对__autoload()函数,spl_autoload_register()将通过使用spl_autoload() 或 spl_autoload_call()高效替换引擎缓存。

If there must be multiple autoload functions, spl_autoload_register() allows for this. It effectively creates a queue of autoload functions, and runs through each of them in the order they are defined. By contrast, __autoload() may only be defined once. spl_autoload_register()运行注册多个自动转载函数。它高效地创建一个自动加载函数队列,按照定义的顺序依次执行。相比之下,__autoload()只能定义一次。

-参数:
autoload_function
    The autoload function being registered. If no parameter is provided, then the default implementation of spl_autoload() will be registered. 如果没有参数提供,默认的spl_autoload()实现将被注册(就是说如果提供了参数,那么spl_autoload()就不会注册到堆栈了)。
throw
    This parameter specifies whether spl_autoload_register() should throw exceptions when the autoload_function cannot be registered. 当自动转载函数不能被注册时是否抛出异常,默认是抛出。
prepend
    If true, spl_autoload_register() will prepend the autoloader on the autoload stack instead of appending it. 默认注册的函数是添加到堆栈顶部,这个参数为ture是是添加到堆栈底。

-返回值
Returns TRUE on success or FALSE on failure. 

-Changelog
5.3.0 	Namespaces support was introduced.
5.3.0 	The prepend parameter was added. 

从这个参考可以得出一个结论,PHP内部维护一个__autoload的堆栈,注册了新的函数就压入这个堆栈中。如果调用了spl_autoload_register函数(不管是否提供了注册函数,如果没有,默认spl_autoload函数将压入__autoload堆栈),而代码中定义了__autoload()函数,那么这个函数将不会被使用了,但是可以显式把__autoload()函数压入__autoload堆栈中作为它的第一个元素。当调用了spl_autoload_register后,如果没有提供函数给它(即第一个参数为空),那么spl_autoload()函数压入了__autoload堆栈,它最为第一个函数。

spl_autoload()函数会自动在路径中查找具有小写类名和.php扩展或者.ini扩展名,或者任何注册到spl_autoload_extensions()函数中的其它扩展名的文件。

spl_autoload_unregister函数可以去掉已经注册到__autoload堆栈中的函数。

spl_autoload_functions函数可以返回全部已经注册到__autoload堆栈中的函数。

对比以下例子的输出:

//看看当前的__autoload堆栈内容,应该是空的
print_r(spl_autoload_functions());
echo '------------->
'; //调用一个空的函数,然后看看内容,应该输出spl_autoload spl_autoload_register(); print_r(spl_autoload_functions()); //先压入a函数,然后再压缩b函数,不过顺序变成了b->a spl_autoload_register("a"); spl_autoload_register("b",true, true); print_r(spl_autoload_functions()); //清空 spl_autoload_unregister('a'); spl_autoload_unregister('b'); spl_autoload_unregister('spl_autoload'); //已经没有函数了,但是__aotoload堆栈已经激活了,将返回一个空数组 print_r(spl_autoload_functions()); echo '
-------------
'; spl_autoload_register("a"); spl_autoload_register("b"); print_r(spl_autoload_functions()); //触发自动转载函数 $c = new C();

程序运行后的输出:

------------->
Array ( [0] => spl_autoload ) Array ( [0] => b [1] => spl_autoload [2] => a ) Array ( )
-------------
Array ( [0] => a [1] => b ) i am in a function. i am in b function.
Fatal error: Class 'C' not found in D:\www\spl_autoload.php on line 37

简单来说,__autoload()是PHP5提供的自动装载的支持,后来发现它缺乏灵活性,从PHP5.1.2版本起又提供了spl_autoload_register()这种解决方案。

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

Quickstart – Zend Framework 创建表单

For our guestbook to be useful, we need a form for submitting new entries.
Our first order of business is to create the actual form class. 建立实际的表单类。 First, create the directory application/forms/. This directory will contain form classes for the application. 包含了表单类。 Next, we’ll create a form class in application/forms/Guestbook.php:

// application/forms/Guestbook.php

class Application_Form_Guestbook extends Zend_Form
{
    public function init()
    {
        // Set the method for the display form to POST
        $this->setMethod('post');

        // Add an email element
        $this->addElement('text', 'email', array(
            'label'      => 'Your email address:',
            'required'   => true,
            'filters'    => array('StringTrim'),
            'validators' => array(
                'EmailAddress',
            )
        ));

        // Add the comment element
        $this->addElement('textarea', 'comment', array(
            'label'      => 'Please Comment:',
            'required'   => true,
            'validators' => array(
                array('validator' => 'StringLength', 'options' => array(0, 20))
                )
        ));

        // Add a captcha
        $this->addElement('captcha', 'captcha', array(
            'label'      => 'Please enter the 5 letters displayed below:',
            'required'   => true,
            'captcha'    => array(
                'captcha' => 'Figlet',
                'wordLen' => 5,
                'timeout' => 300
            )
        ));

        // Add the submit button
        $this->addElement('submit', 'submit', array(
            'ignore'   => true,
            'label'    => 'Sign Guestbook',
        ));

        // And finally add some CSRF protection
        $this->addElement('hash', 'csrf', array(
            'ignore' => true,
        ));
    }
}

这个表单类继承Zend_Form。

The above form defines five elements: an email address field, a comment field, a CAPTCHA for preventing spam submissions, a submit button, and a CSRF protection token.

Next, we will add a signAction() to our GuestbookController which will process the form upon submission. To create the action and related view script, execute the following: 建立sign方法,用来处理表单的提交。
# Unix-like systems:
% zf.sh create action sign guestbook

# DOS/Windows:
zf.bat create action sign guestbook
This will create a signAction() method in our controller, as well as the appropriate view script. 自动建立sign方法和对应的视图脚本。

Let’s add some logic into our guestbook controller’s sign action. We need to first check if we’re getting a POST or a GET request; in the latter case, we’ll simply display the form. However, if we get a POST request, we’ll want to validate the posted data against our form, and, if valid, create a new entry and save it. The logic might look like this: 建立逻辑

// application/controllers/GuestbookController.php

class GuestbookController extends Zend_Controller_Action
{
    // snipping indexAction()...

    public function signAction()
    {
        $request = $this->getRequest();
        $form    = new Application_Form_Guestbook();

        if ($this->getRequest()->isPost()) {
            if ($form->isValid($request->getPost())) {
                $model = new Application_Model_Guestbook($form->getValues());
                $model->save();
                return $this->_helper->redirector('index');
            }
        }

        $this->view->form = $form;
    }
}

获取请求对象,实例化一个表单对象,验证POST的数据,生成对应的模型,然后保存数据,最后定位到index, 默认把表单对象传递到视图。

Of course, we also need to edit the view script; edit application/views/scripts/guestbook/sign.phtml to read:

<!-- application/views/scripts/guestbook/sign.phtml -->

Please use the form below to sign our guestbook!

<?php
$this->form->setAction($this->url());
echo $this->form;

表单的外表是可以完全自定义的,这里提供了一个建立表单的非常直接的例子,Zend_Form提供了完整的功能,包括表单的验证等。

永久链接: http://blog.ifeeline.com/165.html