【问题标题】:Overloading properties to ensure they are only set on first access重载属性以确保它们仅在首次访问时设置
【发布时间】:2014-12-22 21:26:08
【问题描述】:

我有一种情况,我希望仅在实际访问类属性时才对其进行初始化,因为初始化该属性包括可能不必要的数据库调用。我可以使用 getter 方法,但这似乎有点矫枉过正,不断地调用这个方法(我希望该属性就在那里,因为它在 90% 的时间里被非常频繁地使用)。

所以我在考虑使用 PHP 的 overloading,经过一些测试,它似乎工作正常。

class MyClass
{
    public function getName()
    {
        return 'Bob';
    }

    public function doSomething()
    {
        var_dump($this->name);
        var_dump($this->name);
        var_dump($this->name);
    }

    public function __get($property)
    {
        var_dump('Getting property.');

        if($property == 'name') {
            $this->name = $this->getName();
            return $this->name;
        }
    }
}

在这里调用 doSomething() 正是我想要的:

string(17) "获取属性。"

字符串(3)“鲍勃”

字符串(3)“鲍勃”

字符串(3)“鲍勃”

第一次通过 getName() 方法获取属性,随后的属性现在直接设置并且可以正常访问。

所以这就是我想要的。不是在初始化类时设置 $this->name,而是在第一次访问时设置它。我知道即时响应可能是我可以直接调用 getName(),但对于这种特殊情况,我宁愿通过属性访问。该属性当前在类初始化时永久设置,无论是否被访问。

编辑:为了澄清起见,该属性实际上是现实世界应用程序中的 Eloquent 对象,而不是包含名称的字符串!以上只是我创建的示例,用于测试我的潜在解决方案是否可以正常工作,并显示我想要做什么。此外,还有一些这些属性。在没有被访问的情况下,我宁愿不初始化属性。

我的问题。这是对重载的合理使用,还是会被其他开发人员普遍反对?性能方面,考虑到重载仅用于第一次访问,这一切都好吗?

谢谢

【问题讨论】:

  • 为什么大家都卡在90年代,好像我们的网站都跑在386上。用默认值实例化一个字符串会杀死服务器还是什么?我们必须求助于这种东西来节省几个字节的 RAM,就像我们仍然在最大只有 512k 的机器上一样?
  • 你的瓶颈会出现在访问文件的东西上,比如读取会话,而不是访问内存中的变量。如果你真的很在意,不要使用 PHP。严肃地说,PHP 是一种脚本语言,因此根据定义,每个请求都会使用更多内存。不仅如此,还有关联数组,而不仅仅是数字索引?哈哈。而且您担心直接调用成员而不是通过getter。来吧。
  • 为什么我们不回去将 5 或 6 个布尔标志打包到一个 int 中,同时进行二进制操作。
  • 您对我的问题的居高临下和讽刺的态度是不受欢迎的。人们来这里是为了提问和学习,而不是被嘲笑。你也没有真正回答我的问题。我要求澄清您关于属性访问与方法调用的第一条评论,是否有任何区别以及以何种速率(100 次调用、1000 次调用、10000 次调用等)。您回答说文件读取量更大,可能是瓶颈,这很明显,但不是问题所问的。
  • 顺便说一句,我刚刚在您的第一条评论中注意到您写道“使用默认值实例化一个字符串会杀死服务器吗?”。该属性是一个 Eloquent 对象,涉及数据库查询以将其拉起,而不是默认字符串值!我将在我的问题中澄清这一点。此外,在现实世界的应用程序中,这些属性中的一些包含不同的对象。

标签: php overloading magic-methods


【解决方案1】:

您所指的是“lazy loading”。因为只有当它被调用时才会设置成员变量。在第一次调用时,该值被按摩,然后被存储,因此后续调用不会受到必须重新按摩数据的性能影响。

但对于这种特殊情况,我宁愿通过属性访问

嗯?所以你宁愿做$object->someMemberVariable vs $object->getValueForMember()?为什么?后者使您能够在单个位置改变数据(如有必要),而不必将责任放在调用代码上,这可能导致重复的逻辑,这是您不想要的。考虑一个时间戳变量,而不是在您调用 $object->createdAt 的任何地方格式化日期,您可以将日期格式化一次,然后使其在任何需要它的地方可用。

在您的代码 sn-p 中,您已经过度设计了 __get 调用 b/c,您将其视为具体访问器的包装器。

__get__set 调用的最佳用途是在 ORM 中,它们不会创建潜在的数百个具体访问器,而是通过 __call 方法来表现它们。

-- 更新--

您几乎回答了自己的问题。你在这里做的是微优化,它的回报是递减的。但是如果你按照你说的去做

首先优化的其他领域(数据库性能、文件读取、 在内存中保存东西等))

您将获得更高的投资回报率。

你可以按照 Alex 在 cmets 中的建议去做,你可以给用户对象添加一个包装方法:

$user->getAddress()->getStreetName();

// Becomes

$user->getStreetName();

虽然一开始这可能看起来是优化的,但它在做同样的事情,只是稍微更改调用代码,以获得最小的收益。 但是有时添加,我称之为“便利包装”,可以增加一些好处。方便的包装器只是简单地包装调用以减轻调用代码的复杂性:

echo sprintf('%s %s %s', $user->getFirstName(), $user->getMiddleName(), $user->getLastName());

// Becomes

echo $user->getFullName();

还有一个但是,一些 ORM(和框架)会掩盖这样一个事实,即它们会进行额外的数据库调用来检索数据(延迟加载)。所以使用上面的地址示例;即使您进行了数据库调用以返回user 表中的数据,对getAddress() 的调用也需要另一个数据库调用以返回user_address 表中的数据。所以在这个例子中你可以做的是实现我所说的超级对象,很像一个普通的对象,但只有一个披风,jk。这就是我的意思:

// No extra DB call, as id is part of the primary table and was pulled on initial DB call
$user->getId(); 

// Even tho the ORM knows there is a relationship here it wasn't smart enough to pull it in on the initial call, so another DB call must be made
$user->getAddress()->getStreetName();

// What we want to do is incorporate our own method to retrieve a super object, so we can join all tables during initial db call and make the data available to the view

$query = 'SELECT u.*, ua.* FROM user AS a LEFT OUTER JOIN user_address AS ua WHERE u.id = 1';

// Hydrate user object accordingly
....

return $hydratedObject;

现在,使用超级对象,对存储在其他表中的辅助数据的任何调用都不会花费您的数据库命中,这将真正有助于优化您的应用。

有点偏离正题,但简而言之,像$user->getAddress()->getStreetName() 这样的调用绝对没有问题。事实上,这被称为 fluent interface 并且可能会很长,具体取决于类的编写方式:

$user->getCats()->getFirst()->getColor();

【讨论】:

  • 我一直被告知方法调用很受欢迎。鉴于此属性被大量访问,那将是很多方法调用。这最终不会加起来,还是这种事情对于任何应用程序来说都超出了规模,甚至减少了几毫秒?我知道首先还有其他优化领域(数据库性能、文件读取、将内容保存在内存中等),但如果最好通过属性直接访问对象,我不想执行数百个 getter 方法调用(这对我来说似乎更有意义)。
  • 此外,我觉得在任何地方都调用 $this->getUser()->id, $this->getUser()->username 而不仅仅是 $this->user-> id,$this->user->username。我更喜欢看起来干净的东西,并且发现在整个节目中不需要方法调用就更容易理解,所有这些都是为了访问对我来说最好表示为简单属性的东西。它现在被设置为一个属性,我只是想取消一些数据库调用,在某些请求中甚至没有访问该属性。
  • @Mike 如果你担心调用$this->user->id,你为什么不为$this->getUserId()写一个方法,看起来一点也不乱并且在重复使用方面为您提供了更大的灵活性。
  • 这个帮助回答你的问题?
猜你喜欢
  • 2015-01-15
  • 2018-10-24
  • 1970-01-01
  • 1970-01-01
  • 2021-05-13
  • 1970-01-01
  • 1970-01-01
  • 2022-12-12
  • 1970-01-01
相关资源
最近更新 更多