标签归档:zend

Zend Framework 2.x 之 Zend\Uri

Zend\Uri

Overview
Zend\Uri is a component that aids in manipulating and validating Uniform Resource Identifiers (URIs) [1]. Zend\Uri exists primarily to service other components, such as Zend\Http\, but is also useful as a standalone utility.(主要服务于其他组件,也可以单独使用,当要提取URL的各个部分时,它是非常有用的)

URIs always begin with a scheme, followed by a colon. The construction of the many different schemes varies significantly. The Zend\Uri component provides the Zend\Uri\UriFactory that returns a class implementing the Zend\Uri\UriInterface which specializes in the scheme if such a class is registered with the Factory.

Creating a New URI
Zend\Uri\UriFactory will build a new URI from scratch if only a scheme is passed to Zend\Uri\UriFactory::factory().(只传递scheme)

Creating a New URI with ZendUriUriFactory::factory()

// To create a new URI from scratch, pass only the scheme
// followed by a colon.
$uri = Zend\Uri\UriFactory::factory('http:');

// $uri instanceof Zend\Uri\UriInterface

…….

Manipulating an Existing URI

To manipulate an existing URI, pass the entire URI as string to Zend\Uri\UriFactory::factory().

Manipulating an Existing URI with Zend\Uri\UriFactory::factory()

// To manipulate an existing URI, pass it in.
$uri = Zend\Uri\UriFactory::factory('http://www.zend.com');

// $uri instanceof Zend\Uri\UriInterface

The URI will be parsed and validated. If it is found to be invalid, a Zend\Uri\Exception\InvalidArgumentException will be thrown immediately. Otherwise, Zend\Uri\UriFactory::factory() will return a class implementing Zend\Uri\UriInterface that specializes in the scheme to be manipulated.(自动验证,不通过抛异常)

Common Instance Methods

$uri = Zend\Uri\UriFactory::factory('mailto:john.doe@example.com');
$scheme = $uri->getScheme();  // "mailto"

$uri = Zend\Uri\UriFactory::factory('mailto:john.doe@example.com');
$scheme = $uri->getUserinfo();  // "john.doe"

$uri = Zend\Uri\UriFactory::factory('mailto:john.doe@example.com');
$scheme = $uri->getHost();  // "example.com"

$uri = Zend\Uri\UriFactory::factory('http://example.com:8080');
$scheme = $uri->getPort();  // "8080"

$uri = Zend\Uri\UriFactory::factory('http://example.com');
$scheme = $uri->getPort();  // "80"

// 这个跟Nginx中URI一样,主要包含开始的斜杠
$uri = Zend\Uri\UriFactory::factory('http://example.com:80/my/path?a=b&c=d#token');
$scheme = $uri->getPath();  // "/my/path"

$uri = Zend\Uri\UriFactory::factory('http://example.com:80/my/path?a=b&c=d#token');
$scheme = $uri->getQuery();  // "a=b&c=d"

$uri = Zend\Uri\UriFactory::factory('http://example.com:80/my/path?a=b&c=d#token');
$scheme = $uri->getQueryAsArray();
// array(
//  'a' => 'b',
//  'c' => 'd',
// )

$uri = Zend\Uri\UriFactory::factory('http://example.com:80/my/path?a=b&c=d#token');
$scheme = $uri->getFragment();  // "token"
$uri = Zend\Uri\UriFactory::factory('http://www.zend.com');
echo $uri->toString();  // "http://www.zend.com"
// Alternate method:
echo (string) $uri;     // "http://www.zend.com"

$uri = Zend\Uri\UriFactory::factory('http://www.zend.com');
$isValid = $uri->isValid();  // TRUE

Zend Framework 2.x 之 Zend\Cache

Zend\Cache

Zend\Cache\Storage\Adapter

Overview

*Storage adapters are wrappers for real storage resources such as memory and the filesystem, using the well known adapter pattern.

*They come with tons of methods to read, write and modify stored items and to get information about stored items and the storage.

*All adapters implement the interface Zend\Cache\Storage\StorageInterface and most extend Zend\Cache\Storage\Adapter\AbstractAdapter, which comes with basic logic.

*Configuration is handled by either Zend\Cache\Storage\Adapter\AdapterOptions, or an adapter-specific options class if it exists. You may pass the options instance to the class at instantiation or via the setOptions() method, or alternately pass an associative array of options in either place (internally, these are then passed to an options class instance). Alternately, you can pass either the options instance or associative array to the Zend\Cache\StorageFactory::factory method.

Note
Many methods throw exceptions

Because many caching operations throw an exception on error, you need to catch them manually or you can use the plug-in Zend\Cache\Storage\Plugin\ExceptionHandler with throw_exceptions set to false to automatically catch them. You can also define an exception_callback to log exceptions.

Quick Start

*Caching adapters can either be created from the provided Zend\Cache\StorageFactory factory, or by simply instantiating one of the Zend\Cache\Storage\Adapter\* classes. 可通过工厂,或直接创建

*To make life easier, the Zend\Cache\StorageFactory comes with a factory method to create an adapter and create/add all requested plugins at once.

use Zend\Cache\StorageFactory;

// Via factory:
$cache = StorageFactory::factory(array(
    'adapter' => array(
        'name'    => 'apc',
        'options' => array('ttl' => 3600),
    ),
    'plugins' => array(
        'exception_handler' => array('throw_exceptions' => false),
    ),
));

// Alternately:
$cache  = StorageFactory::adapterFactory('apc', array('ttl' => 3600));
$plugin = StorageFactory::pluginFactory('exception_handler', array(
    'throw_exceptions' => false,
));
$cache->addPlugin($plugin);

// Or manually:
$cache  = new Zend\Cache\Storage\Adapter\Apc();
$cache->getOptions()->setTtl(3600);

$plugin = new Zend\Cache\Storage\Plugin\ExceptionHandler();
$plugin->getOptions()->setThrowExceptions(false);
$cache->addPlugin($plugin);

Basic Configuration Options

*Basic configuration is handled by either Zend\Cache\Storage\Adapter\AdapterOptions, or an adapter-specific options class if it exists. You may pass the options instance to the class at instantiation or via the setOptions() method, or alternately pass an associative array of options in either place (internally, these are then passed to an options class instance). Alternately, you can pass either the options instance or associative array to the Zend\Cache\StorageFactory::factory method.

*The following configuration options are defined by Zend\Cache\Storage\Adapter\AdapterOptions and are available for every supported adapter. Adapter-specific configuration options are described on adapter level below.

Option Data Type Default Value Description
ttl integer 0 Time to live
namespace string “zfcache” The “namespace” in which cache items will live
key_pattern null``|``string null Pattern against which to validate cache keys
readable boolean true Enable/Disable reading data from cache
writable boolean true Enable/Disable writing data to cache

实际,真要使用,就上面的简单代码。有大量的适配器,基本上,每个适配器都对应一个Option对象。可用的选项是不一样的,而公共的就是上面给出的这些。

….

Zend Framework 2.x 之 Zend\Barcode

Zend\Barcode是条形码的实现。这个工具实际很常用。注意,没有包含二维码。

Introduction to Zend\Barcode
Overview

Zend\Barcode\Barcode provides a generic way to generate barcodes. The Zend\Barcode component is divided into two subcomponents: barcode objects and renderers. Objects allow you to create barcodes independently of the renderer. Renderer allow you to draw barcodes based on the support required.(分两个对象,条形码 和 渲染器)

Barcode creation using Zend\Barcode\Barcode class

Using Zend\Barcode\Barcode::factory

Zend\Barcode\Barcode uses a factory method to create an instance of a renderer that extends Zend\Barcode\Renderer\AbstractRenderer. The factory method accepts five arguments.(工厂方法创建一个渲染器,有5个参数)

*The name of the barcode format (e.g., “code39”) or a Traversable object (required) 条码格式
*The name of the renderer (e.g., “image”) (required) 渲染器(就是制定要什么渲染器)
*Options to pass to the barcode object (an array or a Traversable object) (optional) 传递给条码对象的参数,可选
*Options to pass to the renderer object (an array or a Traversable object) (optional) 传递给渲染器的参数,可选
*Boolean to indicate whether or not to automatically render errors. If an exception occurs, the provided barcode object will be replaced with an Error representation (optional default TRUE) 出错时替代品

Getting a Renderer with Zend\Barcode\Barcode::factory()
Zend\Barcode\Barcode::factory() instantiates barcode classes and renderers and ties them together(把条码对象和渲染器对象搞在一起). In this first example, we will use the Code39 barcode type together with the Image renderer.

use Zend\Barcode\Barcode;

// Only the text to draw is required
$barcodeOptions = array('text' => 'ZEND-FRAMEWORK');

// No required options
$rendererOptions = array();
$renderer = Barcode::factory(
    'code39', 'image', $barcodeOptions, $rendererOptions
);

Drawing a barcode

When you draw the barcode, you retrieve the resource in which the barcode is drawn. To draw a barcode, you can call the draw() of the renderer, or simply use the proxy method provided by Zend\Barcode\Barcode.

Drawing a barcode with the renderer object

use Zend\Barcode\Barcode;

// Only the text to draw is required
$barcodeOptions = array('text' => 'ZEND-FRAMEWORK');

// No required options
$rendererOptions = array();

// Draw the barcode in a new image,
$imageResource = Barcode::factory(
    'code39', 'image', $barcodeOptions, $rendererOptions
)->draw();

Drawing a barcode with Zend\Barcode\Barcode::draw()

use Zend\Barcode\Barcode;

// Only the text to draw is required
$barcodeOptions = array('text' => 'ZEND-FRAMEWORK');

// No required options
$rendererOptions = array();

// Draw the barcode in a new image,
$imageResource = Barcode::draw(
    'code39', 'image', $barcodeOptions, $rendererOptions
);

Rendering a barcode

When you render a barcode, you draw the barcode, you send the headers and you send the resource (e.g. to a browser). To render a barcode, you can call the render() method of the renderer or simply use the proxy method provided by Zend\Barcode\Barcode.

Rendering a barcode with the renderer object

use Zend\Barcode\Barcode;

// Only the text to draw is required
$barcodeOptions = array('text' => 'ZEND-FRAMEWORK');

// No required options
$rendererOptions = array();

// Draw the barcode in a new image,
// send the headers and the image
Barcode::factory(
    'code39', 'image', $barcodeOptions, $rendererOptions
)->render();

Rendering a barcode with Zend\Barcode\Barcode::render()

use Zend\Barcode\Barcode;

// Only the text to draw is required
$barcodeOptions = array('text' => 'ZEND-FRAMEWORK');

// No required options
$rendererOptions = array();

// Draw the barcode in a new image,
// send the headers and the image
Barcode::render(
    'code39', 'image', $barcodeOptions, $rendererOptions
);

Barcode::render()比较直接,内部跟先factory后draw然后rend()应该是一致。使用上,基本就是这样了。一个条码对象,一个渲染器。还需要知道的是针对这两个对象的配置参数。

Zend\Barcode Objects

Barcode objects allow you to generate barcodes independently独立 of the rendering support(独立存在). After generation, you can retrieve the barcode as an array of drawing instructions that you can provide to a renderer.(大概是批量的意思吧)

Objects have a large number of options. Most of them are common to all objects. These options can be set in three ways:(参数传递的方法)
*As an array or a Traversable object passed to the constructor.
*As an array passed to the setOptions() method.
*Via individual setters for each configuration type.

Different ways to parameterize a barcode object

use Zend\Barcode\Object;

$options = array('text' => 'ZEND-FRAMEWORK', 'barHeight' => 40);

// Case 1: constructor
$barcode = new Object\Code39($options);

// Case 2: setOptions()
$barcode = new Object\Code39();
$barcode->setOptions($options);

// Case 3: individual setters
$barcode = new Object\Code39();
$barcode->setText('ZEND-FRAMEWORK')
        ->setBarHeight(40);

参数多,使用数组传递比较直观。

Common Options 公共配置
In the following list, the values have no units; we will use the term “unit.” For example, the default value of the “thin bar” is “1 unit”. The real units depend on the rendering support (see the renderers documentation for more information). Setters are each named by uppercasing the initial letter of the option and prefixing the name with “set” (e.g. “barHeight” becomes “setBarHeight”). All options have a corresponding getter prefixed with “get” (e.g. “getBarHeight”). Available options are:

Common Options
Option Data Type Default Value Description
barcodeNamespace String Zend\Barcode\Object Namespace of the barcode; for example, if you need to extend the embedding objects
barHeight Integer 50 Height of the bars
barThickWidth Integer 3 Width of the thick bar
barThinWidth Integer 1 Width of the thin bar
factor Integer, Float, String or Boolean 1 Factor by which to multiply bar widths and font sizes (barHeight, barThinWidth, barThickWidth and fontSize)
foreColor Integer 0x000000 (black) Color of the bar and the text. Could be provided as an integer or as a HTML value (e.g. “#333333”)
backgroundColor Integer or String 0xFFFFFF (white) Color of the background. Could be provided as an integer or as a HTML value (e.g. “#333333”)
orientation Integer, Float, String or Boolean 0 Orientation of the barcode
font String or Integer NULL Font path to a TTF font or a number between 1 and 5 if using image generation with GD (internal fonts)
fontSize Float 10 Size of the font (not applicable with numeric fonts)
withBorder Boolean FALSE Draw a border around the barcode and the quiet zones
withQuietZones Boolean TRUE Leave a quiet zone before and after the barcode
drawText Boolean TRUE Set if the text is displayed below the barcode
stretchText Boolean FALSE Specify if the text is stretched all along the barcode
withChecksum Boolean FALSE Indicate whether or not the checksum is automatically added to the barcode
withChecksumInText Boolean FALSE Indicate whether or not the checksum is displayed in the textual representation
text String NULL The text to represent as a barcode

Particular case of static setBarcodeFont() 条码的字体(比较特殊)
You can set a common font for all your objects by using the static method Zend\Barcode\Barcode::setBarcodeFont(). This value can be always be overridden for individual objects by using the setFont() method.
(调用这个方法,可以给所有条码对象设置相同字体)

use Zend\Barcode\Barcode;

// In your bootstrap:
Barcode::setBarcodeFont('my_font.ttf');

// Later in your code:
Barcode::render(
    'code39',
    'pdf',
    array('text' => 'ZEND-FRAMEWORK')
); // will use 'my_font.ttf'

// or:
Barcode::render(
    'code39',
    'image',
    array(
        'text' => 'ZEND-FRAMEWORK',
        'font' => 3
    )
); // will use the 3rd GD internal font

Common Additional Getters

<table border="1" id="zend-barcode-objects-common-getters-table" class="docutils"><caption>Common Getters</caption>
<colgroup><col width="21%"><col width="6%"><col width="73%"></colgroup><thead valign="bottom"><tr class="row-odd"><th class="head">Getter</th>
<th class="head">Data Type</th>
<th class="head">Description</th>
</tr></thead><tbody valign="top"><tr class="row-even"><td>getType()</td>
<td>String</td>
<td>Return the name of the barcode class without the namespace (e.g. Zend\Barcode\Object\Code39 returns simply “code39”)</td>
</tr><tr class="row-odd"><td>getRawText()</td>
<td>String</td>
<td>Return the original text provided to the object</td>
</tr><tr class="row-even"><td>getTextToDisplay()</td>
<td>String</td>
<td>Return the text to display, including, if activated, the checksum value</td>
</tr><tr class="row-odd"><td>getQuietZone()</td>
<td>Integer</td>
<td>Return the size of the space needed before and after the barcode without any drawing</td>
</tr><tr class="row-even"><td>getInstructions()</td>
<td>Array</td>
<td>Return drawing instructions as an array.</td>
</tr><tr class="row-odd"><td>getHeight($recalculate = false)</td>
<td>Integer</td>
<td>Return the height of the barcode calculated after possible rotation</td>
</tr><tr class="row-even"><td>getWidth($recalculate = false)</td>
<td>Integer</td>
<td>Return the width of the barcode calculated after possible rotation</td>
</tr><tr class="row-odd"><td>getOffsetTop($recalculate = false)</td>
<td>Integer</td>
<td>Return the position of the top of the barcode calculated after possible rotation</td>
</tr><tr class="row-even"><td>getOffsetLeft($recalculate = false)</td>
<td>Integer</td>
<td>Return the position of the left of the barcode calculated after possible rotation</td>
</tr></tbody></table>
不同类型的条形码对象,有自己特定参数。

Zend\Barcode Renderers

Renderers have some common options. These options can be set in three ways:
*As an array or a Traversable object passed to the constructor.
*As an array passed to the setOptions() method.
*As discrete values passed to individual setters.

Different ways to parameterize a renderer object

use Zend\Barcode\Renderer;

$options = array('topOffset' => 10);

// Case 1
$renderer = new Renderer\Pdf($options);

// Case 2
$renderer = new Renderer\Pdf();
$renderer->setOptions($options);

// Case 3
$renderer = new Renderer\Pdf();
$renderer->setTopOffset(10);

Common Options

In the following list, the values have no unit; we will use the term “unit.” For example, the default value of the “thin bar” is “1 unit.” The real units depend on the rendering support. The individual setters are obtained by uppercasing the initial letter of the option and prefixing the name with “set” (e.g. “barHeight” => “setBarHeight”). All options have a correspondent getter prefixed with “get” (e.g. “getBarHeight”). Available options are:

Common Options
Option Data Type Default Value Description
rendererNamespace String Zend\Barcode\Renderer Namespace of the renderer; for example, if you need to extend the renderers
horizontalPosition String “left” Can be “left”, “center” or “right”. Can be useful with PDF or if the setWidth() method is used with an image renderer.
verticalPosition String “top” Can be “top”, “middle” or “bottom”. Can be useful with PDF or if the setHeight() method is used with an image renderer.
leftOffset Integer 0 Top position of the barcode inside the renderer. If used, this value will override the “horizontalPosition” option.
topOffset Integer 0 Top position of the barcode inside the renderer. If used, this value will override the “verticalPosition” option.
automaticRenderError Boolean FALSE Whether or not to automatically render errors. If an exception occurs, the provided barcode object will be replaced with an Error representation. Note that some errors (or exceptions) can not be rendered.
moduleSize Float 1 Size of a rendering module in the support.
barcode Zend\Barcode\Object NULL The barcode object to render.

An additional getter exists: getType(). It returns the name of the renderer class without the namespace (e.g. Zend\Barcode\Renderer\Image returns “image”).

Zend\Barcode\Renderer\Image

The Image renderer will draw the instruction list of the barcode object in an image resource. The component requires the GD extension. The default width of a module is 1 pixel.

Available options are:

Zend\Barcode\Renderer\Image Options
Option Data Type Default Value Description
height Integer 0 Allow you to specify the height of the result image. If “0”, the height will be calculated by the barcode object.
width Integer 0 Allow you to specify the width of the result image. If “0”, the width will be calculated by the barcode object.
imageType String “png” Specify the image format. Can be “png”, “jpeg”, “jpg” or “gif”.

一般都会渲染为一个图片输出。

Zend\Db TableGateway操作

Zend\Db TableGateway操作个人觉得设计不够友好。在内部如果需要构建复杂查询,还是直接SQL来得实在。但是对于简单的CURL,它就非常的擅长。Zend\Db 表的封装比1.x时代还少,大部分同学真正要的是简便的CURD操作,复杂的SQL,还是需要自己组装。不过Select构建器,还是基本可以满足很多情况的。

$table = new Zend\Db\TableGateway\TableGateway('product',$adapter);


// 从TableGateway返回Sql,再从Sql返回一个Select对象
// 这个Select表名是已知的
// 相对于执行了$select->from($table)
$select = $table->getSql()->select();

/*
$select->where('id > 10')->order('id DESC')->limit(10);

// 然后把这个Select对象丢给TableGateway的selectWith()方法
// selectWith()只接收Select对象,select()方法接收Where对象等
$resultSet = $table->selectWith($select);
print_r($resultSet->toArray());
*/

/*
$select->where(function($where){
    $where->lessThan('id',10);
    $where->greaterThan('id',5);
    return $where;
})->order('id DESC')->limit(10);

$resultSet = $table->selectWith($select);
print_r($resultSet->toArray());
*/

/*
// 获取Select的Where对象,不能用where()方法,因为它是构建where条件
// 所以比较操蛋,绝对的不舒服
$where = $select->where;
$where->lessThan('id',10);
$where->greaterThan('id',5);
$select->order('id DESC')->limit(10);

$resultSet = $table->selectWith($select);
print_r($resultSet->toArray());
*/

// where可以是一个字符串,也可以是一个数组,字符串表示条件一个条件
// 第二参数表示是OR 或  AND,默认为AND,但是第一个where不会添加前置符
// 链式操作可以添加多个条件,是OR 是  AND由后面的where的第二参数决定 
// $sqlSring = $select->where('id > 30','OR')->where('id < 40','OR')->getSqlString();
// SELECT "product".* FROM "product" WHERE id > 30 OR id < 40

// where()方法还可接收一个数组,数组的单元是每个条件,这些条件是OR是AND还是由第二参数决定
// 可以看到,这样搞法很容易引起歧义
// $sqlSring = $select->where(array('id > 30','name like "%vfeelit%"'))->where(array('id > 40'),'OR')->getSqlString();
// SELECT "product".* FROM "product" WHERE id > 30 AND name like "%vfeelit%" OR id > 40

$select->where(function($where){
    $subWhereId = clone $where;
    $subWhereTitle = clone $where;
    
    $subWhereId->lessThan('id',10);
    //$subWhereId->or;
    $subWhereId->greaterThan('id',20);
    $where->addPredicate($subWhereId);
    
    $subWhereTitle->equalTo('title', 'a');
    //$subWhereTitle->or;
    $subWhereTitle->equalTo('title', 'b');
    $where->addPredicate($subWhereTitle);
    
    return $where;
});
// 这就是构建复杂查询的方法,有没有活生生把人气死的感觉?
// addPredicate就是添加一个断言,就是一个整体,用括号括起来
$sqlSring = $select->getSqlString();

注:翻回来看看,Zend Framework这个实现也有点落伍了。3.x不兼容2.x是分分钟有可能的事情。这个官方出品的东西,视乎总无法把握得住发展主流(MVC部分)。不过,它的一些组件个人还是觉得不错的,我也经常使用。DB封装这块,看起来到3.x时,估计也要重写了。

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);
	}
}

Zend Framework 2.x 之 Zend\Authentication

Introduction to Zend\Authentication

The Zend\Authentication component provides an API for authentication and includes concrete具体的 authentication adapters for common use case scenarios.

Zend\Authentication is concerned only with authentication身份验证 and not with authorization授权. Authentication is loosely defined as determining whether an entity actually is what it purports to be (i.e., identification), based on some set of credentials证书. Authorization, the process of deciding whether to allow an entity access to, or to perform operations upon, other entities is outside the scope of Zend\Authentication. For more information about authorization and access control with Zend Framework, please see the Zend\Permissions\Acl or Zend\Permissions\Rbac component. 授权相关参考Zend\Permissions\Acl

Note
There is no Zend\Authentication\Authentication class, instead the class Zend\Authentication\AuthenticationService is provided. This class uses underlying authentication adapters and persistent持续性 storage backends.

认证实际设计两个内容,一个是认证数据在什么地方(取出认证),第二个是认证后如何持久性(一般在会话中标记)。数据来源就是这里的适配器。

Adapters
Zend\Authentication adapters are used to authenticate against a particular type of authentication service, such as LDAP, RDBMS, or file-based storage. Different adapters are likely to have vastly different options and behaviors, but some basic things are common among authentication adapters. For example, accepting authentication credentials (including a purported identity), performing queries against the authentication service, and returning results are common to Zend\Authentication adapters. 不同适配器有不同的选项

Each Zend\Authentication adapter class implements Zend\Authentication\Adapter\AdapterInterface. This interface defines one method, authenticate()仅一个方法, that an adapter class must implement for performing an authentication query. Each adapter class must be prepared prior to calling authenticate(). Such adapter preparation includes setting up credentials (e.g., username and password) and defining values for adapter-specific configuration options, such as database connection settings for a database table adapter.

The following is an example authentication adapter that requires a username and password to be set for authentication. Other details, such as how the authentication service is queried, have been omitted for brevity:

use Zend\Authentication\Adapter\AdapterInterface;

class My\Auth\Adapter implements AdapterInterface
{
    /**
     * Sets username and password for authentication
     *
     * @return void
     */
    public function __construct($username, $password)
    {
        // ...
    }

    /**
     * Performs an authentication attempt
     *
     * @return \Zend\Authentication\Result
     * @throws \Zend\Authentication\Adapter\Exception\ExceptionInterface
     *               If authentication cannot be performed
     */
    public function authenticate()
    {
        // ...
    }
}

As indicated in its docblock, authenticate() must return an instance of Zend\Authentication\Result (or of a class derived from Zend\Authentication\Result). If for some reason performing an authentication query is impossible, authenticate() should throw an exception that derives from Zend\Authentication\Adapter\Exception\ExceptionInterface.

Results
Zend\Authentication adapters return an instance of Zend\Authentication\Result with authenticate() in order to represent the results of an authentication attempt. Adapters populate the Zend\Authentication\Result object upon construction, so that the following four methods provide a basic set of user-facing operations that are common to the results of Zend\Authentication adapters:
*isValid()- returns TRUE if and only if the result represents a successful authentication attempt
*getCode()- returns a Zend\Authentication\Result constant identifier for determining the type of authentication failure or whether success has occurred. This may be used in situations where the developer wishes to distinguish among several authentication result types. This allows developers to maintain detailed authentication result statistics, for example. Another use of this feature is to provide specific, customized messages to users for usability reasons, though developers are encouraged to consider the risks of providing such detailed reasons to users, instead of a general authentication failure message. For more information, see the notes below.
*getIdentity()- returns the identity of the authentication attempt
*getMessages()- returns an array of messages regarding a failed authentication attempt

A developer may wish to branch based on the type of authentication result in order to perform more specific operations. Some operations developers might find useful are locking accounts after too many unsuccessful password attempts, flagging an IP address after too many nonexistent identities are attempted, and providing specific, customized authentication result messages to the user. The following result codes are available:

use Zend\Authentication\Result;

Result::SUCCESS
Result::FAILURE
Result::FAILURE_IDENTITY_NOT_FOUND
Result::FAILURE_IDENTITY_AMBIGUOUS
Result::FAILURE_CREDENTIAL_INVALID
Result::FAILURE_UNCATEGORIZED

The following example illustrates how a developer may branch on the result code:

// inside of AuthController / loginAction
$result = $this->auth->authenticate($adapter);

switch ($result->getCode()) {

    case Result::FAILURE_IDENTITY_NOT_FOUND:
        /** do stuff for nonexistent identity **/
        break;

    case Result::FAILURE_CREDENTIAL_INVALID:
        /** do stuff for invalid credential **/
        break;

    case Result::SUCCESS:
        /** do stuff for successful authentication **/
        break;

    default:
        /** do stuff for other failure **/
        break;
}

Identity Persistence 身份持续性
Authenticating a request that includes authentication credentials is useful per se, but it is also important to support maintaining the authenticated identity without having to present the authentication credentials with each request.

HTTP is a stateless protocol, however, and techniques such as cookies and sessions have been developed in order to facilitate maintaining state across multiple requests in server-side web applications.

Default Persistence in the PHP Session 默认
By default, Zend\Authentication provides persistent storage of the identity from a successful authentication attempt using the PHP session. Upon a successful authentication attempt, Zend\Authentication\AuthenticationService::authenticate() stores the identity from the authentication result into persistent storage. Unless specified otherwise, Zend\Authentication\AuthenticationService uses a storage class named Zend\Authentication\Storage\Session, which, in turn, uses Zend\Session. A custom class may instead be used by providing an object that implements Zend\Authentication\Storage\StorageInterface to Zend\Authentication\AuthenticationService::setStorage(). 可以改变存储的地方

Note
If automatic persistent storage of the identity is not appropriate for a particular use case, then developers may forget using the Zend\Authentication\AuthenticationService class altogether, instead using an adapter class directly.自动存储不适合,可以直接使用适配器。

Modifying the Session Namespace

Zend\Authentication\Storage\Session uses a session namespace of ‘Zend_Auth‘. This namespace may be overridden by passing a different value to the constructor of Zend\Authentication\Storage\Session, and this value is internally passed along to the constructor of Zend\Session\Container. This should occur before authentication is attempted, since Zend\Authentication\AuthenticationService::authenticate() performs the automatic storage of the identity.

use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Storage\Session as SessionStorage;

$auth = new AuthenticationService();

// Use 'someNamespace' instead of 'Zend_Auth'
$auth->setStorage(new SessionStorage('someNamespace'));

/**
 * @todo Set up the auth adapter, $authAdapter
 */

// Authenticate, saving the result, and persisting the identity on
// success
$result = $auth->authenticate($authAdapter);

Chain Storage
Implementing Customized Storage
Using a Custom Storage Class

Usage
There are two provided ways to use Zend\Authentication adapters:
*indirectly, through Zend\Authentication\AuthenticationService::authenticate() 间接使用
*directly, through the adapter’s authenticate() method 直接使用

The following example illustrates how to use a Zend\Authentication adapter indirectly, through the use of the Zend\Authentication\AuthenticationService class:登录实现

use Zend\Authentication\AuthenticationService;

// instantiate the authentication service
$auth = new AuthenticationService();

// Set up the authentication adapter
$authAdapter = new My\Auth\Adapter($username, $password);

// Attempt authentication, saving the result
$result = $auth->authenticate($authAdapter);

if (!$result->isValid()) {
    // Authentication failed; print the reasons why
    foreach ($result->getMessages() as $message) {
        echo "$message\n";
    }
} else {
    // Authentication succeeded; the identity ($username) is stored
    // in the session
    // $result->getIdentity() === $auth->getIdentity()
    // $result->getIdentity() === $username
}

Once authentication has been attempted in a request, as in the above example, it is a simple matter to check whether a successfully authenticated identity exists:(判断登录)

use Zend\Authentication\AuthenticationService;

$auth = new AuthenticationService();

/**
 * @todo Set up the auth adapter, $authAdapter
 */

if ($auth->hasIdentity()) {
    // Identity exists; get it
    $identity = $auth->getIdentity();
}

To remove an identity from persistent storage, simply use the clearIdentity() method. This typically would be used for implementing an application “logout” operation:(退出登录)

$auth->clearIdentity();

When the automatic use of persistent storage is inappropriate for a particular use case, a developer may simply bypass the use of the Zend\Authentication\AuthenticationService class, using an adapter class directly. Direct use of an adapter class involves configuring and preparing an adapter object and then calling its authenticate() method. Adapter-specific details are discussed in the documentation for each adapter. The following example directly utilizes My\Auth\Adapter:(直接使用适配器)

// Set up the authentication adapter
$authAdapter = new My\Auth\Adapter($username, $password);

// Attempt authentication, saving the result
$result = $authAdapter->authenticate();

if (!$result->isValid()) {
    // Authentication failed; print the reasons why
    foreach ($result->getMessages() as $message) {
        echo "$message\n";
    }
} else {
    // Authentication succeeded
    // $result->getIdentity() === $username
}

通过Zend\Authentication\AuthenticationService可以自动存储持久信息,如果这个情况不合适,可以直接使用具体的适配体,但是要自己处理持久性。

Database Table Authentication

Note
Zend\Authentication\Adapter\DbTable has been deprecated, as its responsibilities have been splitted off into根据职责分裂为 Zend\Authentication\Adapter\DbTable\CallbackCheck and Zend\Authentication\Adapter\DbTable\CredentialTreatmentAdapter. Use Zend\Authentication\Adapter\DbTable\CredentialTreatmentAdapter instead of Zend\Authentication\Adapter\DbTable.

Introduction
Zend\Authentication\Adapter\DbTable provides the ability to authenticate against credentials stored in a database table. Because Zend\Authentication\Adapter\DbTable requires an instance of Zend\Db\Adapter\Adapter to be passed to its constructor, each instance is bound to必然会 a particular database connection. Other configuration options may be set through the constructor and through instance methods, one for each option.

The available configuration options include:(可用配置)
*tableName: This is the name of the database table that contains the authentication credentials, and against which the database authentication query is performed.
*identityColumn: This is the name of the database table column used to represent the identity. The identity column must contain unique values, such as a username or e-mail address.
*credentialColumn: This is the name of the database table column used to represent the credential. Under a simple identity and password authentication scheme, the credential value corresponds to the password. See also the credentialTreatment option.
*credentialTreatment: In many cases, passwords and other sensitive data are encrypted, hashed, encoded, obscured, salted or otherwise treated through some function or algorithm. By specifying a parameterized treatment string with this method, such as ‘MD5(?)‘ or ‘PASSWORD(?)‘, a developer may apply such arbitrary SQL upon input credential data. Since these functions are specific to the underlying RDBMS, check the database manual for the availability of such functions for your database system.(Treatment,就是对证书(密码)如何处理)

Basic Usage
As explained in the introduction, the Zend\Authentication\Adapter\DbTable constructor requires an instance of Zend\Db\Adapter\Adapter that serves as the database connection to which the authentication adapter instance is bound. First, the database connection should be created.

The following code creates an adapter for an in-memory database, creates a simple table schema, and inserts a row against which we can perform an authentication query later. This example requires the PDO SQLite extension to be available:

use Zend\Db\Adapter\Adapter as DbAdapter;

// Create a SQLite database connection
$dbAdapter = new DbAdapter(array(
                'driver' => 'Pdo_Sqlite',
                'database' => 'path/to/sqlite.db'
            ));

// Build a simple table creation query
$sqlCreate = 'CREATE TABLE [users] ('
           . '[id] INTEGER  NOT NULL PRIMARY KEY, '
           . '[username] VARCHAR(50) UNIQUE NOT NULL, '
           . '[password] VARCHAR(32) NULL, '
           . '[real_name] VARCHAR(150) NULL)';

// Create the authentication credentials table
$dbAdapter->query($sqlCreate);

// Build a query to insert a row for which authentication may succeed
$sqlInsert = "INSERT INTO users (username, password, real_name) "
           . "VALUES ('my_username', 'my_password', 'My Real Name')";

// Insert the data
$dbAdapter->query($sqlInsert);

马格尼….
With the database connection and table data available, an instance of Zend\Authentication\Adapter\DbTable may be created. Configuration option values may be passed to the constructor or deferred as parameters to setter methods after instantiation:(配置参数传入构造函数,或实例化后调用set方法,Zend\Authentication\Adapter\DbTable当期只是简单继承DbTable\CredentialTreatmentAdapter)

use Zend\Authentication\Adapter\DbTable as AuthAdapter;

// Configure the instance with constructor parameters...
$authAdapter = new AuthAdapter($dbAdapter,
                               'users',	   // 表名
                               'username', // 用户名列
                               'password'  // 密码列
                               );

// ...or configure the instance with setter methods
$authAdapter = new AuthAdapter($dbAdapter);

$authAdapter
    ->setTableName('users')
    ->setIdentityColumn('username')
    ->setCredentialColumn('password')
;

At this point, the authentication adapter instance is ready to accept authentication queries. In order to formulate规划 an authentication query, the input credential values are passed to the adapter prior to calling the authenticate() method:

// Set the input credential values (e.g., from a login form)
$authAdapter
    ->setIdentity('my_username')
    ->setCredential('my_password')
;

// Perform the authentication query, saving the result

就是先传递用户名和密码过去,然后认证。(大家应该都明白了)

In addition to the availability of the getIdentity() method upon the authentication result object, Zend\Authentication\Adapter\DbTable also supports retrieving the table row upon authentication success:

// Print the identity
echo $result->getIdentity() . "\n\n";

// Print the result row
print_r($authAdapter->getResultRowObject());

/* Output:
my_username

Array
(
    [id] => 1
    [username] => my_username
    [password] => my_password
    [real_name] => My Real Name
)
*/

认证之后,可以取回匹配的行。

Since the table row contains the credential value, it is important to secure the values against unintended意外 access.

When retrieving the result object, we can either specify what columns to return, or what columns to omit:(可以指定省略哪些列 返回哪些)

$columnsToReturn = array(
    'id', 'username', 'real_name'
);
print_r($authAdapter->getResultRowObject($columnsToReturn));

/* Output:

Array
(
   [id] => 1
   [username] => my_username
   [real_name] => My Real Name
)
*/

$columnsToOmit = array('password');
print_r($authAdapter->getResultRowObject(null, $columnsToOmit);

/* Output:

Array
(
   [id] => 1
   [username] => my_username
   [real_name] => My Real Name
)

玛尼,有多少用,这个。

Advanced Usage: Persisting a DbTable Result Object 保存结果

By default, Zend\Authentication\Adapter\DbTable returns the identity supplied back to the auth object upon successful authentication. Another use case scenario使用场景, where developers want to store to the persistent storage mechanism of Zend\Authentication an identity object containing other useful information, is solved by using the getResultRowObject() method to return a stdClass object. The following code snippet illustrates its use:

// authenticate with Zend\Authentication\Adapter\DbTable
$result = $this->_auth->authenticate($adapter);

if ($result->isValid()) {
    // store the identity as an object where only the username and
    // real_name have been returned
    $storage = $this->_auth->getStorage();
    $storage->write($adapter->getResultRowObject(array(
        'username',
        'real_name',
    )));

    // store the identity as an object where the password column has
    // been omitted
    $storage->write($adapter->getResultRowObject(
        null,
        'password'
    ));

    /* ... */

} else {

    /* ... */

}

Advanced Usage By Example

While the primary purpose of the Zend\Authentication component (and consequently Zend\Authentication\Adapter\DbTable) is primarily authentication and not authorization, there are a few instances and problems that toe the line between which domain they fit within. Depending on how you’ve decided to explain your problem, it sometimes makes sense to solve what could look like an authorization problem within the authentication adapter.

With that disclaimer放弃 out of the way, Zend\Authentication\Adapter\DbTable has some built in mechanisms that can be leveraged for additional checks at authentication time to solve some common user problems.

use Zend\Authentication\Adapter\DbTable as AuthAdapter;

// The status field value of an account is not equal to "compromised"
$adapter = new AuthAdapter($db,
                           'users',
                           'username',
                           'password',
                           'MD5(?) AND status != "compromised"'
                           );

// The active field value of an account is equal to "TRUE"
$adapter = new AuthAdapter($db,
                           'users',
                           'username',
                           'password',
                           'MD5(?) AND active = "TRUE"'
                           );

Another scenario can be the implementation of a salting mechanism. Salting is a term referring to a technique which can highly improve your application’s security. It’s based on the idea that concatenating a random string to every password makes it impossible to accomplish a successful brute force attack on the database using pre-computed hash values from a dictionary.

Therefore, we need to modify our table to store our salt string:

$sqlAlter = "ALTER TABLE [users] "
          . "ADD COLUMN [password_salt] "
          . "AFTER [password]";

Here’s a simple way to generate a salt string for every user at registration:

$dynamicSalt = '';
for ($i = 0; $i < 50; $i++) {
    $dynamicSalt .= chr(rand(33, 126));
}

And now let’s build the adapter:

$adapter = new AuthAdapter($db,
                           'users',
                           'username',
                           'password',
                           "MD5(CONCAT('staticSalt', ?, password_salt))"
                          );

Digest Authentication
HTTP Authentication Adapter
LDAP Authentication
Authentication Validator

梳理一个认证实现过程
1 需要一个适配器,这个适配器提供了认证数据来源(比如Dbtable)
2 实例化一个适配器,然后在调用认证之前传递实际用户名和密码过去
3 调用认证方法返回认证结果
4 处理持久化(如果通过AuthenticationService间接使用的话,这个步骤可以自动完成)

// 需要传递$dbAdapter
$authAdapter = new Zend\Authentication\Adapter\DbTable($dbAdapter)
// 配置表,用户和密码字段
$authAdapter
    ->setTableName('users')
    ->setIdentityColumn('username')
    ->setCredentialColumn('password')
;
// 传递实际的用户名和密码过去
$authAdapter
    ->setIdentity('my_username')
    ->setCredential('my_password')
;
// 开始验证
$result = $authAdapter->authentication()

// 如果成功可以取到匹配的行数据,注意它是返回到适配器中,不是在$result中
print_r($authAdapter->getResultRowObject());

// 调用authentication()后返回的是一个Zend\Authentication\Result对象
// 有三个关键字段$code认证代码(1代表成功) $identity(用户名) $messages信息
// 对于结果的处理,可以分情况处理,比如多次登录失败就如何处理等 成功写入会话等
switch ($result->getCode()) {
    case Result::FAILURE_IDENTITY_NOT_FOUND:
        /** do stuff for nonexistent identity **/
        break;

    case Result::FAILURE_CREDENTIAL_INVALID:
        /** do stuff for invalid credential **/
        break;

    case Result::SUCCESS:
        /** do stuff for successful authentication **/
        break;

    default:
        /** do stuff for other failure **/
        break;		
}
// 以下展示如何写入会话 注意这里的getStorage()是Zend\Authentication\AuthenticationService的方法
if ($result->isValid()) {
    // store the identity as an object where only the username and
    // real_name have been returned
    $storage = $this->_auth->getStorage();
    $storage->write($adapter->getResultRowObject(array(
        'username',
        'real_name',
    )));

    // store the identity as an object where the password column has
    // been omitted
    $storage->write($adapter->getResultRowObject(
        null,
        'password'
    ));

    /* ... */

} else {

    /* ... */

}

使用Zend\Authentication\AuthenticationService可以自动管理持久化问题,否则这个就需要自行处理。(信息保存到一个叫Zend_Auth的容器中,信息对应的索引是storage,当然,这个容器名称可以改)

大体看了下文档和源代码,这个组件可以说百分百过度设计了。实在太重了。还好,一般只是在登录时使用一下而已。

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

Zend\Paginator 使用实例

分页在应用开发中是必不可少的,Zend\Paginator提供的分页组件,总体感觉还算不错。数据集合分页本身的逻辑并不复杂,三个输入参数:页大小,页号,总记录数,就可以计算当前页数据已经分页导航(前一页后一页等),但是由于使用太频繁了,把这个抽象出来总是由价值的。

以下是测试例子:

<?php
$rootPath = dirname(__DIR__);
$library = $rootPath."/library";

chdir($rootPath);
if(file_exists('vendor/autoload.php')){
    $loader = include '/vendor/autoload.php';
}else{
    exit("Autoload Failed. ");
}
$loader->setPsr4("Zend\\",$library.'/zf2_psr4');

$dbAdapter = new Zend\Db\Adapter\Adapter(array(
    'hostname' => 'localhost',
    'database' => 'test',
    'username' => 'root',
    'password' => '',

    //     'driver' => 'mysqli',
    //     'charset' =>'utf8'

    'driver' => 'pdo_mysql',
    'driver_options' => array(
        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
    ),
));

/// 0 初始化
$query = $_GET;
unset($query['page']);

// 0 - 1 数组
// $array = array();
// for($i=0;$i<666;$i++) {
//     $array[] = array("first_name"=>"ifeeline".$i,"last_name"=>"vvvvvvv".$i);
// }
// $arrayAdapter = new Zend\Paginator\Adapter\ArrayAdapter($array);
// $paginator = new Zend\Paginator\Paginator($arrayAdapter);


// 0 - 2 DbSelect
// $select = new Zend\Db\Sql\Select();
// $select->from("datatables_demo")->columns(array("id","first_name","last_name","email"))
// ->where("age > 20");

// $dbSelectAdapter = new Zend\Paginator\Adapter\DbSelect($select, $dbAdapter);
// $paginator = new Zend\Paginator\Paginator($dbSelectAdapter);

// 0 - 3 TableGateway
$tableGateway = new Zend\Db\TableGateway\TableGateway("datatables_demo", $dbAdapter);
$tableGatewayAdapter = new Zend\Paginator\Adapter\DbTableGateway($tableGateway);
$paginator = new Zend\Paginator\Paginator($tableGatewayAdapter);


//$paginator->setDefaultScrollingStyle("All");
//$paginator->setDefaultScrollingStyle("Jumping");
$paginator->setDefaultScrollingStyle("Elastic");
//$paginator->setDefaultScrollingStyle("Sliding"); 默认

/// 1 配置
// 设置显示的页码范围
$paginator->setPageRange(10);

/// 2 修正当前页码
// 总页数
$totalPage = count($paginator);

// 获取当前页码
$page = !empty($_GET['page'])?(int)$_GET['page']:1;
if($page < 1) {
    $page = 1;
} else if($page > $totalPage) {
    $page = $totalPage;
}
$query['page'] = $page;


// 设置当前页码
$paginator->setCurrentPageNumber($page);

/// 3 取得页数据
$datas = $paginator->getCurrentItems();

/// 4 获取页码控制器
$pageCtrl = $paginator->getPages();

/// 5 页码控制器输出
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>

<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($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 ($pageCtrl->pageCount): ?>
<div class="paginationControl">
<!-- Previous page link -->
<?php if (isset($pageCtrl->previous)): 
    $query['page'] = $pageCtrl->previous;
?>
  <a href="<?php echo "paginator.php?".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 "paginator.php?".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 "paginator.php?".http_build_query($query);?>">
    下一页
  </a>
<?php else: ?>
  <span class="disabled">下一页</span>
<?php endif; ?>
</div>
<?php endif;?>

<br /><br />

<?php 
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="paginator.php?<?php echo 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="paginator.php?<?php echo 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 "paginator.php?".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="paginator.php?<?php echo 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="paginator.php?<?php echo http_build_query($query);?>">
                    末页
      </a>
    <?php else: ?>
      <span class="disabled">末页</span>
    <?php endif; ?>
</div>
<?php endif; ?>

<br /><br />

<?php if ($pageCtrl->pageCount): ?>
<select id="paginationControl" size="1">
<?php foreach ($pageCtrl->pagesInRange as $pageIndex): ?>
  <?php $selected = ($pageIndex == $pageCtrl->current) ? ' selected="selected"' : ''; ?>
  <option value=""<?php echo $selected ?>>
    <?php echo $pageIndex; ?>
  </option>
<?php endforeach; ?>
</select>
<?php endif; ?>
</body>
</html>

paginator

Zend Framework 2.x 之 Zend\Mail

Introduction to Zend\Mail

Getting started
Zend\Mail provides generalized普遍 functionality to compose组成 and send both text and MIME-compliant multipart email messages. Mail can be sent with Zend\Mail via the Mail\Transport\Sendmail, Mail\Transport\Smtp or the Mail\Transport\File transport. Of course, you can also implement your own transport by implementing the Mail\Transport\TransportInterface.(邮件传送出去使用Sendmail或Smtp很容易理解,但是File就有点困惑)

Simple email with Zend\Mail
A simple email consists of one or more recipients(一个或多个接收人), a subject, a body and a sender. To send such a mail using Zend\Mail\Transport\Sendmail, do the following:

use Zend\Mail;

$mail = new Mail\Message();
$mail->setBody('This is the text of the email.');
$mail->setFrom('Freeaqingme@example.org', 'Sender\'s name');
$mail->addTo('Matthew@example.com', 'Name of recipient');
$mail->setSubject('TestSubject');

$transport = new Mail\Transport\Sendmail();
$transport->send($mail);

总体上,就是构建一个消息,然后使用一个Transport发送出去。

Note
Minimum definitions
In order to send an email using Zend\Mail you have to specify at least one recipient as well as a message body. Please note that each Transport may require additional parameters to be set.

For most mail attributes there are “get” methods to read the information stored in the message object. for further details, please refer to the API documentation.

You also can use most methods of the Mail\Message object with a convenient fluent interface.(说可以链式构建消息)

use Zend\Mail;

$mail = new Mail\Message();
$mail->setBody('This is the text of the mail.')
     ->setFrom('somebody@example.com', 'Some Sender')
     ->addTo('somebody_else@example.com', 'Some Recipient')
     ->setSubject('TestSubject');

Configuring the default sendmail transport

The most simple to use transport is the Mail\Transport\Sendmail transport class. It is essentially a wrapper to the PHP mail() function. If you wish to pass additional parameters to the mail() function, simply create a new transport instance and pass your parameters to the constructor.

Passing additional parameters
This example shows how to change the Return-Path of the mail() function.

use Zend\Mail;

$mail = new Mail\Message();
$mail->setBody('This is the text of the email.');
$mail->setFrom('Freeaqingme@example.org', 'Dolf');
$mail->addTo('matthew@example.com', 'Matthew');
$mail->setSubject('TestSubject');

$transport = new Mail\Transport\Sendmail('-freturn_to_me@example.com');
$transport->send($mail);

Note
Safe mode restrictions
Supplying additional parameters to the transport will cause the mail() function to fail if PHP is running in safe mode.(启动安全模式可能使得发邮件失败)

Note
Choosing your transport wisely聪明地
Although the sendmail transport is the transport that requires only minimal configuration, it may not be suitable for your production environment. This is because emails sent using the sendmail transport will be more often delivered to SPAM-boxes. This can partly be remedied by using the SMTP Transport combined with an SMTP server that has an overall good reputation. Additionally, techniques such as SPF and DKIM may be employed to ensure even more email messages are delivered as should.(使用Sendmail发送多层垃圾,使用SMTP)

Zend\Mail\Message

Overview
The Message class encapsulates a single email message as described in RFCs 822 and 2822. It acts basically as a value object for setting mail headers and content.

If desired, multi-part email messages may also be created. This is as trivial平常的 as creating the message body using the Zend\Mime component, assigning it to the mail message body.

The Message class is simply a value object. It is not capable能胜任的 of sending or storing itself; for those purposes, you will need to use, respectively, a Transport adapter or Storage adapter.

Quick Start
Creating a Message is simple: simply instantiate例示 it.

use Zend\Mail\Message;

$message = new Message();

Once you have your Message instance, you can start adding content or headers. Let’s set who the mail is from, who it’s addressed to, a subject, and some content:

$message->addFrom("matthew@zend.com", "Matthew Weier O'Phinney")
        ->addTo("foobar@example.com")
        ->setSubject("Sending an email from Zend\Mail!");
$message->setBody("This is the message body.");

You can also add recipients to carbon-copy (“Cc:”) or blind carbon-copy (“Bcc:”). 抄送 和 密送

$message->addCc("ralph.schindler@zend.com")
        ->addBcc("enrico.z@zend.com");

If you want to specify an alternate address to which replies may be sent, that can be done, too.

$message->addReplyTo("matthew@weierophinney.net", "Matthew");

Interestingly, RFC822 allows for multiple “From:” addresses. When you do this, the first one will be used as the sender, unless you specify a “Sender:” header. The Message class allows for this.

/*
 * Mail headers created:
 * From: Ralph Schindler <ralph.schindler@zend.com>, Enrico Zimuel <enrico.z@zend.com>
 * Sender: Matthew Weier O'Phinney <matthew@zend.com></matthew>
 */
$message->addFrom("ralph.schindler@zend.com", "Ralph Schindler")
        ->addFrom("enrico.z@zend.com", "Enrico Zimuel")
        ->setSender("matthew@zend.com", "Matthew Weier O'Phinney");

可以有多个from,默认第一个为sender,可用setSender明确设置sender。

By default, the Message class assumes ASCII encoding for your email. If you wish to use another encoding, you can do so; setting this will ensure all headers and body content are properly encoded using quoted-printable encoding. 默认ASCII编码

$message->setEncoding("UTF-8");

If you wish to set other headers, you can do that as well. 添加邮件头信息

/*
 * Mail headers created:
 * X-API-Key: FOO-BAR-BAZ-BAT
 */
$message->getHeaders()->addHeaderLine('X-API-Key', 'FOO-BAR-BAZ-BAT');

Sometimes you may want to provide HTML content, or multi-part content. To do that, you’ll first create a MIME message object, and then set it as the body of your mail message object. When you do so, the Message class will automatically set a “MIME-Version” header, as well as an appropriate “Content-Type” header.

In addition you can check how to add attachment to your message E-mail Attachments.

use Zend\Mail\Message;
use Zend\Mime\Message as MimeMessage;
use Zend\Mime\Part as MimePart;

$text = new MimePart($textContent);
$text->type = "text/plain";

$html = new MimePart($htmlMarkup);
$html->type = "text/html";

$image = new MimePart(fopen($pathToImage, 'r'));
$image->type = "image/jpeg";

$body = new MimeMessage();
$body->setParts(array($text, $html, $image));

$message = new Message();
$message->setBody($body);

If you want a string representation of your email, you can get that:

echo $message->toString();

Finally, you can fully introspect内现 the message – including getting all addresses of recipients and senders, all headers, and the message body.

// Headers
// Note: this will also grab all headers for which accessors/mutators exist in
// the Message object itself.
foreach ($message->getHeaders() as $header) {
    echo $header->toString();
    // or grab values: $header->getFieldName(), $header->getFieldValue()
}

// The logic below also works for the methods cc(), bcc(), to(), and replyTo()
foreach ($message->getFrom() as $address) {
    printf("%s: %s\n", $address->getEmail(), $address->getName());
}

// Sender
$address = $message->getSender();
if(!is_null($address)) {
   printf("%s: %s\n", $address->getEmail(), $address->getName());
}

// Subject
echo "Subject: ", $message->getSubject(), "\n";

// Encoding
echo "Encoding: ", $message->getEncoding(), "\n";

// Message body:
echo $message->getBody();     // raw body, or MIME object
echo $message->getBodyText(); // body as it will be sent

Once your message is shaped to your liking, pass it to a mail transport in order to send it!

$transport->send($message);

消息准备好,传递给Transport的send方法。

Zend\Mail\Transport
Overview
Transports take care of the actual delivery of mail. Typically, you only need to worry about two possibilities: using PHP’s native mail() functionality, which uses system resources to deliver mail, or using the SMTP protocol for delivering mail via a remote server. Zend Framework also includes a “File” transport, which creates a mail file for each message sent; these can later be introspected as logs or consumed for the purposes of sending via an alternate transport mechanism later.(提供了FileTransport,用来日志等)

The Zend\Mail\Transport interface defines exactly one method, send(). This method accepts a Zend\Mail\Message instance, which it then introspects and serializes in order to send. 只有一个send()方法

Quick Start
Using a mail transport is typically as simple as instantiating it, optionally configuring it, and then passing a message to it. 实例化 配置 传递一个消息给它

Sendmail Transport Usage
SMTP Transport Usage

use Zend\Mail\Message;
use Zend\Mail\Transport\Smtp as SmtpTransport;
use Zend\Mail\Transport\SmtpOptions;

$message = new Message();
$message->addTo('matthew@zend.com')
        ->addFrom('ralph.schindler@zend.com')
        ->setSubject('Greetings and Salutations!')
        ->setBody("Sorry, I'm going to be late today!");

// Setup SMTP transport using LOGIN authentication
$transport = new SmtpTransport();
$options   = new SmtpOptions(array(
    'name'              => 'localhost.localdomain',
    'host'              => '127.0.0.1',
    'connection_class'  => 'login',
    'connection_config' => array(
        'username' => 'user',
        'password' => 'pass',
    ),
));
$transport->setOptions($options);
$transport->send($message);

File Transport Usage

use Zend\Mail\Message;
use Zend\Mail\Transport\File as FileTransport;
use Zend\Mail\Transport\FileOptions;

$message = new Message();
$message->addTo('matthew@zend.com')
        ->addFrom('ralph.schindler@zend.com')
        ->setSubject('Greetings and Salutations!')
        ->setBody("Sorry, I'm going to be late today!");

// Setup File transport
$transport = new FileTransport();
$options   = new FileOptions(array(
    'path'              => 'data/mail/',
    'callback'  => function (FileTransport $transport) {
        return 'Message_' . microtime(true) . '_' . mt_rand() . '.txt';
    },
));
$transport->setOptions($options);
$transport->send($message);

InMemory Transport Usage

use Zend\Mail\Message;
use Zend\Mail\Transport\InMemory as InMemoryTransport;

$message = new Message();
$message->addTo('matthew@zend.com')
        ->addFrom('ralph.schindler@zend.com')
        ->setSubject('Greetings and Salutations!')
        ->setBody("Sorry, I'm going to be late today!");

// Setup InMemory transport
$transport = new InMemoryTransport();
$transport->send($message);

// Verify the message:
$received = $transport->getLastMessage();

The InMemory transport is primarily of interest when in development or when testing.(InMemoryTransport就是一个垃圾桶。)

Zend\Mail\Transport\SmtpOptions
Zend\Mail\Transport\FileOptions

Zend Framework 2.x 之 Zend\Log

Zend\Log

Overview
Zend\Log\Logger is a component for general purpose logging. It supports multiple log backends, formatting messages sent to the log, and filtering messages from being logged. These functions are divided into the following objects(功能分层如下对象):
*A Logger (instance of Zend\Log\Logger) is the object that your application uses the most. You can have as many Logger objects as you like; they do not interact不会交互. A Logger object must contain at least one Writer, and can optionally contain one or more Filters.(Logger必须至少有一个writer和可选的包含一个或多个过滤器)
*A Writer (inherits from Zend\Log\Writer\AbstractWriter) is responsible for saving data to storage.
*A Filter (implements Zend\Log\Filter\FilterInterface) blocks log data from being saved. A filter is applied to an individual writer(过滤器应用到writer). Filters can be chained.
*A Formatter (implements Zend\Log\Formatter\FormatterInterface) can format the log data before it is written by a Writer.(写前格式化) Each Writer has exactly one Formatter(每个Writer只有一个Formatter).

Creating a Log
To get started logging, instantiate a Writer and then pass it to a Logger instance:(初始化一个writer,传递给Logger)

$logger = new Zend\Log\Logger;
$writer = new Zend\Log\Writer\Stream('php://output');

$logger->addWriter($writer);

It is important to note that the Logger must have at least one Writer. You can add any number of Writers using the Log’s addWriter() method.(Writer至少有一个)

You can also add a priority优先级 to each writer. The priority is specified as number and passed as second argument in the addWriter() method(优先级作为addWriter()的第二参数).

Another way to add a writer to a Logger is to use the name of the writer as follow:

$logger = new Zend\Log\Logger;

$logger->addWriter('stream', null, array('stream' => 'php://output'));

In this example we passed the stream php://output as parameter (as array).

Logging Messages
To log a message, call the log() method of a Log instance and pass it the message with a corresponding priority:

$logger->log(Zend\Log\Logger::INFO, 'Informational message');

The first parameter of the log() method is an integer priority and the second parameter is a string message. The priority must be one of the priorities recognized by the Logger instance. This is explained in the next section. There is also an optional third parameter used to pass extra informations to the writer’s log.

A shortcut is also available.(快捷方法) Instead of calling the log() method, you can call a method by the same name as the priority:

	
$logger->log(Zend\Log\Logger::INFO, 'Informational message');
$logger->info('Informational message');

$logger->log(Zend\Log\Logger::EMERG, 'Emergency message');
$logger->emerg('Emergency message');

Destroying a Log
If the Logger object is no longer needed, set the variable containing it to NULL to destroy it. This will automatically call the shutdown() instance method of each attached Writer before the Log object is destroyed:

$logger = null;

Explicitly destroying the log in this way is optional and is performed automatically at PHP shutdown.
销毁一个Logger是可选的(脚本结束即销毁),复制null就销毁(shutdown())

Using Built-in Priorities 使用内置的优先级
The Zend\Log\Logger class defines the following priorities:

EMERG   = 0;  // Emergency: system is unusable
ALERT   = 1;  // Alert: action must be taken immediately
CRIT    = 2;  // Critical: critical conditions
ERR     = 3;  // Error: error conditions
WARN    = 4;  // Warning: warning conditions
NOTICE  = 5;  // Notice: normal but significant condition
INFO    = 6;  // Informational: informational messages
DEBUG   = 7;  // Debug: debug messages

These priorities are always available, and a convenience method of the same name is available for each one.快捷方式就是它的名称。

The priorities are not arbitrary非随意. They come from the BSD syslog protocol, which is described in RFC-3164. The names and corresponding priority numbers are also compatible with another PHP logging system, PEAR Log, which perhaps promotes interoperability between it and Zend\Log\Logger.(说它不是无中生有)

Priority numbers descend in order of importance. EMERG (0) is the most important priority. DEBUG (7) is the least important priority of the built-in priorities. You may define priorities of lower importance than DEBUG(可以定义比DEBUG更低的级别). When selecting the priority for your log message, be aware of this priority hierarchy and choose appropriately.

Understanding Log Events
When you call the log() method or one of its shortcuts, a log event is created. This is simply an associative array with data describing the event that is passed to the writers. The following keys are always created in this array: timestamp, message, priority, and priorityName.(这些值总是被记录)

The creation of the event array is completely transparent透明完成.

Log PHP Errors
Zend\Log\Logger can also be used to log PHP errors and intercept Exceptions. Calling the static method registerErrorHandler($logger) will add the $logger object before the current PHP error handler, and will pass the error along as well.

	
$logger = new Zend\Log\Logger;
$writer = new Zend\Log\Writer\Stream('php://output');

$logger->addWriter($writer);

Zend\Log\Logger::registerErrorHandler($logger);

If you want to unregister the error handler you can use the unregisterErrorHandler() static method.

Zend\Log\Logger events from PHP errors fields matching handler ( int $errno , string $errstr [, string $errfile [, int $errline [, array $errcontext ]]] ) from set_error_handler
Name Error Handler Parameter Description
message errstr Contains the error message, as a string.
errno errno Contains the level of the error raised, as an integer.
file errfile Contains the filename that the error was raised in, as a string.
line errline Contains the line number the error was raised at, as an integer.
context errcontext (optional) An array that points to the active symbol table at the point the error occurred. In other words, errcontext will contain an array of every variable that existed in the scope the error was triggered in. User error handler must not modify error context.

You can also configure a Logger to intercept Exceptions using the static method registerExceptionHandler($logger) 记录异常。

Writers
A Writer is an object that inherits from Zend\Log\Writer\AbstractWriter. A Writer’s responsibility is to record log data to a storage backend.(记录数据到后台)

Writing to Streams
Zend\Log\Writer\Stream sends log data to a PHP stream.

To write log data to the PHP output buffer, use the URL php://output. Alternatively, you can send log data directly to a stream like STDERR (php://stderr).

$writer = new Zend\Log\Writer\Stream('php://output');
$logger = new Zend\Log\Logger();
$logger->addWriter($writer);

$logger->info('Informational message');

To write data to a file, use one of the Filesystem URLs:

$writer = new Zend\Log\Writer\Stream('/path/to/logfile');
$logger = new Zend\Log\Logger();
$logger->addWriter($writer);

$logger->info('Informational message');

By default, the stream opens in the append mode (“a”). To open it with a different mode, the Zend\Log\Writer\Stream constructor accepts an optional second parameter for the mode.

The constructor of Zend\Log\Writer\Stream also accepts an existing stream resource:

$stream = @fopen('/path/to/logfile', 'a', false);
if (! $stream) {
    throw new Exception('Failed to open stream');
}

$writer = new Zend\Log\Writer\Stream($stream);
$logger = new Zend\Log\Logger();
$logger->addWriter($writer);

$logger->info('Informational message');

You cannot specify the mode for existing stream resources. Doing so causes a Zend\Log\Exception to be thrown.

Writing to Databases

Filters
Overview

A Filter object blocks a message from being written to the log.

You can add a filter to a specific Writer using addFilter() method of that Writer:

use Zend\Log\Logger;

$logger = new Logger();

$writer1 = new Zend\Log\Writer\Stream('/path/to/first/logfile');
$logger->addWriter($writer1);

$writer2 = new Zend\Log\Writer\Stream('/path/to/second/logfile');
$logger->addWriter($writer2);

// add a filter only to writer2
$filter = new Zend\Log\Filter\Priority(Logger::CRIT);
$writer2->addFilter($filter);

// logged to writer1, blocked from writer2
$logger->info('Informational message');

// logged by both writers
$logger->emerg('Emergency message');

Formatters
A Formatter is an object that is responsible for taking an event array describing a log event and outputting a string with a formatted log line.

Some Writers are not line-oriented and cannot use a Formatter. An example is the Database Writer, which inserts the event items directly into database columns. For Writers that cannot support a Formatter, an exception is thrown if you attempt to set a Formatter.

Simple Formatting
Zend\Log\Formatter\Simple is the default formatter默认. It is configured automatically when you specify no formatter. The default configuration is equivalent to the following:

$format = '%timestamp% %priorityName% (%priority%): %message%' . PHP_EOL;
$formatter = new Zend\Log\Formatter\Simple($format);

……
http://framework.zend.com/manual/current/en/modules/zend.log.formatters.html