Практические применения магических методов PHP - __get, __set и __call

Я вообще старался держаться подальше от магических методов PHP, потому что они, похоже, запутывают открытый интерфейс объекта. Тем не менее, они, кажется, используются все больше и больше, по крайней мере, в коде, который я прочитал, поэтому я должен спросить: существует ли какой-либо консенсус относительно того, когда их использовать? Существуют ли общие шаблоны для использования этих трех магических методов?

Ответы

Ответ 1

__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'

Ответ 2

Основная причина в том, что вам не нужно вводить столько же. Вы можете использовать их, например, для записи ORM и действовать как неявные сеттеры/получатели:

с помощью __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() имеют еще одно преимущество перед публичными членами: вы можете проверить/форматировать свои данные.

Ответ 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);
    }

 }

Затем вы можете заполнить $propertybag из SQL-запроса в одной строке, а не задавать целую кучу свойств один за другим.

Кроме того, он позволяет вам иметь определенные свойства, которые доступны только для чтения (т.е. не позволяют изменять их с помощью __set()). Возможно, полезно для поля идентификатора, например.

Кроме того, вы можете поместить код в __get() и __set(), чтобы вы могли сделать что-то более сложное, чем просто получать или устанавливать одну переменную. Например, если у вас есть поле storeID, вы также можете указать свойство storeName. Вы можете реализовать это в __get() через перекрестный поиск ссылок, поэтому вам может не понадобиться имя, которое должно быть сохранено в классе. И, конечно, storeName не хотел бы реализовываться в __get().

Здесь много возможностей.

Конечно, есть некоторые способы использования магических методов. Самый большой для меня - это тот факт, что вы потеряете автоматическую полную функциональность в своей среде IDE. Это может или не важно для вас.

Ответ 4

Так как магические методы могут сэкономить вам много кодов, когда речь заходит о повторяющихся задачах, таких как определение членов, их заполнение, а затем их извлечение - вместо того, чтобы выполнять эту скучную длинную работу, вы можете использовать упомянутые 3 метода, чтобы сократить время, чтобы закодировать все это. При необходимости я могу привести несколько примеров, которые они могут найти в различных учебниках по сети.

Я не знаю, является ли это общим консенсусом, но обычный должен применяться - используйте, где это необходимо. Если вы обнаружите, что выполняете повторяющуюся задачу (определите член, заполните его, получите член, вызовите функции X, которые немного отличаются) - магические методы могут вам помочь.

Ответ 5

Один общий шаблон состоит в том, чтобы иметь один дескриптор для ваших клиентов и прокси-сервер для вызовов инкапсулированных объектов или синглетов на основе соглашений или конфигураций именования.

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');

Те же принципы могут использоваться для управления различными бэкэдами одинаковой функциональности без изменения драйвера.

Ответ 6

Всякий раз, когда вы хотите, до тех пор, пока магические свойства/методы задокументированы; недокументированной магии следует избегать, если не работать с очень абстрактным слоем кода, например при разработке 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 {

}