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

2024-03-18

我在用着__get() http://php.net/manual/en/language.oop5.overloading.php使我的一些属性“动态”(仅在请求时初始化它们)。这些“假”属性存储在私有数组属性中,我正在 __get 中检查该属性。

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


编辑:速度测试

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

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

以下是我所做的测试,这些测试让我认为性能下降的论点是不合理的:

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

(t1 = 带 switch 的 magic,t2 = getter,t3 = 带进一步 getter 调用的 magic)

不确定 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

Another method:
 // 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');
  }

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

缺点是该属性变得可写:(


以下是我的 Win7 机器上使用 PHP 5.3.6 的 Zend 调试器报告的代码结果:

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

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

关于您所写的不必要的复杂性

它并没有真正增加我的应用程序的复杂性

当然可以。通过查看代码的嵌套深度,您可以轻松发现它。好的代码留在左边。你的 if/switch/case/if 有四层深。这意味着有更多可能的执行路径,这将导致更高的结果圈复杂度 http://phpmd.org/rules/codesize.html,这意味着更难维护和理解。

这是 A 类的数字(不包括常规 Getter。输出缩短为PHPLoc https://github.com/sebastianbergmann/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 意味着这已经处于中等复杂度的边缘。每增加一个案例到交换机中,这个数字就会增加 2。此外,它会将您的代码变成程序混乱,因为所有逻辑都在 switch/case 内部,而不是将其划分为离散单元,例如单吸气剂。

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 块以进行更改,而使用普通的旧 Getter,您只需重载需要更改的 Getter。

是的,添加所有 Getter 需要更多的打字工作,但它也更简单,最终会产生更易于维护的代码,并且还有一个好处是为您提供显式 API,这使我们可以看到您的其他语句

我特别希望我的 API 是“隔离的”;该文档应该告诉其他人如何使用它:P

我不知道你所说的“隔离”是什么意思,但如果你的 API 无法表达它的作用,那么它就是糟糕的代码。如果我必须阅读你的文档,因为你的 API 没有告诉我如何通过查看它来与之交互,那么你就做错了。你正在混淆代码。在数组中声明属性而不是在类级别(它们所属的位置)声明它们会迫使您为其编写文档,这是额外且多余的工作。好的代码易于阅读并且具有自记录功能。考虑购买罗伯特·马丁的书《干净的代码》。

话虽如此,当你说

唯一的原因就是api的美观;

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

现在结束:

我没有看到任何真正的缺点,我想这是值得的

希望你现在就能看到他们。这不值得。

有关延迟加载的其他方法,请参阅Martin Fowler 的 PoEAA 中的各种延迟加载模式 http://martinfowler.com/eaaCatalog/lazyLoad.html:

延迟加载有四种主要类型。延迟初始化使用特殊的标记值(通常为空)来指示字段未加载。每次访问该字段都会检查该字段的标记值,如果已卸载,则加载它。虚拟代理是一个与真实对象具有相同接口的对象。第一次调用它的一个方法时,它会加载真实的对象,然后进行委托。价值持有者是一个具有 getValue 方法的对象。客户端调用getValue来获取真实的对象,第一次调用会触发加载。 Aghost是没有任何数据的真实对象。第一次调用方法时,ghost 会将完整数据加载到其字段中。

这些方法略有不同,并且有不同的权衡。您还可以使用组合方法。本书包含完整的讨论和示例。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 __get() (魔术)来模拟只读属性和延迟加载 的相关文章

  • Laravel 5.1 中的VerifyCsrfToken.php 第 53 行:(Firefox 浏览器)中出现 TokenMismatchException?

    我试图找出为什么会出现这个错误 即使它是全新安装的 我在我的项目中遇到了这个错误 所以我用谷歌搜索 没有一个答案对我有用 所以我创建了新项目并复制了所有控制器 视图和模型 几个小时后工作正常 再次出现令牌不匹配错误 为什么在 laravel
  • 关于加拿大短信网关提供商的建议[关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 我很好奇 如果我能够接受传入的短信到某个号码 然后将其传递给 PHP 中的服务器端应用程序 会带来多少麻烦 金钱 我最终会通过电子邮件地址发回短信 有
  • 如何在 Zend Framework 中存储 cron 作业的脚本?

    因为 ZF 的所有 URL 都依赖于 mod 重写 所以我并不清楚应该在哪里存储用于 cron 作业的本地脚本 有人有什么建议 或者有 正式接受 的方式吗 我用模块化目录结构 http framework zend com manual e
  • 具有动态表单名称的 form_widget

    在我的 Twig 模板中 我有一个 FOR 循环 它创建多个表单 如下所示 for thing in things set form id myform thing Id set form name attribute form myfor
  • 使用 php 更改白天和黑夜的背景?

    我正在制作一个 tumblr 页面 我的 html 页面有两种不同的背景 我希望白天背景从早上 7 点到晚上 8 点显示 夜间背景从晚上 8 点到早上 7 点显示 我决定用 php 来做这件事 但对于 php 来说我是个新手 我的朋友给我发
  • 将函数中的会话变量传递给 codeigniter 中的助手

    这就是我正在尝试做的事情 这是控制器中的功能 public function get started if test login this gt session gt all userdata this gt load gt view te
  • Pandas hub_table 更快的替代品

    我正在使用熊猫pivot table在大型数据集 1000 万行 6 列 上运行 由于执行时间至关重要 因此我尝试加快流程 目前 处理整个数据集大约需要 8 秒 这太慢了 我希望找到替代方案来提高速度 性能 我当前的 Pandas 数据透视
  • 使用 PHP 的 Google Glass GDK 身份验证

    我正在尝试点击此链接来验证 GDK 中的用户 https developers google com glass develop gdk authentication https developers google com glass de
  • 如何将 mysql 转换为 mysqli? [复制]

    这个问题在这里已经有答案了 我厌倦了将 mysql 转换为 mysqli 但似乎收到了很多错误和警告 连接到数据库没有问题 但其余代码似乎错误 我做错了什么 sql
  • 使用日语“Enter”键进行搜索功能

    我在日语方面遇到了问题 我有一个允许用户搜索数据的表单 当用户输入要搜索的字符串并按 Enter 键时 搜索功能就会执行 我的代码是 formSearch input keyup function event var key event c
  • Laravel 5.2 带有可变参数的命名路由用法

    我有这样的路线 Open New Subscription page Route get account subscriptions create menu uses gt Subscriptions SubscriptionControl
  • Pandas dataframe:每批行的操作

    我有一个熊猫数据框df我想计算每批行的一些统计信息 例如 假设我有一个batch size 200000 对于每批batch sizerows 我想要一列的唯一值的数量ID我的数据框 我怎样才能做这样的事情呢 这是我想要的一个例子 prin
  • 将数组拆分为特定数量的块

    我知道array chunk 允许将数组拆分为多个块 但块的数量根据元素的数量而变化 我需要的是始终将数组拆分为特定数量的数组 例如 4 个数组 以下代码将数组分为 3 个块 两个块各有 2 个元素 1 个块有 1 个元素 我想要的是将数组
  • 如何用 kevent() 替换 select() 以获得更高的性能?

    来自Kqueue 维基百科页面 http en wikipedia org wiki Kqueue Kqueue 在内核和用户空间之间提供高效的输入和输出事件管道 因此 可以修改事件过滤器以及接收待处理事件 同时每次主事件循环迭代仅使用对
  • 如何在html中制作多行类型的文本框?

  • 如何从字符串中删除所有数字?

    我想删除字符串 0 9 中的所有数字 我写了这段有效的代码 words preg replace 0 words remove numbers words preg replace 1 words remove numbers words
  • PHP文件上传

    如果我想在文件名转到服务器的永久位置 而不是临时位置 之前更改文件名 我该如何执行此操作 代码如下
  • 如何在 codeigniter 查询中使用 FIND_IN_SET?

    array array classesID gt 6 this gt db gt select gt from this gt table name gt where array gt order by this gt order by q
  • 哪些属性有助于运行时 .Net 性能?

    我正在寻找可用于通过向加载器 JIT 编译器或 ngen 提供提示来确保 Net 应用程序获得最佳运行时性能的属性 例如我们有可调试属性 http msdn microsoft com en us library k2wxda47 aspx
  • 过度使用委托对性能来说是一个坏主意吗? [复制]

    这个问题在这里已经有答案了 考虑以下代码 if IsDebuggingEnabled instance Log GetDetailedDebugInfo GetDetailedDebugInfo 可能是一个昂贵的方法 因此我们只想在调试模式

随机推荐