spaf/simputils

一组简单、最小化但实用的工具(属性、字符串、日期时间等)

1.1.6 2023-11-17 16:11 UTC

README

🇺🇦 #StandWithUkraine

这是乌克兰官方银行捐赠链接

https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi

SimpUtils

这些徽章目前已经过时了 :)

Build Status codecov

SimpUtils 是一个微框架,提供简单且轻量级的开发工具和原型设计工具。此外,还有一些工具用于舒适和高效的优化和调试。

框架故意没有太多 composer 依赖项,以确保它不会使您的 vendor 文件夹膨胀。

框架的主要目的是提升开发体验,它不反对任何流行的框架巨头,如 "Yii2/3","Laravel" 或 "Zend"。SimpUtils 可以轻松地与任何框架结合使用。

框架扩展了 PHP 语言的一些实用功能。提供类似于原生类的功能,但提高了它们的性能。规范命名和架构。

框架的所有方面都旨在提高代码开发和可读性。所有组件和功能都旨在对常见用例直观且透明,但在需要时非常灵活。

附言。我为我自己的项目开发了此框架,但非常乐意与任何感兴趣的人分享。请随意参与!建议、错误报告。我非常愿意听取您的意见。

许可证是 "MIT"

软件按“原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性、针对特定目的的适用性和非侵权性保证。在任何情况下,作者或版权所有者均不对任何索赔、损害或其他责任承担责任,无论源于合同、侵权或其他原因,与软件或其使用或其他交易有关。

变更日志

变更日志

重要说明

  1. 目前,JSON 序列化和反序列化功能无法正常工作。请现在不要依赖它!重要!当这个问题得到修复,并且您正在使用当前逻辑时,您可能会遇到损坏的代码逻辑。请勿使用 \spaf\simputils\PHP::serialize()\spaf\simputils\PHP::deserialize() 代码与 JSON 机制,您可以将机制切换到原生的 PHP,如下所示(解决方案)
      PHP::$serialization_mechanism = PHP::SERIALIZATION_TYPE_PHP;
      PHP::init();
    这将使用原生的 PHP 机制进行序列化,这应该从本版本(1.1.3)开始正常工作。

文档

关于文档的重要说明:由于迫切需要稳定版本,我不得不删除所有文档(由于我的架构修订,它已经过时)。所以请稍等一下,在稳定版本发布后,我将提供更多文档。第一个稳定版本必须在架构方面进行润色,因此文档将在最近的将来发布。对此表示歉意。

一些

  1. 术语表
  2. 结构
  3. 重要说明 - 这可以帮助您进行故障排除

安装

最低 PHP 版本:8.0

当前框架版本:1.1.6

composer require spaf/simputils "^1"

请注意,库的开发应遵循语义版本控制,因此同一主版本内的功能应该是向后兼容的(除了bug和一些罕见的问题)。

关于语义版本控制更多信息: 语义版本控制说明

"dev-main" 稳定版本

从这一点开始,我决定在最终发布之前,发布包含一些近期更新(我需要)的稳定版本。将其视为“ALPHA”发布。

尽管如此,请避免在生产环境中使用它,如果你只是想通过工单联系我,我会更快地发布稳定版本!

请记住,尽管它应该是稳定的,但它仍然是“未发布”的,甚至可能包含未完成的概念。请避免在没有正当理由的情况下使用它。

重要:如果你不确定它是什么,请不要使用此版本。对于生产环境,始终使用上面的安装方法!

composer require spaf/simputils "dev-main"

此分支的所有功能都将被适当打包和发布,请放心。

"dev-dev" 不稳定版本

此外,我决定拥有一个不稳定版本,其中包含我需要的更多近期更新,以及未完成和损坏的概念。

请记住,它可能会完全破坏你的代码,未完成的概念或甚至损坏的代码在这里可能非常常见。

我强烈建议避免使用此代码,除非你非常确定你在做什么!

重要:如果你不确定它是什么,请不要使用此版本。

composer require spaf/simputils "dev-dev"

此分支的所有功能都将被适当打包和发布,请放心。

快速亮点和示例

只是一些非常简化的功能示例:)

  1. 渲染器
  2. 处理URL
  3. 数据处理和可执行文件处理
  4. 属性
  5. 日期时间
  6. 高级PHP Info对象
  7. IPv4模型
  8. 类似路径的Box数组
  9. Box数组可伸缩特性 (paramsAlike())
  10. "with" 爱心

处理URL

"URL"对象(UrlObject)的新功能已经到来,并且几乎完成。经过最近更新,它已经足够测试,可以被认为是稳定的(尽管仍然可能发生一些非常规的bug)。

示例

use function spaf\simputils\basic\url;

PHP::init();

$url = url('localhost', ['booo', 'fooo'], ['godzila' => 'tamdam', '#' => 'jjj'], port: 8080);

pr($url, "{$url}");

输出将会是

spaf\simputils\models\UrlObject Object
(
    [for_system] => ****
    [for_user] => https://:8080/booo/fooo?godzila=tamdam#jjj
    [host] => localhost
    [orig] => ****
    [params] => spaf\simputils\models\Box Object
        (
            [godzila] => tamdam
        )

    [password] => ****
    [path] => spaf\simputils\models\Box Object
        (
            [0] => booo
            [1] => fooo
        )

    [port] => 8080
    [processor] => spaf\simputils\models\urls\processors\HttpProtocolProcessor
    [protocol] => https
    [relative] => /booo/fooo?godzila=tamdam#jjj
    [sharpy] => jjj
    [user] => 
)

https://:8080/booo/fooo?godzila=tamdam#jjj

它不仅可以生成,还可以解析,甚至可以组合部分

use function spaf\simputils\basic\url;
PHP::init();

$url = url(
	// It will first parse this, and extract all the relevant stuff
	'http://my.spec.domain.com.ru.at/path1/path2?param1=val1&param2=val2',

	// Then it will use path and add it to the existing path
	['path_3', 'path_4'],

	// And after that adds the params to existing ones (or overrides them by key)
	[
		'param_30' => 'val-30',
		'param_40' => 'val-40',
		// Sharpy can be defined/redefined like this
		'#' => 'new-sharpy!'
	]
);

pr($url, "{$url}");

输出将会是


spaf\simputils\models\UrlObject Object
(
    [for_system] => ****
    [for_user] => http://my.spec.domain.com.ru.at/path1/path2/path_3/path_4?param1=val1&param2=val2&param_30=val-30&param_40=val-40#new-sharpy!
    [host] => my.spec.domain.com.ru.at
    [orig] => ****
    [params] => spaf\simputils\models\Box Object
        (
            [param1] => val1
            [param2] => val2
            [param_30] => val-30
            [param_40] => val-40
        )

    [password] => ****
    [path] => spaf\simputils\models\Box Object
        (
            [0] => path1
            [1] => path2
            [2] => path_3
            [3] => path_4
        )

    [port] => 80
    [processor] => spaf\simputils\models\urls\processors\HttpProtocolProcessor
    [protocol] => http
    [relative] => /path1/path2/path_3/path_4?param1=val1&param2=val2&param_30=val-30&param_40=val-40#new-sharpy!
    [sharpy] => new-sharpy!
    [user] => 
)

http://my.spec.domain.com.ru.at/path1/path2/path_3/path_4?param1=val1&param2=val2&param_30=val-30&param_40=val-40#new-sharpy!

之后,你可以单独获取部分并与之互动。

例如,我们获取“路径”部分,它们被作为Box数组返回,你可以顺序地处理它们,但一旦将它们转换为字符串,它们就又变成了“路径”字符串

use function spaf\simputils\basic\url;
PHP::init();

$url = url(
	// It will first parse this, and extract all the relevant stuff
	'http://my.spec.domain.com.ru.at/path1/path2?param1=val1&param2=val2',

	// Then it will use path and add it to the existing path
	['path_3', 'path_4'],

	// And after that adds the params to existing ones (or overrides them by key)
	[
		'param_30' => 'val-30',
		'param_40' => 'val-40',
		// Sharpy can be defined/redefined like this
		'#' => 'new-sharpy!'
	]
);

$path = $url->path;
$path->append('HUGE-PATH-ADDITION');
$path[1] = 'I_REPLACED_PATH2_PIECE';
$stringified_path = "My path really is: {$path}";

pr($stringified_path);

输出将会是

My path really is: path1/I_REPLACED_PATH2_PIECE/path_3/path_4/HUGE-PATH-ADDITION

另一个值得注意的时刻是,当你修改“路径”Box对象时,它也会影响URL对象。(如果你想避免这种情况,请始终克隆对象)

上面的相同示例,但现在输出整个URL对象

use function spaf\simputils\basic\url;
PHP::init();

$url = url(
	// It will first parse this, and extract all the relevant stuff
	'http://my.spec.domain.com.ru.at/path1/path2?param1=val1&param2=val2',

	// Then it will use path and add it to the existing path
	['path_3', 'path_4'],

	// And after that adds the params to existing ones (or overrides them by key)
	[
		'param_30' => 'val-30',
		'param_40' => 'val-40',
		// Sharpy can be defined/redefined like this
		'#' => 'new-sharpy!'
	]
);

// Important, here is the assigning by reference, not cloning!
$path = $url->path;

$path->append('HUGE-PATH-ADDITION');
$path[1] = 'I_REPLACED_PATH2_PIECE';

pr($url, "{$url}");

输出将会是

spaf\simputils\models\UrlObject Object
(
    [for_system] => ****
    [for_user] => http://my.spec.domain.com.ru.at/path1/I_REPLACED_PATH2_PIECE/path_3/path_4/HUGE-PATH-ADDITION?param1=val1&param2=val2&param_30=val-30&param_40=val-40#new-sharpy!
    [host] => my.spec.domain.com.ru.at
    [orig] => ****
    [params] => spaf\simputils\models\Box Object
        (
            [param1] => val1
            [param2] => val2
            [param_30] => val-30
            [param_40] => val-40
        )

    [password] => ****
    [path] => spaf\simputils\models\Box Object
        (
            [0] => path1
            [1] => I_REPLACED_PATH2_PIECE
            [2] => path_3
            [3] => path_4
            [4] => HUGE-PATH-ADDITION
        )

    [port] => 80
    [processor] => spaf\simputils\models\urls\processors\HttpProtocolProcessor
    [protocol] => http
    [relative] => /path1/I_REPLACED_PATH2_PIECE/path_3/path_4/HUGE-PATH-ADDITION?param1=val1&param2=val2&param_30=val-30&param_40=val-40#new-sharpy!
    [sharpy] => new-sharpy!
    [user] => 
)

http://my.spec.domain.com.ru.at/path1/I_REPLACED_PATH2_PIECE/path_3/path_4/HUGE-PATH-ADDITION?param1=val1&param2=val2&param_30=val-30&param_40=val-40#new-sharpy!

所有上述功能都与params类似。

重要:对于字符串参数,只有$host参数进行完整解析,对于$path(uri)参数,以及$params参数进行附加路径(+params+sharpy)解析,而$params不进行“字符串解析”。字符串不允许作为$params参数的数据类型(至少目前是这样)。

当前页面 / 活跃URL

为获取当前URL准备的新方法(仅在Web上工作,在CLI上不会工作,除非伪造一些参数)

PHP::init();

$url = PHP::currentUrl();

pd($url);

输出可能取决于你的Web服务器


spaf\simputils\models\UrlObject Object
(
    [for_system] => ****
    [for_user] => https://:8080/booo/fooo?godzila=tamdam#jjj
    [host] => localhost
    [orig] => ****
    [params] => spaf\simputils\models\Box Object
        (
            [godzila] => tamdam
        )

    [password] => ****
    [path] => spaf\simputils\models\Box Object
        (
            [0] => booo
            [1] => fooo
        )

    [port] => 8080
    [processor] => spaf\simputils\models\urls\processors\HttpProtocolProcessor
    [protocol] => https
    [relative] => /booo/fooo?godzila=tamdam#jjj
    [sharpy] => jjj
    [user] => 
)

这允许你确定$url对象是当前URL还是活跃URL

$current = PHP::currentUrl();
$url = url(port: 9090);

pr("current: {$current}");
pr("url: {$url}");
pr("the same: ".Boolean::to($url->isCurrent()));

输出可能取决于你的Web服务器


current: https://:8080/booo/fooo?godzila=tamdam#jjj
url: https://:9090/
the same: false

UrlObject::isCurrent()有许多优秀的参数可以调整。除此之外,还有一个用于比较两个不同URL的方法,不仅仅是当前网页URL的UrlObject::isSimilar(),其结构几乎相同。

一些功能尚未完全实现

  • URL扩展就像“俄罗斯套娃”
  • "params"参数解析字符串
  • 支持除HTTP(S)之外的其他协议
  • 也许还有其他一些功能!

文件、数据文件和可执行文件处理

文件基础设施

与文件一起工作可以非常简单

use spaf\simputils\Boolean;
use spaf\simputils\PHP;
use function spaf\simputils\basic\fl;
use function spaf\simputils\basic\pd;
use function spaf\simputils\basic\pr;

require_once 'vendor/autoload.php';

PHP::init();

// Creating file in RAM
$file = fl();
pr("File size before write: {$file->size_hr}");
$file->content = "A secret data string :)))) !";
pr("File size after write: {$file->size_hr}");
pr("Content of the file: \"{$file->content}\"");

pr("===============================");

pr("Does file exist before moving from ram: ".Boolean::to($file->exists));
$file->move('/tmp', 'LOCAL_FILE_SYSTEM_FILE_random_name.txt');
pr("Does file exist after moving to HD: ".Boolean::to($file->exists));

pr("===============================");

// The location pointed is exactly the same, where we saved the file from RAM
$new_file = fl("/tmp/LOCAL_FILE_SYSTEM_FILE_random_name.txt");
pr("File {$new_file} | exists: ".Boolean::to($new_file->exists));
pr("File {$new_file} | size: {$new_file->size_hr}");
pr("File {$new_file} | content: \"{$new_file->content}\"");

// Moving from RAM to HD is possible, but from HD to RAM is not yet implemented.

// IMPORTANT: "content" read or write DOES REAL READING/WRITING EVERY SINGLE CALL!
//            So please if you need to use it multiple times in code - please store it in a var

重要:“内容”读取或写入每次调用都进行实际读取/写入!因此,如果您需要在代码中多次使用它,请将其存储在变量中。

输出将会是

File size before write: 0 B
File size after write: 28 B
Content of the file: "A secret data string :)))) !"
===============================
Does file exist before move from ram: false
Does file exist after move to HD: true
===============================
File /tmp/LOCAL_FILE_SYSTEM_FILE_random_name.txt | exists: true
File /tmp/LOCAL_FILE_SYSTEM_FILE_random_name.txt | size: 28 B
File /tmp/LOCAL_FILE_SYSTEM_FILE_random_name.txt | content: "A secret data string :)))) !"

一个酷炫的特性是创建没有实际硬盘文件的真实RAM文件,然后将它们从RAM移动/保存到硬盘文件系统。

顺便说一句,目前没有明确的属性来区分哪个文件是RAM文件,哪个是硬盘文件,但这个特性应该会出现在未来的版本中。

在上面的例子中,使用了简单的文本数据。处理能力取决于资源应用程序处理器,可以按您希望的任何方式进行定制。

目前,有几个这样的例子

  • \spaf\simputils\models\files\apps\CsvProcessor - CSV文件处理
  • \spaf\simputils\models\files\apps\DotEnvProcessor - ".env"文件处理
  • \spaf\simputils\models\files\apps\JsonProcessor - JSON文件处理
  • \spaf\simputils\models\files\apps\TextProcessor - 默认用于任何未识别的文件类型
  • \spaf\simputils\models\files\apps\PHPFileProcessor - 特殊处理器,出于安全原因不允许直接使用

默认情况下,如果文件未被识别,则使用TextProcessor

在创建文件对象时可以显式使用处理器,也可以稍后重新分配

use spaf\simputils\models\files\apps\JsonProcessor;
use spaf\simputils\models\files\apps\TextProcessor;
use spaf\simputils\PHP;
use function spaf\simputils\basic\bx;
use function spaf\simputils\basic\fl;
use function spaf\simputils\basic\pr;

require_once 'vendor/autoload.php';

PHP::init();

$fl = fl(app: new JsonProcessor());
$fl->content = bx([
	'something' => 'another',
	'a' => 12,
	'b' => [
		'33' => 500,
		'gg' => 'TTTTTT'
	]
]);
pr("Simple ARRAY returned from the file: ", $fl->content);

$fl->app = new TextProcessor();
pr("Textual content of the file: ", $fl->content);

这是一个处理文件的好方法,其中文件的读取/解析/生成内容取决于应用程序。

顺便说一句,在处理不同类型的单个文件对象时发现了一个小错误,请现在避免更改文件类型。#124

请勿使用PHPFileProcessor。这是一个非常特殊的处理器,在框架的一些罕见情况下使用。但它绝不应该被显式使用。不要覆盖它

文件应用程序处理器可以通过“mime类型”设置为默认值,而不仅仅是通过为每个对象显式指定确切的资源应用程序处理器。

use spaf\simputils\attributes\Property;
use spaf\simputils\generic\BasicResource;
use spaf\simputils\generic\BasicResourceApp;
use spaf\simputils\models\File;
use spaf\simputils\PHP;
use function spaf\simputils\basic\fl;
use function spaf\simputils\basic\pr;

require_once 'vendor/autoload.php';

PHP::init();

/**
 * @property ?string $dummy_text
 */
class DummyWrapperProcessor extends BasicResourceApp {

	#[Property]
	protected ?string $_dummy_text = '== Default / Special / Dummy / Text ==';

	/**
	 * @inheritDoc
	 */
	public function getContent(mixed $fd, ?BasicResource $file = null): mixed {
		return $this->_dummy_text;
	}

	/**
	 * @inheritDoc
	 */
	public function setContent(mixed $fd, mixed $data, ?BasicResource $file = null): void {
		$this->dummy_text = $data;
	}

}

PHP::redef(File::class)::$processors['text/plain'] = DummyWrapperProcessor::class;

$file = fl();
pr($file->content);
$file->content = '-- NEW STRING --';
pr($file->content);

重要PHP::redef(File::class)是引用File类的一种简单方法,它比直接使用File类更安全,因为您可能会在系统中重新定义一些模型/类 - 在这种情况下,您可能会破坏兼容性。PHP::redef(File::class)用于避免此类问题。

此外text/plain MIME类型是未指定情况下的默认类型。因此,如果您重新定义它,任何未指定/未识别的文件都将使用它!

您可以重新定义支持的所有MIME类型集。甚至可以创建您自己的。

数据文件

数据文件是具有配置或存储在文件中的某些信息的应用程序的一组范围文件。

通常希望将小配置保存到JSON或“PHP-Array”文件中,然后读取并使用它们。

或者存储一些测试用例或数据字典以供数据库迁移使用。

所有这些都可以通过数据文件轻松实现。

要使用数据文件,您需要指定允许的文件夹,这些文件夹将从中使用这些文件。如果不指定这些目录,则文件将无法作为“数据文件”访问。

use spaf\simputils\FS;
use spaf\simputils\PHP;
use function spaf\simputils\basic\pd;
use function spaf\simputils\basic\pr;

require_once 'vendor/autoload.php';

PHP::init([
	'allowed_data_dirs' => [
	    // This is the specification of the data folders.
	    // It's always considered from the "working_dir"
		'data',
	]
]);

$data_php = FS::data(['data', 'my-test-php-array-inside.php']);
pr($data_php);

$data_php = FS::data(['data', 'spec', 'my-spec.json']);
pr($data_php);

请注意,数据是通过文件基础设施检索的。这就是为什么您也可以通过“文件”对象直接使用。

use spaf\simputils\FS;
use spaf\simputils\PHP;
use function spaf\simputils\basic\pd;
use function spaf\simputils\basic\pr;

require_once 'vendor/autoload.php';

PHP::init([
	'allowed_data_dirs' => [
		'data'
	]
]);

$data_php = FS::dataFile(['data', 'my-test-php-array-inside.php']);
pr($data_php->content);

$data_php = FS::dataFile(['data', 'spec', 'my-spec.json']);
pr($data_php->content);

输出将完全相同。

建议使用FS::data()而不是FS::dataFile()

可执行文件处理

文件基础设施不应用于执行“PHP”文件(除数据文件之外)。

以下代码

use spaf\simputils\PHP;
use function spaf\simputils\basic\fl;
use function spaf\simputils\basic\pd;
use function spaf\simputils\basic\pr;

require_once 'vendor/autoload.php';

PHP::init();

$file = fl(['run.php']);
pr($file->content);

use spaf\simputils\PHP;
use function spaf\simputils\basic\fl;
use function spaf\simputils\basic\pd;
use function spaf\simputils\basic\pr;

require_once 'vendor/autoload.php';

PHP::init();

// spec-file-exec contains PHP code and system identifies it by mime-type as PHP code
$file = fl(['spec-file-exec']);
pr($file->content);

上述两种情况都会引发异常


Fatal error: Uncaught spaf\simputils\exceptions\ExecutablePermissionException: Executables like PHP should not be processed through the File infrastructure (except some rare cases) in /home/ivan/development/php-simputils/src/models/files/apps/PHPFileProcessor.php:33
Stack trace:
#0 /home/ivan/development/php-simputils/src/generic/BasicResourceApp.php(80): spaf\simputils\models\files\apps\PHPFileProcessor->getContent(Resource id #59, Object(spaf\simputils\models\File))
#1 /home/ivan/development/php-simputils/src/models/File.php(454): spaf\simputils\generic\BasicResourceApp->__invoke(Object(spaf\simputils\models\File), Resource id #59, true, NULL)
#2 /home/ivan/development/php-simputils/src/traits/PropertiesTrait.php(74): spaf\simputils\models\File->getContent(NULL, 'get', 'content')
#3 /home/ivan/development/php-simputils/run.php(16): spaf\simputils\generic\SimpleObject->__get('content')
#4 {main}
  thrown in /home/ivan/development/php-simputils/src/models/files/apps/PHPFileProcessor.php on line 33

Process finished with exit code 255

由于安全原因,这种行为是非常故意的!请不要尝试覆盖这种行为。

特殊注意事项

  1. 对于每个 PHP::init() 过程,都会搜索并处理 ".env" 文件,因此很容易指定/修改环境变量。这些值可以通过 env() 函数访问。如果您想禁用它
      PHP::init([
        'disable_init_for' => [
          DotEnvInitBlock::class,
        ]
      ]);
  2. 所有环境变量必须全部大写!!! 因此,如果您有如“test_1”这样的 .env 变量,那么在 env('TEST_1') 中始终使用大写!这是由于最佳实践而必须的。

属性

use spaf\simputils\generic\SimpleObject;
use spaf\simputils\PHP;
// Important to use exactly this DateTime class that is provided by the library
use spaf\simputils\models\DateTime;

require_once 'vendor/autoload.php';

/**
 * @property ?int $field_int
 * @property ?string $field_str
 * @property ?DateTime $field_dt
 */
class MyObjectOne extends SimpleObject {

	#[Property]
	protected ?int $_field_int = 22;

	#[Property(valid: 'upper')]
	protected ?string $_field_str = null;

	#[Property]
	protected ?DateTime $_field_dt = null;
}

// Always should be ran first in the runtime code
PHP::init();

////////////////////////

$m = new MyObjectOne;

pr($m); // Print out the whole content of the object

$m->field_int = 55.542;
$m->field_str = 'special string that is being normalized transparently';
$m->field_dt = '2000-11-20 01:23:45';

pd($m); // The same as pr(), but it dies after that

输出将是

MyObjectOne Object
(
    [field_dt] => 
    [field_int] => 22
    [field_str] => 
)

MyObjectOne Object
(
    [field_dt] => spaf\simputils\models\DateTime Object
        (
            [_simp_utils_property_batch_storage] => Array
                (
                )

            [_orig_value:protected] => 
            [date] => 2000-11-20 01:23:45.000000
            [timezone_type] => 3
            [timezone] => Europe/Berlin
        )

    [field_int] => 55
    [field_str] => SPECIAL STRING THAT IS BEING NORMALIZED TRANSPARENTLY
)

重要:在 DateTime 对象中忽略一些附加字段,如 “_orig_value” 和 “_simp_utils_property_batch_storage”,这是由于 PHP 引擎的一个真正讨厌的 bug,开发者似乎已经完全忽略了。

使用特殊属性 DebugHide 隐藏一些字段以从调试输出中隐藏的酷方法

use spaf\simputils\attributes\DebugHide;
use spaf\simputils\attributes\Property;
use spaf\simputils\generic\SimpleObject;
use spaf\simputils\PHP;
// Important to use exactly this DateTime class that is provided by the library
use spaf\simputils\models\DateTime;

require_once 'vendor/autoload.php';


/**
 * @property ?int $field_int
 * @property ?string $field_str
 * @property ?DateTime $field_dt
 * @property ?string $password
 */
class MyObjectOne extends SimpleObject {

	#[Property]
	protected ?int $_field_int = 22;

	#[Property(valid: 'upper')]
	protected ?string $_field_str = null;

	#[DebugHide]
	#[Property]
	protected ?DateTime $_field_dt = null;

	#[DebugHide(false)]
	#[Property(valid: 'lower')]
	protected ?string $_password = null;
}

// Always should be ran first in the runtime code
PHP::init();

////////////////////////

$m = new MyObjectOne;

pr($m); // Print out the whole content of the object

$m->field_int = 55.542;
$m->field_str = 'special string that is being normalized transparently';
$m->field_dt = '2000-11-20 01:23:45';
$m->password = 'MyVeRRy Secrete PaSWorT!@#';

pr($m);
pd("My password really is: {$m->password}");

输出将是

MyObjectOne Object
(
    [field_int] => 22
    [field_str] => 
    [password] => ****
)

MyObjectOne Object
(
    [field_int] => 55
    [field_str] => SPECIAL STRING THAT IS BEING NORMALIZED TRANSPARENTLY
    [password] => ****
)

My password really is: myverry secrete paswort!@#

日期时间

简单快速地遍历日期周期

use spaf\simputils\PHP;
use spaf\simputils\models\DateTime;
use function spaf\simputils\basic\now;
use function spaf\simputils\basic\pr;
use function spaf\simputils\basic\ts;

// Setting default user output format to Austrian
PHP::init([
	'l10n' => 'AT'
]);

foreach (DT::walk('2000-05-05', '2000-05-09', '12 hours') as $dt) {
	pr("$dt");
}

// Output would be:
//  05.05.2000 00:00
//  05.05.2000 12:00
//  06.05.2000 00:00
//  06.05.2000 12:00
//  07.05.2000 00:00
//  07.05.2000 12:00
//  08.05.2000 00:00
//  08.05.2000 12:00

// Changing locale settings for the user output to US format
$conf->l10n = 'US';

// Do the same iterations again, and output would be in US format
foreach (DT::walk('2000-05-05', '2000-05-09', '12 hours') as $dt) {
	pr("$dt");
}

// Output would be:
//  05/05/2000 12:00 AM
//  05/05/2000 12:00 PM
//  05/06/2000 12:00 AM
//  05/06/2000 12:00 PM
//  05/07/2000 12:00 AM
//  05/07/2000 12:00 PM
//  05/08/2000 12:00 AM
//  05/08/2000 12:00 PM


// The same can be achieved using directly DateTime without DT helper

$conf->l10n = 'RU';

// Both bellow are equivalents. Shortcut is a better choice
$obj1 = new DateTime('2001-05-17 15:00', 'UTC');
$obj2 = ts('2001-06-01 16:00');

foreach ($obj1->walk($obj2, '1 day') as $dt) {
	pr("$dt");
}

// Output would be something like:
//  17.05.2001 15:00
//  18.05.2001 15:00
//  19.05.2001 15:00
//  20.05.2001 15:00
//  21.05.2001 15:00
//  22.05.2001 15:00
//  23.05.2001 15:00
//  24.05.2001 15:00
//  25.05.2001 15:00
//  26.05.2001 15:00
//  27.05.2001 15:00
//  28.05.2001 15:00
//  29.05.2001 15:00
//  30.05.2001 15:00
//  31.05.2001 15:00
//  01.06.2001 15:00

使用棱镜 DateTime

// Setting default user output format to Austrian
use function spaf\simputils\basic\ts;

PHP::init([
	'l10n' => 'AT'
]);

$dt = ts('2100-12-12 13:33:56.333');

echo "Date: {$dt->date}\n";
echo "Time: {$dt->time}\n";

// Output would be:
//  Date: 12.12.2100
//  Time: 13:33

// Chained object modification
echo "{$dt->date->add('22 days')->add('36 hours 7 minutes 1000 seconds 222033 microseconds 111 milliseconds')}\n";

// Output would be:
//  05.01.2101


// What is interesting, is that "date" prism just sub-supplying those "add()" methods to the
// target $dt object, so if we check now the complete condition of $dt it would contain all
// those modifications we did in chain above
echo "{$dt->format(DT::FMT_DATETIME_FULL)}";
// Would output:
//  2101-01-05 01:57:36.666033


// And Time prism would work the same way, but outputting only time part
echo "{$dt->time}\n";
// Output would be:
//  01:57

DateTime 这两个棱镜适用于任何 DateTime simputils 对象。

所有这些数学运算都是原生在 PHP 中完成的,因此它是 PHP 的固有功能。整体使用情况已部分改进,因此可以方便地链式和输出。

所有对象修改都可以通过 modify() 方法实现(add()sub() 的工作方式非常相似)。您可以在此了解更多信息: https://php.ac.cn/manual/en/datetime.modify.php

获取两个日期/时间之间差异的示例

use function spaf\simputils\basic\ts;

$dt = ts('2020-01-09');

echo "{$dt->diff('2020-09-24')}\n";
// Output would be:
//  + 8 months 15 days


// You can use any date-time references
$obj2 = ts('2020-05-19'); 
echo "{$dt->diff($obj2)}\n";
// Output would be:
//  + 4 months 10 days

// Just another interesting chained difference calculation
echo "{$dt->add('10 years')->add('15 days 49 hours 666 microseconds')->diff('2022-01-29')}\n";
// Output would be:
//  - 7 years 11 months 28 days 1 hour 666 microseconds

如果您想获取所有这些修改的差异怎么办?通常,您可以采用以下方法

use function spaf\simputils\basic\ts;

$dt = ts('2020-01-09');
$dt_backup = clone $dt;
$dt->add('10 years')->add('15 days 49 hours 666 microseconds')->sub('23 months');
echo "{$dt->diff($dt_backup)}\n";

// Output would be:
//  - 8 years 1 month 17 days 1 hour 666 microseconds

但上面的示例有点繁琐,以下方法可能会稍微优雅一些

use function spaf\simputils\basic\ts;

$dt = ts('2020-01-09');
$dt->add('10 years')->add('15 days 49 hours 666 microseconds')->sub('23 months');
echo "{$dt->diff()}\n";
// Output would be:
//  - 8 years 1 month 17 days 1 hour 666 microseconds

// This way the diff will use for the first argument the initial value that was saved before first
// "modification" methods like "add()" or "sub()" or "modify()" was applied.

// Reminding, that any of the "modification" methods would cause modification of the target
// DateTime object

重要:所有更改都是累积的,因为它们使用带有 false 首参数的 $this->snapshotOrigValue(false)。如果您不带参数或带有 true 调用该方法,它将用当前的值覆盖条件。

DatePeriod 的示例

use spaf\simputils\PHP;
use function spaf\simputils\basic\pr;
use function spaf\simputils\basic\ts;


$conf = PHP::init([
	'l10n' => 'AT'
]);

$dp = ts('2020-01-01')->walk('2020-05-05', '1 day');

pr("$dp");

// Output would be:
//  01.01.2020 00:00 - 05.05.2020 00:00

DateInterval 的示例

use spaf\simputils\PHP;
use spaf\simputils\models\DateInterval;

$conf = PHP::init([
	'l10n' => 'AT'
]);

$di = DateInterval::createFromDateString('2 days');

pr("$di");
// Output would be:
//  + 2 days

建议始终使用与 DateTime 相关的 SimpUtils 类的版本。

高级PHP Info对象

使用它非常简单。它是一个类似于数组(箱)的对象,包含几乎完整的 PHP Info 数据。您可以通过您舒适的方式访问和遍历它。它也与常见的 IDE 自动完成兼容(仅限顶级字段)。

您可以访问顶级字段(那些直接在对象上的字段)

  1. 以属性/字段类似的方式
    use spaf\simputils\PHP;
    $phpi = PHP::info();
    echo "{$phpi->cpu_architecture}";
  2. 以数组类似的方式(也提供箱功能)
    use spaf\simputils\PHP;
    $phpi = PHP::info();
    echo "{$phpi['cpu_architecture']}";
  3. 遍历对象
    use spaf\simputils\PHP;
    $i = 0;
    foreach (PHP::info() as $k => $v) {
        echo "{$k} ====> {$v}\n";
        if ($i++ > 4) {
            // Just a small limiter
            break;
        }
    }

附加好处

  1. 所有版本都包装在 Version 类中(开箱即用的版本比较等)
  2. 对象只创建一次,可以通过 PHP::info() 访问(手动可以有多个)
  3. 对象是从 Box 派生的,这意味着它具有所有这些好处(所有底层数组也被 Boxed,因此整个 PHP Info 的内容都可以通过 Box 功能访问)
  4. 包含大量信息,并且可能会在未来扩展更多相关信息。

使用高级 PHP Info 对象的理由

原生的 phpinfo() 只返回静态文本表示,这非常不方便使用。有关原生 phpinfo() 的信息,您可以在这里找到: https://php.ac.cn/manual/ru/function.phpinfo.php

IPv4模型

简单示例

$ic = PHP::init([
	'l10n' => 'AT',
]);

/**
 * @property ?string $name
 * @property ?IPv4 $my_ip
 */
class Totoro extends SimpleObject {

	#[Property]
	protected ?string $_name = null;

	#[Property]
	protected ?IPv4 $_my_ip = null;

}

$t = new Totoro;

$t->name = 'Totoro';
$t->my_ip = '127.0.0.1/16';
$t->my_ip->output_with_mask = false;

pr("I am {$t->name} and my address is {$t->my_ip} (and ip-mask is {$t->my_ip->mask})");

输出将会是

I am Totoro and my address is 127.0.0.1 (and ip-mask is 255.255.0.0)

类似路径的Box数组

这是 Box 模型的新功能。

现在有新的简短版本方法 Box::pathAlike()

PHP::init();

$bx = bx(['TEST', 'PATH', 'alike', 'box'])->pathAlike();

pd($bx, "{$bx}");

输出将会是

spaf\simputils\models\Box Object
(
    [0] => TEST
    [1] => PATH
    [2] => alike
    [3] => box
)

TEST/PATH/alike/box

以下是用不同示例的手动方式

PHP::init();

$b = bx(['TEST', 'PATH', 'alike', 'box']);

pr("{$b}"); // In this case JSON

$b->joined_to_str = true;

pr("{$b}");

$b->separator = '/';

pr("{$b}");

$b->separator = ' ## ';

pr("{$b}");

输出将会是


["TEST","PATH","alike","box"]
TEST, PATH, alike, box
TEST/PATH/alike/box
TEST ## PATH ## alike ## box

Box-array 的可拉伸特性

它几乎与“Path-Alike”完全相同,但它将包括“键”在内的盒子字符串化。

示例 1

PHP::init();

$bx = bx([
    'key1' => 'val1',
    'key2' => 'val2',
    'key3' => 'val3',
    'key4' => 'val4',
])->stretched('=');

pd($bx, "{$bx}");

输出将会是

spaf\simputils\models\Box Object
(
    [key1] => val1
    [key2] => val2
    [key3] => val3
    [key4] => val4
)

key1=val1, key2=val2, key3=val3, key4=val4

可能已经很明显了,它非常适合用于URL参数。

示例2

PHP::init();

$bx = bx([
	'key1' => 'val1',
	'key2' => 'val2',
	'key3' => 'val3',
	'key4' => 'val4',
])->stretched('=', '&');

// or shorter and more intuitive:

$bx = bx([
	'key1' => 'val1',
	'key2' => 'val2',
	'key3' => 'val3',
	'key4' => 'val4',
])->paramsAlike();

pd($bx, "{$bx}");

输出将会是

spaf\simputils\models\Box Object
(
    [key1] => val1
    [key2] => val2
    [key3] => val3
    [key4] => val4
)

key1=val1&key2=val2&key3=val3&key4=val4

需要注意的是,这种方法并不是直接将对象转换为字符串!它们在对象中存储特殊的配置,当您开始将Box转换为字符串时,它将使用保存的设置。

值包装器和htmlAttrAlike()

对于类似HTML属性的情况,只需使用此方法

$bx = bx([
	'data-my-attr-1' => 'test',
	'data-my-attr-2' => 'test2',
])->htmlAttrAlike();
// You can specify first argument " or ' to control which wrapper symbols are used.
// Or you could even specify callable to pre-process and wrap value automatically!

输出将会是

data-my-attr-1="test" data-my-attr-2="test2"

但如果您想要进行“值处理”而不是仅仅包装,则可以使用扩展功能

$bx = bx([
	'data-my-attr-1' => 'test',
	'data-my-attr-2' => 'test2',
])->stretched(' = ', ' ', function ($val, $key, $bx) {
	return "(`{$val}`)";
});

输出将会是

data-my-attr-1 = (`test`) data-my-attr-2 = (`test2`)

包装,包装,包装

对于扩展功能,您可以使用 $value_wrap$key_wrap 分别包装每个部分。它们以相同的方式工作,但包装它们各自的对应部分。之后或代替它,如果提供了 $stretcher 参数,则该函数/可调用/闭包将被用于包装整个对。

请记住,如果您为 keyvalue 指定了包装器,它们将先于 stretcher 可调用函数被调用!

下面的示例将有助于理解逻辑。

$bx = bx([
	'key1' => 'val1',
	'key2' => 'val2',
	'key3' => 'val3',
])->stretched(fn($v, $k) => "(\"{$k}\": \"{$v}\")",  ' || ', '!', '?');

pd("$bx");

输出将会是

("?key1?": "!val1!") || ("?key2?": "!val2!") || ("?key3?": "!val3!")

"with" 爱心

Python特定的命令 with 可以通过元魔法和可调用函数轻松实现。

简单示例

PHP::init();

class Totoro extends SimpleObject {

	protected function ___withStart($obj, $callback) {
		pr('PREPARED! %)');
//		$callback($obj);
//		return true;
	}

	protected function ___withEnd($obj) {
		pr('POST DONE %_%');
	}

}

$obj = new Totoro;

with($obj, function () {
	pr('HEY! :)');
});

您可以从可调用函数中轻松访问目标对象。

$obj = new Totoro;

with($obj, function ($obj) {
	pr('HEY! :)', $obj);
});

// or less elegant way:
with($obj, function () use ($obj) {
	pr('HEY! :)', $obj);
});

上面的示例可以组合使用,如果您想要使用外部作用域中的更多内容,但为了保持优雅的方式:)

$obj = new Totoro;

$var1 = 1;
$var2 = 0.2;
$var3 = 'CooCoo';

with($obj, function ($obj) use ($var1, $var2, $var3) {
	pr('HEY! :)', $obj, $var1, $var2, $var3);
});

显然,语法不像Python中那样可爱,但功能上是相同的。

附言。请注意,with() 功能依赖于“元魔法”特性,并且对象应使用该特性或实现 ___withStart()___withEnd() 的2个方法。

非常重要的一点是要记住,以上只是一个非常简化的描述,基本上是冰山一角!未来几周将添加许多有用的功能描述。

额外的composer脚本

您可以在composer中使用一些额外的脚本来测试和分析代码。

列出所有可用的脚本

composer run -l

输出类似以下内容

scripts:
  test             Run the whole PHPUnit test suit                                                                                              
  coverage         Run the whole PHPUnit test with Coverage suit. Output in HTML at "docs/coverage/html"                                        
  coverage-clover  Run the whole PHPUnit test with Coverage suit. Output in clover xml at "docs/coverage/"                                      
  mess             Runs the mess script as defined in composer.json.                                                                            
  pipeline-mess    Runs phpmd Mess Analysis on some scopes and return non 0 exit status if rules are violated. Reasonable for CI/CD pipelines.

自动测试和覆盖率

测试

需要 PHPUnitphp-mbstringphp-xdebugphp-bcmath(GMP扩展将不起作用。它会有精度损失,因此某些测试将失败)

对于APT-GET兼容的操作系统,这些可以通过以下方式安装

sudo apt install php-mbstring php-xdebug php-bcmath

运行测试

composer run test

代码覆盖率

需要 PHPUnit

composer run coverage

消息分析

需要 phpmd

composer run mess