目前为止,我们的程序还过于简单,因为它只有一个页面。为了加点调料进去,我们疯狂一下,再做一个说“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映射提取到一个配置文件中”来强化你的框架。

index.php.前端控制器,前端控制器 - Symfony开源 - Symfony中国相关推荐

  1. php开源路由器,路由 - Symfony开源 - Symfony中国

    对于任何严谨的web应用程序而言美观的URL是绝对必须的.这意味着日渐淘汰的 index.php?article_id=57 这类丑陋的URL要被 /read/intro-to-symfony 取代. ...

  2. 恐龙快跑小程序对接流量主源码+前端 v5.0.4 全开源微擎框架

    简介: 恐龙快跑小程序对接流量主源码+前端 v5.0.4 全开源优点: 首页流量主赚取收益 用户冲分领奖品机制,冲到某个分数即可兑换相应奖品 用户游戏死亡后需要充值金币或者转发给好友获取金币复活. 盈 ...

  3. 大前端–Vue前端体系、前后端分离

    大前端–Vue前端体系.前后端分离 前言 Soc:关注点分离原则 HTML+CSS+JS(视图):给用户看,刷新后台给的数据 网络通信:axios 页面跳转:vue-router 状态管理:vuex ...

  4. 【前端】前端及其技术栈

    文章目录 0)前端 1.什么是前端? 2.为什么需要前端? 前端的发展简史: 3.前端开发的类型 Web前端开发: 客户端(APP)开发: 4.前端与后端的交互 Ajax: Socket: *前端技术 ...

  5. GMTC 大前端时代前端监控的最佳实践

    摘要: 今天我分享的内容分成三个部分: 第一部分是"大前端时代前端监控新的变化", 讲述这些年来,前端监控一些新的视角以及最前沿的一些思考. 第二部分"前端监控的最佳实践 ...

  6. GMTC 大前端时代前端监控的最佳实践 1

    摘要: 今天我分享的内容分成三个部分: 第一部分是"大前端时代前端监控新的变化", 讲述这些年来,前端监控一些新的视角以及最前沿的一些思考. 第二部分"前端监控的最佳实践 ...

  7. Asp.Net MVC控制器、控制器动作和动作结果

    原文链接:http://www.asp.net/learn/mvc/ 这篇教程探索了ASP.NET MVC控制器(controller).控制器动作(controller action)和动作结果(a ...

  8. jmeter使用if控制器_Jmeter(七)_if控制器+循环控制器+计数器控制接口分支

    最近查阅了一下网上关于if控制器的文章,大同小异,几乎找不到原创,于是决定自己写一篇 下午测试接口,遇到了一个审核的流程.逻辑很简单,就是审核不通过之后返回去继续修改再提交,然后再审核,直到通过为止. ...

  9. 零基础不建议学前端_web前端开发零基础怎样入门-哈尔滨前端学习

    web前端开发零基础怎样入门-哈尔滨前端学习,俗话说,知己知彼,百战百胜.要想学好web前端,首先要了解什么是web前端,下面由小编来给大家介绍一下: 1什么是web? Web就是在Http协议基础之 ...

最新文章

  1. 黑盒测试方法之边界值分析法
  2. unity桌面设置vnc_win7系统通过VNCViewer访问Ubuntu桌面环境的操作方法
  3. python背景怎么自定义铃声_Python 上课铃声的定时播放(具有较强的自我管理能力.jpg)...
  4. Spring Boot异常处理
  5. C# 6 的新特性~
  6. C#中的函数式编程:递归与纯函数(二) 学习ASP.NET Core Razor 编程系列四——Asp.Net Core Razor列表模板页面...
  7. wince车机刷系统刷机包_2020年刷机包是不是越小越精简,越小越流畅好用
  8. 二项分布与负二项分布卡片
  9. 在div中加本地html,div加载另一个HTML页面
  10. 猫哥教你写爬虫 041--模拟登录-cookie
  11. 条码扫描枪有什么接口?应该怎么选择扫描枪的接口
  12. threejs使用tweenjs实现点击标签过渡到相应视角
  13. mysql explain不准确_mysql explain预估剖析
  14. 广告公司网站该怎么做和运营
  15. 分享一个好用的屏幕截取动图的工具
  16. 利用正则表达式来验证邮箱
  17. FileReader读取文件的三种方式
  18. sql server 入门篇
  19. 《量子信息与量子计算简明教程》第三章·量子纠缠状态及其应用 (上)
  20. 每日一道leetcode(python)876. 链表的中间结点

热门文章

  1. OpenVINO开发教程之八 – 道路分割
  2. 初次遇见NLP:从词向量到BERT
  3. 分布式系统的面试题11
  4. java-第十一章-类的无参方法-计算器运算
  5. let 与 expr Shell运算比较 let强强胜出
  6. 第3章 View的事件体系
  7. PARAMETER FILE研究
  8. 【Linux 驱动】第九章 与硬件通信
  9. CCNA入门---交换机端口安全的四种行为
  10. vista——最恰当的中文译名应该是“喂死它”