gpt4 book ai didi

php - 使用 __get() (魔术)模拟只读属性和延迟加载

转载 作者:IT王子 更新时间:2023-10-29 00:11:46 28 4
gpt4 key购买 nike

我正在使用 __get()使我的一些属性“动态”(仅在请求时初始化它们)。这些“假”属性存储在私有(private)数组属性中,我正在 __get 中检查它。

无论如何,您认为为每个属性创建方法比在 switch 语句中创建方法更好吗?


编辑:速度测试

我只关心性能,@Gordon 提到的其他东西对我来说并不那么重要:

  • 不必要的复杂性 - 它并没有真正增加我的应用程序的复杂性
  • 脆弱的非显而易见的 API - 我特别希望我的 API 是“隔离的”;文档应该告诉其他人如何使用它:P

下面是我所做的测试,这让我认为性能命中论证是不合理的:

50.000 次调用的结果(在 PHP 5.3.9 上):

enter image description here

(t1 = 带开关的魔法,t2 = getter,t3 = 带进一步 getter 调用的魔法)

不确定 t3 上的“Cum”是什么意思。它不能是累积时间,因为 t2 应该有 2K 那么...

代码:

class B{}



class A{
protected
$props = array(
'test_obj' => false,
);

// magic
function __get($name){
if(isset($this->props[$name])){
switch($name){

case 'test_obj':
if(!($this->props[$name] instanceof B))
$this->props[$name] = new B;

break;
}

return $this->props[$name];
}

trigger_error('property doesnt exist');
}

// standard getter
public function getTestObj(){
if(!($this->props['test_obj'] instanceof B))
$this->props['test_obj'] = new B;

return $this->props['test_obj'];
}
}



class AA extends A{

// magic
function __get($name){
$getter = "get".str_replace('_', '', $name); // give me a break, its just a test :P

if(method_exists($this, $getter))
return $this->$getter();


trigger_error('property doesnt exist');
}


}


function t1(){
$obj = new A;

for($i=1;$i<50000;$i++){
$a = $obj->test_obj;

}
echo 'done.';
}

function t2(){
$obj = new A;

for($i=1;$i<50000;$i++){
$a = $obj->getTestObj();

}
echo 'done.';
}

function t3(){
$obj = new AA;

for($i=1;$i<50000;$i++){
$a = $obj->test_obj;

}
echo 'done.';
}

t1();
t2();
t3();

ps:为什么我要使用 __get() 而不是标准的 getter 方法?唯一的原因是 api 的美感;因为我没有看到任何真正的缺点,我想这是值得的 :P


编辑:更多速度测试

这次我用microtime测量了一些平均值:

PHP 5.2.4 和 5.3.0(相似结果):

t1 - 0.12s
t2 - 0.08s
t3 - 0.24s

PHP 5.3.9,xdebug 处于事件状态,这就是它这么慢的原因:

t1 - 1.34s
t2 - 1.26s
t3- 5.06s

禁用 xdebug 的 PHP 5.3.9:

t1 - 0.30
t2 - 0.25
t3 - 0.86


另一种方法:

 // magic
function __get($name){
$getter = "get".str_replace('_', '', $name);

if(method_exists($this, $getter)){
$this->$name = $this->$getter(); // <-- create it
return $this->$name;
}


trigger_error('property doesnt exist');
}

具有请求名称的公共(public)属性将在第一次 __get 调用后动态创建。这解决了速度问题——在 PHP 5.3 中获得 0.1s(比标准 getter 快 12 倍),以及 Gordon 提出的可扩展性问题。您可以简单地覆盖子类中的 getter。

缺点是属性变得可写:(

最佳答案

这是 Zend Debugger 在我的 Win7 机器上使用 PHP 5.3.6 报告的代码结果:

Benchmark results

如您所见,对 __get 方法的调用比常规调用慢很多(3-4 倍)。对于总共 50k 个调用,我们仍然处理不到 1s,因此在小规模使用时可以忽略不计。但是,如果您打算围绕魔术方法构建整个代码,您将需要分析最终应用程序以查看它是否仍然可以忽略不计。

关于相当无趣的性能方面就这么多了。现在让我们来看看你认为“不那么重要”的是什么。我要强调这一点,因为它实际上比性能方面重要得多。

关于你写的不必要的复杂性

it doesn't really increase my app complexity

当然可以。您可以通过查看代码的嵌套深度轻松发现它。好的代码留在左边。你的 if/switch/case/if 有四层深。这意味着有更多可能的执行路径,这将导致更高的 Cyclomatic Complexity。 ,这意味着更难维护和理解。

这是 A 类的数字(没有常规的 Getter。输出从 PHPLoc 缩短):

Lines of Code (LOC):                                 19
Cyclomatic Complexity / Lines of Code: 0.16
Average Method Length (NCLOC): 18
Cyclomatic Complexity / Number of Methods: 4.00

值为 4.00 意味着这已经处于中等复杂性的边缘。每将一个额外的 case 放入 switch,这个数字就会增加 2。此外,它会将您的代码变成程序上的困惑,因为所有逻辑都在 switch/case 内部,而不是将其分成离散的单元,例如单 setter/getter 。

Getter,即使是延迟加载的 Getter,也不需要适度复杂。考虑具有普通旧 PHP Getter 的同一个类:

class Foo
{
protected $bar;
public function getBar()
{
// Lazy Initialization
if ($this->bar === null) {
$this->bar = new Bar;
}
return $this->bar;
}
}

在此运行 PHPLoc 将为您提供更好的圈复杂度

Lines of Code (LOC):                                 11
Cyclomatic Complexity / Lines of Code: 0.09
Cyclomatic Complexity / Number of Methods: 2.00

对于您添加的每一个额外的普通旧 Getter,这将保持为 2。

另外,考虑到当你想使用你的变体的子类型时,你将不得不重载 __get 并复制并粘贴整个 switch/case block 来进行更改,而使用普通的旧的 Getter,您只需重载需要更改的 Getters。

是的,添加所有 Getters 需要更多的打字工作,但它也更简单,最终会导致代码更易于维护,并且还具有为您提供显式 API 的好处,这将我们引向您的其他声明

I specifically want my API to be "isolated"; The documentation should tell others how to use it :P

我不知道您所说的“隔离”是什么意思,但如果您的 API 无法表达它的作用,那么它就是糟糕的代码。如果我必须阅读您的文档,因为您的 API 没有告诉我如何通过查看它来与它交互,那么您做错了。您正在混淆代码。在数组中声明属性而不是在类级别(它们所属的位置)声明它们会强制您为其编写文档,这是额外的和多余的工作。好的代码易于阅读和 self 记录。考虑购买 Robert Martin 的书“Clean Code”。

话虽如此,当你说

the only reason is the api beauty;

那我说:那就不要用__get 因为它会起到相反的效果。它会使 API 变得丑陋。魔术是复杂且不明显的,这正是导致那些 WTF 时刻的原因:

Code Quality: WTF per minute

现在结束:

i don't see any real disadvantages, I guess it's worth it

希望您现在能看到它们。这不值得。

有关延迟加载的其他方法,请参阅 various Lazy Loading patterns from Martin Fowler's PoEAA :

There are four main varieties of lazy load. Lazy Initialization uses a special marker value (usually null) to indicate a field isn't loaded. Every access to the field checks the field for the marker value and if unloaded, loads it. Virtual Proxy is an object with the same interface as the real object. The first time one of its methods are called it loads the real the object and then delegates. Value Holder is an object with a getValue method. Clients call getValue to get the real object, the first call triggers the load. A ghost is the real object without any data. The first time you call a method the ghost loads the full data into its fields.

These approaches vary somewhat subtly and have various trade-offs. You can also use combination approaches. The book contains the full discussion and examples.

关于php - 使用 __get() (魔术)模拟只读属性和延迟加载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9342543/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com