Проблема
Часто приходится писать так (примеры кода на Yii, но подход можно применить к любому коду):
$model = new User();
$model->name = 'Вася';
if (!$model->save())
throw new RuntimeException('Can not save!');
Или так:
$model = User::model()->find();
if (!$model)
throw new CHttpException(404, 'User not found!');
Кеширование:
$dependency = new \caching\TagDependency('Post');
$posts = Post::model()->cache(1000, $dependency)->findAll();
Транзакции:
$trx = $this->getDbConnection()->beginTransaction();
try {
if (!$user->makePayment())
throw new \RuntimeException('Can not complete!');
$trx->commit();
} catch (\Exception $e) {
$trx->rollback();
throw $e;
}
Слишком много кода!
Решение
Вышеприведенные куски кода можно сократить до:
Возбуждаем исключение в случае неудачного результата:
$model->saveException();
User::model()->findException();
Post::model()->findAllCached();
Оборачиваем метод в транзакцию:
$user->makePaymentTrx();
Также возможны комбинации:
User::model()->findTrxCached()
Как это реализовать?
На Yii создаем свой класс ActiveRecord и наследуем от него свои модели:
class ActiveRecord extends CActiveRecord
{
public function __call($name, $args)
{
if (preg_match('/^(.+)(cached|exception|trx)$/i', $name, $matches)) {
switch (strtolower($matches[2])) {
case 'cached':
return $this->cachedMethod($matches[1], $args);
case 'exception':
return $this->exceptionMethod($matches[1], $args);
case 'trx':
return $this->trxMethod($matches[1], $args);
}
}
return parent::__call($name, $args);
}
public function trxMethod($method, $args)
{
$trx = $this->getDbConnection()->beginTransaction();
try {
$value = call_user_func_array(array($this, $method), $args);
$trx->commit();
} catch (\Exception $e) {
$trx->rollback();
throw $e;
}
return $value;
}
public function exceptionMethod($method, $args)
{
$value = call_user_func_array(array($this, $method), $args);
if (!$value)
throw new Exception('False result!');
return $value;
}
public function cachedMethod($method, $args)
{
$key = get_class($this) . $method . serialize($this->getPrimaryKey()) . serialize($args) . serialize($this->getDbCriteria());
$key = md5($key);
$value = \Yii::app()->cache->get($key);
if ($value === false) {
$value = call_user_func_array(array($this, $method), $args);
\Yii::app()->cache->set($key, $value, 0, new \caching\TagDependency(get_class($this)));
} else {
//reset scope as in find*() methods
$this->resetScope();
}
return $value;
}
}
Основа метода — регулярное выражение:
preg_match('/^(.+)(cached|exception|trx)$/i', $name, $matches)
Вы можете добавить сюда свои суффиксы.
Преимущества:
- Рутинные операции занимают гораздо меньше места в коде.
- Быстрее и проще писать код.
Недостатки:
- IDE не видит такие методы или их надо прописывать вручную, что не очень удобно.