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

6 марта, 2008

Заставка к статье Автоматическое создание методов на PHP

Если вы занимались объектно-ориентированным программированием, то, безусловно, знаете, что в реальных приложениях количество методов классов может быть довольно большим. Причем во многих из этих методов код попросту повторяется.

Типичный пример – методы установки и чтения свойств (или, если использовать английскую терминологию, getters and setters). Названия этих методов обычно совпадают с названиями свойств класса, только к ним добавляется приставка get или set.

Примечание. Свойства и методы класса – это переменные и функции, объявленные в нем.

Написать такие методы несложно. По-идее, они должны просто возвращать/присваивать значение свойствам. Но на практике, при присваивании нужно еще и проверять полученное значение.

Теперь представьте, что вы пишите класс, который должен хранить данные одной записи из таблицы в базе данных. А в этой таблице 20 полей. Т.е. нужно написать 40 методов. Они, конечно, простые, тем не менее, каждый все равно должен быть протестирован, а это приличный кусок работы.

В этой статье я покажу способ, позволяющий автоматизировать написание свойств.

Идея заключается в использовании метода __call(). Если этот метод объявлен в классе, то вызовы всех несуществующих методов будут переданы ему. Первый параметр __call будет содержать имя вызванного метода, а второй – массив с переданными параметрами.

Рассмотрим небольшой пример. Допустим, у нас есть класс.

  1. class UserData {
  2.     private $name;
  3.     private $nic;
  4.     private $email;
  5.  
  6.     //конструктор
  7.     function UserData() {
  8.     }
  9.  
  10.     function __call($method, $val = null) {
  11.     }
  12. }

Теперь мы можем использовать этот класс.

  1. $uData = new UserData();
  2. $uData->setName("my_name");

Этот код создает объект класса UserData и вызывает его метод setName. Такого метода в классе нет. Вместо него php вызовет метод __call. Его параметры будут равны:
$method – “setName”, а $val[0] – “my_name”.

Таким образом, мы можем вызывать методы, которые на самом деле не объявлены.
Если с теорией все понятно, напишем метод, который будет обрабатывать вызовы типа:
get<PropertyName> и set<PropertyName> (PropertyName может быть любым именем свойства класса).

  1. function __call($method, $val = null) {
  2.     $vars = get_class_vars("UserData");
  3.     $operation = substr($method, 0, 3);
  4.     $property = strtolower(substr($method, 3));
  5.     if (in_array($property, array_keys($vars))) {
  6.         switch ($operation) {
  7.             case "get": {
  8.                 return $this->$property;
  9.             }
  10.             case "set": {
  11.                 if ($this->validate($property, $val) === TRUE) {
  12.                     $this->$property = $val[0];
  13.                 }
  14.                 else {
  15.                     //Тут можно использовать другой метод сообщения об ошибке
  16.                     trigger_error("Validation not pass. Метод: ".$method, E_USER_ERROR);
  17.                 }
  18.                 return;
  19.             }
  20.             //если указана неподдерживаемая операция
  21.             default: {
  22.                 trigger_error("Unsupported method", E_USER_ERROR);
  23.             }
  24.         }
  25.     }
  26.     //если указано несуществующее имя свойства
  27.     else {
  28.         trigger_error("Unsupported method", E_USER_ERROR);
  29.     }
  30. }

Рассмотрим этот метод подробнее.

В начале мы получаем перечень свойств класса (строка 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.

Например:

  1. private $rules = array("email" => "callback_isValidEmail", "name" => "is_string");

Рассмотрим метод validate подробнее.

  1. function validate($property, $val) {
  2.     if (!isset($val[0])) {
  3.         return FALSE;
  4.     }
  5.     $validationRes = TRUE;
  6.     if (array_key_exists($property, $this->rules)) {
  7.         $validationRes = FALSE;
  8.         $rules_array = explode("|", $this->rules[$property]);
  9.         foreach ($rules_array as $curRule) {
  10.             if (substr($curRule, 0, 9) === "callback_") {
  11.                 $m = substr($curRule, 9);
  12.                 if (call_user_func(array("UserData", $m), $val[0]) === TRUE) {
  13.                     $validationRes = TRUE;
  14.                 }
  15.                 else {
  16.                     $validationRes = FALSE;
  17.                     break;
  18.                 }
  19.             }
  20.             else {
  21.                 if(call_user_func($curRule, $val[0]) === TRUE) {
  22.                     $validationRes = TRUE;
  23.                 }
  24.                 else {
  25.                     $validationRes = FALSE;
  26.                     break;
  27.                 }
  28.             }
  29.         }
  30.     }
  31.     return $validationRes;
  32. }

В строках 2-4 мы проверяем установлен ли параметр метода.

Примечание. Метод set... по определению может получить только один параметр, и он всегда будет находиться в нулевом элементе массива $val.

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

Предварительно мы проверяем, содержит ли название функции приставку callback_ (строка 10). Если приставка найдена, убираем ее и вызываем метод данного класса (в этом примере класс должен содержать метод isValidEmail($value), который будет выполнять проверку формата адреса электронной почты).

Если все проверки прошли успешно, то метод validate вернет TRUE и значение будет присвоено свойству класса.

Заключение

Как видите, два метода позволяют существенно сократить объем работы (если, конечно, у ваш класс содержит не 2-3 свойства).

И, самое главное, этот подход дает возможность изменять свойства класса и правила проверки, не меняя код методов.

До встречи!

Понравилась статья? Подписывайтесь на продолжение rss link !

Или на мой твиттер twitter link

]]>

Добавьте эту страницу в google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru

]]>

Опубликовано в PHP Комментарии (12) »

]]>

Комментарии (12)

Вы можете отслеживать обсуждение записи с помощью RSS 2.0 rss link

Вы также можете оставить комментарий, или трекбек с Вашего сайта.

]]>
  1. Sam

    А как же дополнение кода?

    • В смысле? Не понял вопрос.

      • Sam

        Я и многие другие пользуют IDE вроде PDT или Zend Studio. Там есть такая замечательная штука как дополнение кода, а именно методов класса. Без неё на написание контроллера уйдёт больше времени, чем на описание всех методов руками.

        • Дошло, сам использую PDT.
          Дополнение кода вещь, конечно, очень полезная, но при этом все равно каждый метод пишется (вставляется) отдельно.
          А я хотел убрать дублирование кода вообще, т.е. свести все в один метод.
          Если будет найдена ошибка, придется исправлять только один метод.

  2. А зачем изменять private свойства? Не логичнее ли их тогда просто объявить как public и использовать классическое объявление свойства:

    $myClass->myobject = true; ?

    • В этом случае нельзя выполнить автоматическую проверку значения при установке свойства.

      • Т.е. все это ради проверки корректности задаваемых значений?

        Способ интересный, не думал о нем. Но логичнее использовать __get() и __set(). Не вижу смысла изобретать велосипед

        • Не столько ради проверки, сколько ради сокращения кода и упрощения его поддержки (внесения изменений).

          Насчет __get() и __set() согласен, в данном примере их использовать удобнее, но я хотел показать более универсальный вариант, который подойдет для методов с любыми названиями. Просто get/set самый распространенный пример.

  3. Ali

    Не совсем согласен по нескольким пунктам.
    Зачем морочить голову с переопределением __call (его можно использовать для более полезных вещей), если есть другая магия? Я имею ввиду __get() и __set(). Эти методы как раз для таких задач и предназначены. И валидацию устроить можно.
    Еще одна проблема – скорость выполнения, любая магия, к сожалению, ее снижает…
    А вообще, чтобы не морочиться с геттерами и сеттерами вручную, можно использовать фреймворки и автогенерацию кода по модели (тот же symfony с успехом это делает). А уж рефакторинг как проходит на ура…

    • Да, __get() и __set() здесь подходят лучше, но хотелось показать общий пример с разбором названия метода, определением параметров и т.д.

      А вот автогенерация вариант хороший. Но это отдельная тема :-)

      Насчет скорости полностью согласен. К сожалению, ничего "на шару" не дается.

  4. Всё на Java становится похоже. Вообще спорный вопрос – если валидация будет иметь кучу своих типов.. не лучше ли иметь отдельно ясно выраженные set-функции чем одну с большим switch ?

]]>

Оставить комментарий

* - обязательные для заполнения поля

]]>