Если вы занимались объектно-ориентированным программированием, то, безусловно, знаете, что в реальных приложениях количество методов классов может быть довольно большим. Причем во многих из этих методов код попросту повторяется.
Типичный пример – методы установки и чтения свойств (или, если использовать английскую терминологию, 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 свойства).
И, самое главное, этот подход дает возможность изменять свойства класса и правила проверки, не меняя код методов.
До встречи!


