Если вы занимались объектно-ориентированным программированием, то, безусловно, знаете, что в реальных приложениях количество методов классов может быть довольно большим. Причем во многих из этих методов код попросту повторяется.
Типичный пример – методы установки и чтения свойств (или, если использовать английскую терминологию, 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) { $vars = get_class_vars("UserData"); $operation = substr($method, 0, 3); $property = strtolower(substr($method, 3)); if (in_array($property, array_keys($vars))) { switch ($operation) { case "get": { return $this->$property; } case "set": { if ($this->validate($property, $val) === TRUE) { $this->$property = $val[0]; } else { //Тут можно использовать другой метод сообщения об ошибке trigger_error("Validation not pass. Метод: ".$method, E_USER_ERROR); } return; } //если указана неподдерживаемая операция default: { trigger_error("Unsupported method", E_USER_ERROR); } } } //если указано несуществующее имя свойства else { trigger_error("Unsupported method", E_USER_ERROR); } }
Рассмотрим этот метод подробнее.
В начале мы получаем перечень свойств класса (строка 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.
Например:
private $rules = array("email" => "callback_isValidEmail", "name" => "is_string");
Рассмотрим метод validate
подробнее.
function validate($property, $val) { if (!isset($val[0])) { return FALSE; } $validationRes = TRUE; if (array_key_exists($property, $this->rules)) { $validationRes = FALSE; $rules_array = explode("|", $this->rules[$property]); foreach ($rules_array as $curRule) { if (substr($curRule, 0, 9) === "callback_") { $m = substr($curRule, 9); if (call_user_func(array("UserData", $m), $val[0]) === TRUE) { $validationRes = TRUE; } else { $validationRes = FALSE; break; } } else { if(call_user_func($curRule, $val[0]) === TRUE) { $validationRes = TRUE; } else { $validationRes = FALSE; break; } } } } return $validationRes; }
В строках 2-4 мы проверяем установлен ли параметр метода.
Примечание. Метод set...
по определению может получить только один параметр, и он всегда будет находиться в нулевом элементе массива $val
.
После этого, мы проверяем, заданы ли правила для данного свойства. Если они заданы, то с помощью call_user_func(...)
вызываем перечисленные функции.
Предварительно мы проверяем, содержит ли название функции приставку callback_ (строка 10). Если приставка найдена, убираем ее и вызываем метод данного класса (в этом примере класс должен содержать метод isValidEmail($value)
, который будет выполнять проверку формата адреса электронной почты).
Если все проверки прошли успешно, то метод validate
вернет TRUE и значение будет присвоено свойству класса.
Заключение
Как видите, два метода позволяют существенно сократить объем работы (если, конечно, у ваш класс содержит не 2-3 свойства).
И, самое главное, этот подход дает возможность изменять свойства класса и правила проверки, не меняя код методов.
До встречи!