重载属性,以确保仅在首次访问时设置它们


Overloading properties to ensure they are only set on first access

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

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

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((正是我想要的:

字符串(17( "获得财产。">

字符串(3( "鲍勃">

字符串(3( "鲍勃">

字符串(3( "鲍勃">

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

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

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

我的问题。这是对重载的合理使用,还是其他开发人员通常会皱眉?性能方面,考虑到过载仅用于首次访问,一切都很好吗?

谢谢

您所指的称为"延迟加载"。因为成员变量只有在被调用时才会被设置。在第一次调用时,该值将被处理,然后存储,因此后续调用不会受到必须重新调整数据的性能影响。

但对于这种特殊情况,我宁愿通过该物业访问

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

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

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

--更新--

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

首先优化其他领域(数据库性能、文件读取、 将东西保存在记忆中,等等((

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

您可以按照 Alex 在评论中的建议进行操作,也可以向用户对象添加包装器方法:

$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();