php $_server['request_uri'] null,Bootstrap 文件中使用 $_SERVER ['REQUEST_URI'] 遇到的一个小坑...

接手了别人的代码,框架是 lumen,发现做单元测试的时候,报错如下:

[ErrorException]

Undefined index: REQUEST_URI

查了一下代码发现 bootstrap/app.php 中有这么一段代码:

if (strpos($_SERVER['REQUEST_URI'], 'backend') === 0) {

$app->register(App\Providers\PermissionServiceProvider::class);

$app->group(['middleware' => ['operation', 'permission'], 'namespace' => 'App\Http\Controllers\Backend'], function() use ($app) {

require __DIR__ . '/../routes/backend.php';

});

} else {

$app->register(App\Providers\AuthServiceProvider::class);

require __DIR__ . '/../routes/frontend.php';

}

在 phpunit 这种命令行方式运行的情况下,$_SERVER['REQUEST_URI'] 的确是找不到的。但是为什么单元测试可以模拟发送请求呢?为什么query路径可以被单元测试模拟呢?小小的挖一下不难发现,在单元测试中使用的get,post,put,delete 等之类的方法,是对 call 方法的包装,这里就直接贴一下 部分源码:

举例 get 方法源码:

public function get($uri, array $headers = [])

{

$server = $this->transformHeadersToServerVars($headers);

$this->call('GET', $uri, [], [], [], $server);

return $this;

}

发现其中核心部分就是 $this->call() 这句,继续追 call:

public function call($method, $uri, $parameters = [], $cookies = [], $files = [], $server = [], $content = null)

{

$this->currentUri = $this->prepareUrlForRequest($uri);

$symfonyRequest = SymfonyRequest::create(

$this->currentUri, $method, $parameters,

$cookies, $files, $server, $content

);

return $this->response = $this->app->prepareResponse(

$this->app->handle(Request::createFromBase($symfonyRequest))

);

}

会发现其中,就到了SymfonyRequest::create() 这句($this->currentUri 这里比较简单,有兴趣过程自己追下),于是我们就来看看这个 create 是如何工作的(这下要追到这里了 \vendor\symfony\http-foundation\Request.php):

照例还是先贴源码,

public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null)

{

$server = array_replace(array(

'SERVER_NAME' => 'localhost',

'SERVER_PORT' => 80,

'HTTP_HOST' => 'localhost',

'HTTP_USER_AGENT' => 'Symfony/3.X',

'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',

'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5',

'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',

'REMOTE_ADDR' => '127.0.0.1',

'SCRIPT_NAME' => '',

'SCRIPT_FILENAME' => '',

'SERVER_PROTOCOL' => 'HTTP/1.1',

'REQUEST_TIME' => time(),

), $server);

$server['PATH_INFO'] = '';

$server['REQUEST_METHOD'] = strtoupper($method);

$components = parse_url($uri);

if (isset($components['host'])) {

$server['SERVER_NAME'] = $components['host'];

$server['HTTP_HOST'] = $components['host'];

}

if (isset($components['scheme'])) {

if ('https' === $components['scheme']) {

$server['HTTPS'] = 'on';

$server['SERVER_PORT'] = 443;

} else {

unset($server['HTTPS']);

$server['SERVER_PORT'] = 80;

}

}

if (isset($components['port'])) {

$server['SERVER_PORT'] = $components['port'];

$server['HTTP_HOST'] = $server['HTTP_HOST'].':'.$components['port'];

}

if (isset($components['user'])) {

$server['PHP_AUTH_USER'] = $components['user'];

}

if (isset($components['pass'])) {

$server['PHP_AUTH_PW'] = $components['pass'];

}

if (!isset($components['path'])) {

$components['path'] = '/';

}

switch (strtoupper($method)) {

case 'POST':

case 'PUT':

case 'DELETE':

if (!isset($server['CONTENT_TYPE'])) {

$server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';

}

// no break

case 'PATCH':

$request = $parameters;

$query = array();

break;

default:

$request = array();

$query = $parameters;

break;

}

$queryString = '';

if (isset($components['query'])) {

parse_str(html_entity_decode($components['query']), $qs);

if ($query) {

$query = array_replace($qs, $query);

$queryString = http_build_query($query, '', '&');

} else {

$query = $qs;

$queryString = $components['query'];

}

} elseif ($query) {

$queryString = http_build_query($query, '', '&');

}

$server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : '');

$server['QUERY_STRING'] = $queryString;

return self::createRequestFromFactory($query, $request, array(), $cookies, $files, $server, $content);

}

有趣的地方来了,先随便浏览下发现了一个好像不太对的地方:不是说没有那个啥 REQUEST_URI,怎么看起来好像这里有,那再看清楚一点这里是 $server['REQUEST_URI'] ,之前那个地方是 $_SERVER['REQUEST_URI'],一个是普通变量,一个是超全局变量。那么为什么 symfony 不去使用 PHP 自带的超全局变量,而非要自己搞个 $server 呢?

这个类里面还有一个方法解释了这个疑问:

/**

* Creates a new request with values from PHP's super globals.

*

* @return static

*/

public static function createFromGlobals()

{

// With the php's bug #66606, the php's built-in web server

// stores the Content-Type and Content-Length header values in

// HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.

$server = $_SERVER;

if ('cli-server' === PHP_SAPI) {

if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {

$server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH'];

}

if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {

$server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE'];

}

}

$request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server);

if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')

&& in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))

) {

parse_str($request->getContent(), $data);

$request->request = new ParameterBag($data);

}

return $request;

}

重点在 $server = $_SERVER 这句,回答了刚才的疑问,这句就说明 $server 本质上是对 $_SERVER 的包装,在没有对它进行后面的处理之前,他们基本是一样的。(注释中的 bug ,有兴趣就可以看下。)

回到完毕这个问题,我们继续跳回,刚才那个 create 方法, symfony 自己通过 http_build_query 对 query path 进行了重新包装。所以在使用phpunit的时候,不需要依赖于超全局的系统变量就可以模拟 HTTP 请求了。

开头的那个坑其实也很好填,加个prefix就行了,无需自己再去判断路径:

//backend

{

$app->register(App\Providers\PermissionServiceProvider::class);

$app->group(['middleware' => ['operation', 'permission'],

'namespace' => 'App\Http\Controllers\Backend',

'prefix' => 'backend',

], function() use ($app) {

require __DIR__ . '/../routes/backend.php';

});

}

//frontend

{

$app->register(App\Providers\AuthServiceProvider::class);

require __DIR__ . '/../routes/frontend.php';

}

水平有限,如有不妥之处,欢迎指正。

本作品采用《CC 协议》,转载必须注明作者和本文链接

每天进步一点点

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值