axy / env
环境访问的抽象
Requires
- php: >=5.4.0
- axy/errors: ~1.0.2
README
环境访问的抽象(PHP)。
- 该库不依赖任何其他依赖(除了 composer 包)。
- 已测试于 PHP 5.4+、PHP 7、HHVM(Linux上)、PHP 5.5(Windows上)。
- 安装:
composer require axy/env
。 - 许可:MIT。
文档
该库为访问环境提供抽象层。环境包括
- 当前时间和时区
- PHP 运行时设置
- 输入数据(
$_GET
、$_POST
...) - 服务器数据(
$_SERVER
、$_ENV
) - HTTP 头和 Cookie
- 等等...任何可能影响系统其他部分的参数
如果某些代码直接访问超级全局数组或调用如 time()
或 header()
的函数,则该代码变得与环境相关联。这种代码难以测试和配置。
该库提供了环境包装器(axy\env\Env
类的实例)。应用程序代码从外部获取此包装器并通过它与环境交互。默认情况下,包装器只是将请求委派给标准函数。但这种行为可以被重新定义(用于测试或其他目的)。
示例
use axy\env\Factory; class Service { public function __construct($env = null) { $this->env = Factory::create($env); } public function action() { $timeOfAction = $this->env->getCurrentTime(); // ... } private $env; } // ... $service = new Service(); $service->action();
默认情况下将使用标准环境,并且 $this->env->getCurrentTime()
返回当前时间。但此行为可以被改变。
$service = new Service(['time' => '2014-11-04 10:11:12']); $service->action(); // the service will receive the specified time
配置
具有标准行为的环境包装器
use axy\env\Env; $env = new Env();
在配置中指定了非标准行为
$config = [ 'time' => '2015-11-04 11:11:11', 'get' => [ 'id' => 5, ], ]; $env = new Env($config);
配置可以通过数组(如上所示)或通过 axy\env\Config
的实例指定
$config = new Config(); $config->time = '2015-11-04 11:11:11'; $config->get = ['id' => 5]; $env = new Env($config);
数组简单,但对象支持 IDE 中的自动完成。
配置的具体参数在下面的相关章节中描述。
克隆
$config = new Config(); $config->time = '2015-11-04 11:11:11'; $env = new Env($config); $config->time = '2011-02-03 10:10:10'; echo date('j.m.Y', $env->getCurrentTime()); // 4.11.2015, config is cloned
当前时间
检查当前时间(返回一个时间戳)
$env->getCurrentTime(void): int
参数 time
指定当前时间。它可以是整数或数字字符串(时间戳)或字符串,用于 strtotime。
- 1234567890 - 时间戳(2009-02-14 02:31:30)
- "1234567890" - 类似
- "2015-11-04 10:11:12" - 当前时区中的时间
- "2015-11-04" - "2015-11-04 00:00:00"
- "+1 month" - 相对时间,用于
strtotime()
$config->time = '2015-11-04 00:01:02'; $env = new Env($config); echo date('j.m.Y', $env->getCurrentTime()); // 4.11.2015
改变时间
上面示例中的 $env->getCurrentTime()
总是返回相同的时间。对于长时间运行的场景,时间改变可能很重要。
/** * Cron daemon * Once an hour to kill, not to eat a lot of memory */ $startTime = time(); while (time() - $startTime() < 3500) { step(); sleep(5); }
如果指定了 timeChanging
,则时间将会改变。
$env = new Env([ 'time' => '1980-01-02 11:20:30', 'timeChanging' => true, ]); echo date('H:i:s', $env->getCurrentTime()).PHP_EOL; // 11:20:30 sleep(7); echo date('H:i:s', $env->getCurrentTime()).PHP_EOL; // 11:20:37
自定义函数代替 time()
如果未指定 time
,则 getCurrentTime()
调用全局函数 time()
的包装器(见下文“全局函数”部分)。它可以被覆盖。
$config = [ 'functions' => [ 'time' => 'myOwnTimeImplementation', ], ];
超级全局变量
通过以下方式提供超级全局数组 $_SERVER
、$_ENV
、$_GET
、$_POST
、$_REQUEST
、$_COOKIE
、$_FILES
$env->server
$env->env
$env->get
$env->post
$env->request
$env->cookie
$env->files
可以在配置中覆盖它们
$config = [ 'get' => ['x' => 1], ]; $env = new Env($config); $env->get['x']; // 1 $env->post['x']; // $_POST['x']
全局函数
魔术 __call
将委派给全局函数。
$env->strlen('string'); // 6 $env->header('Content-Type: text/plain'); // Send header
覆盖
$config = [ 'functions' => [ 'header' => function ($header) { // save header to a local storage }, ], ]; $env = new Env(); $env->header('Content-Type: text/plain'); // The header will not be sent
检查函数的存在 $env->isFunctionExists(string $name):bool
。
echo()
也可以被覆盖。
示例
函数 getallheaders()
并非在所有环境中都存在。
if ($env->isFunctionExists('getallheaders')) { return $env->getallheaders(); }
覆盖
$config = [ 'functions' => [ 'getallheaders' => function () { if (function_exists('getallheaders')) { return getallheaders(); } else { return parseServerVarsForHeaders(); } }, ], ]; // now $env->getallheaders() always available
或
$config = [ 'functions' => [ 'getallheaders' => null, // never ], ];
函数列表
它旨在用于访问环境的函数。在 phpdoc 中用于自动完成的列表如下
- header
- setcookie
- getallheaders
- headers_list
- http_response_code
- ini_set
- ini_get
- getenv
- error_reporting
- date_default_timezone_get
- date_default_timezone_set
- set_error_handler
- set_exception_handler
echo()
但是理论上它可以用于任何函数。
流
访问 PHP I/O 流。
$env->streams->stdout->write('Output');
容器 $env->streams
包含以下对象
stdin
stdout
stderr
input
output
这些对象实现了接口 IStream。
您可以创建具有接口 IStream 的对象并欺骗流
$config = [ 'streams' => [ 'stdin' => new MyStream(), ], ]; $env = new Env($config);
您可以使用类 Stream
和 StreamMock
Stream
是对流的包装
$config = [ 'streams' => [ 'stdin' => new \axy\env\Stream(fopen('/my/file.txt')), ], ]; $env = new Env($config);
StreamMock
StreamMock 是对字符串的包装
$mock = new \axy\env\StreamMock('content'); $mock->read(3); // "con" $mock->read(); // "tent" $mock->write('!'); $mock->setPosition(0); $mock->read(); // "content!"
除了 IStream
的方法外,StreamMock
还支持以下方法
setContent(string $content [, int $position])
setPosition(int $position)
getContent(): string
getPosition(): int
工厂
use axy\env\Factory; $env = Factory::getStandard();
方法 getStandard()
返回标准行为的 env 包装器。
方法 create($config)
创建一个具有覆盖行为的包装器。它可以接受
一个数组
$config = [ 'time' => 123456, ]; $env = Factory::create($config);
一个 Config
实例
$config = new Config(); $config->time = 123456, $env = Factory::create($config);
或一个 Env
实例本身
$env1 = new Env([ 'time' => 123456, ]); $env2 = Factory::create($env1); // $env1 === $env2
默认情况下(NULL
)指定标准包装器。
目标服务可以以不同的格式接受包装器,而无需处理它们。
use axy\env\Factory; class Service { public function __construct($env = null) { $this->env = Factory::create($env); } // ... } $service = new Service(['time' => '2011-01-01']);
异常
库可以抛出以下异常
axy\errors\InvalidConfig
- 配置中的未知字段(《['time' => 12345, 'unknown' => 67890]》)
- 字段类型错误(《['server' => 5]》,必须是数组)
- 字段中的数据错误(《[time => 'wrong time string']》)。
- 函数包装器不可调用(在调用期间检测到)。
axy\errors\FieldNotFound
- 字段未找到(《echo $env->unknown;》)。
- 函数未找到(《echo $env->unknown(5);》)。
axy\errors\Disabled
- 配置中禁用了函数
axy\errors\ContainerReadOnly
$env->server = []
或unset($env->server)
。