PHP
内置函数太多,记不住怎么办?尤其PHP
一直被人垢病函数名不统一,这就导致更不容易记忆了。这时候是不是在想,要是我随便定义一个变量,就能列出可以对其进行操作的函数就好了,虽然列出来也不一定知道用哪个,但起码一般情况下可以根据函数名猜到哪个才是要用的函数。例如$a = "Hello world";
,我在IDE
上输入$a->
,IDE
就能将可用函数显示出来。简单,这不就是面向对象吗?那就动手吧。
首先定义一个函数,用于将基础类型封装成类,以字符串为例,Str
类有一个静态函数wrap
接收一个字符串参数,返回Str
的实例,并且将$value
参数赋给$value
属性,那么wrap
的返回值就是对象,可以以->
符号来调用属于Str
的方法了。
<?php
class Str
{
private mixed $value;
public static function wrap(string $value): Str
{
return new self($value);
}
public function __construct(string $value)
{
$this->value = $value;
}
}
这时候就碰到一个问题,字符串操作函数那么多,难道我们要一个个封装进类里面?这种方法也太落后且难维护了吧。幸好PHP
有个魔术方法__call
,思路打开,那么我们是不是可以通过__call
来调用所有函数?实践起来吧。
public function __call($callable, $args)
{
$this->value = $callable(...$args);
return $this;
}
但是现在还有个问题,就是例如封装了一个字符串"foo"
,我想对它调用strlen
函数,显然Str::wrap("foo")->strlen()
是行不通的,因为没有参数,默认将封闭的值传进去?在调用strlen
时没问题,但是并非所有函数都将操作参数放在第一位,所以需要让__call
方法知道我们的参数位置才行。方法是定义一个唯一值来标记封装值所在位置,这里我们使用uniqid
函数,虽然没有一些uuid
库那么标准,但在这里也够用了。然后对__call
方法进行一定调整,调整后的代码如下:
<?php
if (!defined('wrapped_placeholder')) {
define('wrapped_placeholder', 'wrapped-placeholder-' . uniqid());
}
class Str
{
private mixed $value;
public static function wrap(string $value): Str
{
return new self($value);
}
public function __construct(string $value)
{
$this->value = $value;
}
public function __call($callable, $args)
{
foreach ($args as &$arg) {
if ($arg === wrapped_placeholder) {
$arg = $this->value;
}
}
$this->value = $callable(...$args);
return $this;
}
}
现在基本雏形已经出来了。使用方法如下:
<?php
print_r(Str::wrap("foo")->strlen(wrapped_placeholder));
// 输出:
// Str Object
// (
// [value:Str:private] => 3
// )
大概就是这样,但结果是Str
对象,显然不是我们想要的结果。再加个函数获取实际的结果吧。
public function get(): mixed
{
return $this->value;
}
由于get
方法直接通过之前的__call
方法逻辑是行不通的,需要对其修改,当方法不可调用时,就调用对象内部的方法。
public function __call($callable, $args)
{
if (!is_callable($callable)) {
return (function (callable $callable) use ($args) {
return $callable(...$args);
})($this->$callable);
}
foreach ($args as &$arg) {
if ($arg === wrapped_placeholder) {
$arg = $this->value;
}
}
$this->value = $callable(...$args);
return $this;
}
完结,撒花。
<?php
if (!defined('wrapped_placeholder')) {
define('wrapped_placeholder', 'wrapped-placeholder-' . uniqid());
}
class Str
{
private mixed $value;
public static function wrap(string $value): Str
{
return new self($value);
}
public function __construct(string $value)
{
$this->value = $value;
}
public function __call($callable, $args)
{
if (!is_callable($callable)) {
return (function (callable $callable) use ($args) {
return $callable(...$args);
})($this->$callable);
}
foreach ($args as &$arg) {
if ($arg === wrapped_placeholder) {
$arg = $this->value;
}
}
$this->value = $callable(...$args);
return $this;
}
public function get(): mixed
{
return $this->value;
}
}
$res = Str::wrap('hello_world_that_is_so_interesting_just_a_test')->strtoupper(wrapped_placeholder)->str_replace(['A', 'E', 'I', 'O', 'U'], ['a', 'e', 'i', 'o', 'u'], wrapped_placeholder)->get();
var_dump($res);
// 输出:
// string(46) "HeLLo_WoRLD_THaT_iS_So_iNTeReSTiNG_JuST_a_TeST"
等等,我们做这些的目的是啥?是为了IDE
提示啊,调用是可以了,但IDE
它没提示呢,差了最关键的一步。IDE
的提示使用文档注解,跟俗称的注释差不多吧,就在Str
顶部加上函数原型即可。PHP
内置的参数这么多,总不能一个个自己写吧,配套原型生成代码这就给你,以下代码就不详细说明了,将其运行输出结果复制到Str
顶部的文档注释就可以。
<?php
class Stubs
{
private static array $stubs;
private static function toString(mixed $value)
{
if ($value === false) {
return 'false';
} else if ($value === true) {
return 'true';
} else if ($value === null) {
return 'null';
} else if (is_array($value) || is_object($value) || is_string($value)) {
return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
} else {
return $value;
}
}
/**
* @param $type
* @return void
* @throws ReflectionException
*/
public static function gen($type): void
{
$ret = [];
foreach (get_defined_functions() as $functionType => $functions) {
foreach ($functions as $function) {
if (!str_contains($function, '\\')) { // 忽略一些包含命名空间的函数
$refFunc = new ReflectionFunction($function);
$params = $refFunc->getParameters();
$retType = (string)$refFunc->getReturnType();
// 只有参数或返回值匹配包装类型的函数才返回
$typePattern = "~$type~"; // 匹配类型的正则,如 "string|int"、"string","int|string"、"?string"
// $matchRetType = !empty(preg_match($typePattern, $retType));
$matchParamType = false;
foreach ($params as $item) {
$paramType = (string)$item->getType();
if (preg_match($typePattern, $paramType)) {
$matchParamType = true;
}
}
if ($matchParamType) {
$ret[] = [
'func' => $function,
'retType' => $retType,
'params' => $params,
];
}
}
}
}
self::$stubs = $ret;
}
/**
* @return void
*/
public static function print(): void
{
foreach (self::$stubs as $stub) {
$func = $stub['func'];
$retType = $stub['retType'];
$paramsArray = [];
/**
* @var ReflectionParameter $item
*/
foreach ($stub['params'] as $item) {
$type = (string)$item->getType();
$name = $item->getName();
$prefixName = '$';
$value = '';
if ($item->isPassedByReference()) {
$prefixName = '&' . $prefixName;
}
if ($item->isOptional() && $item->isDefaultValueAvailable()) {
$value = '= ' . self::toString($item->getDefaultValue());
}
$paramsArray[] = trim(implode(' ', [$type, $prefixName . $name, $value]));
// debug
// if ($func == '') {
// var_dump([$type, $prefixName. $name, $value], implode(' ', [$type, $prefixName. $name, $value]));
// }
}
$params = implode(',', $paramsArray);
printf("@method self %s(%s): %s\n",
$func,
$params,
$retType
);
}
}
}
try {
Stubs::gen('string');
Stubs::print();
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
可能会由于文档注释过多,PhpStorm
会比较卡(解决方法就是不直接打开使用封装类,而是在另一个文件引入封装类文件),vs code
则没那么卡,提示效果如下。
注意的是,这样做虽然写起来很方便,但性能会比直接调用原生函数差。
本文参考代码:php-pipe-operator。