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

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


person Major Productions    schedule 12.05.2011    source источник
comment
stackoverflow.com/questions/5780386/   -  person Lightness Races in Orbit    schedule 12.05.2011


Ответы (6)


__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'
person Denis de Bernardy    schedule 12.05.2011
comment
Интересный. Не могли бы вы немного конкретизировать свой пример __call()? Я немного не понимаю, что такое $method и $entity. - person Major Productions; 12.05.2011
comment
$entity: какой бы ни был ваш объект. __call() вызов запрошенного метода с входными аргументами. $events возражает что-то вроде менеджера событий symfony, WP do_action() и т.д. - person Denis de Bernardy; 12.05.2011
comment
Я видел, как __call используется для горизонтального повторного использования кода. Этот пробел будет заполнен traits, но я видел умную реализацию __call, чтобы попытаться подражать traits. - person Clement Herreman; 13.05.2011

Основная причина в том, что вам не нужно печатать так много. Вы можете использовать их, скажем, для записи 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() имеют еще одно преимущество перед общедоступными участниками: вы можете проверять/форматировать свои данные.

person jwueller    schedule 12.05.2011

Это позволяет вам делать такие вещи:

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()). Может быть полезно, например, для поля ID.

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

Там много возможностей.

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

person Spudley    schedule 12.05.2011

Так как волшебные методы могут сэкономить вам МНОГО кода, когда дело доходит до повторяющихся задач, таких как определение членов, их заполнение и последующее их извлечение — вместо того, чтобы выполнять эту скучную, длинную работу, вы можете использовать упомянутые 3 метода, чтобы сократить время написания кода. все это. Если нужно, я могу привести несколько примеров, хотя их можно найти в различных учебниках в сети.

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

person Michael J.V.    schedule 12.05.2011
comment
Скучная работа — не повод прибегать к магическим методам. Эти методы усложняют понимание кода. Вызов X-функций, которые немного отличаются, особенно тот случай, когда вы должны явно указывать немного. Магические методы следует использовать в некоторых случаях, например, когда список атрибутов объекта динамический (как в случае ORM). - person Clement Herreman; 12.05.2011
comment
Итак, если у вас есть 100 возможных членов класса, которые вам понадобятся, вы не будете использовать магические методы __set и __get для ускорения этой скучной работы, вы будете явно определять их один за другим? Думаю, нет. Так что я с вами не согласен. Магические методы следует использовать там, где это уместно и где они помогают ускорить работу. Если вам противно - что ж, ваше время, а не мое :) - person Michael J.V.; 12.05.2011
comment
@Michael J.V.: Если у вас есть 100 возможных участников, вы, вероятно, делаете это неправильно. - person sholsinger; 12.05.2011
comment
С каких это пор программисты перестали мыслить абстрактно? Я действительно ошибаюсь, если я делаю ORM, где я не знаю, сколько столбцов таблицы / таблиц / баз данных у меня может быть? Действительно ли необходимо явно определять каждый возможный сценарий, прежде чем комментарии, подобные вашему, перестанут приходить? Нет, я не делаю это неправильно. Кроме того, выполнение ORM - это всего лишь одна вещь, в которой вы ожидаете переменное количество возможных атрибутов. Если это 2 или 1000, не имеет значения, является ли это переменным количеством необходимых членов. - person Michael J.V.; 12.05.2011
comment
Не правильнее ли было бы сказать, что в данном случае речь идет о динамизме? Невозможно явно написать сеттеры/геттеры для ORM, потому что карта по определению является динамической. Невозможно заранее узнать количество членов, которые необходимо сгенерировать или получить к ним доступ. - person Major Productions; 12.05.2011
comment
@Michael JV: Это был просто комментарий о наличии 100 участников в экземпляре ORM. ORM обычно сопоставляется с источником данных, и если ваш источник данных имеет 100 «столбцов», как в случае с таблицей базы данных, то, вероятно, пришло время переосмыслить вашу структуру данных. - person sholsinger; 13.05.2011
comment
Хотите указать причины, почему? Вы никогда не видели таблицы базы данных с более чем 100 столбцами? Если у вас ограниченный опыт в чем-то, не думайте, что вы правы. Вы когда-нибудь видели медицинскую базу данных и сколько столбцов в ней? Нет, не время переосмысливать структуру данных, совершенно небрежно предполагать то, что вы предполагаете. - person Michael J.V.; 13.05.2011
comment
@Michael JV: Да, я бы сделал 100 геттеров и сеттеров. Может быть, я бы сгенерировал код, но да, определенно. Чем меньше магии в коде, тем он читабельнее и проще для понимания. Вы бы предпочли метод __set() с массивным switch($member) { case 'name': /* some stuff */; break; case 'age': /* other special process */; break; /* Add 98 others case here */ } ? Не я, и я верю людям, которые в конечном итоге будут поддерживать такой код. - person Clement Herreman; 13.05.2011
comment
Таким образом, вы считаете, что код сгенерировать 100 сеттеров и 100 геттеров проще, чем иметь единую точку входа, где вы можете определить дополнительные причины через случай переключения? И вы ни на секунду не задумались, правы вы или нет в своем утверждении? Это кричит ужасная идея, как бы вы на это ни посмотрели! Я по-прежнему придерживаюсь своего утверждения - я бы использовал магические функции, ЕСЛИ они подходят. Моим первым выбором был бы общедоступный член, представляющий собой массив, прежде чем фактически использовать __get и __set, которые будут действовать как мой заполнитель. Если бы это было невозможно, я бы использовал магические методы. Но код генерирует 100 методов... - person Michael J.V.; 13.05.2011
comment
<precision> Code generating from the private attribute just the first time <I write the class.</precision> Говоря только о читабельности и простоте понимания кода, да, явное объявление геттеров и сеттеров лучше. Попытайтесь понять бизнес-классы Magento, я думаю, вы поймете мою точку зрения. Почему это плохо с вашей точки зрения? Слишком много символов для ввода с клавиатуры? Слишком длинный файл класса? Я искренне не понимаю. - person Clement Herreman; 13.05.2011
comment
Вы не понимаете, почему я считаю плохим набирать 100 членов, 100 сеттеров и 100 геттеров, если я могу сделать это менее чем за 10 строк кода? Вам понятнее иметь 100 сеттеров/геттеров/членов, чем группировать похожие задачи в две магические функции? Я удивлен. То, что вы говорите, не имеет абсолютно никакого смысла и побеждает цель программирования. Извините, но я не могу воспринимать вас всерьез, поэтому я прекращаю этот спор здесь. - person Michael J.V.; 13.05.2011

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

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

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

person Mel    schedule 12.05.2011
comment
Это было бы проще без магических методов с использованием простого полиморфизма. Вы заново изобретаете колесо здесь. - person jwueller; 12.05.2011

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

}
person rich remer    schedule 24.04.2015