月度归档:2015年10月

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”.

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

Laravel 队列详解

配置
config/queue.php
QUEUE_DRIVER现在改为在.env中设置,最简单的方式是使用数据库,改为database。然后建立队列数据表:

php artisan queue:table

建立一个数据迁移文件,运行一下:

//composer dump-autoload
php artisan migrate

然后就在数据库中建立jobs表。

能够进队列的工作都放在App\Jobs目录下(以前叫Commands,5.1中也兼容Commands,实际上就该了个名字而已),如下建立一个可以放入队列的命令:

php artisan make:job SendEmail --queued
//php artisan make:command SendEmail --queued

注意–queued参数,表示这个命令是可以压入队列的,通常意味着是后台执行了。工作类中有一个handle方法,工作运行时的方法。

要把一个工作放入队列执行可以使用:

Queue::push(new SendEmail($message));

一些细节:————————————————–
Queue是一个Facade,对应关系Queue – queue – Illuminate\Queue\QueueManager,正常流程,在容器中看不到queue这个key。查看流程中的deferredServices数组:

#deferredServices: array:84 [
    "queue" => "Illuminate\Queue\QueueServiceProvider"
    "queue.worker" => "Illuminate\Queue\QueueServiceProvider"
    "queue.listener" => "Illuminate\Queue\QueueServiceProvider"
    "queue.failer" => "Illuminate\Queue\QueueServiceProvider"
]

可以知道,Illuminate\Queue\QueueServiceProvider是一个deferred服务,从实用的角度,定义一个服务提供者时,只要定义protected $defer = true(默认为false),就会标记为延时服务。框架的服务在app.conf的providers数组中(Illuminate\Queue\QueueServiceProvider::class, 它的$defer为true),这些服务会在Illuminate\Foundation\Bootstrap\RegisterProviders的bootstrap()方法中进入(这个是框架bootstrap阶段其中的一个步骤),方法中运行$app->registerConfiguredProviders(),见名知意,就是注册配置的服务,具体的规则准守以上的规则(细节就不再跟踪了)。从deferredServices的输出可以猜测,需要queue或queue.work等时,这个QueueServiceProvider就会被启动,这个应该跟一般的服务启动是一样的,这里是延迟到了真需要时才启动(这种对于不是每次请求都需要,或者只是某些请求才需要的服务,会非常有用)。使用当使用Queue这个facade时,对应的服务启动,这个服务会注册一个叫queue的Illuminate\Queue\QueueManager的实例。通过它来进行队列任务管理。

一个最简单的场景,压一个工作进入具体的队列,那么这个管理器需要提供push方法,不过在push之前,要先链接上具体的队列链接上,那么它应该有一个connection的概念,可能有多个队列,那么总是有默认的吧,等等,很多的Manager都类似。也就是说,Manager通常充当一个生产工厂和一个监视器。(这里只是队列里面的生产者,还有消费者,当然它们都是需要先链上队列软件的)

    public function __call($method, $parameters)
    {
        $callable = [$this->connection(), $method];

        return call_user_func_array($callable, $parameters);
    }

它的push方法就是这样来的。用如下图了描述这个组件:
queue
Connectors是在Service中填充的,它决定哪些Connectors可用,通过调用Connector产生具体的Connection,它是队列的抽象,实际上它才是主战场,比如它有push,pop方法,对应入队和出队。平时说的队列客户端,就是这么个东西。
—————————————————————————————

可以具体的控制器代码中调用:

//控制器中的使用
$job = (new SendEmail($message))->onQueue('emails');
$this->dispatch($job);

// 类似
Queue::pushOn('emails', new SendEmail($message));

这样命令发送到emails队列执行。注意,命名的队列必须启用。

一些细节:—————————————————–
App\Http\Controllers\Controller中use了DispatchesJobs这个trail,里面定义了:

    protected function dispatch($job)
    {
        return app('Illuminate\Contracts\Bus\Dispatcher')->dispatch($job);
    }

这个Illuminate\Contracts\Bus\Dispatcher实际会启动一个延后的服务叫Illuminate\Bus\BusServiceProvider,这个服务的register方法:

    public function register()
    {
        $this->app->singleton('Illuminate\Bus\Dispatcher', function ($app) {
            return new Dispatcher($app, function () use ($app) {
                return $app['Illuminate\Contracts\Queue\Queue'];
            });
        });

        $this->app->alias(
            'Illuminate\Bus\Dispatcher', 'Illuminate\Contracts\Bus\Dispatcher'
        );

        $this->app->alias(
            'Illuminate\Bus\Dispatcher', 'Illuminate\Contracts\Bus\QueueingDispatcher'
        );
    }

Illuminate\Bus\Dispatcher内部最终会启动$app[‘Illuminate\Contracts\Queue\Queue’],它也对应一个延后服务,不过它被闭包函数包围了,在调用Illuminate\Bus\Dispatcher实例的dispatch()方法前还不会启动queue,跟踪一下这个方法:

    public function dispatch($command, Closure $afterResolving = null)
    {
        if ($this->queueResolver && $this->commandShouldBeQueued($command)) {
            return $this->dispatchToQueue($command);
        } else {
            return $this->dispatchNow($command, $afterResolving);
        }
    }

这里的$this->queueResolver就是Illuminate\Bus\Dispatcher实例生成时传递的那个闭包函数(用来启动queue),判断$command是否可入队,如果可以,就调用dispatchToQueue($command)负责这个事情,否则就是dispatchNow():

    public function dispatchToQueue($command)
    {
        // 这里启动queue
        $queue = call_user_func($this->queueResolver);
        
        if (! $queue instanceof Queue) {
            throw new RuntimeException('Queue resolver did not return a Queue implementation.');
        }
        // 如果有queue方法
        if (method_exists($command, 'queue')) {
            return $command->queue($queue, $command);
        } else {
            return $this->pushCommandToQueue($queue, $command);
        }
    }

更多细节就不跟踪了,具体用法会有例子。不过这里需要说明,能入队的,必须是实现了Illuminate\Contracts\Queue\ShouldQueue接口的对象(实际它啥也没有,只是为了区分)。另外,可以dispatch的,不仅是入队的对象,也可以是马上要执行的对象(dispatchNow负责),它可以把控制器中的大逻辑或者可重用的逻辑提取出来作为一个工作,这对分离大控制器和提取重用逻辑非常有用。

控制器中的dispatch()获取app(‘Illuminate\Contracts\Bus\Dispatcher’),返回一个Bus分发器,在任何时候,我们都可以直接这样干(我们的代码一般都是在控制器中,所以不需要那么生硬的调用),获取到Bus分发器后就可以分发工作了。使用Bus的好处是既可以分发可以入队的工作,也可以不是。使用Queue就只能分入队的工作。

对于能入队的工作,仅仅需要知道这么一个流程,就足够了:一个工作对象会被序列化后入队,出队时反序列化,工作对象的数据自然需要通过它的构造函数带入(出队时才能有对应数据),反序列化后会去执行工作的handle方法。

关于入队,现在知道的,应该已经足够多了。不过还有一个出队问题。我们需要一个监听器,连续不停地扫描队列,取出任务执行,所以最简单的方法是拉起一个主进程,其中循环,不管是否取到工作,都拉起一个子进程,有则执行,无则空跑,然后就退出。Laravel本身提供的php artisan queue:listen就是这种方式。这个方式要说优点的话,就实现简单。缺点非常明显,每次拉起一个子进程,载入框架,完了释放,进程号增长非常快,存在内存回收问题(你要觉得PHP进程退出了内存就释放然后就可以回收了,我表示无语)。从测试来看,这个方法CPU占用过多,资源浪费严重。简直可以用操蛋来形容。

Laravel提供的第二种监听队列的方法,是令人满意的。就是php artisan queue:work –daemon的方式,这个方式也是循环(要实现不间断监听,只能做循环),不过它不会每次拉起一个进程来载入整个框架,而是只会载入一次,然后内部循环监听队列,取到任务就执行任务,取不到就继续循环。这个方式消费资源较少。不过可能存在内存泄露问题,可以模仿PHP-FPM管理器,每个PHP进程在处理了预定次数的脚本后自杀。另外,框架只载入一次,那么在框架初始化过程中打开的文件,数据库链接等,是不会长时间等着的,所以,必须在内循环每次对需要用到的资源再次初始化,就算空跑也是如此,Laravel的work模式就是这样的,所以,如果开启了20个work守护进程,没有设置sleep参数,假设框架开启数据库链接,那么你会看到大量的到数据库的链接,这个情况可以加入sleep参数来缓解,实际可以改为如果取到空任务,可以不需要再次初始化资源,直接返回。这个可能会改进吧….
—————————————————————————————

执行监听

#connection代表队列链接,比如使用数据库为队列
#connection就是database
php artisan queue:listen connection

php artisan queue:listen

php artisan queue:listen --timeout=60

php artisan queue:listen --sleep=5

php artisan queue:work

实际上queue:listen和queue:work效果是差不多的,区别在于queue:listen本身是一个监听器(内部循环),而queue:work是一个工作,执行完就退出(最多取一个,如果队列为空,马上退出),可以用queue:work –daemon让其变成一个监听器。如果不指定监听哪个队列,那么就是在监听名为default的队列,可以通过–queue指定要监听的队列,–queue可以接多个队列名,分别代表优先级(最先的优先级最高)。两种监听方法,测试来看,当抛出异常时,queue:work进程会退出(需要配合进程监视器来监控),而queue:listen则不会。

所以,最常用的做法,无非这样:

#这个用法建议不用
php artisan queue:listen --queue=emails --sleep=3 --tries=3
#效果差不多
php artisan queue:work --queue=emails --sleep=3 --tries=3 --daemon

监听名字为emails的队列,最多尝试3次。

已失败的工作

php artisan queue:failed-table
php artisan migrate

队列失败时自动放入。可以在命令中定义:

public function failed()
{
    // 当工作失败的时候会被调用……
}

这样就可以处理失败作业了,比如发送邮件等?

补充:
目前的队列使用数据库来进行模拟,看起来工作良好,不过数据库并不擅长干这个。实际使用上,当开启多个队列,每个队列启动多个监听进程时,很容易出现死锁,这个锁是来自数据库InnoDB的,相互在等待锁释放,这应该是个Bug。所以,目前比较理想的是使用beanstalk来作为队列,说起来,这个东西使用起来异常简单。

1 安装,参考http://blog.ifeeline.com/1268.html

#主要命令
mkdir -p /var/log/beanstalkd
/usr/local/bin/beanstalkd -b /var/log/beanstalkd -l 127.0.0.1

选项-b指定一个目录,用来存放队列数据。

2 设置supervistor监控(http://blog.ifeeline.com/2082.html)

vi beanstalkd.conf 

[program:beanstalkd]
command=/usr/local/bin/beanstalkd -b /var/log/beanstalkd -l 127.0.0.1
autostart=true
autorestart=true
user=root
redirect_stderr=true
stdout_logfile=/mnt/www/ebt/storage/logs/beanstalkd.log

3 Laravel中的设置

#安装依赖 Laravel依赖这个包链接beanstalkd
php composer.phar require pda/pheanstalk

#修改.env
QUEUE_DRIVER=beanstalkd

#修改config/queue.conf
'beanstalkd' => [
            'driver' => 'beanstalkd',
            'host'   => '192.168.1.168',
            'queue'  => 'default',
            'ttr'    => 60,
        ],

就这样,设置全部完成。这里把database改为beanstalkd后,对于失败的Job,框架仍然会把其放入failed_job表中。

——————————————————————————————————–
为了更加高效的使用Beanstalk,可以使用https://github.com/phalcongelist/beanspeak提供的C扩展驱动。不过需要按照约定重新封装一下:

# 替换系统的服务提供者
<?php
namespace Vfeelit\Queue;

use Illuminate\Queue\QueueServiceProvider as SystemQueueServiceProvider;
use Vfeelit\Queue\Connectors\BeanstalkdConnector;

class QueueServiceProvider extends SystemQueueServiceProvider
{
    protected function registerBeanstalkdConnector($manager)
    {
        if (extension_loaded('beanspeak')) {
            $manager->addConnector('beanstalkd', function () {
                return new BeanstalkdConnector;
            });
        } else {
            parent::registerBeanstalkdConnector($manager);
        }  
    }
}

#客户端封装
<?php
namespace Vfeelit\Queue;

use Illuminate\Queue\Queue;
use Illuminate\Contracts\Queue\Queue as QueueContract;
use Vfeelit\Queue\Jobs\BeanstalkdJob;

class BeanstalkdQueue extends Queue implements QueueContract
{
    /**
     * The Beanspeak\Client instance.
     *
     * @var Beanspeak\Client
     */
    protected $beanspeak;

    /**
     * The name of the default tube.
     *
     * @var string
     */
    protected $default;

    /**
     * The "time to run" for all pushed jobs.
     *
     * @var int
     */
    protected $timeToRun;

    /**
     * Create a new Beanspeak\Client queue instance.
     *
     * @param  \Beanspeak\Client  $beanspeak
     * @param  string  $default
     * @param  int  $timeToRun
     * @return void
     */
    public function __construct(\Beanspeak\Client $beanspeak, $default, $timeToRun)
    {
        $this->default = $default;
        $this->timeToRun = $timeToRun;
        $this->beanspeak = $beanspeak;
    }

    /**
     * Push a new job onto the queue.
     *
     * @param  string  $job
     * @param  mixed   $data
     * @param  string  $queue
     * @return mixed
     */
    public function push($job, $data = '', $queue = null)
    {
        return $this->pushRaw($this->createPayload($job, $data), $queue);
    }

    /**
     * Push a raw payload onto the queue.
     *
     * @param  string  $payload
     * @param  string  $queue
     * @param  array   $options
     * @return mixed
     */
    public function pushRaw($payload, $queue = null, array $options = [])
    {
        return $this->beanspeak->useTube($this->getQueue($queue))->put(
            $payload, 1024, 0, $this->timeToRun
        );
    }

    /**
     * Push a new job onto the queue after a delay.
     *
     * @param  \DateTime|int  $delay
     * @param  string  $job
     * @param  mixed   $data
     * @param  string  $queue
     * @return mixed
     */
    public function later($delay, $job, $data = '', $queue = null)
    {
        $payload = $this->createPayload($job, $data);

        $beanspeak = $this->beanspeak->useTube($this->getQueue($queue));

        return $beanspeak->put($payload, 1024, $this->getSeconds($delay), $this->timeToRun);
    }

    /**
     * Pop the next job off of the queue.
     *
     * @param  string  $queue
     * @return \Illuminate\Contracts\Queue\Job|null
     */
    public function pop($queue = null)
    {
        $queue = $this->getQueue($queue);

        $job = $this->beanspeak->watchOnly($queue)->reserve(0);

        if ($job instanceof \Beanspeak\Job) {
            return new BeanstalkdJob($this->container, $this->beanspeak, $job, $queue);
        }
    }

    /**
     * Delete a message from the Beanstalk queue.
     *
     * @param  string  $queue
     * @param  string  $id
     * @return void
     */
    public function deleteMessage($queue, $id)
    {
        $job = $this->beanspeak->useTube($this->getQueue($queue))->peekJob($id);
        
        if ($job instanceof \Beanspeak\Job) {
            $job->delete($id);
        }
    }

    /**
     * Get the queue or return the default.
     *
     * @param  string|null  $queue
     * @return string
     */
    public function getQueue($queue)
    {
        return $queue ?: $this->default;
    }

    /**
     * Get the underlying \Beanspeak\Client instance.
     *
     * @return \Beanspeak\Client
     */
    public function getBeanspeak()
    {
        return $this->beanspeak;
    }	
}

#连接器封装
<?php
namespace Vfeelit\Queue\Connectors;

use Illuminate\Queue\Connectors\ConnectorInterface;
use Illuminate\Support\Arr;
use Vfeelit\Queue\BeanstalkdQueue;

class BeanstalkdConnector implements ConnectorInterface
{
    /**
     * Establish a queue connection.
     *
     * @param  array  $config
     * @return \Illuminate\Contracts\Queue\Queue
     */
    public function connect(array $config)
    {
	
		$client = new \Beanspeak\Client([
			'host' => Arr::get($config, 'host', '127.0.0.1'),
			'port' => Arr::get($config, 'port', 11300),
			'timeout' => Arr::get($config, 'timeout', 60),
			'persistent' => Arr::get($config, 'persistent', true),
			'wretries' => Arr::get($config, 'wretries', 8)
		]);
		
		$client->connect();

        return new BeanstalkdQueue(
            $client, $config['queue'], Arr::get($config, 'ttr', 60)
        );
    }
}

# Job封装
<?php

namespace Vfeelit\Queue\Jobs;

use Illuminate\Queue\Jobs\Job;
use Illuminate\Contracts\Queue\Job as JobContract;
use Illuminate\Container\Container;

class BeanstalkdJob extends Job implements JobContract
{
    /**
     * The Beanspeak\Client instance.
     *
     * @var \Beanspeak\Client
     */
    protected $beanspeak;

    /**
     * The Beanspeak\Job job instance.
     *
     * @var \Beanspeak\Job
     */
    protected $job;

    /**
     * Create a new job instance.
     *
     * @param  \Illuminate\Container\Container  $container
     * @param  \Beanspeak\Client  $beanspeak
     * @param  \Beanspeak\Job  $job
     * @param  string  $queue
     * @return void
     */
    public function __construct(Container $container,
                                \Beanspeak\Client $beanspeak,
                                \Beanspeak\Job $job,
                                $queue)
    {
        $this->job = $job;
        $this->queue = $queue;
        $this->container = $container;
        $this->beanspeak = $beanspeak;
    }

    /**
     * Fire the job.
     *
     * @return void
     */
    public function fire()
    {
        $this->resolveAndFire(json_decode($this->getRawBody(), true));
    }

    /**
     * Get the raw body string for the job.
     *
     * @return string
     */
    public function getRawBody()
    {
        return $this->job->getBody();
    }

    /**
     * Delete the job from the queue.
     *
     * @return void
     */
    public function delete()
    {
        parent::delete();

        $this->job->delete();
    }

    /**
     * Release the job back into the queue.
     *
     * @param  int   $delay
     * @return void
     */
    public function release($delay = 0)
    {
        parent::release($delay);

        $priority = 1024;

        $this->job->release($priority, $delay);
    }

    /**
     * Bury the job in the queue.
     *
     * @return void
     */
    public function bury()
    {
        parent::release();

        $this->job->bury();
    }

    /**
     * Get the number of times the job has been attempted.
     *
     * @return int
     */
    public function attempts()
    {
        $stats = $this->job->stats();

		$reserves = 0;
		if (!empty($stats['reserves'])) {
			$reserves = (int) $stats['reserves'];
		}

        return $reserves;
    }

    /**
     * Get the job identifier.
     *
     * @return string
     */
    public function getJobId()
    {
        return $this->job->getId();
    }

    /**
     * Get the IoC container instance.
     *
     * @return \Illuminate\Container\Container
     */
    public function getContainer()
    {
        return $this->container;
    }

    /**
     * Get the underlying Beanspeak\Client instance.
     *
     * @return \Beanspeak\Client
     */
    public function getBeanspeak()
    {
        return $this->beanspeak;
    }

    /**
     * Get the underlying Beanspeak\Job job.
     *
     * @return \Pheanstalk\Job
     */
    public function getPheanstalkJob()
    {
        return $this->job;
    }
}

服务提供者中对于没有启用beanspeak扩展的情况做了处理,没有则走原有逻辑。

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时,估计也要重写了。

Composer包依赖管理器快速参考

基本用法:
http://docs.phpcomposer.com/01-basic-usage.html#Basic-usage

一 建立composer.json

{
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

指令”require”表示依赖的包,是一个列表,每个元素是包名对应版本号,报名分两部分,分别是供应商 和 包名称,然后是对应的版本号。版本号的声明可以用使用一般的比较运算符,比如>=1.0,更加常见的是~1.2,表示最低依赖1.2,从1.2到2.0(不包括2.0)。

二 然后执行install

php composer.phar install

安装完成之后(检测composer.json,下载所有依赖的包),就会产生composer.lock文件,意味着当前的项目使用的依赖被锁定,因为如果composer.lock存在,那么在install时会先被检测到,那么composer.json将被跳过而使用composer.lock里面锁定的依赖(更新 或 建立vendor目录,下载包)。

如果需要更新所有依赖,可以运行update(而不是install),这样就会检测composer.json,下载新依赖,重新产生composer.lock文件。如果只要更新一个或几个包,可以php composer.phar update monolog/monolog […],这样也会更新最终的composer.lock文件。

三 自动装载
先依赖包安装之后,就需要使用它:

#只需要保护这个文件
require 'vendor/autoload.php';

它实际返回一个loader,预先扫描vendor/composer/中的autoload_classmap.php、autoload_namespaces.php和autoload_psr4.php,先看刚才的install做了什么工作:

<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'Monolog' => array($vendorDir . '/monolog/monolog/src'),
);

把依赖的包添加到了这里,那么这个自动loader就到这里来寻找对应的库。意思就是install(或update)时,这里的文件会被更新。

那么如果要自动load自己的类库,编辑这里的文件也可以实现。不过一般情况都不这么干。可以往composer.json里面添加:

"autoload": {
        "classmap": [
            "database"
        ],
        "psr-4": {
            "eBay\\": "app/"
        }
}

然后运行php composer.phar update即可,注意classmap的方式对应的是目录,它扫描目录里面的所有文件,找出类名(支持命名空间),然后对应其路径:

return array(
    'Craw' => $baseDir . '/database/Craw.php',
    'Test' => $baseDir . '/database/test/tt.php',
    'Vfeelit\\Need' => $baseDir . '/database/test/ab.php',
);

这个方法对于引入非规范的类,可以说是一网打尽,美中不足的是我可能不需要扫描整个目录。

看看psr-4文件:

<?php
// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);

return array(
    'eBay\\' => array($baseDir . '/app'),
);

注:这里的app目录不需要一定存在,因为它不会扫描目录。

引入类库的方式可以直接使用方法的loader实例:

$loader = require 'vendor/autoload.php';
$loader->add('Acme\\Test\\', __DIR__);

四 关于下载包的来源
众所周知的原因,composer下载很慢,可以使用国内镜像(在composer.josn中添加):

"repositories": [
        {"type": "composer", "url": "http://packagist.phpcomposer.com"},
        {"packagist": false}
    ]

五 项目与包
项目叫project,包叫package。只要你有一个 composer.json 文件在目录中,那么整个目录就是一个包。当你添加一个 require 到项目中,你就是在创建一个依赖于其它库的包。你的项目和库之间唯一的区别是,你的项目是一个没有名字的包。为了使它成为一个可安装的包,你需要给它一个名称。你可以通过 composer.json 中的 name 来定义:

{
    "name": "acme/hello-world",
    "require": {
        "monolog/monolog": "1.0.*"
    }
}

在这种情况下项目的名称为 acme/hello-world,其中 acme 是供应商的名称。供应商的名称是必须填写的。

六 平台软件包
Composer 将那些已经安装在系统上,但并不是由 Composer 安装的包视为一个虚拟的平台软件包。这包括PHP本身,PHP扩展和一些系统库。(简单参考一下即可):

"require": {
        "php": ">=5.5.9",  		///这里
        "laravel/framework": "5.1.*",
        "league/flysystem-aws-s3-v3": "~1.0",
        "maatwebsite/excel": "~2.0.0",
        "chumper/zipper": "0.6.x"
    },

七 版本 标签 分支
http://docs.phpcomposer.com/02-libraries.html

八 发布到 packagist

九 命令行
http://docs.phpcomposer.com/03-cli.html,这部分内容中的global不好理解。大概是提供了已给命令行工具。其它都不难理解。

十 脚本
http://docs.phpcomposer.com/articles/scripts.html,这部分内容是在运行composer时触发的事件。

最后,composer.josn完整的参考http://docs.phpcomposer.com/04-schema.html。

Nginx Location配置 – 被坑记

server {
    listen       80;
    server_name  xxx.com;
    root /mnt/xxx/public;
    index index.php;

    error_page 404 /index.php;
    
    if (!-e $request_filename) {
        rewrite ^/(.+)$ /index.php last;
        break;
    }

    location / {
	root /var/xxx/public;
        #尝试实际文件,然后判断是不是目录,在然后就是到首页(实际是被重新,重新发起location匹配)
	try_files $uri $uri/ /index.php?$query_string; 
    }

    location ~* ^.+\.(css|js|jpeg|jpg|gif|png|ico|eot|ttf|woff|svg) {
        expires 30d;
    }

    location ~* \.(eot|ttf|woff|svg|html)$ {
        add_header Access-Control-Allow-Origin *;
    }

    location ~ \.php$ {
	client_max_body_size 500M;
        fastcgi_connect_timeout 300;
        fastcgi_send_timeout 300;
        fastcgi_read_timeout 300;
        fastcgi_buffer_size 32k;
        fastcgi_buffers 256 32k;

        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
        #fastcgi_param APPLICATION_ENV testing;
   }

首先,第一个if语句是判断对应的rui不存在(非目录和文件),那么就重写到index.php,我们知道last重写相当于continue语句,由于这个if在server块中,所以它不会再次发起请求了,故而下面的break是多余的,写上也没有问题。

还是再次复习location指令的用法吧:
location指令
语法:location [=|~|~*|^~] /uri {…} 注意看明白格式****中括号表示可以省略
使用环境:server

该指令运行对不同的URI进行不同的配置,即可以使用字符串,也可以使用正则表达式。使用正则表达式,须使用以下前缀。
1 ~* 表示不区分大小写的匹配
2 ~ 表示区分大小写的匹配
(意思是以这两个字符开头的,就是一定是正则匹配)
在匹配过程中,Nginx将首先匹配字符串,然后再匹配正则表达式。匹配到第一个正则表达式后,会停止搜索(搜索顺序时先出现的优先)。如果匹配到正则表达式,则使用正则表达式匹配,如果没有匹配到正则表达式,则使用字符串的搜索结果。

特殊用法:
可以使用前缀^~来禁止匹配到字符串后,再去检查正则表达式。
使用前缀=可以进行精确的URI匹配,如果找到匹配的URI,则停止查询。

注意,Nginx中的变量$uri由于都是以/开头的,当访问首页是其实访问的就是/,浏览器总是把斜杠添加上然后才发出请求,就算没有加上斜杠发出来了请求,服务器也会进行一个加了斜杠的重定向,如果访问一个目录时,请求到达服务器后如何确认它是一个目录,服务器会进行一个加了斜杠的重定向。

另外,location指令中能使用的前缀只有上面列出的四个,而那些在if中合法的!~ 和 !~*在location中是不合法的,这个稍不注意就会让人困惑。

location的正则匹配是一旦匹配就停止,所以那些允许的匹配应该写在前,不允许的应该写在后,具体的写在最前面。

所有的location匹配放入一个循环,字符串优先匹配(最先的先匹配,后面的后匹配),然后匹配正则,正则一旦匹配,就使用这个正则匹配(触发被重写,会重新发起一次对locaiton的匹配),否则就使用最后的字符匹配,总体上满足先具体,后一般的原则。

回到配置,第一个字符匹配,基本上,如果后面的没有匹配,就都是它了。后面的是三个正则匹配,一旦匹配,就用它。现在假如URI是get.js.php 和 get.css.php,这个时候它匹配了第二个正则而不是第三个正则,所以被坑就在这里了。导致明明要执行PHP脚本的,生硬的把get.js.php源码返回。争取的写法应该是在最后加上$字符。

CentOS 7.X 装机参考

#查看文件描述符限制
ulimit -n

#调整文件描述符(文件最后添加内容)
vi /etc/security/limits.conf
* soft nproc 65535
* hard nproc 65535
* soft nofile 65535
* hard nofile 65535

#RAID卡查看
dmesg | grep -i raid
[    1.521079] Areca RAID Controller0: Model ARC-1200, F/W V1.49 2010-12-02
[    1.521203] scsi host0: Areca SATA RAID Controller (RAID6 capable)
[    1.648025] scsi 0:0:16:0: Processor         Areca    RAID controller  R001 PQ: 0 ANSI: 0

cat /proc/scsi/scsi
Attached devices:
Host: scsi0 Channel: 00 Id: 00 Lun: 00
  Vendor: Areca    Model: ARC-1200-VOL#00  Rev: R001
  Type:   Direct-Access                    ANSI  SCSI revision: 05
Host: scsi0 Channel: 00 Id: 16 Lun: 00
  Vendor: Areca    Model: RAID controller  Rev: R001
  Type:   Processor                        ANSI  SCSI revision: 00
Host: scsi3 Channel: 00 Id: 00 Lun: 00
  Vendor: ATA      Model: ST1000NM0033-9ZM Rev: SN04
  Type:   Direct-Access                    ANSI  SCSI revision: 05

lspci | grep -i raid
03:00.0 RAID bus controller: Areca Technology Corp. ARC-1200 2-Port PCI-Express to SATA II RAID Controller


#查看版本
cat /etc/system-release
cat /etc/issue 	#CentOS 7下不会再输出版本

# 总核数 = 物理CPU个数 X 每颗物理CPU的核数 
# 总逻辑CPU数 = 物理CPU个数 X 每颗物理CPU的核数 X 超线程数
# 查看物理CPU个数
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
# 查看每个物理CPU中core的个数(即核数)
cat /proc/cpuinfo| grep "cpu cores"| uniq
# 查看逻辑CPU的个数
cat /proc/cpuinfo| grep "processor"| wc -l

#查看网卡
ip addr
ip link
ip -s link
ifconfig 	#CentOS 7下,默认不再安装此工具

#本机DNS配置,配置文件/etc/resolv.conf
options timeout:1 attempts:1 rotate
nameserver 192.168.1.1
nameserver 8.8.8.8

#CentOS 7下网卡命名发生了变化,以前的ethX这种命名不见
#如果要改回来,需要修改/etc/default/grub文件
#添加net.ifnames=0 biosdevname=0
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=auto vconsole.font=latarcyrheb-sun16 vconsole.keymap=us net.ifnames=0 biosdevname=0 rhgb quiet"
GRUB_DISABLE_RECOVERY="true"

#修改网卡配置信息(注意网卡名称)
#CentOS 7中除了名称改变,其它几乎是一致的
vi vi /etc/sysconfig/network-scripts/ifcfg-eno16777736
TYPE=Ethernet
BOOTPROTO=static
IPADDR=192.168.1.168
NETMASK=255.255.255.0
GATEWAY=192.168.1.1
DEFROUTE=yes
PEERDNS=yes
PEERROUTES=yes
IPV4_FAILURE_FATAL=no
NAME=eno16777736
UUID=7f9bd132-fb60-4ec8-8021-734f15dc0ff2
DEVICE=eno16777736
ONBOOT=yes

#CentOS 7 主机名相关状态(相关信息可以通过hostnamectl完成)
[root@localhost ~]# hostnamectl status
   Static hostname: localhost.localdomain
         Icon name: computer
           Chassis: n/a
        Machine ID: 2c3cda62de28434a894128e7ade1627b
           Boot ID: bd5d076f8a8c42358fe8ef4dbc6f0929
    Virtualization: vmware
  Operating System: CentOS Linux 7 (Core)
       CPE OS Name: cpe:/o:centos:centos:7
            Kernel: Linux 3.10.0-229.el7.x86_64
      Architecture: x86_64

#查看主机名
hostname

#CentOS 7中修改主机名
vi /etc/hostname
localhost.localdomain

#CentOS 6.x中(CentOS中不存在/etc/sysconfig/network)
vi /etc/sysconfig/network
NETWORKING=yes		#ipv4网络
NETWORKING_IPV6=no	#ipv6网络
HOSTNAME=vfeelit	#主机名称

#CentOS 7时间相关
[root@localhost ~]# timedatectl
      Local time: Sat 2015-11-07 22:38:42 PST
  Universal time: Sun 2015-11-08 06:38:42 UTC
        RTC time: Sun 2015-11-08 06:38:42
        Timezone: America/Los_Angeles (PST, -0800)
     NTP enabled: yes
NTP synchronized: yes
 RTC in local TZ: no
      DST active: no
 Last DST change: DST ended at
                  Sun 2015-11-01 01:59:59 PDT
                  Sun 2015-11-01 01:00:00 PST
 Next DST change: DST begins (the clock jumps one hour forward) at
                  Sun 2016-03-13 01:59:59 PST
                  Sun 2016-03-13 03:00:00 PDT
# 列出所有时区
timedatectl list-timezones 

# 将硬件时钟调整为与本地时钟一致, 0 为设置为 UTC 时间
timedatectl set-local-rtc 1 

# 设置系统时区为上海
timedatectl set-timezone Asia/Shanghai 

# 也可以使用如下方法(CentOS 6.x中使用的方法)
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

##同步时间(yum install ntp)
ntpdate asia.pool.ntp.org

##查看当前时区的时间
date -R
Sun, 08 Nov 2015 14:45:07 +0800

#本地设置(修改为中文)
vi /etc/locale.conf
ANG="zh_CN.UTF-8"

#关闭Selinux
vi /etc/selinux/config
#关闭Selinux后可能无法开机,Ctrl+F1切换终端可以查看,编辑/etc/grub2.conf
#在语言设置之后添加selinux=0

#赋予rc.loal执行权限(CentOS 6.x中不需要)
ls -lha /etc/rc.d/rc.local 
-rw-r--r--. 1 root root 473 3月   6 2015 /etc/rc.d/rc.local
chmod +x /etc/rc.d/rc.local

#查看当前运行级别(runlevel)
cat /etc/systemd/system/default.target
#修改运行级别(先删除/etc/systemd/system/default.target符合链接)
rm -f /etc/systemd/system/default.target
#运行级别3
ln -sf /lib/systemd/system/multi-user.target /etc/systemd/system/default.target
#运行级别5
ln -sf /lib/systemd/system/graphical.target /etc/systemd/system/default.target

#去掉一批自启动服务(systemctl list-unit-files|grep enabled)
#如果要停止当期服务,使用systemctl stop xxx 
systemctl disable auditd.service
systemctl disable avahi-daemon.service
systemctl disable firewalld.service
systemctl disable cups.service
systemctl disable irqbalance.service
systemctl disable iscsi.service
systemctl disable kdump.service
systemctl disable lvm2-monitor.service
systemctl disable mdmonitor.service
systemctl disable microcode.service
systemctl disable multipathd.service
systemctl disable NetworkManager.service
systemctl disable postfix.service
systemctl disable tuned.service
systemctl disable remote-fs.target
systemctl disable dm-event.socket
systemctl disable iscsid.socket
systemctl disable iscsiuio.socket
systemctl disable lvm2-lvmetad.socket

#安装
#使用系统默认安装的Chrony服务即可(http://blog.ifeeline.com/2554.html)
#1、ntpd.service #时间同步,不需要开机启动

2、nscd.service #缓存(默认没有DNS缓存)
有提供passwd, group, hosts, services,这里主要使用hosts
vi /etc/nscd.conf

#       logfile                 /var/log/nscd.log
        threads                 8
        max-threads             128
        server-user             nscd
#       stat-user               somebody
        debug-level             5
#       reload-count            5
        paranoia                no
#       restart-interval        3600


        enable-cache            hosts           yes
        positive-time-to-live   hosts           5
        negative-time-to-live   hosts           10
        suggested-size          hosts           211
        check-files             hosts           yes
        persistent              hosts           yes
        shared                  hosts           yes
        max-db-size             hosts           33554432
3、sysstat.service #系统性能
4、supervisord.service #进程监控

配置第三方YUM源:

#默认
yum repolist
base/7/x86_64        CentOS-7 - Base
extras/7/x86_64      CentOS-7 - Extras
updates/7/x86_64     CentOS-7 - Updates 

######
#安装epel,这个软件包,默认被extras收录,可以直接yum安装
yum install 
#也可以手动安装
wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
rpm -ivh epel-release-7-5.noarch.rpm
#确认
yum repolist
base/7/x86_64        CentOS-7 - Base
epel/x86_64          Extra Packages for Enterprise Linux 7 - x86_64
extras/7/x86_64      CentOS-7 - Extras 
updates/7/x86_64     CentOS-7 - Updates 

######
##-------ius源,它依赖epel(yum install epel-release)
#https://dl.iuscommunity.org/pub/ius/stable/CentOS/7/x86_64/repoview/
#来自这个源的软件包,习惯加一个u
wget https://rhel7.iuscommunity.org/ius-release.rpm
rpm -ivh ius-release.rpm

##--------Webtatic源,也依赖epel(yum install epel-release),这是一个理想的php(5.4-5.6,7),nginx的RPM源
#http://repo.webtatic.com/yum/el7/x86_64/RPMS/
#来自这个源的软件包,习惯加一个w
wget https://mirror.webtatic.com/yum/el7/webtatic-release.rpm
rpm -ivh webtatic-release.rpm

#安装PHP
yum install php71w-bcmath.x86_64 php71w-cli.x86_64 php71w-common.x86_64 php71w-fpm.x86_64 php71w-gd.x86_64 php71w-mbstring.x86_64 php71w-mcrypt.x86_64 php71w-mysqlnd.x86_64 php71w-opcache.x86_64 php71w-pdo.x86_64 php71w-pecl-apcu.x86_64 php71w-pecl-mongodb.x86_64 php71w-pecl-redis.x86_64 php71w-pgsql.x86_64 php71w-process.x86_64 php71w-recode.x86_64 php71w-soap.x86_64 php71w-tidy.x86_64 php71w-xml.x86_64 php71w-intl.x86_64 php71w-devel.x86_64

#安装PHP后修改对应文件权限
find . -user apache
/var/log/php-fpm		#要改
/var/cache/httpd
/var/cache/httpd/proxy
/var/lib/dav
/run/httpd/htcacheclean

find . -group apache
/usr/sbin/suexec
/var/cache/httpd
/var/cache/httpd/proxy
/var/lib/dav
/var/lib/php/wsdlcache		#要改	
/var/lib/php/session		#要改
/run/httpd
/run/httpd/htcacheclean


##--------remi源,也依赖epel(yum install epel-release)
##Remi源可以安装多个不同版本的PHP,而且扩展比较齐全,对于单一的服务部署,推荐使用Webtatic源
##否则可以用Remi源,安装完成后会自动添加服务管理脚本 /lib/systemd/system/php**-php-fpm.service
wget http://rpms.remirepo.net/enterprise/remi-release-7.rpm
rpm -Uvh remi-release-7.rpm
## 安装完成后,会对应多个仓库
-rw-r--r-- 1 root root  456 3月  21 06:28 remi-php54.repo
-rw-r--r-- 1 root root 1.3K 3月  21 06:28 remi-php70.repo
-rw-r--r-- 1 root root 1.3K 3月  21 06:28 remi-php71.repo
-rw-r--r-- 1 root root 1.3K 3月  21 06:28 remi-php72.repo
-rw-r--r-- 1 root root 2.6K 3月  21 06:28 remi.repo
-rw-r--r-- 1 root root  750 3月  21 06:28 remi-safe.repo
如果希望把某个版本作为主版本(就是安装到/usr/bin等目录),可以使用yum --enablerepo=remi-php71 install ****, 否则将独立安装。

// PHP 7.1
yum --enablerepo=remi install php71-php php71-php-bcmath php71-php-cli php71-php-common php71-php-devel php71-php-fpm php71-php-gd php71-php-intl php71-php-json php71-php-mbstring php71-php-mcrypt php71-php-mysqlnd php71-php-opcache php71-php-pdo php71-php-pecl-apcu php71-php-pecl-apcu-devel php71-php-pecl-crypto php71-php-pecl-igbinary php71-php-pecl-igbinary-devel php71-php-pecl-imagick php71-php-pecl-imagick-devel php71-php-pecl-mongodb php71-php-pecl-uuid php71-php-pecl-zip php71-php-pgsql php71-php-process php71-php-recode php71-php-soap php71-php-tidy php71-php-xml php71-php-xmlrpc php71-php-pecl-amqp php71-php-pecl-redis4 php71-php-pecl-swoole2 php71-php-pecl-uploadprogress php71-php-phalcon3

// PHP 5.4
// php54-php-pecl-swoole2 php54-php-phalcon3无对应
yum --enablerepo=remi install php54-php php54-php-bcmath php54-php-cli php54-php-common php54-php-devel php54-php-fpm php54-php-gd php54-php-intl php54-php-json php54-php-mbstring php54-php-mcrypt php54-php-mysqlnd php54-php-opcache php54-php-pdo php54-php-pecl-apcu php54-php-pecl-apcu-devel php54-php-pecl-crypto php54-php-pecl-igbinary php54-php-pecl-igbinary-devel php54-php-pecl-imagick php54-php-pecl-imagick-devel php54-php-pecl-mongodb php54-php-pecl-uuid php54-php-pecl-zip php54-php-pgsql php54-php-process php54-php-recode php54-php-soap php54-php-tidy php54-php-xml php54-php-xmlrpc php54-php-pecl-amqp php54-php-pecl-redis4 php54-php-pecl-uploadprogress 

// Remi源安装的配置放入到/opt/remi/php**/root/, 配置安装到了/etc/opt/remi/php**/(注:PHP5.5以下版本,配置在安装目录)

// 添加服务启动脚本(一般已经存在)
vi /usr/lib/systemd/system/php71-php-fpm.service
[Unit]
Description=The PHP FastCGI Process Manager
After=syslog.target network.target

[Service]
Type=notify
EnvironmentFile=/etc/opt/remi/php71/sysconfig/php-fpm
ExecStart=/opt/remi/php71/root/usr/sbin/php-fpm --nodaemonize
ExecReload=/bin/kill -USR2 $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

######
PHP-FPM以SOCK方式运行时,务必对SOCK文件用户和组权限做正确设置,比如:
listen.owner = www
listen.group = www
listen.mode = 0660

###### 
##为了避免意外更新,改为默认不启用
vi /etc/yum.repos.d/ius.repo	  #把ius的enable改为0
vi /etc/yum.repos.d/webtatic.repo #把webtatic的enable改为0
vi /etc/yum.repos.d/remi.repo	  #remi容器默认enable改为0

#######
##查看软件包的通用方法(先搜索,后安装)
yum repolist #查看启用仓库
yum --enablerepo=ius info php56u.x86_64 #查看某个源中的具体软件包信息
yum --disablerepo="*" --enablerepo="epel" list available | less #列出所有
yum --disablerepo="*" --enablerepo="ius" search php #源中搜索
yum install --enablerepo="ius" php56u.x86_64 #安装具体软件包

安装MySQL:(http://devdocs.magento.com/guides/v2.0/install-gde/prereq/mysql.html#instgde-prereq-mysql-centos)

#安装源
wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm 
rpm -ivh mysql-community-release-el7-5.noarch.rpm
#从源安装MySQL
yum -y install mysql-server
#MySQL初始化
mysql_secure_installation
#开启服务
systemctl start mysqld.service

————————————————————–
CentOS 7.X的如下变化最让人不习惯:
一、CentOS的Services使用了systemd来代替sysvinit管理

systemd的服务管理程序:
systemctl是主要的工具,它融合之前service和chkconfig的功能于一体。可以使用它永久性或只在当前会话中启用/禁用服务。

直接运行systemctl可以列出正在运行的服务状态:

[root@vfeelit ~]# systemctl
UNIT                                         LOAD   ACTIVE SUB       DESCRIPTION
proc-sys-fs-binfmt_misc.automount            loaded active waiting   Arbitrary Executable File Formats File System
sys-devices-pl...serial8250-tty-ttyS0.device loaded active plugged   /sys/devices/platform/serial8250/tty/ttyS0
sys-devices-pl...serial8250-tty-ttyS1.device loaded active plugged   /sys/devices/platform/serial8250/tty/ttyS1
sys-devices-pl...serial8250-tty-ttyS2.device loaded active plugged   /sys/devices/platform/serial8250/tty/ttyS2
sys-devices-pl...serial8250-tty-ttyS3.device loaded active plugged   /sys/devices/platform/serial8250/tty/ttyS3
sys-devices-vb...728-block-xvdb-xvdb1.device loaded active plugged   /sys/devices/vbd-51728/block/xvdb/xvdb1
sys-devices-vbd\x2d51728-block-xvdb.device   loaded active plugged   /sys/devices/vbd-51728/block/xvdb
sys-devices-vb...768-block-xvda-xvda1.device loaded active plugged   /sys/devices/vbd-768/block/xvda/xvda1
sys-devices-vbd\x2d768-block-xvda.device     loaded active plugged   /sys/devices/vbd-768/block/xvda
sys-devices-vif\x2d0-net-eth0.device         loaded active plugged   /sys/devices/vif-0/net/eth0
sys-devices-vif\x2d1-net-eth1.device         loaded active plugged   /sys/devices/vif-1/net/eth1
sys-module-configfs.device                   loaded active plugged   /sys/module/configfs
sys-subsystem-net-devices-eth0.device        loaded active plugged   /sys/subsystem/net/devices/eth0
sys-subsystem-net-devices-eth1.device        loaded active plugged   /sys/subsystem/net/devices/eth1
-.mount                                      loaded active mounted   /

## systemd-cgls树形方式展示
[root@vfeelit ~]# systemd-cgls
├─1 /usr/lib/systemd/systemd --switched-root --system --deserialize 23
├─user.slice
│ └─user-0.slice
│   └─session-5468.scope
│     ├─2837 sshd: root@pts/0    
│     ├─2845 -bash
│     ├─3003 systemd-cgls
│     └─3004 systemd-cgls
└─system.slice
  ├─php-fpm.service
  │ ├─ 2821 php-fpm: pool www              
  │ ├─ 2916 php-fpm: pool www              
  │ ├─ 2972 php-fpm: pool www              
  │ ├─32313 php-fpm: master process (/etc/php-fpm.conf
  │ ├─32316 php-fpm: pool www              
  │ ├─32317 php-fpm: pool www              
  │ ├─32318 php-fpm: pool www              
  │ ├─32319 php-fpm: pool www             
  │ ├─32320 php-fpm: pool www 

如何启动/关闭、启用/禁用服务
启动一个服务:systemctl start postfix.service
关闭一个服务:systemctl stop postfix.service
重启一个服务:systemctl restart postfix.service
显示一个服务的状态:systemctl status postfix.service
在开机时启用一个服务:systemctl enable postfix.service
在开机时禁用一个服务:systemctl disable postfix.service
查看服务是否开机启动:systemctl is-enabled postfix.service; echo $?
查看已启动的服务列表:systemctl list-unit-files | grep enabled

启用服务就是在当前“runlevel”的配置文件目录/etc/systemd/system/multi-user.target.wants/里,建立/usr/lib/systemd/system里面对应服务配置文件的软链接;禁用服务就是删除此软链接。(这种搞法非常常见)

[root@vfeelit multi-user.target.wants]# pwd
/etc/systemd/system/multi-user.target.wants
[root@vfeelit multi-user.target.wants]# ls -lah
crond.service -> /usr/lib/systemd/system/crond.service
nginx.service -> /usr/lib/systemd/system/nginx.service
nscd.service -> /usr/lib/systemd/system/nscd.service
ntpd.service -> /usr/lib/systemd/system/ntpd.service
php-fpm.service -> /usr/lib/systemd/system/php-fpm.service
rsyslog.service -> /usr/lib/systemd/system/rsyslog.service
sshd.service -> /usr/lib/systemd/system/sshd.service
supervisord.service -> /usr/lib/systemd/system/supervisord.service
sysstat.service -> /usr/lib/systemd/system/sysstat.service

CentOS 6.x中也是如此,但是语法已经不一样了。

二、修改系统运行级别

切换到运行级别
systemd使用比sysvinit的运行级更为自由的target替代。第3运行级用multi-user.target替代。第5运行级用graphical.target替代。runlevel3.target和runlevel5.target分别是指向 multi-user.target和graphical.target的符号链接。

可以使用下面的命令切换到“运行级别3 ”:
systemctl isolate multi-user.target或systemctl isolate runlevel3.target
可以使用下面的命令切换到“运行级别5 ”:
systemctl isolate graphical.target或systemctl isolate runlevel5.target

改变默认运行级别
systemd使用链接来指向默认的运行级别。在创建新的链接前,可以通过下面命令删除存在的链接: rm /etc/systemd/system/default.target

默认启动运行级别3 :
ln -sf /lib/systemd/system/multi-user.target /etc/systemd/system/default.target

默认启动运行级别5 :
ln -sf /lib/systemd/system/graphical.target /etc/systemd/system/default.target

systemd不使用/etc/inittab文件。

查看当前运行级别
旧的runlevel命令在systemd下仍然可以使用。可以继续使用它,尽管systemd使用 ‘target’ 概念(多个的 ‘target’ 可以同时激活)替换了之前系统的runlevel。等价的systemd命令是systemctl list-units –type=target

其他配置工具:
1、setup和ntsysv工具还是保留了,但是功能已大大减弱,以前ntsysv工具可以控制所有系统服务的自启动,现在只能控制少部分服务。
2、/etc/resolv.conf这个DNS配置文件没变。
3、/etc/sysconfig/network-scripts/ifcfg-ens192网卡配置文件名字和一些选项有所变化。
4、引导方式改用grub2引导,grub2有如下特点:1、模块化设计;2、支持多体系硬件架构;3、支持国际化多语言;4、独立内存管理;5、支持脚本语言。

最后再稍作对比总结,三个基本任务,查看启动的服务,设置服务是否开机启动,当期关闭和启动服务

查看启动的服务
chkconfig –list -> systemctl list-unit-files|grep enabled(/etc/systemd/system/multi-user.target.wants)
设置服务是否开机启动
chkconfig –levels 35 xxxx off -> systemctl enable postfix.service
当期关闭和启动服务
service xxx stop -> systemctl start postfix.service

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

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