index.php.前端控制器,前端控制器 - Symfony开源 - Symfony中国

目前为止,我们的程序还过于简单,因为它只有一个页面。为了加点调料进去,我们疯狂一下,再做一个说“goodbye”的页面:

1

2

3

4

5

6

7

8

9

10// framework/bye.php

require_once __DIR__.'/vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

$response = new Response('Goodbye!');

$response->send();

如你所见,上面代码中很多都与我们写的第一个页面完全相同。我们提取出这部分代码并在页面间共享。代码共享,听起来是个不错的计划,可以创建我们第一个“真正”的框架。

以PHP方式来重构(代码),差不多就是“创建一个包容文件”:

1

2

3

4

5

6

7

8// framework/init.php

require_once __DIR__.'/vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

$response = new Response();

我们看看(在两个页面中的)实际运行情况:

1

2

3

4

5

6

7// framework/index.php

require_once __DIR__.'/init.php';

$input = $request->get('name', 'World');

$response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));

$response->send();

再来是“Goodbye”页面:

1

2

3

4

5// framework/bye.php

require_once __DIR__.'/init.php';

$response->setContent('Goodbye!');

$response->send();

我们的确把共享代码给转移到一个“中心地带(central place)”,但它并不像一个很好的抽象层,不是吗?对于所有页面,我们仍要保留send()方法,页面也不像个模板,我们始终不能正确地测试代码。

还有,添加一个新页,意味着我们必须创建一个新的PHP脚本,其名字通过URL(http://127.0.0.1:4321/bye.php)完全暴露给末级用户:PHP脚本名称与客户端URL之间是直接的映射关系。这是因为,派发的请求(request)直接依赖于服务器。为了提高灵活性,也许把这种派遣转移到我们的代码中是个好主意。这可以通过把所有客户端请求发送(routing)到一个独立PHP脚本来轻松实现。

把一个单一PHP脚本暴露给(exposing)末级用户是一个被称之为前端控制器的设计模式。

这样一个脚本可能像下面这样:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23// framework/front.php

require_once __DIR__.'/vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

$response = new Response();

$map = array(

'/hello' => __DIR__.'/hello.php',

'/bye' => __DIR__.'/bye.php',

);

$path = $request->getPathInfo();

if (isset($map[$path])) {

require $map[$path];

} else {

$response->setStatusCode(404);

$response->setContent('Not Found');

}

$response->send();

那么新的hello.php脚本应该是下面这样:

1

2

3// framework/hello.php

$input = $request->get('name', 'World');

$response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));

在front.php脚本中,$map用相对应的PHP脚本路径(paths)关联了URL路径。

作为一个奖励,如果客户端在请求一个“没有定义在URL映射中”的路径时,我们返回一个自定义的404页;现在你可以控制你的网站了。

为了能访问页面,从现在起你必须使用front.php:

http://127.0.0.1:4321/front.php/hello?name=Fabien

http://127.0.0.1:4321/front.php/bye

/hello和/bye就是页面的路径(paths)。

多数web server,比如Apache或nginx,都可以对请求的URLs进行重写(rewrite),这样就能去掉前端控制器的脚本(名称)了,进而你键入http://127.0.0.1:4321/hello?name=Fabien即可,看上去好多了。

其中的小魔法在于Request::getPathInfo()方法的使用,通过移除前端控制器的“脚本名称”连同其子目录(如果有必要的话,参考上面的灯炮tip)——它将返回请求的路径部分。

你毋须设置web server来测试代码。只需替换掉$request = Request::createFromGlobals();,改以$request = Request::create('/hello?name=Fabien');这种,其中的参数就是你打算模拟的“URL路径”。

现在的服务器已经可以通过相同的脚本(front.php)来访问所有页面了,我们还可以进一步从web根目录中移除所有无关的PHP文件以增加安全性:

1

2

3

4

5

6

7

8

9

10

11example.com

├── composer.json

├── composer.lock

├── src

│ └── pages

│ ├── hello.php

│ └── bye.php

├── vendor

│ └── autoload.php

└── web

└── front.php

现在,重新配置你的web server根目录指向web/,其他所有文件将不再能从客户端访问到。

要在浏览器中测试这种改变(http://127.0.0.1:4321/front.php/hello?name=Fabien),运行PHP内置的server:

1$ php -S 127.0.0.1:4321 -t web/ web/front.php

为了让这个新结构得以运行,你不得不在不同的PHP文件中调整一些路径。这些改变作为练习留给读者完成。

在每个页面中最后一个“重复性”的东西是调用setContent()。我们可以将全部页面转换成“模板”,只需打出(echoing)内容部分再直接从前端控制器脚本中调用setContent()即可:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15// example.com/web/front.php

// ...

$path = $request->getPathInfo();

if (isset($map[$path])) {

ob_start();

include $map[$path];

$response->setContent(ob_get_clean());

} else {

$response->setStatusCode(404);

$response->setContent('Not Found');

}

// ...

同时hello.php的脚本要被转换成一个模板:

1

2

3

4

<?php $name = $request->get('name', 'World') ?>

Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>

我们这个框架的第一个版本现已齐活:

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// example.com/web/front.php

require_once __DIR__.'/../vendor/autoload.php';

use Symfony\Component\HttpFoundation\Request;

use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

$response = new Response();

$map = array(

'/hello' => __DIR__.'/../src/pages/hello.php',

'/bye' => __DIR__.'/../src/pages/bye.php',

);

$path = $request->getPathInfo();

if (isset($map[$path])) {

ob_start();

include $map[$path];

$response->setContent(ob_get_clean());

} else {

$response->setStatusCode(404);

$response->setContent('Not Found');

}

$response->send();

添加一个新页面将是如下两步的过程:在映射中添加一个入口,然后在src/pages/中创建一个模板。在模板中,通过$request变量取得Request内容,然后通过$response变量可以调整响应头。

如果你决定止步于此,你也许还能通过“把URL映射提取到一个配置文件中”来强化你的框架。

阅读终点,创作起航,您可以撰写心得或摘录文章要点写篇博文。去创作
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值