Ale*_*lex 13 php oop performance
我正在使用__get()使我的一些属性"动态"(仅在请求时初始化它们).这些"假"属性存储在私有数组属性中,我在__get中检查.
无论如何,你认为为每个属性创建方法更好,而不是在switch语句中进行吗?
我只关心性能,@戈登提到的其他东西对我来说并不重要:
所以这里是我做的测试,这让我觉得表现不佳是不合理的:
50.000次调用的结果(在PHP 5.3.9上):

(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();
Run Code Online (Sandbox Code Playgroud)
ps:为什么我要使用__get()而不是标准的getter方法?唯一的原因是api美; 因为我没有看到任何真正的缺点,我想这是值得的:P
这次我用microtime来测量一些平均值:
PHP 5.2.4和5.3.0(类似结果):
t1 - 0.12s
t2 - 0.08s
t3 - 0.24s
Run Code Online (Sandbox Code Playgroud)
PHP 5.3.9,xdebug处于活动状态,这就是它如此缓慢的原因:
t1 - 1.34s
t2 - 1.26s
t3- 5.06s
Run Code Online (Sandbox Code Playgroud)
禁用xdebug的PHP 5.3.9:
t1 - 0.30
t2 - 0.25
t3 - 0.86
Run Code Online (Sandbox Code Playgroud)
// 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');
}
Run Code Online (Sandbox Code Playgroud)
在第一次__get调用之后,将动态创建具有所请求名称的公共属性.这解决了速度问题 - 在PHP 5.3中获得0.1秒(比标准getter快12倍),以及Gordon提出的可扩展性问题.您可以简单地覆盖子类中的getter.
缺点是属性变得可写:(
Gor*_*don 18
以下是Zend Debugger在我的Win7机器上使用PHP 5.3.6报告的代码结果:

正如您所看到的,对__get方法的调用比常规调用慢很多(3-4倍).对于总共50k的呼叫,我们仍然处理不到1s,因此在小规模使用时可以忽略不计.但是,如果您打算围绕魔术方法构建整个代码,则需要对最终应用程序进行概要分析,以确定它是否仍然可以忽略不计.
非常无趣的表现方面.现在让我们来看看你认为"不重要"的东西.我要强调的是,因为它实际上比性能方面更重要.
关于你所写的不需要的附加复杂性
它并没有真正增加我的应用程序复杂性
当然可以.您可以通过查看代码的嵌套深度轻松发现它.好的代码留在左边.你的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
Run Code Online (Sandbox Code Playgroud)
值为4.00意味着这已经处于边缘以缓和复杂性.放入交换机的每个附加情况下,此数字增加2.此外,它会将您的代码变成程序混乱,因为所有逻辑都在交换机/案例中,而不是将其划分为离散单元,例如单个Getters.
Getter,即使是懒惰的装载,也不需要中等复杂.考虑使用普通的旧PHP Getter的同一个类:
class Foo
{
protected $bar;
public function getBar()
{
// Lazy Initialization
if ($this->bar === null) {
$this->bar = new Bar;
}
return $this->bar;
}
}
Run Code Online (Sandbox Code Playgroud)
在此上运行PHPLoc将为您提供更好的Cyclomatic Complexity
Lines of Code (LOC): 11
Cyclomatic Complexity / Lines of Code: 0.09
Cyclomatic Complexity / Number of Methods: 2.00
Run Code Online (Sandbox Code Playgroud)
对于您添加的每个额外的普通旧Getter,这将保持在2.
另外,考虑到当您想要使用变体的子类型时,您将不得不重载__get并复制并粘贴整个开关/案例块以进行更改,而使用普通的旧Getter,您只需重载需要更改的Getters .
是的,添加所有Getters的打字工作更多,但它也更简单,最终会带来更易维护的代码,并且还有为您提供明确API的好处,这将引导我们进行您的其他声明
我特别希望我的API"孤立"; 文档应告诉其他人如何使用它:P
我不知道"隔离"是什么意思,但如果你的API无法表达它的作用,那就是代码很差.如果我必须阅读您的文档,因为您的API没有通过查看它告诉我如何与它进行交互,那么您做错了.你正在混淆代码.声明数组中的属性而不是在类级别(它们所属的位置)声明它们会强制您为它编写文档,这是额外的和多余的工作.好的代码易于阅读和自我记录.考虑购买罗伯特马丁的书"清洁代码".
有了这个说,当你说
唯一的原因是api美;
然后我说:然后不要使用,__get因为它会产生相反的效果.它会使API变得丑陋.魔术是复杂而不明显的,这正是导致WTF时刻的原因:

现在就结束:
我没有看到任何真正的缺点,我想这是值得的
你希望现在能看到它们.这不值得.
有关延迟加载的其他方法,请参阅Martin Fowler的PoEAA中的各种延迟加载模式:
懒惰载荷主要有四种.延迟初始化使用特殊标记值(通常为null)来指示未加载字段.对字段的每次访问都会检查字段中的标记值,如果已卸载,则加载它.虚拟代理是与真实对象具有相同接口的对象.第一次调用其中一个方法时,它会加载真实的对象然后委托.Value Holder是一个带有getValue方法的对象.客户端调用getValue来获取真实对象,第一个调用触发负载.一个鬼魂是没有任何数据的真正对象.第一次调用方法时,ghost会将完整数据加载到其字段中.
这些方法略有不同,并有各种权衡取舍.您也可以使用组合方法.这本书包含完整的讨论和例子.