Практическое применение магических методов PHP - получение, установка и вызов
Я обычно старался держаться подальше от волшебных методов PHP, потому что они, похоже, запутывают открытый интерфейс объекта. Тем не менее, они, похоже, используются все чаще и чаще, по крайней мере, в коде, который я прочитал, поэтому я должен спросить: есть ли какой-либо консенсус в отношении того, когда их использовать? Существуют ли какие-либо общие закономерности для использования этих трех магических методов?
6 answers
__call()
Я видел, как он использовался для реализации поведения, например, для добавления дополнительных функций в класс через подключаемый интерфейс.
Псевдокод вот так:
$method = function($self) {};
$events->register('object.method', $method);
$entity->method(); // $method($this);
Это также облегчает написание в основном похожих функций, например, в ORMS. например:
$entity->setName('foo'); // set column name to 'foo'
__get()/__set()
Я в основном видел, как он использовался для переноса доступа к частным переменным.
ORM - лучший пример, который приходит на ум:
$entity->name = 'foo'; // set column name to 'foo'
Основная причина заключается в том, что вам не нужно вводить так много. Вы могли бы использовать их, скажем, для записи 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()
имеют еще одно преимущество перед общедоступными участниками: вы можете проверять/форматировать свои данные.
Это позволяет вам делать такие вещи:
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. Это может иметь или не иметь для вас значения.
Поскольку магические методы могут сэкономить вам МНОГО времени на кодировании, когда дело доходит до повторяющихся задач, таких как определение элементов, их заполнение и последующее извлечение - вместо выполнения этой скучной, длинной работы вы можете использовать упомянутые 3 метода, чтобы сократить время на кодирование всего этого. При необходимости я могу привести несколько примеров, которые можно найти в различных учебных пособиях по сети.
Я не знаю, является ли это общим консенсусом, но следует применять обычное - использовать там, где это уместно. Если вы найдете самостоятельно выполнять повторяющиеся задачи (определять участника, заполнять участника, получать участника, вызывать функции X, которые немного отличаются) - вам могут помочь магические методы.
Один из распространенных шаблонов заключается в том, чтобы иметь единый дескриптор для ваших клиентов и передавать вызовы инкапсулированным объектам или синглетонам на основе соглашений об именовании или конфигураций.
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');
Одни и те же принципы могут быть использованы для управления различными бэкэндами одной и той же функциональности без необходимости менять драйвер.
Когда захотите, при условии, что магические свойства/методы задокументированы; недокументированной магии следует избегать, если только вы не работаете с очень абстрактным слоем кода, например, при разработке 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 {
}