MVC 应用(MVC Applications)

在Phalcon,策划MVC操作背后的全部困难工作通常都可以 通过 Phalcon\Mvc\Application 做到。这个组件封装了全部后端所需要的复杂 操作,实例化每一个需要用到的组件并与项目整合在一起,从而使得MVC模式可以如期地运行。

单模块或多模块应用(Single or Multi Module Applications)

通过这个组件,你可以运行各式各样的MVC结构:

单模块(Single Module)

单一的MVC应用仅仅包含了一个模块。可以使用命名空间,但不是必需的。 这样类型的应用可能会有以下文件目录结构:

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

如果未使用命名空间,以下的启动文件可用于编排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();
}

如果使用了命名空间,则可以使用以下启动文件(译者注:主要区别在于使用$loader的方式):

<?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)

多模块的应用使用了相同的文档根目录但拥有多个模块。在这种情况下,可以使用以下的文件目录结构:

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

在apps/下的每一个目录都有自己的MVC结构。Module.php文件代表了各个模块不同的配置,如自动加载器和自定义服务:

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

}

还需要一个指定的启动文件来加载多模块的MVC架构:

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

如果你想在启动文件保持模块的配置,你可以使用匿名函数来注册对应的模块:

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

Phalcon\Mvc\Application 有多个模块注册时,通常 每个都是需要的,以便每一个被匹配到的路由都能返回一个有效的模块。每个已经注册的模块都有一个相关的类来提供建立和启动自身的函数。 而每个模块定义的类都必须实现registerAutoloaders()和registerServices()这两个方法,这两个函数会在模块即被执行时被

理解默认行为(Understanding the default behavior)

如果你已经看过了 tutorial 或者已经通过 Phalcon Devtools 生成了代码, 你将很容易识别以下的启动文件:

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

控制器中全部核心的工作都会在handle()被回调时触发执行。

<?php

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

手动启动(Manual bootstrapping)

如果你不想使用 Phalcon\Mvc\Application ,以上的代码可以改成这样:

<?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();

以下代码替换了 Phalcon\Mvc\Application ,虽然缺少了视图组件, 但却更适合Rest风格的API接口:

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

另外一个修改就是在分发器中对抛出异常的捕捉可以将请求转发到其他的操作:

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

尽管上面的代码比使用 Phalcon\Mvc\Application 而需要的代码远远要累赘得很, 但它为启动你的应用提供了一个可修改、可定制化的途径。 因为根据你的项目需要,你可以想对实例什么和不实例化什么进行完全的控制,或者想用你自己的组件来替代那些确定和必须的组件从而扩展默认的功能。

应用事件(Application Events)

Phalcon\Mvc\Application 可以把事件发送到 EventsManager (如果它激活的话)。 事件将被当作”application”类型被消费掉。目前已支持的事件如下:

事件名称 消费于
boot 当应用处理它首个请求时被执行
beforeStartModule 在初始化模块之前,仅当模块被注册时
afterStartModule 在初始化模块之后,仅当模块被注册时
beforeHandleRequest 在执行分发环前
afterHandleRequest 在执行分发环后

以下示例演示了如何将侦听器绑定到组件:

<?php

use Phalcon\Events\Manager as EventsManager;

$eventsManager = new EventsManager();

$application->setEventsManager($eventsManager);

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

外部资源(External Resources)