13

我通常尽量远离 PHP 的魔法方法,因为它们似乎混淆了对象的公共接口。也就是说,它们似乎越来越多地被使用,至少在我读过的代码中,所以我不得不问:对于何时使用它们是否有任何共识?使用这三种魔术方法有什么共同的模式吗?

4

6 回答 6

4

主要原因是您不需要输入那么多。例如,您可以将它们用于 ORM 记录并充当隐式 setter/getter:

使用__call()

$user = new User();
$user->setName("Foo Bar");
$user->setAge(42);
$user->save();

使用__set()

$user->name = "Foo Bar";
$user->age = 42;

映射到一个简单的数组:

array(
    "name" => "Foo Bar",
    "age"  => 42
)

将这样的数组写入数据库比进行大量手动调用来收集所有需要的信息要容易得多。__set()并且__get()比公共成员有另一个优势:您可以验证/格式化您的数据。

于 2011-05-12T14:50:51.910 回答
4

__call()

我已经看到它用于实现行为,例如通过可插件接口向类添加额外的功能。

伪代码如下:

$method = function($self) {};
$events->register('object.method', $method);
$entity->method(); // $method($this);

它还使得编写大部分相似的函数变得更容易,例如在 ORM 中。例如:

$entity->setName('foo'); // set column name to 'foo'

__get()/__set()

我主要看到它用于包装对私有变量的访问。

ORM 是我想到的最好的例子:

$entity->name = 'foo'; // set column name to 'foo'
于 2011-05-12T15:00:27.307 回答
3

它允许您执行以下操作:

class myclass {
    private $propertybag;

    public function __get($name) {
        if(isset($this->propertybag[$name]) {return $this->propertybag[$name];}
        throw new Exception("Unknown property " . (string) $name);
    }

 }

然后,您可以在一行中从 SQL 查询中进行填充$propertybag,而不是一个一个地设置一大堆属性。

此外,它还允许您拥有只读的特定属性(即不允许通过 修改它们__set())。例如,可能对 ID 字段有用。

此外,您可以将代码放入__get()and__set()中,因此您可以做一些比获取或设置单个变量更复杂的事情。例如,如果您有一个storeID字段,您可能还想提供一个storeName属性。您可以__get()通过交叉引用查找来实现它,因此您可能不需要将名称实际存储在类中。当然storeName也不想在__get().

那里有很多可能性。

当然,使用魔法方法也有一些缺点。对我来说最大的问题是您失去了 IDE 中的自动完成功能。这对您可能很重要,也可能无关紧要。

于 2011-05-12T15:31:58.780 回答
0

由于魔术方法可以在涉及重复性任务(例如定义成员、填充它们然后检索它们)时为您节省大量编码 - 您可以使用上述 3 种方法来缩短编码时间,而不是做那些无聊、冗长的工作所有这一切。如果需要,我可以提供一些示例,它们可以在网上的各种教程中找到。

我不知道这是否是普遍共识,但通常应该适用 - 在适当的情况下使用。如果您发现自己在执行重复性任务(定义成员、填充成员、获取成员、调用稍有不同的 X 函数)- 魔术方法可能会对您有所帮助。

于 2011-05-12T15:01:49.823 回答
0

一种常见的模式是为您的客户端提供一个句柄,并根据命名约定或配置代理对封装对象或单例的调用。

class db
{
    static private $instance = null;

    static public function getInstance()
    {
        if( self::$instance == NULL )
            self::$instance = new db;

        return self::$instance;
    }

    function fetch()
    {
        echo "I'm fetching\n";
    }
}

class dataHandler
{
    function __call($name, $argv)
    {
        if( substr($name, 0, 4) == 'data' )
        {
            $fn = substr($name, 4);
            db::getInstance()->$fn($argv);
        }
    }
}

$dh = new dataHandler;
$dh->datafetch('foo', 'bar');

相同的原理可用于驱动相同功能的不同后端,而无需更改驱动程序。

于 2011-05-12T15:19:14.380 回答
0

只要您愿意,只要记录了神奇的属性/方法即可;除非使用非常抽象的代码层,例如在开发 ORM 时,否则应避免使用未记录的魔法。

在抽象层可接受

/**
 * DB backed model base class.
 */
class Model {
    protected $attributes = [];

    function __get($name) {
        return @$this->attributes[$name];
    }
}

记录时可接受

/**
 * User model backed by DB tables.
 * @property-read string $first_name
 * @property-read string $last_name
 */
class UserModel extends Model {

}

懒惰且不可接受(在使用 ORM 时很常见)

/**
 * This class is magical and awesome and I am a lazy shithead!
 */
class UserModel extends WhoCaresWhenEverythingIsMagical {

}
于 2015-04-24T20:47:25.010 回答