分类目录归档:Zend Framework

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,当然,这个容器名称可以改)

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

Zend Framework 2.x 之 Zend\Crypt

Zend\Crypt组件封装了大部分细节,让加解密非常容易。

Introduction to Zend\Crypt

Zend\Crypt provides support of some cryptographic用密码写的 tools. The available features are:
*encrypt-then-authenticate加密然后验证 using symmetric对称 ciphers密码 (the authentication step is provided using HMAC);
*encrypt/decrypt using symmetric and public key algorithm(对称和公钥算法) (e.g. RSA algorithm);
*generate digital sign数字签名 using public key algorithm (e.g. RSA algorithm);
*key exchange using the Diffie-Hellman method;
*Key derivation function (e.g. using PBKDF2 algorithm);
*Secure password hash 安全密码哈希(e.g. using Bcrypt algorithm);
*generate Hash values;
*generate HMAC values;

The main scope of this component is to offer an easy and secure way to protect and authenticate鉴定 sensitive data in PHP. Because the use of cryptography密码系统 is not so easy we recommend to use the Zend\Crypt component only if you have a minimum background on this topic. For an introduction to cryptography we suggest the following references:推荐课程
*Dan Boneh “Cryptography course” Stanford University, Coursera – free online course
*N.Ferguson, B.Schneier, and T.Kohno, “Cryptography Engineering”, John Wiley & Sons (2010)
*B.Schneier “Applied Cryptography”, John Wiley & Sons (1996)

PHP-CryptLib—————
Most of the ideas behind the Zend\Crypt component have been inspired启发 by the PHP-CryptLib project of Anthony Ferrara. PHP-CryptLib is an all-inclusive pure PHP cryptographic library for all cryptographic needs. It is meant to be easy to install and use, yet extensible可展开的 and powerful enough for even the most experienced developer.

Encrypt/decrypt using block ciphers 分组密码加解密
Zend\Crypt\BlockCipher implements the encrypt-then-authenticate mode using HMAC to provide authentication.

The symmetric cipher对应秘钥 can be chosen with a specific adapter that implements the Zend\Crypt\Symmetric\SymmetricInterface. We support the standard algorithms of the Mcrypt extension. The adapter that implements the Mcrypt is Zend\Crypt\Symmetric\Mcrypt.

In the following code we reported an example on how to use the BlockCipher class to encrypt-then-authenticate a string using the AES block cipher (with a key of 256 bit) and the HMAC algorithm (using the SHA-256 hash function).

use Zend\Crypt\BlockCipher;
// 目前对称分组密码中只有mcrypt实现
$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey('encryption key');

// 每次输出均不一样
$result = $blockCipher->encrypt('this is a secret message');
echo "Encrypted text: $result \n";

// 都得到原始内容
echo "Decrypted text:".$blockCipher->decrypt($result)."\n";

The BlockCipher is initialized using a factory method with the name of the cipher adapter to use (mcrypt) and the parameters to pass to the adapter (the AES algorithm). In order to encrypt a string we need to specify an encryption key and we used the setKey() method for that scope. The encryption is provided by the encrypt() method.

The output of the encryption is a string, encoded in Base64 (default), that contains the HMAC value, the IV vector, and the encrypted text. The encryption mode used is the CBC (with a random IV by default) and SHA256 as default hash algorithm of the HMAC. The Mcrypt adapter encrypts using the PKCS#7 padding mechanism by default. You can specify a different padding method using a special adapter for that (Zend\Crypt\Symmetric\Padding). The encryption and authentication keys used by the BlockCipher are generated with the PBKDF2 algorithm, used as key derivation function from the user’s key specified using the setKey() method.

Key size
BlockCipher try to use always the longest size of the key for the specified cipher. For instance, for the AES algorithm it uses 256 bits and for the Blowfish algorithm it uses 448 bits.

You can change all the default settings passing the values to the factory parameters. For instance, if you want to use the Blowfish algorithm, with the CFB mode and the SHA512 hash function for HMAC you have to initialize the class as follow:

use Zend\Crypt\BlockCipher;

$blockCipher = BlockCipher::factory('mcrypt', array(
                                'algo' => 'blowfish',
                                'mode' => 'cfb',
                                'hash' => 'sha512'
                            ));

Recommendation
If you are not familiar with symmetric encryption techniques we strongly suggest to use the default values of the BlockCipher class. The default values are: AES algorithm, CBC mode, HMAC with SHA256, PKCS#7 padding.

To decrypt a string we can use the decrypt() method. In order to successfully decrypt a string we have to configure the BlockCipher with the same parameters of the encryption.解密是要跟加密时配置一样

We can also initialize the BlockCipher manually without use the factory method. We can inject the symmetric cipher adapter directly to the constructor of the BlockCipher class. For instance, we can rewrite the previous example as follow:

use Zend\Crypt\BlockCipher;
use Zend\Crypt\Symmetric\Mcrypt;

$blockCipher = new BlockCipher(new Mcrypt(array('algo' => 'aes')));
$blockCipher->setKey('encryption key');
$result = $blockCipher->encrypt('this is a secret message');
echo "Encrypted text: $result \n";

这里描述的是对称加解密,实际使用非常简单(有默认值)。

Encrypt/decrypt a file 加解密文件
Zend\Crypt\FileCipher implements the encryption of decryption of a file using a symmetric cipher in CBC mode with the encrypt-then-authenticate approach, using HMAC to provide authentication (the same solution used by Zend\Crypt\BlockCipher component).

Encrypt and decrypt a file is not an easy task, especially a big file(对大文件加解密不是一个容易的任务). For instance, in CBC mode you must be sure to handle the IV correctly for each block. That means, if you are reading a big file you need to use a buffer and be sure to use the last block of the buffer as new IV for the next encryption step.

The FileCipher uses a symmetric cipher, with the Zend\Crypt\Symmetric\Mcrypt component.

The usage of this component is very simple, you just need to create an instance of FileCipher and specify the key, and you are ready to encrypt/decrypt any file:

use Zend\Crypt\FileCipher;

$fileCipher = new FileCipher;
$fileCipher->setKey('encryption key');
// encryption
if ($fileCipher->encrypt('path/to/file_to_encrypt', 'path/to/output')) {
    echo "The file has been encrypted successfully\n";
}
// decryption
if ($fileCipher->decrypt('path/to/file_to_decrypt', 'path/to/output')) {
    echo "The file has been decrypted successfully\n";
}

By default FileCipher uses the AES encryption algorithm (with a key of 256 bit) and the SHA-256 hash algorithm to authenticate the data using the HMAC function. This component uses the PBKDF2 key derivation algorithm to generate the encryption key and the authentication key, for the HMAC, based on the key specified using the method setKey().

If you want to change the encryption algorithm, you can use the setCipherAlgorithm() function, for instance you can specity to use the Blowfish encryption algorihtm using setCipherAlgorithm(‘blowfish’). You can retrieve the list of all the supported encryption algorithm in your environment using the function getCipherSupportedAlgorithms(), it will return an array of all the algorithm name.

If you need to customize the cipher algorithm, for instance changing the Padding mode, you can inject your Mcrypt object in the FileCipher using the setCipher() method. The only parameter of the cipher that you cannot change is the cipher mode, that will be CBC in any case.

Output format
The output of the encryption file is in binary format(加密输出是二进制格式). We used this format to do not impact on the output size. If you encrypt a file using the FileCipher component, you will notice that the output file size is almost the same of the input size, just some bytes more to store the HMAC and the IV vector. The format of the output is the concatenation of the HMAC, the IV and the encrypted file.
这里使用的对称加密文件和加密字符串几乎一样。

Key derivation function
这个主题大概就是在对称加解密中使用的随机key的产生(内部使用,实现每次输出不一样)….

Password
In the Zend\Crypt\Password namespace you can find all the password formats supported by Zend Framework. We currently support the following passwords:
bcrypt;
Apache (htpasswd).

If you need to choose a password format to store the user’s password we suggest to use the bcrypt algorithm that is considered secure against brute forcing attacks (see the details below).

Bcrypt
The bcrypt algorithm is an hashing algorithm that is widely used and suggested by the security community to store user’s passwords in a secure way.

Classic hashing mechanisms like MD5 or SHA, with or without a salt value(加盐), are not considered secure anymore (read this post to know why).

The security of bcrypt is related to the speed of the algorithm. Bcrypt is very slow, it can request even a second to generate an hash value. That means a brute force attack is impossible to execute, due to the amount of time that its need.

Bcrypt uses a cost parameter that specify the number of cycles to use in the algorithm. Increasing this number the algorithm will spend more time to generate the hash output. The cost parameter is represented by an integer value between 4 to 31. The default cost value of the Zend\Crypt\Password\Bcrypt component is 10, that means about 0.07 second using a CPU Intel i5 at 3.3Ghz (the cost parameter is a relative value according to the speed of the CPU used). We changed the default value of the cost parameter from 14 to 10, starting from Zend Framework 2.3.0, due to high computational time to prevent potential denial-of-service attacks (you can read this article Aggressive password stretching for more information).

If you want to change the cost parameter of the bcrypt algorithm you can use the setCost() method. Please note, if you change the cost parameter, the resulting hash will be different. This will not affect the verification process of the algorithm, therefore not breaking the password hashes you already have stored. Bcrypt reads the cost parameter from the hash value, during the password authentication. All of the parts needed to verify the hash are all together, separated with $’s, first the algorithm, then the cost, the salt, and then finally the hash.

The example below shows how to use the bcrypt algorithm to store a user’s password:

use Zend\Crypt\Password\Bcrypt;

$bcrypt = new Bcrypt();
$securePass = $bcrypt->create('user password');

The output of the create() method is the hash of the password. This value can then be stored in a repository like a database (the output is a string of 60 bytes).

Bcrypt truncates input > 72 bytes
The input string of the bcrypt algorithm is limited to 72 bytes. If you use a string with a length more than this limit, bcrypt will consider only the first 72 bytes. If you need to use a longer string, you should pre-hash it using SHA256 prior to passing it to the bcrypt algorithm: $hashedPassword = \Zend\Crypt\Hash::compute(‘sha256’, $password);

To verify if a given password is valid against a bcrypt value you can use the verify() method. An example is reported below:

use Zend\Crypt\Password\Bcrypt;

$bcrypt = new Bcrypt();
$securePass = 'the stored bcrypt value';
$password = 'the password to check';

if ($bcrypt->verify($password, $securePass)) {
    echo "The password is correct! \n";
} else {
    echo "The password is NOT correct.\n";
}

In the bcrypt uses also a salt value to improve the randomness of the algorithm. By default, the Zend\Crypt\Password\Bcrypt component generates a random salt for each hash. If you want to specify a preselected salt you can use the setSalt() method.

We provide also a getSalt() method to retrieve the salt specified by the user. The salt and the cost parameter can be also specified during the constructor of the class, below is reported an example:

use Zend\Crypt\Password\Bcrypt;

$bcrypt = new Bcrypt(array(
    'salt' => 'random value',
    'cost' => 11
));

…….

Public key cryptography(公钥,也叫非对称)
Public key cryptography¶

Public-key cryptography refers to a cryptographic system requiring two separate keys, one of which is secret and one of which is public. Although different, the two parts of the key pair are mathematically linked. One key locks or encrypts the plaintext, and the other unlocks or decrypts the cyphertext. Neither key can perform both functions. One of these keys is published or public, while the other is kept private.

In Zend Framework we implemented two public key algorithms: Diffie-Hellman key exchange and RSA.(提供两种公钥算法)

Diffie-Hellman
这个跳过。

RSA
RSA is an algorithm for public-key cryptography that is based on the presumed difficulty of factoring large integers, the factoring problem. A user of RSA creates and then publishes the product of two large prime numbers大素数, along with an auxiliary value, as their public key. The prime factors must be kept secret. Anyone can use the public key to encrypt a message, but with currently published methods, if the public key is large enough, only someone with knowledge of the prime factors can feasibly decode the message. Whether breaking RSA encryption is as hard as factoring is an open question known as the RSA problem.

The RSA algorithm can be used to encrypt/decrypt message and also to provide authenticity and integrity generating a digital signature of a message. Suppose that Alice wants to send an encrypted message to Bob. Alice must use the public key of Bob to encrypt the message. Bob can decrypt the message using his private key. Because Bob he is the only one that can access to his private key, he is the only one that can decrypt the message. If Alice wants to provide authenticity and integrity of a message to Bob she can use her private key to sign the message. Bob can check the correctness of the digital signature using the public key of Alice. Alice can provide encryption, authenticity and integrity of a message to Bob using the previous schemas in sequence, applying the encryption first and the digital signature after.

以上描述了RSA基本实现与应用。

Below we reported some examples of usage of the Zend\Crypt\PublicKey\Rsa class in order to:
*generate a public key and a private key;
*encrypt/decrypt a string;
*generate a digital signature of a file.

Generate a public key and a private key
In order to generate a public and private key you can use the following code:

use Zend\Crypt\PublicKey\RsaOptions;

$rsaOptions = new RsaOptions(array(
    'pass_phrase' => 'test' //密码
));

$rsaOptions->generateKeys(array(
    'private_key_bits' => 2048,
));

file_put_contents('private_key.pem', $rsaOptions->getPrivateKey());
file_put_contents('public_key.pub', $rsaOptions->getPublicKey());

This example generates a public and private key of 2048 bit storing the keys in two separate files, the private_key.pem for the private key and the public_key.pub for the public key. You can also generate the public and private key using OpenSSL from the command line (Unix style syntax):

ssh-keygen -t rsa

Encrypt and decrypt a string
Below is reported an example on how to encrypt and decrypt a string using the RSA algorithm. You can encrypt only small strings. The maximum size of encryption is given by the length of the public/private key – 88 bits. For instance, if we use a size of 2048 bit you can encrypt string with a maximum size of 1960 bit (245 characters). This limitation is related to the OpenSSL implementation for a security reason related to the nature of the RSA algorithm.

The normal application of a public key encryption algorithm is to store a key or a hash of the data you want to respectively encrypt or sign. A hash is typically 128-256 bits (the PHP sha1() function returns a 160 bit hash). An AES encryption key is 128 to 256 bits. So either of those will comfortably fit inside a single RSA encryption.

use Zend\Crypt\PublicKey\Rsa;

$rsa = Rsa::factory(array(
    'public_key'    => 'public_key.pub',
    'private_key'   => 'private_key.pem',
    'pass_phrase'   => 'test',
    'binary_output' => false
));

$text = 'This is the message to encrypt';

$encrypt = $rsa->encrypt($text);
printf("Encrypted message:\n%s\n", $encrypt);

$decrypt = $rsa->decrypt($encrypt);

if ($text !== $decrypt) {
    echo "ERROR\n";
} else {
    echo "Encryption and decryption performed successfully!\n";
}

注意:加密一个字符串有长度限制(加密大数据不是其应用)。

Generate a digital signature of a file
Below is reported an example of how to generate a digital signature of a file.

use Zend\Crypt\PublicKey\Rsa;

$rsa = Rsa::factory(array(
    'private_key'   => 'path/to/private_key',
    'pass_phrase'   => 'passphrase of the private key',
    'binary_output' => false
));

$file = file_get_contents('path/file/to/sign');

$signature = $rsa->sign($file, $rsa->getOptions()->getPrivateKey());
$verify    = $rsa->verify($file, $signature, $rsa->getOptions()->getPublicKey());

if ($verify) {
    echo "The signature is OK\n";
    file_put_contents($filename . '.sig', $signature);
    echo "Signature save in $filename.sig\n";
} else {
     echo "The signature is not valid!\n";
}

数据签名,私钥加密,公钥可验证,主要的应用场景之一。在认证中也大量应用(加密小字符串,私钥解密,识别对方)。

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\Mail 使用范本 使用Mime发送附件(图片 Excel Zip等)

首先需要知道,Zend\Mime\Part抽象了文件(可以是文本文件 HTML文件 图片 Excel zip等),一个Message就是有一个或多个Part组成(一个或多个文件组成),这些文件之间的分隔需要使用Zend\Mime\Mime来进行,所以一个Message还有一个Mime对象。

每个Part,就是文件,根据不同类型,实际可以用的设置会不同,但是接口统一。比如有setType()用来设置Mime类型(就是标识文件的类型),setFilenme()设置文件名(需要带后缀名,可能会根据后缀名识别类型,QQ邮箱就是如此),setEncoding()设置编码(一般针对二进制文件,对二进制流进行编码后传输,所以一般是针对图片 Zip包这些文件,对应文本文件就没有实际作用),setChartset()设置文件编码(这个主要针对文本文件,涉及其中的字符编码,对二进制文件无效),setDisposition()设置是内联还是附件形式(用于邮件)。

另外,Part的getContent()可以获取使用设置的编码编码之后的输出,比如要还原一张图片,反向解码就是二进制流。setType()常见有”text/plain”,”text/html”, “text/xml”, “text/css”, “image/jpeg”, “application/json”, “text/javascript”。对应于HTTP响应,这些值将填充头Content-Type,客户端根据这个值来识别文件类型(决定如何处理)。

<?php
use Yaf\Controller_Abstract as ControllerAbstract;
use Zend\Db\Adapter\Adapter;
use Thousand\Tongtool\Session as TongtoolSession;
use Thousand\Services;
use Zend\Mail;
use Zend\Mail\Message;
use Zend\Mail\Transport\File as FileTransport;
use Zend\Mail\Transport\FileOptions;
use Zend\Mail\Transport\Smtp as SmtpTransport;
use Zend\Mail\Transport\SmtpOptions;
use Zend\Mail\Transport\InMemory as InMemoryTransport;
use Zend\Mime\Message as MimeMessage;
use Zend\Mime\Part as MimePart;

class TestController extends ControllerAbstract
{

    public function helloAction()
    {
        $adapter = Services::get('adapter');
         
        $jobs = $adapter->query("SELECT * FROM job", Adapter::QUERY_MODE_EXECUTE);
         
        print_r($jobs->toArray());
    }
    
    public function sendAction()
    {
        $text = new MimePart('Hello World');
        $text->type = "text/plain";
        
        // 邮件主体,覆盖了纯文本设置
        $html = new MimePart('<span style="color:red">Hi, Vfeelit...<a href="http://blog.ifeeline.com">this is my blog</a');
        $html->type = "text/html";

        // 作为附件 但是被QQ邮箱识别为bin文件
        $image = new MimePart(file_get_contents("D:/mailtest.jpg"));
        $image->type = "image/jpeg";
        
        // 设置MimeMessage
        $body = new MimeMessage();
        $body->setParts(array($text, $html, $image));
        
        
        // 1 设置信息
        $message = new Message();
        $message->addTo('jinsheng.sha@1000shores.com')
                ->addFrom('vfeelit@qq.com')
                ->setSubject('I want you.')
                //->setBody("I want you.");
                ->setBody($body);
        
        /*
                        完整参考
        $message->setTo('xxx')
                ->addTo('ifeeline@qq.com')      // 可以To多个人
                ->setFrom('xxx')
                ->addFrom('vfeelit@qq.com')     // 可以设置多个From
                ->setSender()                   // No addSender,默认是第一个From
                ->setCc()
                ->addCc()                       // 抄送
                ->setBcc()  
                ->addBcc()                      // 密送
                ->setReplyTo()                  // 回复给哪些人,应该是To对应,也可以多加
                ->addReplyTo()                  // 可以回复多个人
                ->setHeaders()                  // No addHeader(s)
                ->setSubject('I want you.')     // No addSubject
                ->setBody("I want you.");       // No addBody
        
        $message->getXxx()                      // 一系列的get方法
        
        $message->setEncoding("UTF-8")          // 默认为ACSSII
                ->fromString()                  // 用原始字符串构建
                ->toString()                    // 消息转换成字符串
                ->isValid()                     // 验证消息,返回布尔值
                
        // 邮件头设置
        $message->getHeaders()->addHeaderLine('X-API-Key', 'FOO-BAR-BAZ-BAT');
        $message->setHeaders() 需要传递一个Zend\Mail\Headers对象,每个单元是一个Zend\Mail\Header
        
        // 关于setBody,如果要发送HTML或者图片,就需要构建一个Zend\Mime\Message,一个Zend\Mime\Message由多个
        // Zend\Mime\Part组成,一个Zend\Mime\Part可以是一段纯文本,一个HTML片段,一张图片
        // 然后调用Zend\Mime\Message的setParts(array()),最后把这个Zend\Mime\Message传递给邮件消息的setBody
        $text = new MimePart($textContent);
        $text->type = "text/plain";
        
        $html = new MimePart($htmlMarkup);
        $html->type = "text/html";
        
        // 如果要成功发送一个图片附件,就必须设置如下几个参数,其中编码保守改为base64,默认是8bit
        // 经测试有些服务商无法读取这个内容
        $image = new MimePart(fopen($pathToImage, 'r'));
        $image->setType("image/jpeg");
        $image->setEncoding(Zend\Mime\Mime::ENCODING_BASE64);
        $image->setDisposition(Zend\Mime\Mime::DISPOSITION_ATTACHMENT);
        $image->setFileName('t.jpg');
        
        // 注意,作为一个附件,仅设置type是不够的,还需要设置文件名,根据文件后缀名了识别类型
        // 至少QQ邮箱是如此,一个Message有一个或多个Part,有一个Zend\Mime\Mime(啥毛?)
        // 多个Part作为内容输出时,需要合并在一起,这时Zend\Mime\Mime就用来参数分割线的了
        $excel = new MimePart(file_get_contents("D:/excel.xls"));
        $excel->setType("application/x-xls"); 
        $excel->setEncoding(Zend\Mime\Mime::ENCODING_BASE64);
        $excel->setDisposition(Zend\Mime\Mime::DISPOSITION_INLINE);
        $excel->setFileName('e.xls');
        $excel->setDescription("这是一个表格");
        $excel->setCharset("UTF-8");

        $body = new MimeMessage();
        $body->setParts(array($text, $html, $image));
        
        $message = new Message();
        $message->setBody($body); 
        */
       
        /* 文件transport
        $transport = new FileTransport();
        $options   = new FileOptions(array(
            // 只有两个参数
            'path'              => ROOT_PATH.'/mail/',
            'callback'  => function (FileTransport $transport) {
                return 'Message_' . microtime(true) . '_' . mt_rand() . '.txt';
            },
        ));
        $transport->setOptions($options);
        */
        
        /* 内存transport 无配置参数
        $transport = new InMemoryTransport();
        */
        
        // 2 启动  SmtpTransport
        $transport = new SmtpTransport();
        // 3 配置
        $options   = new SmtpOptions(array(
            'name'              => 'localhost',
            'host'              => 'smtp.qq.com',
            'port'              => 25,
            //默认是smtp, 测试plain, login都可以发信,但是smtp不行
            'connection_class'  => 'login', 
            'connection_config' => array(
                'username' => 'vfeelit@qq.com',
                'password' => 'xxxx',
                // “ssl” => “tls” and port 587 for TLS or “ssl” => “ssl” and port 465 for SSL.
                'ssl'    => 'tls'
            ),
        ));
        
        /* 
        // 普通配置
        $options   = new SmtpOptions(array(
            'name'              => 'localhost',
            'host'              => 'smtp.qq.com',
            'port'              => 25,
            'connection_class'  => 'plain',
            'connection_config' => array(
                'username' => 'vfeelit@qq.com',
                'password' => 'xxxxx'
            ),
        ));
         
        // 以下是一份ssl配置
        $options   = new SmtpOptions(array(
            'name'              => 'localhost',
            'host'              => 'smtp.qq.com',
            'port'              => 587,
            'connection_class'  => 'plain',
            'connection_config' => array(
                'username' => 'vfeelit@qq.com',
                'password' => 'xxxxx',
                'ssl'      => 'tls'
            ),
        ));
        */
        
        // 4 设置配置
        $transport->setOptions($options);

        // 5 发信
        $transport->send($message);
        
    }
}