Web разработка. Автоматическое создание методов на PHP

Если вы занимались объектно-ориентированным программированием, то, безусловно, знаете, что в реальных приложениях количество методов классов может быть довольно большим. Причем во многих из этих методов код попросту повторяется.
Типичный пример – методы установки и чтения свойств (или, если использовать английскую терминологию, getters and setters). Названия этих методов обычно совпадают с названиями свойств класса, только к ним добавляется приставка get или set.
Примечание. Свойства и методы класса – это переменные и функции, объявленные в нем.
Написать такие методы несложно. По-идее, они должны просто возвращать/присваивать значение свойствам. Но на практике, при присваивании нужно еще и проверять полученное значение.
Теперь представьте, что вы пишите класс, который должен хранить данные одной записи из таблицы в базе данных. А в этой таблице 20 полей. Т.е. нужно написать 40 методов. Они, конечно, простые, тем не менее, каждый все равно должен быть протестирован, а это приличный кусок работы.
В этой статье я покажу способ, позволяющий автоматизировать написание свойств.
Идея заключается в использовании метода __call(). Если этот метод объявлен в классе, то вызовы всех несуществующих методов будут переданы ему. Первый параметр __call будет содержать имя вызванного метода, а второй – массив с переданными параметрами.
Рассмотрим небольшой пример. Допустим, у нас есть класс.
-
class UserData {
-
private $name;
-
private $nic;
-
private $email;
-
-
//конструктор
-
function UserData() {
-
}
-
-
function __call($method, $val = null) {
-
}
-
}
Теперь мы можем использовать этот класс.
-
$uData = new UserData();
-
$uData->setName("my_name");
Этот код создает объект класса UserData и вызывает его метод setName. Такого метода в классе нет. Вместо него php вызовет метод __call. Его параметры будут равны:
$method – “setName”, а $val[0] – “my_name”.
Таким образом, мы можем вызывать методы, которые на самом деле не объявлены.
Если с теорией все понятно, напишем метод, который будет обрабатывать вызовы типа:
get<PropertyName> и set<PropertyName> (PropertyName может быть любым именем свойства класса).
-
function __call($method, $val = null) {
-
switch ($operation) {
-
case "get": {
-
return $this->$property;
-
}
-
case "set": {
-
if ($this->validate($property, $val) === TRUE) {
-
$this->$property = $val[0];
-
}
-
else {
-
//Тут можно использовать другой метод сообщения об ошибке
-
}
-
return;
-
}
-
//если указана неподдерживаемая операция
-
default: {
-
}
-
}
-
}
-
//если указано несуществующее имя свойства
-
else {
-
}
-
}
Рассмотрим этот метод подробнее.
В начале мы получаем перечень свойств класса (строка 2). После этого, по имени метода определяем название операции и свойства (строки 3 и 4).
Затем, если указанное свойство существует (строка 5), выполняем заданную операцию. Т.е. либо возвращаем значение свойства (строки 7-9), либо присваиваем ему новое значение (10-19).
Во всех остальных случаях мы генерируем ошибку.
Как видите, метод содержит 30 строк (если убрать комментарии будет 27). При этом он по-сути создает методы get и set для каждого свойства, объявленного в классе.
Проверка значений
Если вы внимательно смотрели код метода __call(), то, наверное, заметили вызов метода validate() (строка 11).
Этот метод выполняет проверку полученных значений, до присвоения их свойствам. Естественно, использовать одни и те же проверки для всех свойств нельзя, поэтому мы создадим специальный массив с правилами.
Примечание. Идею массива с правилами (и их форму записи) я позаимствовал из библиотеки validation, входящей в состав фреймворка CodeIgniter.
Массив имеет имя $rules. Ключи массива должны совпадать с именами свойств, а значения – перечень правил, разделенных символом “|”.
В качестве правил можно использовать имена стандартных функций php, которые возвращают true или false (например, is_numeric, is_string и т.п.).
Кроме того, можно написать свою собственную функцию для проверки данных. Если вы указываете эту функцию в перечне правил к ее имени нужно добавить приставку "callback_". Функция всегда должна возвращать true или false.
Например:
Рассмотрим метод validate подробнее.
-
function validate($property, $val) {
-
return FALSE;
-
}
-
$validationRes = TRUE;
-
$validationRes = FALSE;
-
foreach ($rules_array as $curRule) {
-
$validationRes = TRUE;
-
}
-
else {
-
$validationRes = FALSE;
-
break;
-
}
-
}
-
else {
-
$validationRes = TRUE;
-
}
-
else {
-
$validationRes = FALSE;
-
break;
-
}
-
}
-
}
-
}
-
return $validationRes;
-
}
В строках 2-4 мы проверяем установлен ли параметр метода.
Примечание. Метод set... по определению может получить только один параметр, и он всегда будет находиться в нулевом элементе массива $val.
После этого, мы проверяем, заданы ли правила для данного свойства. Если они заданы, то с помощью call_user_func(...) вызываем перечисленные функции.
Предварительно мы проверяем, содержит ли название функции приставку callback_ (строка 10). Если приставка найдена, убираем ее и вызываем метод данного класса (в этом примере класс должен содержать метод isValidEmail($value), который будет выполнять проверку формата адреса электронной почты).
Если все проверки прошли успешно, то метод validate вернет TRUE и значение будет присвоено свойству класса.
Заключение
Как видите, два метода позволяют существенно сократить объем работы (если, конечно, у ваш класс содержит не 2-3 свойства).
И, самое главное, этот подход дает возможность изменять свойства класса и правила проверки, не меняя код методов.
До встречи!
Понравилась статья? Подпишитесь на продолжение
!
Опубликовано в PHP
Комментарии (12)
Вы можете отслеживать обсуждение записи с помощью RSS 2.0 ![]()
Вы также можете оставить комментарий, или трекбек с Вашего сайта.
Оставить комментарий







А как же дополнение кода?
В смысле? Не понял вопрос.
Я и многие другие пользуют IDE вроде PDT или Zend Studio. Там есть такая замечательная штука как дополнение кода, а именно методов класса. Без неё на написание контроллера уйдёт больше времени, чем на описание всех методов руками.
Дошло, сам использую PDT.
Дополнение кода вещь, конечно, очень полезная, но при этом все равно каждый метод пишется (вставляется) отдельно.
А я хотел убрать дублирование кода вообще, т.е. свести все в один метод.
Если будет найдена ошибка, придется исправлять только один метод.
А зачем изменять private свойства? Не логичнее ли их тогда просто объявить как public и использовать классическое объявление свойства:
$myClass->myobject = true; ?
В этом случае нельзя выполнить автоматическую проверку значения при установке свойства.
Т.е. все это ради проверки корректности задаваемых значений?
Способ интересный, не думал о нем. Но логичнее использовать __get() и __set(). Не вижу смысла изобретать велосипед
Не столько ради проверки, сколько ради сокращения кода и упрощения его поддержки (внесения изменений).
Насчет __get() и __set() согласен, в данном примере их использовать удобнее, но я хотел показать более универсальный вариант, который подойдет для методов с любыми названиями. Просто get/set самый распространенный пример.
Не совсем согласен по нескольким пунктам.
Зачем морочить голову с переопределением __call (его можно использовать для более полезных вещей), если есть другая магия? Я имею ввиду __get() и __set(). Эти методы как раз для таких задач и предназначены. И валидацию устроить можно.
Еще одна проблема - скорость выполнения, любая магия, к сожалению, ее снижает…
А вообще, чтобы не морочиться с геттерами и сеттерами вручную, можно использовать фреймворки и автогенерацию кода по модели (тот же symfony с успехом это делает). А уж рефакторинг как проходит на ура…
Да, __get() и __set() здесь подходят лучше, но хотелось показать общий пример с разбором названия метода, определением параметров и т.д.
А вот автогенерация вариант хороший. Но это отдельная тема
Насчет скорости полностью согласен. К сожалению, ничего "на шару" не дается.
Всё на Java становится похоже. Вообще спорный вопрос - если валидация будет иметь кучу своих типов.. не лучше ли иметь отдельно ясно выраженные set-функции чем одну с большим switch ?
Ответ на этот вопрос зависит от ваших предпочтений. Оба подхода имеют и достоинства и недостатки.