axy/env

环境访问的抽象

0.2.0 2016-06-24 12:33 UTC

This package is auto-updated.

Last update: 2024-09-08 01:31:00 UTC


README

环境访问的抽象(PHP)。

Latest Stable Version Minimum PHP Version Build Status Coverage Status License

  • 该库不依赖任何其他依赖(除了 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 中用于自动完成的列表如下

但是理论上它可以用于任何函数。

访问 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);

您可以使用类 StreamStreamMock

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)