php自动加载类函数,PHP 基于类的自动加载实现的函数惰性加载

一直在想 PHP 有类的自动载入,为啥子没有函数的自动载入呢?

总得来说就几种方案,其中 rfc 已经被废。

方案 1:Composer files

"autoload": {

"files": [

"common/Infra/functions.php"

]

}

用 composer 动不动就几十个助手函数,90% 以上对我们的多少来说 API 来说都是一种加载负担。

// autoload_files.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));

$baseDir = dirname($vendorDir);

return array(

'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',

'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php',

'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',

'72579e7bd17821bb1321b87411366eae' => $vendorDir . '/illuminate/support/helpers.php',

'6b06ce8ccf69c43a60a1e48495a034c9' => $vendorDir . '/react/promise-timer/src/functions.php',

'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',

'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',

'2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php',

'ebf8799635f67b5d7248946fe2154f4a' => $vendorDir . '/ringcentral/psr7/src/functions_include.php',

'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',

'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',

'cea474b4340aa9fa53661e887a21a316' => $vendorDir . '/react/promise-stream/src/functions_include.php',

'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',

'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',

'cf97c57bfe0f23854afd2f3818abb7a0' => $vendorDir . '/zendframework/zend-diactoros/src/functions/create_uploaded_file.php',

'9bf37a3d0dad93e29cb4e1b1bfab04e9' => $vendorDir . '/zendframework/zend-diactoros/src/functions/marshal_headers_from_sapi.php',

'ce70dccb4bcc2efc6e94d2ee526e6972' => $vendorDir . '/zendframework/zend-diactoros/src/functions/marshal_method_from_sapi.php',

'f86420df471f14d568bfcb71e271b523' => $vendorDir . '/zendframework/zend-diactoros/src/functions/marshal_protocol_version_from_sapi.php',

'b87481e008a3700344428ae089e7f9e5' => $vendorDir . '/zendframework/zend-diactoros/src/functions/marshal_uri_from_sapi.php',

'0b0974a5566a1077e4f2e111341112c1' => $vendorDir . '/zendframework/zend-diactoros/src/functions/normalize_server.php',

'1ca3bc274755662169f9629d5412a1da' => $vendorDir . '/zendframework/zend-diactoros/src/functions/normalize_uploaded_files.php',

'40360c0b9b437e69bcbb7f1349ce029e' => $vendorDir . '/zendframework/zend-diactoros/src/functions/parse_cookie_header.php',

'4a1f389d6ce373bda9e57857d3b61c84' => $vendorDir . '/barryvdh/laravel-debugbar/src/helpers.php',

'6506d72cb66769ba612eb2800e4b0b6e' => $vendorDir . '/hunzhiwange/framework/src/Leevel/Leevel/functions.php',

'05a007f8491620f2bc6b891fc6e46c02' => $vendorDir . '/php-pm/php-pm/src/functions.php',

'0ccdf99b8f62f02c52cba55802e0c2e7' => $vendorDir . '/zircote/swagger-php/src/functions.php',

'629bcf4896f1b026f50c8c0a44b87e34' => $baseDir . '/common/Infra/functions.php',

);

曾经为这些助手函数很烦恼,因为他们都不是惰性加载,并且去掉了他们。

$files = include __DIR__.'/vendor/composer/autoload_files.php';

/**

* Ignore the helper functions.

* Because most of them are useless.

*/

foreach ($files as $fileIdentifier => $_) {

$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;

}

require_once __DIR__.'/vendor/autoload.php';

方案 2:类方法

namespace Hello\World;

class Foo

{

public static function hello(): string

{

return 'world';

}

}

其实本质上还是方法,当然还是类的自动加载。

还有一个它的变种,类当函数。

namespace Hello\World;

class Foo

{

public function __invoke(): string

{

return 'world';

}

}

还有变种

namespace MyNamespace;

class Fn {

private function __construct() {}

private function __wakeup() {}

private function __clone() {}

public static function __callStatic($fn, $args) {

if (!function_exists($fn)) {

$fn = "YOUR_FUNCTIONS_NAMESPACE\\$fn";

require str_replace('\\', '/', $fn) . '.php';

}

return call_user_func_array($fn, $args);

}

}

方案 3:利用类自动导入来实现函数代码

namespace MyNamespace;

class a

{

}

function a()

{

}

function b()

{

}

你可以

use MyNamespace\a;

use function MyNamespace\a;

new a(); // 或者 class_exits(a::class);

a();

上面的实现是否有可以改进的地方呢。比如去掉 class a 的定义,不用 new a(); 这样的怪异用法呢,答案是肯定的。

方案 4: 基于虚拟类的自动导入实现的惰性函数加载方案

函数实现的原型参考

return call_user_func('\\MyNamespace\\Foo\\hello_world', 1, 2);

实现如下

return fn('\\MyNamespace\\Foo\\hello_world', 1, 2);

第二种用法

use function MyNamespace\Foo\hello_world;

return fn(function() {

return hello_world(1, 2);

});

第三种用法

return fn(function($a, $b) {

return hell0_world($a, $b);

}, 1, 2);

我们定义一个类

declare(strict_types=1);

namespace Leevel\Support;

use Closure;

use Error;

/**

* 函数自动导入.

*

* @author Xiangmin Liu <635750556@qq.com>

*

* @since 2019.04.05

*

* @version 1.0

*/

class Fn

{

/**

* 自动导入函数.

*

* @param \Closure|string $fn

* @param array $args

*

* @return mixed

*/

public function __invoke($fn, ...$args)

{

$this->validate($fn);

try {

return $fn(...$args);

} catch (Error $th) {

$fnName = $this->normalizeFn($fn, $th);

if ($this->match($fnName)) {

return $fn(...$args);

}

throw $th;

}

}

/**

* 匹配函数.

*

* @param string $fn

*

* @return bool

*/

protected function match(string $fn): bool

{

foreach (['Fn', 'Prefix', 'Index'] as $type) {

if ($this->{'match'.$type}($fn)) {

return true;

}

}

return false;

}

/**

* 校验类型.

*

* @param \Closure|string $fn

*/

protected function validate($fn): void

{

if (!is_string($fn) && !($fn instanceof Closure)) {

$e = sprintf('Fn first args must be Closure or string.');

throw new Error($e);

}

}

/**

* 整理函数名字.

*

* @param \Closure|string $fn

* @param \Error $th

*

* @return string

*/

protected function normalizeFn($fn, Error $th): string

{

$message = $th->getMessage();

$undefinedFn = 'Call to undefined function ';

if (0 !== strpos($message, $undefinedFn)) {

throw $th;

}

if (is_string($fn)) {

return $fn;

}

return substr($message, strlen($undefinedFn), -2);

}

/**

* 匹配一个函数一个文件.

*

* @param string $fn

* @param string $virtualClass

*

* @return bool

*/

protected function matchFn(string $fn, string $virtualClass = ''): bool

{

if (!$virtualClass) {

$virtualClass = $fn;

}

class_exists($virtualClass);

return function_exists($fn);

}

/**

* 匹配前缀分隔一组函数.

*

* @param string $fn

*

* @return bool

*/

protected function matchPrefix(string $fn): bool

{

if (false === strpos($fn, '_')) {

return false;

}

$fnPrefix = substr($fn, 0, strpos($fn, '_'));

return $this->matchFn($fn, $fnPrefix);

}

/**

* 匹配基于 index 索引.

*

* @param string $fn

*

* @return bool

*/

protected function matchIndex(string $fn): bool

{

if (false === strpos($fn, '\\')) {

return false;

}

$fnIndex = substr($fn, 0, strripos($fn, '\\')).'\\index';

return $this->matchFn($fn, $fnIndex);

}

定义一个助手函数

use Leevel\Support\Fn;

if (!function_exists('fn')) {

/**

* 自动导入函数.

*

* @param \Closure|string $call

* @param array $args

* @param mixed $fn

*

* @return mixed

*/

function fn($fn, ...$args)

{

return (new Fn())($fn, ...$args);

}

}

实现原理如下,我们可以通过 try catch 捕捉到一个函数不存在的错误,利用函数所在命名空间的虚拟类,通过判断虚拟类 class exits 来导入一个类,触发 composer PSR 4 规则来访问路径。

第一优先级,一个文件一个函数

# /data/codes/php/MyNamespace/Foo/single_func.php

# 虚拟类为 MyNamespace\Foo\single_func

namespace MyNamespace\Foo;

function single_func()

{

}

使用方法

fn('\\MyNamespace\\Foo\\single_func');

第二优先级分组模块化:

# /data/codes/php/MyNamespace/Foo/prefix.php

# 虚拟类为 MyNamespace\Foo\prefix

namespace MyNamespace\Foo;

function prefix_a()

{}

function prefix_b_c_d()

{}

使用方法

fn('\\MyNamespace\\Foo\\prefix_a');

第三优先级,index 导入

# /data/codes/php/MyNamespace/Foo/index.php

# 虚拟类为 MyNamespace\Foo\index

namespace MyNamespace\Foo;

function hello()

{}

function world()

{}

使用方法

fn('\\MyNamespace\\Foo\\world');

通过这种方式,我们可以实现函数的惰性加载,当然方法都差不多。目前用这个类来做函数拆分。

注意:分组和 index 索引还是得显示定义虚拟类防止函数不存在时的 class_exits 重复载入。

因为 composer 使用的是 include 会出现重复载入的问题。

vendor/composer/ClassLoader.php

/**

* Scope isolated include.

*

* Prevents access to $this/self from included files.

*/

function includeFile($file)

{

include $file;

}

例如:

declare(strict_types=1);

namespace MyNamespace\Foo;

/**

* 使用方法

*

* ```

* echo fn('\\MyNamespace\\Foo\\foo_bar');

* ```

*

* @param string $extend

* @return string

*/

function foo_bar(string $extend = ''): string

{

return 'foo bar'.$extend;

}

/**

* Prevent duplicate loading.

*/

class index{}

测试用例请稍后访问,已写好,整理中,大家可以用在项目中,不错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值