使用MicroKernelTrait创建你自己的微框架

3.4 版本
维护中的版本

一个传统的Symfony程序包括一个合理的目录结构,各种配置文件以及一个注册了若干bundle的AppKernel文件。这就是一个可以投入使用的全功能程序。

但是你知道吗,你能够仅仅在一个文件中创建这样一个全功能的Symfony程序?多亏了MicroKernelTrait,这完全是可能的。它允许你从一个微小程序起步,然后随你所需地添加功能和结构。

单文件Symfony程序 

从一个空目录开始。使用Composer来获取依赖,即symofny/symfony

1
$  composer require symfony/symfony

接下来创建index.php文件,用它来创建kernel类并执行之:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
 
// require Composer's autoloader / 把Composer的自动加载器包容进来
require __DIR__.'/vendor/autoload.php';
 
class AppKernel extends Kernel
{
    use MicroKernelTrait;
 
    public function registerBundles()
    {
        return array(
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle()
        );
    }
 
    protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
    {
        // PHP equivalent of config.yml / 相当于config.yml的PHP
        $c->loadFromExtension('framework', array(
            'secret' => 'S0ME_SECRET'
        ));
    }
 
    protected function configureRoutes(RouteCollectionBuilder $routes)
    {
        // kernel is a service that points to this class
        // optional 3rd argument is the route name
        // kernel是一个指向这个类的服务。可选的第三个参数是路由名称
        $routes->add('/random/{limit}', 'kernel:randomAction');
    }
 
    public function randomAction($limit)
    {
        return new JsonResponse(array(
            'number' => rand(0, $limit)
        ));
    }
}
 
$kernel = new AppKernel('dev', true);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

这就可以了!要测试它,你可以直接启动PHP内置的web server:

1
$  php -S localhost:8000

> http://localhost:8000/random/10

然后在浏览器中查看JSON响应:

“微”内核中的方法 

当你使用MicroKernelTrait时,你的kernel需要明确具备三个方法,它们分别定义了你的bundles、services和routes:

registerBundles()
与你在普通kernel中见到的registerBundles()相同。
configureContainer(ContainerBuilder $c, LoaderInterface $loader)
这个方法构建和配置了容器。实践中,你要使用loadFromExtension来配置不同的bundles(这相当于[配置]你在普通的config.yml文件中所见到的[那些配置信息])。你也可以在PHP中直接注册服务,或是加载外部配置文件(下例有展示)。
configureRoutes(RouteCollectionBuilder $routes)
在这个方法中你要对程序添加路由。RouteCollectionBuilder有一些方法可以让PHP在添加路由时充满乐趣。你也可以加载外部路由文件(下例有展示)。

高级示例:Twig、Annotations以及Web除错工具条 

MicroKernelTrait的目的不是 要去搞一个单文件程序。而是要给你一个“选择你自己的bundle和结构”的超能力。

首先,你可能希望把你的PHP类放到src/目录下。配置你的composer.json文件来从那里加载它:

1
2
3
4
5
6
7
8
9
10
{
    "require": {
        "...": "..."
    },
    "autoload": {
        "psr-4": {
            "": "src/"
        }
    }
}

现在,假设你希望使用Twig,并且通过annotations来加载路由。对于annotation方式的路由,你需要SensioFrameworkExtraBundle。它已内置在Symfony标准版的项目之中。但在本例,你还是需要下载它:

1
$  composer require sensio/framework-extra-bundle

不要把所有东西 都放到index.php中,创建一个新的app/AppKernel.php以容纳kernel。现在它看起来像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// app/AppKernel.php
 
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Routing\RouteCollectionBuilder;
use Doctrine\Common\Annotations\AnnotationRegistry;
 
// require Composer's autoloader
$loader = require __DIR__.'/../vendor/autoload.php';
// auto-load annotations
AnnotationRegistry::registerLoader(array($loader, 'loadClass'));
 
class AppKernel extends Kernel
{
    use MicroKernelTrait;
 
    public function registerBundles()
    {
        $bundles = array(
            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
            new Symfony\Bundle\TwigBundle\TwigBundle(),
            new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle()
        );
 
        if ($this->getEnvironment() == 'dev') {
            $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
        }
 
        return $bundles;
    }
 
    protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
    {
        $loader->load(__DIR__.'/config/config.yml');
 
        // configure WebProfilerBundle only if the bundle is enabled
        // 配置WebProfilerBundle,只当此bundle被开启时
        if (isset($this->bundles['WebProfilerBundle'])) {
            $c->loadFromExtension('web_profiler', array(
                'toolbar' => true,
                'intercept_redirects' => false,
            ));
        }
    }
 
    protected function configureRoutes(RouteCollectionBuilder $routes)
    {
        // import the WebProfilerRoutes, only if the bundle is enabled
        // 导入WebProfilerRoutes,只当此bundle被开启时
        if (isset($this->bundles['WebProfilerBundle'])) {
            $routes->import('@WebProfilerBundle/Resources/config/routing/wdt.xml', '/_wdt');
            $routes->import('@WebProfilerBundle/Resources/config/routing/profiler.xml', '/_profiler');
        }
 
        // load the annotation routes / 加载annotation路由
        $routes->import(__DIR__.'/../src/App/Controller/', '/', 'annotation');
    }
 
    // optional, to use the standard Symfony cache directory
    // 可选,使用标准的Symfony缓存目录
    public function getCacheDir()
    {
        return __DIR__.'/../var/cache/'.$this->getEnvironment();
    }
 
    // optional, to use the standard Symfony logs directory
    // 可选,使用标准的Symfony日志目录
    public function getLogDir()
    {
        return __DIR__.'/../var/logs';
    }
}

不像之前的kernel,这里加载了一个外部的app/config/config.yml 文件,因为配置信息开始变得巨大了:

1
2
3
4
5
6
# app/config/config.yml
framework:
    secret: S0ME_SECRET
    templating:
        engines: ['twig']
    profiler: { only_exceptions: false }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:framework="http://symfony.com/schema/dic/symfony"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd
        http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
 
    <framework:config secret="S0ME_SECRET">
        <framework:templating>
            <framework:engine>twig</framework:engine>
        </framework:templating>
        <framework:profiler only-exceptions="false" />
    </framework:config>
</container>
1
2
3
4
5
6
7
8
9
10
// app/config/config.php
$container->loadFromExtension('framework', array(
    'secret' => 'S0ME_SECRET',
    'templating' => array(
        'engines' => array('twig'),
    ),
    'profiler' => array(
        'only_exceptions' => false,
    ),
));

它也从src/App/Controller/目录中加载annotation 路由,目录下面有一个文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/ src/App/Controller/MicroController.php
namespace App\Controller;
 
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
 
class MicroController extends Controller
{
    /**
     * @Route("/random/{limit}")
     */
    public function randomAction($limit)
    {
        $number = rand(0, $limit);
 
        return $this->render('micro/random.html.twig', array(
            'number' => $number
        ));
    }
}

模板文件应该放在Resources/views目录下,无论你的kernel 放在哪里。 由于AppKernel位于app/下,那么模板就应该是app/Resources/views/micro/random.html.twig

最后,你需要一个前端控制器来启动和运行程序。创建一个web/index.php

1
2
3
4
5
6
7
8
9
10
11
// web/index.php
 
use Symfony\Component\HttpFoundation\Request;
 
require __DIR__.'/../app/AppKernel.php';
 
$kernel = new AppKernel('dev', true);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
$response->send();
$kernel->terminate($request, $response);

That's it! This /random/10 URL will work, Twig will render, and you'll even get the web debug toolbar to show up at the bottom. The final structure looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
your-project/
├─ app/
|  ├─ AppKernel.php
│  ├─ config/
│  └─ Resources
|     └─ views
|        ├─ base.html.twig
|        └─ micro
|           └─ random.html.twig
├─ src/
│  └─ App
|     └─ Controller
|        └─ MicroController.php
├─ var/
|  ├─ cache/
│  └─ logs/
├─ vendor/
│  └─ ...
├─ web/
|  └─ index.php
├─ composer.json
└─ composer.lock

嘿,看起来很像传统的 Symfony程序呀!你是对的:MicroKernelTrait 就是 Symfony:但你却可以极为轻松地控制你自己的结构和功能。

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)