月度归档:2014年02月

PHP框架Phalcon 之 使用命名空间

例子:

//index.php引导程序
<?php
try {
    $loader = new \Phalcon\Loader();

    $loader->registerDirs(array(
        '../app/controllers/',
    ))->register();

    $loader->registerNamespaces(
        array(
                'Ifeeline'    => "../app/library/Ifeeline/"
        )
    )->register();
    $di = new Phalcon\DI\FactoryDefault();
    $di->set('view', function(){
        $view = new \Phalcon\Mvc\View();
        $view->setViewsDir('../app/views/');
        return $view;
    });
    $application = new \Phalcon\Mvc\Application($di);
    echo $application->handle()->getContent();
} catch(\Phalcon\Exception $e) {
    echo "PhalconException: ", $e->getMessage();
}

控制器目录是必须注册到自动装载器的,只是为了能找到控制器类。然后添加控制器:


<?php
use Ifeeline\Users;
use Ifeeline\Admin\Login;

class IndexController extends \Phalcon\Mvc\Controller
{
        public function indexAction(){
                $users = new Users();
                $login = new Login();
                echo get_class()."In IndexController index Action.\n";
                $this->view->disable();
        }
}

以上使用了Ifeeline\Users和Ifeeline\Admin\Login类,然后在library建立Ifeeline目录,在Ifeeline目录中建立Admin目录:

//Ifeeline/Users.php
<?php
namespace Ifeeline;

class Users{
        public function __construct(){
                echo get_class($this)."\n";
        }
}

//Ifeeline/Admin/Login.php
<?php
namespace Ifeeline\Admin;

class Login{
	public function __construct(){
		echo get_class($this)."\n";
	}
}

访问index控制器输出:

Ifeeline\Users
Ifeeline\Admin\Login
IndexController In IndexController index Action.

Ifeeline\Users和Ifeeline\Admin\Login成功加载实例化,这个说明,注册的命名空间Ifeeline起了作用,所有以Ifeeline开头的类,都会到命名空间Ifeeline注册的目录中取寻找,比如Ifeeline\Admin\Login直接进入Ifeeline注册的目录寻找子目录Admin,再在里面寻找Login.php文件,然后加载这个文件并检查是否存在Ifeeline\Admin\Login类(名称空间+类名=全类名)。在这里,Admin作为一个子目录,那么Login.php的名称空间必须是Ifeeline\Admin,看起来,Ifeeline\Admin是Ifeeline子命名空间,这样理解也可以,不过需要知道的是,Ifeeline\Admin可以和Ifeeline命名空间没有父目录于子目录对应关系,因为完全可以把Ifeeline\Admin注册到另一个目录,这样以Ifeeline\Admin开头的类,就首先进入它指定的目录寻找类。

接下来把注册命名空间的代码去掉,然后修改为如下代码:

    $loader = new \Phalcon\Loader();

    $loader->registerDirs(array(
        '../app/controllers/',
        //'../app/models/',
        '../app/library/'
    ))->register();

其它不变,看看输出:

Ifeeline\Users
Ifeeline\Admin\Login
IndexController In IndexController index Action.

可以看到,输出没有变化。这个说明类也正确加载并实例化。这种做法实际是当要加载一个类时,就去注册的目录中扫描一遍直到找到那个类为止,比如要加载Ifeeline\Admin\Login类,先去app/controllers/目录中看看是否有Ifeeline目录,没有就马上退出来到/app/library目录,发现存储Ifeeline目录,接着继续坚持它之中是否存在Admin目录,发现存在继续在它之中查找Login.php文件,发现存在则加载文件然后坚持类是否存在(命名空间+类),注意,如果Login.php中没有指定命名空间,如下:

class Login{}

那么类是肯定找不到的,因为它要的Ifeeline\Admin\Login类,但是如果在没有给出命名空间的情况下,如下这样做:

class Ifeeline\Admin\Login{}

看起来是可以的,不过由于类名字符串遇到反斜杠,无法解析,导致错误。不过Ifeeline_Admin_Login字符串是正确的,我们来测试一下Phalcon中能不能加载这种类型的类。把Login.php改为:

<?php
//namespace Ifeeline\Admin;

class Ifeeline_Admin_Login{
        public function __construct(){
                echo get_class($this)."\n";
        }
}

然后在控制器中使用如下方法实例一个类:

$login = new Ifeeline_Admin_Login();
....

输出:

Ifeeline\Users
Ifeeline_Admin_Login
IndexController In IndexController index Action.

发现没有问题。这个也是PHP 5.3之前未支持命名空间版本的通行做法,任何时候都要使用完全类名,虽然有些框架也支持把前缀Ifeeline_注册到某个目录,Ifeeline_Admin_注册到其它目录,但是它只是解决了类存放问题,而无法解决总是要使用完全限定类名的事实。

最后总结一下,如果一个组件中要使用某个类,则需要使用use语句表明使用它,否则就要在使用时使用完全限定类名。定义类文件中使用namespace指定它的名空间,为了能找到正确路径,它应该存放在对应的目录结构中,除非你为名空间注册了路径。namespace指定的名空间最终会和类名串在一起用来比对类是否存在。

以上内容虽然不是PHP命名空间的全部,但是是核心部分。虽然实现上跟其它语言相比是个小集合,比如只能用use导入一个类而无法导入一个命名空间。加上有点怪异的语法,大概也只能用看着看着就习惯了来调侃了。

在模型中使用命名空间应该没有什么问题。不过在控制器中使用命名空间可能就点问题了。首先控制器类是在路由之后确定的,那么路由结束之后需要确定控制器的命名空间(可以设置路由器的默认命名空间和默认模块),如果应用是多模块的,路由之后还要确定模块名称(执行具体模块初始化),这样路由编写就复杂一些。具体参考MVC应用一节。

———————————————-
使用命名空间(Working with Namespaces)
Namespaces can be used to avoid class name collisions; this means that if you have two controllers in an application with the same name, a namespace can be used to differentiate them. Namespaces are also useful for creating bundles or modules.
命名空间可以用来避免类名冲突;这个意味着在你的应用中如果你有两个相同名字的控制器,命名空间可以用区分它们。命名空间也可以用来创建包或模块。

设置框架(Setting up the framework)
Using namespaces has some implications when loading the appropriate controller. To adjust the framework behavior to namespaces is necessary to perform one or all of the following tasks:
在加载适当的控制器时使用命名空间有些含蓄。为了调整框架的行为到命名空间有必要执行一个或所有以下任务:

Use an autoload strategy that takes into account the namespaces, for example with Phalcon\Loader:

<?php

$loader->registerNamespaces(
    array(
       'Store\Admin\Controllers'    => "../bundles/admin/controllers/",
       'Store\Admin\Models'    => "../bundles/admin/models/",
    )
);

Specify it in the routes as a separate parameter in the route’s paths:

<?php

$router->add(
    '/admin/users/my-profile',
    array(
        'namespace'  => 'Store\Admin',
        'controller' => 'Users',
        'action'     => 'profile',
    )
);

##Passing it as part of the route:
<?php

$router->add(
    '/:namespace/admin/users/my-profile',
    array(
        'namespace'  => 1,
        'controller' => 'Users',
        'action'     => 'profile',
    )
);

If you are only working with the same namespace for every controller in your application, then you can define a default namespace in the Dispatcher, by doing this, you don’t need to specify a full class name in the router path:
如果在你的应用中对于所有控制器都仅工作在相同的命名空间,这样你可以在Dispatcher中定义一个默认的命名空间,通过这样做,你不需要在路由器路径中指定全类名:

<?php

//Registering a dispatcher
$di->set('dispatcher', function() {
    $dispatcher = new \Phalcon\Mvc\Dispatcher();
    $dispatcher->setDefaultNamespace('Store\Admin\Controllers');
    return $dispatcher;
});

控制器加入命名空间(Controllers in Namespaces)
The following example shows how to implement a controller that use namespaces:

<?php

namespace Store\Admin\Controllers;

class UsersController extends \Phalcon\Mvc\Controller
{

    public function indexAction()
    {

    }

    public function profileAction()
    {

    }

}

模型加入命名空间(Models in Namespaces)
Take the following into consideration when using models in namespaces:

<?php

namespace Store\Models;

class Robots extends \Phalcon\Mvc\Model
{

}

If models have relationships they must include the namespace too:
如果模型有关系它们也必须包含命名空间:

<?php

namespace Store\Models;

class Robots extends Phalcon\Mvc\Model
{
    public function initialize()
    {
        $this->hasMany('id', 'Store\Models\Parts', 'robots_id', array(
            'alias' => 'parts'
        ));
    }
}

In PHQL you must write the statements including namespaces:
在PHQL中你必须把命名空间写入语句:

<?php

$phql = 'SELECT r.* FROM Store\Models\Robots r JOIN Store\Models\Parts p';

PHP框架Phalcon 之 分发控制器(Dispatching Controllers)

这里的主题是Dispatching Controllers,一般应该叫分发控制器,叫调度控制器也可以。Phalcon中这个调度过程由Phalcon\Mvc\Dispatcher完成,它直接叫分发器(dispatcher),在其它一些框架中,对应这个分发器叫前端控制器,不过是一个意思。

Phalcon\Mvc\Dispatcher实现了Phalcon\DI\InjectionAwareInterface接口,所以DI则资源会映射到这个类属性(在用到的时候)。

<?php
class MyController extends \Phalcon\Mvc\Controller
{
        public function indexAction(){
                print_r($this->dispatcher);

                $this->view->disable();
        }
}

//这个方法输出如下内容
Phalcon\Mvc\Dispatcher Object
(
    [_dependencyInjector:protected] => Phalcon\DI\FactoryDefault Object
    [_eventsManager:protected] => 
    [_activeHandler:protected] => MyController Object
        (
            [_dependencyInjector:protected] => Phalcon\DI\FactoryDefault Object
            [_eventsManager:protected] => 
            [dispatcher] => Phalcon\Mvc\Dispatcher Object
        )
    [_finished:protected] => 1
    [_forwarded:protected] => 
    [_moduleName:protected] => 
    [_namespaceName:protected] => 
    [_handlerName:protected] => my
    [_actionName:protected] => index
    [_params:protected] => Array()
    [_returnedValue:protected] => 
    [_lastHandler:protected] => 
    [_defaultNamespace:protected] => 
    [_actionSuffix:protected] => Action
    [_isExactHandler:protected] => 
    [_previousHandlerName:protected] => 
    [_previousActionName:protected] => 
    [_handlerSuffix:protected] => Controller
    [_defaultHandler:protected] => index
    [_defaultAction:protected] => index
)

对应的字段一般都可以通过getXxx获取,_finished表示分发是否完成,如果有forward它会标记为false,分发循环就靠这个标记判断是否结束分发循环。_activeHandler记录了当前被激活的控制器实例,_namespaceName、_moduleName、_handlerName、_actionName、_params这些信息是根据路由信息得出的。_actionSuffix和_handlerSuffix指示了动作和控制的后缀名,这个在定位控制器和方法是用到。

分发器内部有一系列的事件,从dispatch()方法开始,内部开始分发循环,用如下例子跟踪一下:

//引导程序
<?php
try {
    $loader = new \Phalcon\Loader();
    $loader->registerDirs(array(
        '../app/controllers/',
        '../app/models/',
	'../app/library/'
    ))->register();

    $di = new Phalcon\DI\FactoryDefault();

    $dispatcher = new \Phalcon\Mvc\Dispatcher();
    $dispatcher->setDI($di);
    $dispatcher->setControllerName('index');
    $dispatcher->setActionName('index');
    $dispatcher->setParams(array());
    
    $eventsManager = $di->get("eventsManager");
    $eventsManager->attach('dispatch',new MyDispatcherListener());
    $dispatcher->setEventsManager($eventsManager);
    
    $dispatcher->dispatch();
} catch(\Phalcon\Exception $e) {
    echo "PhalconException: ", $e->getMessage();
}

//IndexController
<?php

class IndexController extends \Phalcon\Mvc\Controller
{
        public function onConstruct(){
                echo "In IndexController onConstruct Action.\n";
        }
        public function initialize(){
                echo "In IndexController initialize Action.\n";
        }
        public function beforeExecuteRoute(){
                echo "In IndexController beforeExecuteRoute Action.\n";
        }

        public function afterExecuteRoute(){
                echo "In IndexController afterExecuteRoute Action.\n";
        }
        public function indexAction(){
                echo "In IndexController index Action.\n";
        }
}

//MyDispatcherListener
<?php
class MyDispatcherListener{
	public function __construct(){
		echo "In MyDispatcherListener Construct.\n";
	}
	public function beforeDispatchLoop(){
		echo "In MyDispatcherListener beforeDispatchLoop.\n";
	}
	public function beforeDispatch(){
		echo "In MyDispatcherListener beforeDispatch.\n";
	}
        public function beforeExecuteRoute(){
                echo "In MyDispatcherListener beforeExecuteRoute.\n";
        }
        public function initialize(){
                echo "In MyDispatcherListener initialize.\n";
        }
        public function afterExecuteRoute(){
                echo "In MyDispatcherListener afterExecuteRoute.\n";
        }
        public function beforeNotFoundAction(){
                echo "In MyDispatcherListener beforeNotFoundAction.\n";
        }
        public function beforeException(){
                echo "In MyDispatcherListener beforeException.\n";
        }
        public function afterDispatch(){
                echo "In MyDispatcherListener afterDispatch.\n";
        }
        public function afterDispatchLoop(){
                echo "In MyDispatcherListener afterDispatchLoop.\n";
        }
}

//输出
In MyDispatcherListener Construct.
In MyDispatcherListener beforeDispatchLoop.
In MyDispatcherListener beforeDispatch.
In IndexController onConstruct Action.
In MyDispatcherListener beforeExecuteRoute.
In IndexController beforeExecuteRoute Action.
In IndexController initialize Action.
In IndexController index Action.
In MyDispatcherListener afterExecuteRoute.
In MyDispatcherListener afterDispatch.
In IndexController afterExecuteRoute Action.
In MyDispatcherListener afterDispatchLoop

MyDispatcherListener构造函数先调用,接着触发beforeDispatchLoop,然后触发beforeDispatch,调用调用控制器的onConstruct方法,这个说明beforeDispatch触发之后控制器实例已经生成,然后触发了beforeExecuteRoute(绑定了两个方法),这个是在执行具体的控制器方法前的挂载点,接下来触发initialize,控制器的initialize方法被执行,这个是在调用具体的控制方法前对控制器进行初始化,注意,MyDispatcherListener也有一个initialize方法,它并没有执行,说明initialize只能针对控制器,之后调用控制器的具体方法,之后触发afterExecuteRoute,此时控制的afterExecuteRoute没有触发,这个跟预想的不一致,它是在afterDispatch之后才执行,最后执行afterDispatchLoop。

监听器绑定到事件,事件触发时所有绑定到事件的监听器应该按照顺序执行,比如:

    $eventsManager->attach('dispatch',new MyDispatcherListener());
    $eventsManager->attach('dispatch',new MyDispatcherListenerTwo());

当afterExecuteRoute触发时,MyDispatcherListener 和 MyDispatcherListenerTwo的afterExecuteRoute方法应该按照注册顺序执行。不过这个对应控制器监听器来说,好像是错误,它总是先执行afterDispatch之后,才执行控制器的afterExecuteRoute,而这个afterExecuteRoute在afterDispatch之前已经触发了,这里应该做了点特殊处理,实际上,这个操作是符合逻辑的,控制器的Acion执行后就触发afterExecuteRoute,但是控制器的Action方法只有afterDispatch之后才算完成,所以在afterDispatch之后才触发控制器的afterExecuteRoute。

从上面例子可以看到,我们可以直接实例化dispatcher,手动开始路由分发而不需要application,那么application做了什么工作呢,它实现了和手动分发时一样的工作,为dispatcher指定DI和指定事件管理,根据路由器的路由结果设置dispatcher,调用视图start()方法渲染视图等等。

下面的例子测试一下forward的情况:

//index.php 引导程序
<?php
try {
    $loader = new \Phalcon\Loader();
    $loader->registerDirs(array(
        '../app/controllers/',
        '../app/models/',
	'../app/library/'
    ))->register();

    $di = new Phalcon\DI\FactoryDefault();

    $di->set('view', function(){
        $view = new \Phalcon\Mvc\View();
        $view->setViewsDir('../app/views/');
        return $view;
    });

    $di->set('dispatcher',function() use($di){
	$dispatcher = new \Phalcon\Mvc\Dispatcher();
    	$dispatcher->setDI($di);

    	$eventsManager = $di->get("eventsManager");
    	$eventsManager->attach('dispatch',new MyDispatcherListener());

	$dispatcher->setEventsManager($eventsManager);

	return $dispatcher;
    });

    $application = new \Phalcon\Mvc\Application($di);
    echo $application->handle()->getContent();
} catch(\Phalcon\Exception $e) {
    echo "PhalconException: ", $e->getMessage();
}

//IndexController
<?php

class IndexController extends \Phalcon\Mvc\Controller
{
        public function onConstruct(){
                echo "In IndexController onConstruct Action.\n";
        }
        public function initialize(){
                echo "In IndexController initialize Action.\n";
        }
        public function beforeExecuteRoute(){
                echo "In IndexController beforeExecuteRoute Action.\n";
        }

        public function afterExecuteRoute(){
                echo "In IndexController afterExecuteRoute Action.\n";
        }
        public function indexAction(){
                echo "In IndexController index Action.\n";
                $this->dispatcher->forward(array('controller' => 'index', 'action' => 'test'));
                echo "After forward.\n";
                $this->view->disable();
        }

        public function testAction(){
                echo "In IndexController test Action.\n";
                $this->view->disable();
        }
}

//MyDispatcherListener
<?php
class MyDispatcherListener{
	public function __construct(){
		echo "In MyDispatcherListener Construct.\n";
	}
	public function beforeDispatchLoop(){
		echo "In MyDispatcherListener beforeDispatchLoop.\n";
	}
	public function beforeDispatch(){
		echo "In MyDispatcherListener beforeDispatch.\n";
	}
        public function beforeExecuteRoute(){
                echo "In MyDispatcherListener beforeExecuteRoute.\n";
        }
        public function initialize(){
                echo "In MyDispatcherListener initialize.\n";
        }
	
        public function afterExecuteRoute(){
                echo "In MyDispatcherListener afterExecuteRoute.\n";
        }
        public function beforeNotFoundAction(){
                echo "In MyDispatcherListener beforeNotFoundAction.\n";
        }
        public function beforeException(){
                echo "In MyDispatcherListener beforeException.\n";
        }
        public function afterDispatch(){
                echo "In MyDispatcherListener afterDispatch.\n";
        }
        public function afterDispatchLoop(){
                echo "In MyDispatcherListener afterDispatchLoop.\n";
        }
}

//输出
In MyDispatcherListener Construct.
In MyDispatcherListener beforeDispatchLoop.
In MyDispatcherListener beforeDispatch.
In IndexController onConstruct Action.
In MyDispatcherListener beforeExecuteRoute.
In IndexController beforeExecuteRoute Action.
In IndexController initialize Action.
In IndexController index Action.
After forward.
In MyDispatcherListener afterExecuteRoute.
//----------
In MyDispatcherListener beforeDispatch.
In MyDispatcherListener beforeExecuteRoute.
In IndexController beforeExecuteRoute Action.
In IndexController test Action.
In MyDispatcherListener afterExecuteRoute.
In MyDispatcherListener afterDispatch.
In IndexController afterExecuteRoute Action.
//----------
In MyDispatcherListener afterDispatchLoop.

首先,调用forward之后,不是马上开始新的分发,而是在Action方法的代码执行完后才开始,所以最好在forward之后执行return语句让其直接返回。在执行了afterExecuteRoute之后,就开始新的分发,原本流程剩余的事件不会触发(按照新的分发触发新的事件)。

以上用到的测试程序:https://github.com/vfeelit/Phalcon_Dispatcher

——————————————–
–以下官方文档
调度控制器(Dispatching Controllers)
Phalcon\Mvc\Dispatcher is the component responsible for instantiating controllers and executing the required actions on them in an MVC application. Understanding its operation and capabilities helps us get more out of the services provided by the framework.
在一个MVC应用中Phalcon\Mvc\Dispatcher组件为实例化控制器和执行要求的动作的代表。

循环调度(The Dispatch Loop)
This is an important process that has much to do with the MVC flow itself, especially with the controller part. The work occurs within the controller dispatcher. The controller files are read, loaded, and instantiated. Then the required actions are executed. If an action forwards the flow to another controller/action, the controller dispatcher starts again. To better illustrate this, the following example shows approximately the process performed within Phalcon\Mvc\Dispatcher:
这是一个重要的过程,在MVC流本身有很多工作要做,特别是关于控制器部分。这个工作发生在控制分发器内。控制器文件独立加载和实例化。然后要求的动作被执行。如果一个动作转移它的流到另一个控制器或动作,控制器分发器从新开始。为了更好说明这个,下面的例子展示在Phalcon\Mvc\Dispatcher内类似的执行过程:

<?php

//Dispatch loop
while (!$finished) {

    $finished = true;

    $controllerClass = $controllerName . "Controller";

    //Instantiating the controller class via autoloaders
    $controller = new $controllerClass();

    // Execute the action
    call_user_func_array(array($controller, $actionName . "Action"), $params);

    // '$finished' should be reloaded to check if the flow
    // was forwarded to another controller
    $finished = true;
}

The code above lacks validations, filters and additional checks, but it demonstrates the normal flow of operation in the dispatcher.

循环调度事件(Dispatch Loop Events)
Phalcon\Mvc\Dispatcher is able to send events to an EventsManager if it is present. Events are triggered using the type “dispatch”. Some events when returning boolean false could stop the active operation. The following events are supported:
Phalcon\Mvc\Dispatcher可以发送事件到一个事件管理器。事件被触发通过使用类型dispatch。一些事件当返回布尔类型false可以停止当前活动的操作。如下事件被支持:

Event Name Triggered Can stop operation? Срабатывает для
beforeDispatchLoop Triggered before entering in the dispatch loop. At this point the dispatcher don’t know if the controller or the actions to be executed exist. The Dispatcher only knows the information passed by the Router. Yes Listeners
beforeDispatch Triggered after entering in the dispatch loop. At this point the dispatcher don’t know if the controller or the actions to be executed exist. The Dispatcher only knows the information passed by the Router. Yes Listeners
beforeExecuteRoute Triggered before executing the controller/action method. At this point the dispatcher has been initialized the controller and know if the action exist. Yes Listeners/Controllers
initialize Allow to globally initialize the controller in the request No Controllers
afterExecuteRoute Triggered after executing the controller/action method. As operation cannot be stopped, only use this event to make clean up after execute the action No Listeners/Controllers
beforeNotFoundAction Triggered when the action was not found in the controller Yes Listeners
beforeException Triggered before the dispatcher throws any exception Yes Listeners
afterDispatch Triggered after executing the controller/action method. As operation cannot be stopped, only use this event to make clean up after execute the action Yes Listeners
afterDispatchLoop Triggered after exiting the dispatch loop No Listeners

The INVO tutorial shows how to take advantage of dispatching events implementing a security filter with Acl
INVO例子展示如果使用dispatcher事件通过实现一个Acl安全过滤器

The following example demonstrates how to attach listeners to this component:

<?php

use Phalcon\Mvc\Dispatcher as MvcDispatcher,
    Phalcon\Events\Manager as EventsManager;

$di->set('dispatcher', function(){

    //Create an event manager
    $eventsManager = new EventsManager();

    //Attach a listener for type "dispatch"
    $eventsManager->attach("dispatch", function($event, $dispatcher) {
        //...
    });

    $dispatcher = new MvcDispatcher();

    //Bind the eventsManager to the view component
    $dispatcher->setEventsManager($eventsManager);

    return $dispatcher;

}, true);

An instantiated controller automatically acts as a listener for dispatch events, so you can implement methods as callbacks:
控制器扮演分发器事件的监听器(注意,实际只要三个事件能被控制器监听)

<?php

class PostsController extends \Phalcon\Mvc\Controller
{

    public function beforeExecuteRoute($dispatcher)
    {
        // Executed before every found action
    }

    public function afterExecuteRoute($dispatcher)
    {
        // Executed after every found action
    }

}

转发到其他动作(Forwarding to other actions)
The dispatch loop allows us to forward the execution flow to another controller/action. This is very useful to check if the user can access to certain options, redirect users to other screens or simply reuse code.

<?php

class PostsController extends \Phalcon\Mvc\Controller
{

    public function indexAction()
    {

    }

    public function saveAction($year, $postTitle)
    {

        // .. store some product and forward the user

        // Forward flow to the index action
        $this->dispatcher->forward(array(
            "controller" => "post",
            "action" => "index"
        ));
    }

}

Keep in mind that making a “forward” is not the same as making an HTTP redirect. Although they apparently got the same result. The “forward” doesn’t reload the current page, all the redirection occurs in a single request, while the HTTP redirect needs two requests to complete the process.
记住forward跟HTTP重定向是不一样的。尽管他们视乎获得一样的结果。forward不会重新加载当前也,所有的重定向发生在当前请求中,HTTP重定向需要两次请求来完成这个过程。

More forwarding examples:

<?php

// Forward flow to another action in the current controller
$this->dispatcher->forward(array(
    "action" => "search"
));

// Forward flow to another action in the current controller
// passing parameters
$this->dispatcher->forward(array(
    "action" => "search",
    "params" => array(1, 2, 3)
));

A forward action accepts the following parameters:

Parameter Triggered
controller A valid controller name to forward to.
action A valid action name to forward to.
params An array of parameters for the action
namespace A valid namespace name where the controller is part of

准备参数(Preparing Parameters)
Thanks to the hooks points provided by Phalcon\Mvc\Dispatcher you can easily adapt your application to any URL schema:
利用Dispatcher提供的勾你可以把URL搞成任意格式

For example, you want your URLs look like: http://example.com/controller/key1/value1/key2/value

Parameters by default are passed as they come in the URL to actions, you can transform them to the desired schema:

<?php

use Phalcon\Dispatcher,
    Phalcon\Mvc\Dispatcher as MvcDispatcher,
    Phalcon\Events\Manager as EventsManager;

$di->set('dispatcher', function() {

    //Create an EventsManager
    $eventsManager = new EventsManager();

    //Attach a listener
    $eventsManager->attach("dispatch:beforeDispatchLoop", function($event, $dispatcher) {

        $keyParams = array();
        $params = $dispatcher->getParams();

        //Use odd parameters as keys and even as values
        foreach ($params as $number => $value) {
            if ($number & 1) {
                $keyParams[$params[$number - 1]] = $value;
            }
        }

        //Override parameters
        $dispatcher->setParams($keyParams);
    });

    $dispatcher = new MvcDispatcher();
    $dispatcher->setEventsManager($eventsManager);

    return $dispatcher;
});

If the desired schema is: http://example.com/controller/key1:value1/key2:value, the following code is required:

<?php

use Phalcon\Dispatcher,
    Phalcon\Mvc\Dispatcher as MvcDispatcher,
    Phalcon\Events\Manager as EventsManager;

$di->set('dispatcher', function() {

    //Create an EventsManager
    $eventsManager = new EventsManager();

    //Attach a listener
    $eventsManager->attach("dispatch:beforeDispatchLoop", function($event, $dispatcher) {

        $keyParams = array();
        $params = $dispatcher->getParams();

        //Explode each parameter as key,value pairs
        foreach ($params as $number => $value) {
            $parts = explode(':', $value);
            $keyParams[$parts[0]] = $parts[1];
        }

        //Override parameters
        $dispatcher->setParams($keyParams);
    });

    $dispatcher = new MvcDispatcher();
    $dispatcher->setEventsManager($eventsManager);

    return $dispatcher;
});

获取参数(Getting Parameters)
When a route provides named parameters you can receive them in a controller, a view or any other component that extends Phalcon\DI\Injectable.
当一条路由提供命名参数你可以在控制器,视图或任何其它继承了Phalcon\DI\Injectable组件中获取它们。

<?php

class PostsController extends \Phalcon\Mvc\Controller
{

    public function indexAction()
    {

    }

    public function saveAction()
    {

        // Get the post's title passed in the URL as parameter
        // or prepared in an event
        $title = $this->dispatcher->getParam("title");

        // Get the post's year passed in the URL as parameter
        // or prepared in an event also filtering it
        $year = $this->dispatcher->getParam("year", "int");
    }

}

准备行动(Preparing actions)
You can also define an arbitrary schema for actions before be dispatched.

转换动作名(Camelize action names)
If the original URL is: http://example.com/admin/products/show-latest-products, and for example you want to camelize ‘show-latest-products’ to ‘ShowLatestProducts’, the following code is required:

<?php

use Phalcon\Text,
    Phalcon\Mvc\Dispatcher as MvcDispatcher,
    Phalcon\Events\Manager as EventsManager;

$di->set('dispatcher', function() {

    //Create an EventsManager
    $eventsManager = new EventsManager();

    //Camelize actions
    $eventsManager->attach("dispatch:beforeDispatchLoop", function($event, $dispatcher) {
        $dispatcher->setActionName(Text::camelize($dispatcher->getActionName()));
    });

    $dispatcher = new MvcDispatcher();
    $dispatcher->setEventsManager($eventsManager);

    return $dispatcher;
});

删除遗留的扩展名(Remove legacy extensions)
If the original URL always contains a ‘.php’ extension:

http://example.com/admin/products/show-latest-products.php http://example.com/admin/products/index.php

You can remove it before dispatch the controller/action combination:

<?php

use Phalcon\Mvc\Dispatcher as MvcDispatcher,
    Phalcon\Events\Manager as EventsManager;

$di->set('dispatcher', function() {

    //Create an EventsManager
    $eventsManager = new EventsManager();

    //Remove extension before dispatch
    $eventsManager->attach("dispatch:beforeDispatchLoop", function($event, $dispatcher) {

        //Remove extension
        $action = preg_replace('/\.php$/', '', $dispatcher->getActionName());

        //Override action
        $dispatcher->setActionName($action);
    });

    $dispatcher = new MvcDispatcher();
    $dispatcher->setEventsManager($eventsManager);

    return $dispatcher;
});

注入模型实例(Inject model instances)
In this example, the developer wants to inspect the parameters that an action will receive in order to dynamically inject model instances.

The controller looks like:

<?php

use Phalcon\Text,
    Phalcon\Mvc\Dispatcher as MvcDispatcher,
    Phalcon\Events\Manager as EventsManager;

$di->set('dispatcher', function() {

    //Create an EventsManager
    $eventsManager = new EventsManager();

    $eventsManager->attach("dispatch:beforeDispatchLoop", function($event, $dispatcher) {

        //Possible controller class name
        $controllerName =   Text::camelize($dispatcher->getControllerName()) . 'Controller';

        //Possible method name
        $actionName = $dispatcher->getActionName() . 'Action';

        try {

            //Get the reflection for the method to be executed
            $reflection = new \ReflectionMethod($controllerName, $actionName);

            //Check parameters
            foreach ($reflection->getParameters() as $parameter) {

                //Get the expected model name
                $className = $parameter->getClass()->name;

                //Check if the parameter expects a model instance
                if (is_subclass_of($className, 'Phalcon\Mvc\Model')) {

                    $model = $className::findFirstById($dispatcher->getParams()[0]);

                    //Override the parameters by the model instance
                    $dispatcher->setParams(array($model));
                }
            }

        } catch (\Exception $e) {
            //An exception has occurred, maybe the class or action does not exist?
        }

    });

    $dispatcher = new MvcDispatcher();
    $dispatcher->setEventsManager($eventsManager);

    return $dispatcher;
});

The above example has been simplified for academic purposes. A developer can improve it to inject any kind of dependency or model in actions before be executed.
(这个搞法很难想到应用场景)

处理 Not-Found 错误(Handling Not-Found Exceptions)
Using the EventsManager it’s possible to insert a hook point before the dispatcher throws an exception when the controller/action combination wasn’t found:
使用dispatcher的勾勾,当控制器方法无法找到时抛出异常前做一些处理。

<?php

use Phalcon\Dispatcher,
    Phalcon\Mvc\Dispatcher as MvcDispatcher,
    Phalcon\Events\Manager as EventsManager,
    Phalcon\Mvc\Dispatcher\Exception as DispatchException;

$di->set('dispatcher', function() {

    //Create an EventsManager
    $eventsManager = new EventsManager();

    //Attach a listener
    $eventsManager->attach("dispatch:beforeException", function($event, $dispatcher, $exception) {

        //Handle 404 exceptions
        if ($exception instanceof DispatchException) {
            $dispatcher->forward(array(
                'controller' => 'index',
                'action' => 'show404'
            ));
            return false;
        }

        //Alternative way, controller or action doesn't exist
        if ($event->getType() == 'beforeException') {
            switch ($exception->getCode()) {
                case \Phalcon\Dispatcher::EXCEPTION_HANDLER_NOT_FOUND:
                case \Phalcon\Dispatcher::EXCEPTION_ACTION_NOT_FOUND:
                    $dispatcher->forward(array(
                        'controller' => 'index',
                        'action' => 'show404'
                    ));
                    return false;
            }
        }
    });

    $dispatcher = new \Phalcon\Mvc\Dispatcher();

    //Bind the EventsManager to the dispatcher
    $dispatcher->setEventsManager($eventsManager);

    return $dispatcher;

}, true);

Of course, this method can be moved onto independent plugin classes, allowing more than one class take actions when an exception is produced in the dispatch loop:

<?php

use Phalcon\Mvc\Dispatcher,
    Phalcon\Events\Event,
    Phalcon\Mvc\Dispatcher\Exception as DispatchException;

class ExceptionsPlugin
{
    public function beforeException(Event $event, Dispatcher $dispatcher, $exception)
    {

        //Handle 404 exceptions
        if ($exception instanceof DispatchException) {
            $dispatcher->forward(array(
                'controller' => 'index',
                'action' => 'show404'
            ));
            return false;
        }

        //Handle other exceptions
        $dispatcher->forward(array(
            'controller' => 'index',
            'action' => 'show503'
        ));

        return false;
    }
}

Only exceptions produced by the dispatcher and exceptions produced in the executed action are notified in the ‘beforeException’ events. Exceptions produced in listeners or controller events are redirected to the latest try/catch.

自定义调度器(Implementing your own Dispatcher)
The Phalcon\Mvc\DispatcherInterface interface must be implemented to create your own dispatcher replacing the one provided by Phalcon.

PHP框架Phalcon 之 路由

路由(Routing)
The router component allows defining routes that are mapped to controllers or handlers that should receive the request. A router simply parses a URI to determine this information. The router has two modes: MVC mode and match-only mode. The first mode is ideal for working with MVC applications.
路由器组件允许定义映射到将由控制器或处理器接收的路由。路由器简单解析URI来决定这些信息。路由器有两个模式:MVC模式和仅匹配模式。第一个模式在MVC应用使用是典型的。

定义路由(Defining Routes)
Phalcon\Mvc\Router provides advanced routing capabilities. In MVC mode, you can define routes and map them to controllers/actions that you require. A route is defined as follows:
Phalcon\Mvc\Router提供了先进的路由能力。在MVC模式,你可以定义路由并映射它们到你要求的控制器或动作。一个路由像如下定义:

// Create the router
$router = new \Phalcon\Mvc\Router();

//Define a route
$router->add(
    "/admin/users/my-profile",
    array(
        "controller" => "users",
        "action"     => "profile",
    )
);

//Another route
$router->add(
    "/admin/users/change-password",
    array(
        "controller" => "users",
        "action"     => "changePassword",
    )
);

$router->handle();

The method add() receives as first parameter a pattern and optionally a set of paths as second parameter. In this case, if the URI is exactly: /admin/users/my-profile, then the “users” controller with its action “profile” will be executed. Currently, the router does not execute the controller and action, it only collects this information to inform the correct component (ie. Phalcon\Mvc\Dispatcher) that this is controller/action it should to execute.
方法add()接收一个模式作为第一参数,路径的集合是可选的并可作为第二参数。在这个情况下,如果URI是/admin/users/my-profile,那么users控制器和它的profile动作将被执行。此时,路由器不会执行控制器和动作,它仅收集这些信息去通知正确的组件(例如Phalcon\Mvc\Dispatcher),由它来让控制器或动作去执行。

An application can have many paths, define routes one by one can be a cumbersome task. In these cases we can create more flexible routes:
一个应用可能有很多路径,一个个定义路由是一个笨重的任务。在这个情况下我们可以创建更加灵活的路由:

<?php

// Create the router
$router = new \Phalcon\Mvc\Router();

//Define a route
$router->add(
    "/admin/:controller/a/:action/:params",
    array(
        "controller" => 1,
        "action"     => 2,
        "params"     => 3,
    )
);

In the example above, using wildcards we make a route valid for many URIs. For example, by accessing the following URL (/admin/users/a/delete/dave/301) then:
比如上面的例子,使用使用通配符我们创建一个路由验证为很多URI。比如,访问下面的URL(/admin/users/a/delete/dave/301):

Controller users
Action delete
Parameter dave
Parameter 301

The method add() receives a pattern that optionally could have predefined placeholders and regular expression modifiers. All the routing patterns must start with a slash character (/). The regular expression syntax used is the same as the PCRE regular expressions. Note that, it is not necessary to add regular expression delimiters. All routes patterns are case-insensitive.
方法add()接收一个模式,可选地可以预先定义占位符和正则表达式修饰符。所有的路由模式必须以左斜杠字符(/)开始。正则表达式使用和PCRE正则表达式一样的语法。注意,没有必要去添加正则表达式分隔符。所有路由模式是大小写敏感的。

The second parameter defines how the matched parts should bind to the controller/action/parameters. Matching parts are placeholders or subpatterns delimited by parentheses (round brackets). In the example given above, the first subpattern matched (:controller) is the controller part of the route, the second the action and so on.
第二参数定义了匹配部分如何绑定到控制器/动作/参数。匹配部分是占位符或通过分隔符(小括号)分隔的子模式。在上面给出的例子,第一个子模式(:controller)是路由的控制器部分,第二个是动作等等。

These placeholders help writing regular expressions that are more readable for developers and easier to understand. The following placeholders are supported:
这些占位符可以帮开发者编写更加易读和易懂正则表达式。支持如下占位符:

Placeholder Regular Expression Usage
/:module /([a-zA-Z0-9_-]+) Matches a valid module name with alpha-numeric characters only
/:controller /([a-zA-Z0-9_-]+) Matches a valid controller name with alpha-numeric characters only
/:action /([a-zA-Z0-9_]+) Matches a valid action name with alpha-numeric characters only
/:params (/.*)* Matches a list of optional words separated by slashes. Use only this placeholder at the end of a route
/:namespace /([a-zA-Z0-9_-]+) Matches a single level namespace name
/:int /([0-9]+) Matches an integer parameter

(所谓占位符名字是定死的)

Controller names are camelized, this means that characters (-) and (_) are removed and the next character is uppercased. For instance, some_controller is converted to SomeController.
控制器明确是去杠的,这意味着字符(-)和(_)被移除并且下一个字符是大写的。举例,some_controller被转换成SomeController。

Since you can add many routes as you need using add(), the order in which routes are added indicate their relevance, latest routes added have more relevance than first added. Internally, all defined routes are traversed in reverse order until Phalcon\Mvc\Router finds the one that matches the given URI and processes it, while ignoring the rest.
可以使用add()方法你需要的的多条路由,……。内部,所以定义的路由以相反的顺序遍历,直到Phalcon\Mvc\Router找到一个跟给定的URI匹配的并处理才停止并忽略剩余的。(就是最后定义的路由最优先匹配)

参数名称(Parameters with Names)
The example below demonstrates how to define names to route parameters:
以下例子演示如果定义名称到路由参数:

<?php

$router->add(
    "/news/([0-9]{4})/([0-9]{2})/([0-9]{2})/:params",
    array(
        "controller" => "posts",
        "action"     => "show",
        "year"       => 1, // ([0-9]{4})
        "month"      => 2, // ([0-9]{2})
        "day"        => 3, // ([0-9]{2})
        "params"     => 4, // :params
    )
);

In the above example, the route doesn’t define a “controller” or “action” part. These parts are replaced with fixed values (“posts” and “show”). The user will not know the controller that is really dispatched by the request. Inside the controller, those named parameters can be accessed as follows:
以上例子,路由没有定义“controller” or “action”部分。这些部分被替换为固定值(posts” and “show”)。通过请求用户将不知道真实被分发的控制器。在控制器内部,那些命名参数可以如下这样访问:

<?php

class PostsController extends \Phalcon\Mvc\Controller
{

    public function indexAction()
    {

    }

    public function showAction()
    {

        // Return "year" parameter
        $year = $this->dispatcher->getParam("year");

        // Return "month" parameter
        $month = $this->dispatcher->getParam("month");

        // Return "day" parameter
        $day = $this->dispatcher->getParam("day");

    }

}

Note that the values of the parameters are obtained from the dispatcher. This happens because it is the component that finally interacts with the drivers of your application. Moreover, there is also another way to create named parameters as part of the pattern:
注意参数值是从dispatcher获取的。这因为它是最终和你的应用的驱动器交互组件。此文,也有其他方法创建作为模式的一部分的命名参数 :

<?php

$router->add(
    "/documentation/{chapter}/{name}.{type:[a-z]+}",
    array(
        "controller" => "documentation",
        "action"     => "show"
    )
);

You can access their values in the same way as before:

<?php

class DocumentationController extends \Phalcon\Mvc\Controller
{

    public function showAction()
    {

        // Returns "name" parameter
        $name = $this->dispatcher->getParam("name");

        // Returns "type" parameter
        $type = $this->dispatcher->getParam("type");

    }

}

(请求中对应的URL对应部分的值会映射到模式中的name,type等,这个搞法还是比较怪异一点)

短语法(Short Syntax)

If you don’t like using an array to define the route paths, an alternative syntax is also available. The following examples produce the same result:
如果你不喜欢使用数组定义路由路径,还有一个可选语法。以下例子产生相同结果:

<?php

// Short form
$router->add("/posts/{year:[0-9]+}/{title:[a-z\-]+}", "Posts::show");

// Array form
$router->add(
    "/posts/([0-9]+)/([a-z\-]+)",
    array(
       "controller" => "posts",
       "action"     => "show",
       "year"       => 1,
       "title"      => 2,
    )
);

(这种短语法看起来更加实用)

–混合使用数组和短语法(Mixing Array and Short Syntax)
Array and short syntax can be mixed to define a route, in this case note that named parameters automatically are added to the route paths according to the position on which they were defined:
定义一个路由时数组和短语法可以混用,命名参数根据它们定义的位置自动添加到路由路径:

<?php

//First position must be skipped because it is used for
//the named parameter 'country'
$router->add('/news/{country:[a-z]{2}}/([a-z+])/([a-z\-+])',
    array(
        'section' => 2, //Positions start with 2
        'article' => 3
    )
);

路由到模块(Routing to Modules)
You can define routes whose paths include modules. This is specially suitable to multi-module applications. It’s possible define a default route that includes a module wildcard:
你可以定义包含路径的路由。这个特别适合多模块应用。可以定义一个包含模块通配符的默认路由:

<?php

$router = new Phalcon\Mvc\Router(false);

$router->add('/:module/:controller/:action/:params', array(
    'module' => 1,
    'controller' => 2,
    'action' => 3,
    'params' => 4
));

In this case, the route always must have the module name as part of the URL. For example, the following URL: /admin/users/edit/sonny, will be processed as:
在这个情况下,如有一致必须有模块名称作为URL的一部分,以下的URL:/admin/users/edit/sonny将如下处理:

Module admin
Controller users
Action edit
Parameter sonny

Or you can bind specific routes to specific modules:
或你可以绑定特殊路由到特定模块:

<?php

$router->add("/login", array(
    'module' => 'backend',
    'controller' => 'login',
    'action' => 'index',
));

$router->add("/products/:action", array(
    'module' => 'frontend',
    'controller' => 'products',
    'action' => 1,
));

Or bind them to specific namespaces:
或绑定它们到特定命名空间:

<?php

$router->add("/:namespace/login", array(
    'namespace' => 1,
    'controller' => 'login',
    'action' => 'index'
));

Namespaces/class names must be passed separated:

<?php

$router->add("/:namespace/login", array(
    'namespace' => 1,
    'controller' => 'login',
    'action' => 'index'
));

限制 HTTP 请求传入方式(HTTP Method Restrictions)
When you add a route using simply add(), the route will be enabled for any HTTP method. Sometimes we can restrict a route to a specific method, this is especially useful when creating RESTful applications:
当你简单实用add()方法添加一条路由时,这条路由将对所有HTTP方法启用。有时候我们可以限制一条路由到特定方法,当创建RESTful应用时这个特别有用:

<?php

// This route only will be matched if the HTTP method is GET
$router->addGet("/products/edit/{id}", "Products::edit");

// This route only will be matched if the HTTP method is POST
$router->addPost("/products/save", "Products::save");

// This route will be matched if the HTTP method is POST or PUT
$router->add("/products/update")->via(array("POST", "PUT"));

使用转换(Using convertions)
Convertions allow to freely transform the route’s parameters before passing them to the dispatcher, the following examples show how to use them:
转换允许路由参数在传递给分发器之前自由转换,以下例子展示如何使用它们:

<?php

//The action name allows dashes, an action can be: /products/new-ipod-nano-4-generation
$router
    ->add('/products/{slug:[a-z\-]+}', array(
        'controller' => 'products',
        'action' => 'show'
    ))
    ->convert('slug', function($slug) {
        //Transform the slug removing the dashes
        return str_replace('-', '', $slug);
    });

路由分组(Groups of Routes)
If a set of routes have common paths they can be grouped to easily maintain them:
如果一组路由有公共的路径,为了容易维护可以对它们进行分组:

<?php

$router = new \Phalcon\Mvc\Router();

//Create a group with a common module and controller
$blog = new \Phalcon\Mvc\Router\Group(array(
    'module' => 'blog',
    'controller' => 'index'
));

//All the routes start with /blog
$blog->setPrefix('/blog');

//Add a route to the group
$blog->add('/save', array(
    'action' => 'save'
));

//Add another route to the group
$blog->add('/edit/{id}', array(
    'action' => 'edit'
));

//This route maps to a controller different than the default
$blog->add('/blog', array(
    'controller' => 'blog',
    'action' => 'index'
));

//Add the group to the router
$router->mount($blog);

You can move groups of routes to separate files in order to improve the organization and code reusing in the application:
你可以把路由组到一个特定的文件中以在应用中可以提升组织和编码重用:

<?php

class BlogRoutes extends Phalcon\Mvc\Router\Group
{
    public function initialize()
    {
        //Default paths
        $this->setPaths(array(
            'module' => 'blog',
            'namespace' => 'Blog\Controllers'
        ));

        //All the routes start with /blog
        $this->setPrefix('/blog');

        //Add a route to the group
        $this->add('/save', array(
            'action' => 'save'
        ));

        //Add another route to the group
        $this->add('/edit/{id}', array(
            'action' => 'edit'
        ));

        //This route maps to a controller different than the default
        $this->add('/blog', array(
            'controller' => 'blog',
            'action' => 'index'
        ));

    }
}

Then mount the group in the router:
然后mount这个组到路由器:

<?php

//Add the group to the router
$router->mount(new BlogRoutes());

匹配路由(Matching Routes)
A valid URI must be passed to Router in order to let it checks the route that matches that given URI. By default, the routing URI is taken from the $_GET[‘_url’] variable that is created by the rewrite engine module. A couple of rewrite rules that work very well with Phalcon are:
一个可用的URI必须传递到路由器是为了让它检查路由是否和给定的URI匹配。默认,路由URI由重写引擎模块创建的$_GET[‘_url’]变量中获取:

RewriteEngine On
RewriteCond   %{REQUEST_FILENAME} !-d
RewriteCond   %{REQUEST_FILENAME} !-f
RewriteRule   ^(.*)$ index.php?_url=/$1 [QSA,L]

The following example shows how to use this component in stand-alone mode:

<?php

// Creating a router
$router = new \Phalcon\Mvc\Router();

// Define routes here if any
// ...

// Taking URI from $_GET["_url"]
$router->handle();

// or Setting the URI value directly
$router->handle("/employees/edit/17");

// Getting the processed controller
echo $router->getControllerName();

// Getting the processed action
echo $router->getActionName();

//Get the matched route
$route = $router->getMatchedRoute();

路由命名(Naming Routes)
Each route that is added to the router is stored internally as an object Phalcon\Mvc\Router\Route. That class encapsulates all the details of each route. For instance, we can give a name to a path to identify it uniquely in our application. This is especially useful if you want to create URLs from it.
每条添加到路由器中的路由在内部被存储为一个Phalcon\Mvc\Router\Route对象。这个类封装了每条路由的所有细节。例如,在我们的应用中,可以为路径添加一个用来唯一标示它的名称。如果你想要根据它创建URL时就特别有用:

<?php

$route = $router->add("/posts/{year}/{title}", "Posts::show");

$route->setName("show-posts");

//or just

$router->add("/posts/{year}/{title}", "Posts::show")->setName("show-posts");

Then, using for example the component Phalcon\Mvc\Url we can build routes from its name:
然后,例如使用Phalcon\Mvc\Url组件,我们可以从它的名称构建路由:

<?php

// returns /posts/2012/phalcon-1-0-released
echo $url->get(array(
    "for" => "show-posts",
    "year" => "2012",
    "title" => "phalcon-1-0-released"
));

范例(Usage Examples)
he following are examples of custom routes:

<?php

// matches "/system/admin/a/edit/7001"
$router->add(
    "/system/:controller/a/:action/:params",
    array(
        "controller" => 1,
        "action"     => 2,
        "params"     => 3
    )
);

// matches "/es/news"
$router->add(
    "/([a-z]{2})/:controller",
    array(
        "controller" => 2,
        "action"     => "index",
        "language"   => 1
    )
);

// matches "/es/news"
$router->add(
    "/{language:[a-z]{2}}/:controller",
    array(
        "controller" => 2,
        "action"     => "index"
    )
);

// matches "/admin/posts/edit/100"
$router->add(
    "/admin/:controller/:action/:int",
    array(
        "controller" => 1,
        "action"     => 2,
        "id"         => 3
    )
);

// matches "/posts/2010/02/some-cool-content"
$router->add(
    "/posts/([0-9]{4})/([0-9]{2})/([a-z\-]+)",
    array(
        "controller" => "posts",
        "action"     => "show",
        "year"       => 1,
        "month"      => 2,
        "title"      => 4
    )
);

// matches "/manual/en/translate.adapter.html"
$router->add(
    "/manual/([a-z]{2})/([a-z\.]+)\.html",
    array(
        "controller" => "manual",
        "action"     => "show",
        "language"   => 1,
        "file"       => 2
    )
);

// matches /feed/fr/le-robots-hot-news.atom
$router->add(
    "/feed/{lang:[a-z]+}/{blog:[a-z\-]+}\.{type:[a-z\-]+}",
    "Feed::get"
);

// matches /api/v1/users/peter.json
$router->add('/api/(v1|v2)/{method:[a-z]+}/{param:[a-z]+}\.(json|xml)',
    array(
        'controller' => 'api',
        'version' => 1,
        'format' => 4
    )
);

Beware of characters allowed in regular expression for controllers and namespaces. As these become class names and in turn they’re passed through the file system could be used by attackers to read unauthorized files. A safe regular expression is: /([a-zA-Z0-9_-]+)
注意到在为控制器和命名空间中的正则表达式中允许的字符。

默认行为(Default Behavior)
Phalcon\Mvc\Router has a default behavior providing a very simple routing that always expects a URI that matches the following pattern: /:controller/:action/:params
Phalcon\Mvc\Router有一个默认行为提供了一个非常简单的总是预计一个URI匹配如下模式的路由:/:controller/:action/:params

For example, for a URL like this http://phalconphp.com/documentation/show/about.html, this router will translate it as follows:

Controller documentation
Action show
Parameter about.html

If you don’t want use this routes as default in your application, you must create the router passing false as parameter:
在你的应用中如果不想使用这个作为默认路由,你必须创建路由器时传递false参数:

<?php

// Create the router without default routes
$router = new \Phalcon\Mvc\Router(false);

(默认路由在对待参数方面与常见的有差别)

设置默认路由(Setting the default route)
When your application is accessed without any route, the ‘/’ route is used to determine what paths must be used to show the initial page in your website/application:
当你的应用被访问没有任何路由(就是无法匹配所有路由时),‘/’路由用来决定什么路径被使用来展示初始页:

<?php

$router->add("/", array(
    'controller' => 'index',
    'action' => 'index'
));

(这个就是默认路由的搞法)

没有找到路径(Not Found Paths)
If none of the routes specified in the router are matched, you can define a group of paths to be used in this scenario:
如果没有路由在指定的路由器中被匹配,你可以定义一个路径组用来…

<?php

//Set 404 paths
$router->notFound(array(
    "controller" => "index",
    "action" => "route404"
));

(这个有个疑问,如果定义了默认路由,那不是没有找不到的情况?)

设置默认路径(Setting default paths)
It’s possible to define default values for common paths like module, controller or action. When a route is missing any of those paths they can be automatically filled by the router:
可以定义默认值为公共路径,比如模块控制器或者动作。当一个路由缺失任意路径,它们可以自动被路由器填充:

<?php

//Setting a specific default
$router->setDefaultModule('backend');
$router->setDefaultNamespace('Backend\Controllers');
$router->setDefaultController('index');
$router->setDefaultAction('index');

//Using an array
$router->setDefaults(array(
    'controller' => 'index',
    'action' => 'index'
));

处理结尾额外的斜杆(Dealing with extra/trailing slashes)
Sometimes a route could be accessed with extra/trailing slashes and the end of the route, those extra slashes would lead to produce a not-found status in the dispatcher. You can set up the router to automatically remove the slashes from the end of handled route:

<?php

$router = new \Phalcon\Mvc\Router();

//Remove trailing slashes automatically
$router->removeExtraSlashes(true);

##Or, you can modify specific routes to optionally accept trailing slashes:
<?php

$router->add(
    '/{language:[a-z]{2}}/:controller[/]{0,1}',
    array(
        'controller' => 2,
        'action'     => 'index'
    )
);

匹配回调函数(Match Callbacks)
Sometimes, routes must be matched if they meet specific conditions, you can add arbitrary conditions to routes using the ‘beforeMatch’ callback, if this function return false, the route will be treaded as non-matched:

<?php

$router->add('/login', array(
    'module' => 'admin',
    'controller' => 'session'
))->beforeMatch(function($uri, $route) {
    //Check if the request was made with Ajax
    if ($_SERVER['HTTP_X_REQUESTED_WITH'] == 'xmlhttprequest') {
        return false;
    }
    return true;
});

You can re-use these extra conditions in classes:

<?php

class AjaxFilter
{
    public function check()
    {
        return $_SERVER['HTTP_X_REQUESTED_WITH'] == 'xmlhttprequest';
    }
}
##And use this class instead of the anonymous function:
<?php

$router->add('/get/info/{id}', array(
    'controller' => 'products',
    'action' => 'info'
))->beforeMatch(array(new AjaxFilter(), 'check'));

(这种搞法也算是一大特色,不过有实用价值)

限制主机名(Hostname Constraints)
The router allow to set hostname constraints, this means that specific routes or a group of routes can be restricted to only match if the route also meets the hostname constraint:

<?php

$router->add('/login', array(
    'module' => 'admin',
    'controller' => 'session',
    'action' => 'login'
))->setHostName('admin.company.com');

##Hostname can also be regular expressions:
<?php

$router->add('/login', array(
    'module' => 'admin',
    'controller' => 'session',
    'action' => 'login'
))->setHostName('([a-z+]).company.com');

In groups of routes you can set up a hostname constraint that apply for every route in the group:

<?php

//Create a group with a common module and controller
$blog = new \Phalcon\Mvc\Router\Group(array(
    'module' => 'blog',
    'controller' => 'posts'
));

//Hostname restriction
$blog->setHostName('blog.mycompany.com');

//All the routes start with /blog
$blog->setPrefix('/blog');

//Default route
$blog->add('/', array(
    'action' => 'index'
));

//Add a route to the group
$blog->add('/save', array(
    'action' => 'save'
));

//Add another route to the group
$blog->add('/edit/{id}', array(
    'action' => 'edit'
));

//Add the group to the router
$router->mount($blog);

URI 来源(URI Sources)
By default the URI information is obtained from the $_GET[‘_url’] variable, this is passed by the Rewrite-Engine to Phalcon, you can also use $_SERVER[‘REQUEST_URI’] if required:

<?php

$router->setUriSource(Router::URI_SOURCE_GET_URL); // use $_GET['_url'] (default)
$router->setUriSource(Router::URI_SOURCE_SERVER_REQUEST_URI); // use $_SERVER['REQUEST_URI'] (default)

//Or you can manually pass a URI to the ‘handle’ method:
<?php

$router->handle('/some/route/to/handle');

测试路由(Testing your routes)
Since this component has no dependencies, you can create a file as shown below to test your routes:

<?php

//These routes simulate real URIs
$testRoutes = array(
    '/',
    '/index',
    '/index/index',
    '/index/test',
    '/products',
    '/products/index/',
    '/products/show/101',
);

$router = new Phalcon\Mvc\Router();

//Add here your custom routes
//...

//Testing each route
foreach ($testRoutes as $testRoute) {

    //Handle the route
    $router->handle($testRoute);

    echo 'Testing ', $testRoute, '<br>';

    //Check if some route was matched
    if ($router->wasMatched()) {
        echo 'Controller: ', $router->getControllerName(), '<br>';
        echo 'Action: ', $router->getActionName(), '<br>';
    } else {
        echo 'The route wasn\'t matched by any route<br>';
    }
    echo '<br>';

}

(这里提供了一个测试路由的实例)

匿名路由(Annotations Router)
(这个内容暂时。。。)

注册路由实例(Registering Router instance)
You can register router during service registration with Phalcon dependency injector to make it available inside controller.

You need to add code below in your bootstrap file (for example index.php or app/config/services.php if you use Phalcon Developer Tools)

<?php

/**
* add routing capabilities
*/
$di->set('router', function(){
    require __DIR__.'/../app/config/routes.php';
    return $router;
});

##You need to create app/config/routes.php and add router initialization code, for example:
<?php

$router = new \Phalcon\Mvc\Router();

$router->add("/login", array(
    'controller' => 'login',
    'action' => 'index',
));

$router->add("/products/:action", array(
    'controller' => 'products',
    'action' => 1,
));

return $router;

自定义路由(Implementing your own Router)
The Phalcon\Mvc\RouterInterface interface must be implemented to create your own router replacing the one provided by Phalcon.

PHP框架Phalcon 之 MVC应用

Phalcon提供了一个有用的开发工具,地址:http://docs.phalconphp.com/zh/latest/reference/tools.html,这里使用它生成一个多模块应用骨架:

phalcon project ifeeline --type modules

[root@vfeelit ifeeline]# tree
.
├── apps
│   └── frontend
│       ├── config
│       │   └── config.php
│       ├── controllers
│       │   ├── ControllerBase.php
│       │   └── IndexController.php
│       ├── models
│       ├── Module.php
│       └── views
│           ├── index
│           │   └── index.phtml
│           ├── index.phtml
│           └── layouts
├── config
│   ├── modules.php
│   └── services.php
├── index.html
└── public
    ├── css
    ├── files
    ├── img
    ├── index.php
    ├── js
    └── temp

在apps中的frontend就是一个独立模块,配置放入了config中,首先看看public的index.php:

<?php
use Phalcon\Mvc\Application;
error_reporting(E_ALL);
try {
    require __DIR__ . '/../config/services.php';
    $application = new Application($di);
    require __DIR__ . '/../config/modules.php';
    echo $application->handle()->getContent();
} catch (Exception $e) {
    echo $e->getMessage();
}

这个就是全部内容,config/services.php中是实例化DI,然后向它注册三个服务(router url session),config/modules.php是模块注册:

modules.php 
<?php
/**
 * Register application modules
 */
$application->registerModules(array(
    'frontend' => array(
        'className' => 'Ifeeline\Frontend\Module',
        'path' => __DIR__ . '/../apps/frontend/Module.php'
    )
));

模块的注册统一在这个文件中完成。这里有个关键地方要明白,在路由之后才确定了模块,模块才开始初始化,说明白点就是调用模块文件Module.php里面的Module类的registerAutoloaders()和registerServices($di)方法,这个搞法看起来跟单模块没啥两样,由于registerAutoloaders()只注册自己模块的加载器,所以如果要使用其它模块的模型,那就没有门了,因为其它模块资源无法加载。这里就引发了一个问题,难道每个模块要保持高度独立?模型的业务逻辑无法被其它模块共享?多模块无法使用相同的视图布局?这个绝对是Phalcon框架设计上的缺陷。

关于多模块共享布局,官方给出了解决方案:https://github.com/phalcon/mvc/tree/master/multiple-shared-layouts,实际就是调用视图的setLayoutsDir()方法修改布局目录,然后调用视图的setTemplateAfter()方法附加一个公共布局,所有模块的控制器布局都套入这个公共布局中。

关于模块的模型共享,实际也不难,不是路由之后才初始化具体模块吗,那么可以改为路由后把所有模块都注册了,就能解决这个问题吧。我试着用application提供的afterLoadModule事件实现一个全局Loader来实现模块之间彼此可见,参考:
Phalcon 在模块间共享

—————————————————————————-
MVC Applications MVC应用
All the hard work behind orchestrating组织 the operation of MVC in Phalcon is normally done by Phalcon\Mvc\Application. This component encapsulates all the complex operations required in the background, instantiating every component needed and integrating it with the project, to allow the MVC pattern to operate as desired.

Single or Multi Module Applications 单模块或多模块
With this component you can run various types of MVC structures:

–Single Module 单模块
Single MVC applications consist of one module only. Namespaces can be used but are not necessary. An application like this would have the following file structure:
单个MVC应用程序只包含一个模块。命名空间可以使用,但是没有必要:

single/
    app/
        controllers/
        models/
        views/
    public/
        css/
        img/
        js/

If namespaces are not used, the following bootstrap file could be used to orchestrate the MVC flow:
如果命名空间没有使用,下面的bootstrap文件可以用来协调MVC流:

<?php

use Phalcon\Loader,
    Phalcon\DI\FactoryDefault,
    Phalcon\Mvc\Application,
    Phalcon\Mvc\View;

$loader = new Loader();

$loader->registerDirs(
    array(
        '../apps/controllers/',
        '../apps/models/'
    )
)->register();

$di = new FactoryDefault();

// Registering the view component
$di->set('view', function() {
    $view = new View();
    $view->setViewsDir('../apps/views/');
    return $view;
});

try {

    $application = new Application($di);

    echo $application->handle()->getContent();

} catch (\Exception $e) {
    echo $e->getMessage();
}

If namespaces are used, the following bootstrap can be used:
使用命名空间,则先要注册命名空间对应目录:

<?php

use Phalcon\Loader,
    Phalcon\Mvc\View,
    Phalcon\DI\FactoryDefault,
    Phalcon\Mvc\Dispatcher,
    Phalcon\Mvc\Application;

$loader = new Loader();

// Use autoloading with namespaces prefixes
$loader->registerNamespaces(
    array(
        'Single\Controllers' => '../apps/controllers/',
        'Single\Models'      => '../apps/models/',
    )
)->register();

$di = new FactoryDefault();

// Register the dispatcher setting a Namespace for controllers
$di->set('dispatcher', function() {
    $dispatcher = new Dispatcher();
    $dispatcher->setDefaultNamespace('Single\Controllers');
    return $dispatcher;
});

// Registering the view component
$di->set('view', function() {
    $view = new View();
    $view->setViewsDir('../apps/views/');
    return $view;
});

try {

    $application = new Application($di);

    echo $application->handle()->getContent();

} catch(\Exception $e){
    echo $e->getMessage();
}

–Multi Module 多模块
A multi-module application uses the same document root for more than one module. In this case the following file structure can be used:

multiple/
  apps/
    frontend/
       controllers/
       models/
       views/
       Module.php
    backend/
       controllers/
       models/
       views/
       Module.php
  public/
    css/
    img/
    js/

Each directory in apps/ have its own MVC structure. A Module.php is present to configure specific settings of each module like autoloaders or custom services:
每个在apps中的目录都有它自己的MVC结果。Module.php是每个模块的配置文件用来配置autoloaders or custom services等:

<?php

namespace Multiple\Backend;

use Phalcon\Loader,
    Phalcon\Mvc\Dispatcher,
    Phalcon\Mvc\View,
    Phalcon\Mvc\ModuleDefinitionInterface;

class Module implements ModuleDefinitionInterface
{

    /**
     * Register a specific autoloader for the module
     */
    public function registerAutoloaders()
    {

        $loader = new Loader();

        $loader->registerNamespaces(
            array(
                'Multiple\Backend\Controllers' => '../apps/backend/controllers/',
                'Multiple\Backend\Models'      => '../apps/backend/models/',
            )
        );

        $loader->register();
    }

    /**
     * Register specific services for the module
     */
    public function registerServices($di)
    {

        //Registering a dispatcher
        $di->set('dispatcher', function() {
            $dispatcher = new Dispatcher();
            $dispatcher->setDefaultNamespace("Multiple\Backend\Controllers");
            return $dispatcher;
        });

        //Registering the view component
        $di->set('view', function() {
            $view = new View();
            $view->setViewsDir('../apps/backend/views/');
            return $view;
        });
    }

}

实际就两个方法,注册自动装载和注册服务。

A special bootstrap file is required to load the a multi-module MVC architecture:
多模块bootstrap的写法:

<?php

use Phalcon\Mvc\Router,
    Phalcon\Mvc\Application,
    Phalcon\DI\FactoryDefault;

$di = new FactoryDefault();

//Specify routes for modules
$di->set('router', function () {

    $router = new Router();

    $router->setDefaultModule("frontend");

    $router->add("/login", array(
        'module'     => 'backend',
        'controller' => 'login',
        'action'     => 'index',
    ));

    $router->add("/admin/products/:action", array(
        'module'     => 'backend',
        'controller' => 'products',
        'action'     => 1,
    ));

    $router->add("/products/:action", array(
        'controller' => 'products',
        'action'     => 1,
    ));

    return $router;
});

try {

    //Create an application
    $application = new Application($di);

    // Register the installed modules
    $application->registerModules(
        array(
            'frontend' => array(
                'className' => 'Multiple\Frontend\Module',
                'path'      => '../apps/frontend/Module.php',
            ),
            'backend'  => array(
                'className' => 'Multiple\Backend\Module',
                'path'      => '../apps/backend/Module.php',
            )
        )
    );

    //Handle the request
    echo $application->handle()->getContent();

} catch(\Exception $e){
    echo $e->getMessage();
}

If you want to maintain the module configuration in the bootstrap file you can use an anonymous function to register the module:

<?php

//Creating a view component
$view = new \Phalcon\Mvc\View();

//Set options to view component
//...

// Register the installed modules
$application->registerModules(
    array(
        'frontend' => function($di) use ($view) {
            $di->setShared('view', function() use ($view) {
                $view->setViewsDir('../apps/frontend/views/');
                return $view;
            });
        },
        'backend' => function($di) use ($view) {
            $di->setShared('view', function() use ($view) {
                $view->setViewsDir('../apps/backend/views/');
                return $view;
            });
        }
    )
);

When Phalcon\Mvc\Application have modules registered, always is necessary that every matched route returns a valid module. Each registered module has an associated class offering functions to set the module itself up. Each module class definition must implement two methods: registerAutoloaders() and registerServices(), they will be called by Phalcon\Mvc\Application according to the module to be executed.
向Phalcon\Mvc\Application注册模块时,模块的配置文件中的方法会被执行。

Understanding the default behavior 理解默认行为
If you’ve been following the tutorial or have generated the code using Phalcon Devtools, you may recognize the following bootstrap file:

<?php

try {

    // Register autoloaders
    //...

    // Register services
    //...

    // Handle the request
    $application = new \Phalcon\Mvc\Application($di);

    echo $application->handle()->getContent();

} catch (\Exception $e) {
    echo "Exception: ", $e->getMessage();
}


The core of all the work of the controller occurs when handle() is invoked:

<?php

echo $application->handle()->getContent();

Manual bootstrapping
If you do not wish to use Phalcon\Mvc\Application, the code above can be changed as follows:

<?php

// Get the 'router' service
$router = $di['router'];

$router->handle();

$view = $di['view'];

$dispatcher = $di['dispatcher'];

// Pass the processed router parameters to the dispatcher
$dispatcher->setControllerName($router->getControllerName());
$dispatcher->setActionName($router->getActionName());
$dispatcher->setParams($router->getParams());

// Start the view
$view->start();

// Dispatch the request
$dispatcher->dispatch();

// Render the related views
$view->render(
    $dispatcher->getControllerName(),
    $dispatcher->getActionName(),
    $dispatcher->getParams()
);

// Finish the view
$view->finish();

$response = $di['response'];

// Pass the output of the view to the response
$response->setContent($view->getContent());

// Send the request headers
$response->sendHeaders();

// Print the response
echo $response->getContent();

The following replacement of Phalcon\Mvc\Application lacks of a view component making it suitable for Rest APIs:
以下流程不需要视图:

<?php

// Get the 'router' service
$router = $di['router'];

$router->handle();

$dispatcher = $di['dispatcher'];

// Pass the processed router parameters to the dispatcher
$dispatcher->setControllerName($router->getControllerName());
$dispatcher->setActionName($router->getActionName());
$dispatcher->setParams($router->getParams());

// Dispatch the request
$dispatcher->dispatch();

//Get the returned value by the latest executed action
$response = $dispatcher->getReturnedValue();

//Check if the action returned is a 'response' object
if ($response instanceof Phalcon\Http\ResponseInterface) {

    //Send the request
    $response->send();
}

Yet another alternative that catch exceptions produced in the dispatcher forwarding to other actions consequently:

<?php

// Get the 'router' service
$router = $di['router'];

$router->handle();

$dispatcher = $di['dispatcher'];

// Pass the processed router parameters to the dispatcher
$dispatcher->setControllerName($router->getControllerName());
$dispatcher->setActionName($router->getActionName());
$dispatcher->setParams($router->getParams());

try {

    // Dispatch the request
    $dispatcher->dispatch();

} catch (Exception $e) {

    //An exception has occurred, dispatch some controller/action aimed for that

    // Pass the processed router parameters to the dispatcher
    $dispatcher->setControllerName('errors');
    $dispatcher->setActionName('action503');

    // Dispatch the request
    $dispatcher->dispatch();

}

//Get the returned value by the latest executed action
$response = $dispatcher->getReturnedValue();

//Check if the action returned is a 'response' object
if ($response instanceof Phalcon\Http\ResponseInterface) {

    //Send the request
    $response->send();
}

Although the above implementations are a lot more verbose than the code needed while using Phalcon\Mvc\Application, it offers an alternative in bootstrapping your application. Depending on your needs, you might want to have full control of what should be instantiated or not, or replace certain components with those of your own to extend the default functionality.

Application Events 应用程序事件
Phalcon\Mvc\Application is able to send events to the EventsManager (if it is present). Events are triggered using the type “application”. The following events are supported:

Event Name Triggered
boot Executed when the application handles its first request
beforeStartModule Before initialize a module, only when modules are registered
afterStartModule After initialize a module, only when modules are registered
beforeHandleRequest Before execute the dispatch loop
afterHandleRequest After execute the dispatch loop

The following example demonstrates how to attach listeners to this component:

<?php

use Phalcon\Events\Manager as EventsManager;

$eventsManager = new EventsManager();

$application->setEventsManager($eventsManager);

$eventsManager->attach(
    "application",
    function($event, $application) {
        // ...
    }
);

速卖通API接入之授权

关于速卖通API的授权,官方文档有详细的相关说明。引用如下:


应用程序可通过调用阿里巴巴开放平台提供的API获取到阿里巴巴的会员、交易等数据,因为涉及隐私,在使用前必须获得阿里巴巴会员的授权,方可调用API(公开数据除外),而access_token则做为用户唯一的授权标识。

阿里巴巴开放平台采用OAuth 2.0作为授权协议,授权流程可以简单归纳为:
(1)获取临时令牌;
(2)用临时令牌换取长时令牌以及访问令牌;
(3)访问令牌过期后用长时令牌刷新访问令牌。

由于使用OAuth 2.0作为授权协议,所以它的过程就复杂一些,跟一般SOAP应用手动生成一个访问令牌不同。速卖通的授权简单来说就是首先获取一个临时令牌(一次性),然后使用临时令牌换取访问令牌和刷新令牌,访问令牌有效期为10个小时,刷新令牌为6个月,当过期后需要重新获取,这个时候获取访问令牌有两个方式可选,第一种方法还是先获取临时另外然后用临时令牌换访问令牌,第二种就是用刷新令牌换取访问令牌。一般,在有刷新令牌的情况下,直接使用刷新令牌换取访问令牌,这也同时意味着在第一次换取临时令牌后用临时令牌换取访问令牌和刷新临牌时,应该把它们保存下来,以便一下API调用时使用。

临时令牌仅仅用来换取访问令牌和刷新令牌,而刷新令牌也仅仅用来换取访问令牌,实际调用API时需要使用是访问令牌。

第一步,获取临时令牌
不管是什么方式,总是要解决如何得到服务端给出的临时令牌。实际上最有效的办法就是在向服务器发送请求时,同时把接收这个临时令牌的URL传递过去,而服务器生成临时令牌后,把临时令牌以请求参数的形式附加到这个URL后面,然后服务端发送一个重定向到这个URL的指令,这样,URL对应的程序就能获取到这个临时临牌(请求参数)。
速卖通认证过去

这个图描述了这个过程,APP就是自己的程序。这里的第一步和第二部实际是可以合并的,取决于你希望通过App发送请求,还是由客户端直接发起请求,在我的实现中,使用了第一步和第二部合并由客户端直接发起请求的方法。你可能会被卡这里这一步,因为这里不仅仅是发送请求那么简单。事实上,速卖通的官方文档在描述第一步时如此说“用户使用app,访问在速卖通的隐私数据”,如果首次阅读,感觉文绉绉的,实际意思就是你的程序调用速卖通的API时,在调用API时需要访问临牌,如果没有访问令牌,需要先获取访问令牌,那么这就是一个完整的授权过程,何必把调用API作为第一步,编写文档的人水平不行。

授权过程如下:
首先,用户端或App发起授权请求
http://gw.api.alibaba.com/auth/authorize.htm?client_id=xxx&site=aliexpress&redirect_uri=YOUR_REDIRECT_URL&state=YOUR_PARM&_aop_signature=SIGENATURE
a) client_id:app注册时,分配给app的唯一标示,又称appKey
b) site:site参数标识当前授权的站点,直接填写aliexpress
c) redirect_uri: app的入口地址,授权临时令牌会以queryString的形式跟在该url后返回
d) state:可选,app自定义参数,回跳到redirect_uri时,会原样返回
e) _aop_signature:签名
参数签名(_aop_signature)为所有参数key + value 字符串拼接后排序,把排序结果拼接为字符串后通过bytesToHexString(HAMC-RSA1(data, appSecret))计算签名。 验证签名的方式为对参数执行同样的签名,比较传入的签名结果和计算的结果是否一致,一致为验签通过。

程序代码参考:

//生成签名
$code_arr = array(
	'client_id' => $appKey,
	'redirect_uri' => $redirectUrl,
	'site' => 'aliexpress',
	'state' => $state
);
ksort($code_arr);
foreach ($code_arr as $key=>$val)
	$sign_str .= $key . $val;
$code_sign = strtoupper(bin2hex(hash_hmac("sha1", $sign_str, $appSecret, true)));
			  
$get_code_url = "http://gw.api.alibaba.com/auth/authorize.htm?client_id={$appKey}&site=aliexpress&redirect_uri={$redirectUrl}&state={$state}&_aop_signature={$code_sign}";

发出请求后,客户端将获取到如下需要授权的交互内容:
速卖通认证

然后,输入用户名和密码提交以后就是官方描述的第三步。服务器接收后,如果用户名密码匹配,就会产生临时临牌并且把临时令牌附加到redirect_uri并定位到这个redirect_uri。这个redirect_uri对应的程序需要一段接收这个临时临牌的代码,然后用它来换成访问令牌和刷新令牌,这个过程就是授权的第二步。

第二步,用临时令牌获取访问令牌和刷新令牌
https://gw.api.alibaba.com/openapi/http/1/system.oauth2/getToken/YOUR_APPKEY?grant_type=authorization_code&need_refresh_token=true&client_id= YOUR_APPKEY&client_secret= YOUR_APPSECRET&redirect_uri=YOUR_REDIRECT_URI&code=CODE
注:此接口必须使用POST方法提交;必须使用https
getToken接口参数说明:
a) grant_type为授权类型,使用authorization_code即可
b) need_refresh_token为是否需要返回refresh_token,如果返回了refresh_token,原来获取的refresh_token也不会失效,除非超过半年有效期
c) client_id为app唯一标识,即appKey
d) client_secret为app密钥
e) redirect_uri为app入口地址
f) code为授权完成后返回的一次性令牌
g) 调用getToken接口不需要签名
注:如果超过code有效期(2分钟)或者已经使用code获取了一次令牌,code都将失效,需要返回第二步重新获取code

这里会返回访问令牌和刷新令牌,需要把它们保存下来,由于有有效期,所以保存是需要把时间戳带上,当超时时就需要重新获取。

代码参考:

function getDataUseCurl($url='', $data='', $post=false){
	if('' == $url){ return false; }
	/*
	$urldata = parse_url($url); 
	$headers = array("Host: ".$urldata['host']); 
	curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
	*/
	
	$ch = curl_init();	
	curl_setopt($ch, CURLOPT_HEADER, '');
	curl_setopt($ch, CURLOPT_URL, trim($url));
	curl_setopt($ch, CURLOPT_HEADER, false);
	//curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 
	curl_setopt($ch, CURLOPT_TIMEOUT, 30);
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
	if($post === false){
		curl_setopt($ch, CURLOPT_POST, false);
	}else{
		curl_setopt($ch, CURLOPT_POST, true);
		if('' != $data){
			curl_setopt($ch, CURLOPT_POSTFIELDS,$data);
		}
	}
	if ((int)preg_match('/^HTTPS/i', $url) > 0) { //应该测试有没有SSL支持
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    	curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 
	}
	
	curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
	curl_setopt($ch,CURLOPT_FOLLOWLOCATION,true);
	curl_setopt($ch,CURLOPT_MAXREDIRS,10);
	
	curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0");
	$result = curl_exec($ch);
	$errn = curl_errno($ch);
	curl_close($ch);

	if((int)$errn == 0){
		return $result;
	}
	
	return false;
}


$url = "https://gw.api.alibaba.com/openapi/http/1/system.oauth2/getToken/{$appKey}";
$data = "grant_type=authorization_code&need_refresh_token=true&client_id={$appKey}&client_secret={$appSecret}&redirect_uri={$rurl}&code={$code}";

$bdata = getDataUseCurl($url, $data, $post=true);

$r = json_decode($bdata);
//成功 返回的结果标示了属于哪个账户,所以实际不需要传递用户标识。这里采用显式传递的方法。
/*
{"aliId":"8888888888","resourceOwner":"xxx","expires_in":"36000","access_token":"8795258a-6c8f-43a5-b8d0-763631edb610","refresh_token":"8795258a-6c8f-43a5-b8d0-763631edb610"} 
*/
if(isset($r->access_token) && isset($r->refresh_token)){ 
	$nw = time();
	$anw = $nw + 3600 * 9; // 有效10小时,改为9小时
	$rnw = $nw + 3600*24*30*5.5; // 有效半年,改为5.5月
	$db->Execute("update ".TABLE_AI_ACCOUNT." set code = '".$code."', access_token='".trim($r->access_token)."', access_token_expire='".$anw."', refresh_token='".trim($r->refresh_token)."', refresh_token_expire='".$rnw."' where sid='".trim($sid)."' limit 1");
	return true;
}

这样访问令牌和刷新令牌就保存下来了。授权过程已经完成。不过为了通用性,还需要包装一下。比如调用API时需要需要获取访问令牌,如果访问令牌过期了,就需要使用刷新令牌换取一下访问令牌。所以有如下两个封装函数:

//根据refresh_token刷新access_token
function getAccessTokenByRefreshToken($sid=''){
	global $db;
	
	if('' == $sid){
		return false;
	}else{
		$d = $db->Execute("select * from ".TABLE_AI_ACCOUNT." where sid ='".$db->prepareInput(trim($sid))."' limit 1");
		if($d->RecordCount() < 1){
			return false;
		}
		$id = (int)$d->fields['id'];
		$appKey = trim($d->fields['app_key']);
		$appSecret = trim($d->fields['app_secret']);
    	$refreshToken = trim($d->fields['refresh_token']);
		$refreshToken_expire = trim($d->fields['refresh_token_expire']);
		if(($appKey == '') || ($appSecret == '') || ($refreshToken == '')){ 
			
			return false; 
		}
		
		// 检查$refreshToken是否过期
		if(($refreshToken_expire - time()) < 0){
			return false;
		}
		
		$url = "https://gw.api.alibaba.com/openapi/param2/1/system.oauth2/getToken/{$appKey}";
		$post_data = "grant_type=refresh_token&client_id={$appKey}&client_secret={$appSecret}&refresh_token={$refreshToken}";

		$result = getDataUseCurl($url, $post_data, true);
		
		if($result){
			//成功
			/* {"aliId":"1728303813","resource_owner":"cn1501350670","expires_in":"36000","access_token":"d24c4d16-af2e-46b9-97e5-66eccfc8a8a7"} */
			//错误
			/*{"error":"invalid_request","error_description":"wrong refreshToken"}*/
			$r = json_decode($result);

			//成功返回
			if(isset($r->access_token)){ 
				//刷新
				$db->Execute("update ".TABLE_AI_ACCOUNT." set access_token='".trim($r->access_token)."', access_token_expire='".(time()+3600*9)."' where id = {$id} limit 1");
				return trim($r->access_token);
			}
			//可能是refresh_token过期 或 代码错误
			if(isset($r->error)){ 
				//echo "Error, {$r->error_description}\n";
				return false;
			}
		}
		//CURL错误
		return false;
	}	
}

//获取access_token,在调用具体的API时调用获取这个token
function getAccessToken($sid=''){
	global $db;
	if('' == $sid){
		return false;
	}else{
		$d = $db->Execute("select * from ".TABLE_AI_ACCOUNT." where sid ='".trim($sid)."' limit 1");
		if($d->RecordCount() < 1){
			return false;
		}
		
		$appKey = trim($d->fields['app_key']);
		$appSecret = trim($d->fields['app_secret']);
		$accessToken = trim($d->fields['access_token']);
		$accessTokenExpire = trim($d->fields['access_token_expire']);
    	$refreshToken = trim($d->fields['refresh_token']);
		
		if(($appKey == '') || ($appSecret == '')){ return false; }
		
		if(($accessToken != '') && (($accessTokenExpire - time()) > 0)){
			return $accessToken;
		}
		
		if($refreshToken == ''){ return false; }

		return getAccessTokenByRefreshToken($sid);
	}
}

这里就是授权的全部内容。

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