PHP: Класс-обертка для методов
Порой, при написании некоего кода возникают нестандартные требования. К примеру, при написании модели (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-лента комментариев к этой статье | Обратная ссылка (trackback link)
