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

时间:2014-12-22 21:26:08

标签: php overloading magic-methods

我有一种情况,我希望只有在实际访问它时才初始化类属性,因为初始化属性包括可能不必要的数据库调用。我可以使用一个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)“获取财产。”

     

string(3)“Bob”

     

string(3)“Bob”

     

string(3)“Bob”

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

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

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

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

由于

1 个答案:

答案 0 :(得分:2)

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

  

但是对于这个特殊情况,我宁愿通过财产

访问

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

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

__get__set调用的最佳用途是在ORM中,它们不会创建数百个具体的访问器,但就好像它们通过__call方法存在一样。 / p>

- 更新 -

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

  

其他优化领域(数据库性能,文件读取,   把事情记在内存等))

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

您可以执行Alex在评论中建议的内容,您可以向用户对象添加包装器方法:

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

// Becomes

$user->getStreetName();

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

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

// Becomes

echo $user->getFullName();

另一个 HOWEVER ,一些ORM(和框架)将掩盖这样一个事实:他们将进行额外的数据库调用来检索数据(延迟加载)。所以使用上面的地址示例;即使您进行了数据库调用以返回user表中的数据,对getAddress()的调用也需要另一个数据库调用来返回user_address表中的数据。所以你在这个例子中可以做的是实现我所说的超级对象,就像一个常规对象,但只有一个cape,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();