PHP: Класс-обертка для методов

Stepler, 21.01.2009

Порой, при написании некоего кода возникают нестандартные требования. К примеру, при написании модели (MVC) для сайта, мне нужно было, чтобы любой вызываемый из модели метод был «обернут» неким другим методом, в котором бы выполнялись подготовительные действия. В моем случае должен был, вставлялся блок try - catch, который бы «ловил» исключения при обработке запросов к БД. О том, как решил эту проблему, я хотел бы написать сегодня.

Итак, наиболее простое решение — это создать метод (wrapper) который в качестве параметров будет принимать название и параметр вызываемого метода. В данном случае метод wrapper и является той самой «оберткой». Реализация этого решения может быть следующей:

Exceptions.php

class ExceptionDb extends Exception {}
class ExceptionModel extends Exception {}

ModelExtension.php

class ModelExtension {
  
  function wrapper($method, $data = null) {
    try {
      
      if (!is_callable(array($this, $method)))
        throw new ExceptionModel('Method "' . $method . '" not found'."\n");
        
      try {
        return $this->$method($data);
      } catch(ExceptionDb $e) {
        echo $e->getMessage();
      }
      
    } catch(ExceptionModel $e) {
      echo $e->getMessage();
    }
  }
}

MyModel.php

class MyModel extends ModelExtension {
  
  function query() {
    if (true) {
      echo 'Method "query" is loaded'."\n";
    } else {
      throw new ExceptionDb('Error in method "query"'."\n");
    }
  }
  
  function errorQuery() {
    if (false) {
      echo 'method "errorQuery" is loaded'."\n";
    } else {
      throw new ExceptionDb('Error in method "errorQuery"'."\n");
    }
  }
}

index.php

include 'Exceptions.php';
include 'ModelExtension.php';
include 'MyModel.php';

$myModel = new MyModel();
$myModel->wrapper('query');
$myModel->wrapper('errorQuery');
$myModel->wrapper('anotherQuery'); 

Давайте разберем что я тут понаписал.

В примере участвуют несколько файлов:

  • Exceptions.php - в нем созданы в исключения, для перехвата ошибок в запросах ExceptionDb и в модели ExceptionModel.
  • ModelExtension.php - родительский класс модели. От него наследуются все модели используемые в проекте. В нем же описан метод wrapper. В качестве первого аргумента он принимает название вызываемого метода, вторым аргументом идут данные, передаваемые в вызываемый метод. Перед вызовом метода, через функцию is_callable проверяется что он существует и может быть вызван, в противном случае генерируется исключение ExceptionModel, которое тут же и перехватывается, т.к. конструкция if - else помещена в блок try - catch. Если метод существует, то он вызывается помещенный при этом в блок try - catch, который перехватывает исключения ExceptionDb запросов в БД. Именно этот момент и нужно было реализовать.
  • MyModel.php - тестовая модель. Класс MyModel расширяет ModelExtension. В нем описаны тестовые методы, выполняющиеся успешно (метод query) или завершающиеся возвратом исключения ExceptionDb (метод errorQuery).
  • index.php - собирает все файлы и запускает тестовый пример.

Результатом работы этого примера будут сообщения:

Method «query» is loaded
Error in method «errorQuery»
Method «anotherQuery» not found

Все работает как и ожидалось. Однако очевидно, что конструкция
$model-<wrapper(method [,data]) не совсем удачно решение. Куда удобнее и привычнее использовать $model-<method([data]). Сделать это нам поможет один из «магических» методов PHP, а именно метод __call. Напомню, что он вызывается всякий раз, когда запрашивается несуществующий метод класса. Можно создать псевдо-класс модели, в котором будет описан только метод __call(), который будет перенаправлять вызов методов на реальный класс модели. Чтобы воплотить задуманное, нужно модифицировать имеющийся код:

ModelLoader.php

class ModelLoader {
  private $_modelName;
  private $_model;
  private $_isLoad = false;
  
  function __construct($name) {
    $this->_modelName = $name;
  }
  
  function __call($method, $data = null) {
    try {
      
      if ($this->_isLoad === false) {
        if (!is_callable(array($this->_modelName, '__construct'))) 
          throw new ExceptionModel('Model class "' . $this->_modelName . '" not found');
        
        $this->_create_model_();
        $this->_isLoad = true;
      }
      
      if (!is_callable(array($this->_modelName, $method)))
        throw new ExceptionModel('Method "' . $method . '" not found'."\n");
        
      try {
        return $this->_model->$method($data);
      } catch(ExceptionDb $e) {
        echo $e->getMessage();
      }
      
    } catch(ExceptionModel $e) {
      echo $e->getMessage();
    }
  }
  
  private function _create_model_() {
    $this->_model = new $this->_modelName();
  }
}

MyModel.php

class MyModel {

  function __construct() {}
  ...
  
}

index.php

include 'Exceptions.php';
include 'ModelLoader.php';
include 'MyModel.php';

$myModel = new ModelLoader('MyModel');
$myModel->query();
$myModel->errorQuery();
$myModel->anotherQuery();

В этом примере мы избавились от класса ModelExtension, и добавили новый — ModelLoader.

При создании объекта ModelLoader в него передается название класса модели. Метод __call в классе ModelLoader полностью повторяет метод wrapper, с небольшим дополнением, при первом вызове создается экземпляр модели. Перед созданием проверяется, существует ли вызываемый класс. Для того чтобы осуществить эту проверку, мы дополнили класс MyModel функцией __construct.

Вот впринципе и все. Если запустить index.php то результат будет идентичный результату первого примера.

Акция «Обмен постовыми» продолжается!

Скомпонованный готовый скрипт можно скачать здесь.

Подписаться на обновления блога

Вам понравился наш блог, хотите следить за обновлениями? Подпишитесь на RSS рассылку или рассылку по электронной почте. Так же вы можете следить за нами в Twitter.

Категории: PHP | Комментировать

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

480×60
480×60