Написал статейку о классе обработки ошибок. Оцените.

Бочонок

http://frontender.info
Приятного времени суток. Извините за задержку - потратил время на эксперименты, прочтения дополнительных материалов и осмысление результатов.

Примеры:
С моим классом:
PHP:
<?php
header("content-type: text/html; charset=utf-8");
class db_renamer{
	//! Переменная, содержащая указатель для работы с базой данных
	private $connector;
	//! Переменная, обеспечивающая работу с ошибками
	public $err;
	//! Конструктор. Инициирует класс для работы с SMS.
	/*!
		@param user - имя пользователя
		@param pass - пароль
		@param host - хост
		@param dbname - имя базы данных
		@return false в случае ошибки и true в противном
	*/
	public function __construct($user,$pass,$host,$dbname){
		$this->err = new error_class();
		$this->err->clear_err();
		if(
			empty($user)||
			empty($pass)||
			empty($host)||
			empty($dbname)
		){
			$this->err->set_err("Не все данные заданы");
			return false;
		}
		if(!($this->connector=mysql_connect($host,$user,$pass))){
			$this->err->set_err(mysql_error());
			return false;
		}
		if(!mysql_select_db($dbname,$this->connector)){
			$this->err->set_err(mysql_error());
			return false;
		}
		return true;
	}
	//! Метод переименует имя файла и изменяет его вбазе данных
	/*!
		@param name - имя файла
		@param new_name - новое имя файла
	*/
	public function do_it($name,$new_name){
		$this->err->clear_err();
		
		if(!file_exists($name)){ 
			$this->err->set_err("Файл ".$name." не найден");
			return false;
		}
		if(file_exists($new_name)){
			$this->err->set_err("Файл ".$new_name." уже существует. Файл не был переименован.");
			return false;
		}
		
		$query="
			SELECT
				COUNT(*) AS 'in_base'
			FROM
				file_collector
			WHERE
				name='".$name."'
		";
		if(!($result=mysql_query($query,$this->connector))){
			$this->err->set_err(mysql_error());
			return false;
		}
		if(!($row=mysql_fetch_assoc($result))){
			$this->err->set_err(mysql_error());
			return false;
		}
		if($row['in_base']==0){
			$this->err->set_err("Запись о файле ".$name." не найдена в базе данных.");
			return false;
		}
		
		if(!rename($name,$new_name)){
			$this->err->set_err("Не удалось переименовать файл ".$name." в ".$new_name);
			return false;
		}
		$query="
			UPDATE
				file_collector
			SET
				name='".$new_name."'
			WHERE
				name='".$name."'
			LIMIT 1
		";
		if(!mysql_query($query,$this->connector)){
				$this->err->set_err(mysql_error());
				return false;
		}		
		return true;
	}	
}

require_once("error_class.php");
$err = new error_class("err_overwork");
 
$renamer = new db_renamer('user','pass','localhost','renamer_db');
if($renamer->is_err()!=false){
	$err->fire_err($renamer->is_err());
}
$renamer->do_it('some_file.txt','some_other_file.txt');
if($renamer->is_err()!=false){
	//специфичекий обработчик ошибки
}

function err_overwork($error){
	//общий обработчик ошибки
	echo "<pre>";
	var_dump($error);
    die("</pre>");
}
?>
На чистых исключениях:
PHP:
<?php
header("content-type: text/html; charset=utf-8");
class db_renamer{
	//! Переменная, содержащая указатель для работы с базой данных
	private $connector;
	//! Конструктор. Инициирует класс для работы с SMS.
	/*!
		@param user - имя пользователя
		@param pass - пароль
		@param host - хост
		@param dbname - имя базы данных
		@return false в случае ошибки и true в противном
	*/
	public function __construct($user,$pass,$host,$dbname){
		if(
			empty($user)||
			empty($pass)||
			empty($host)||
			empty($dbname)
		){
			throw new Exception("Не все данные заданы",E_ERROR);
		}
		if(!($this->connector=mysql_connect($host,$user,$pass))){
			throw new Exception(mysql_error(),E_ERROR);
		}
		if(!mysql_select_db($dbname,$this->connector)){
			throw new Exception(mysql_error(),E_ERROR);
		}
	}
	//! Метод переименует имя файла и изменяет его вбазе данных
	/*!
		@param name - имя файла
		@param new_name - новое имя файла
	*/
	public function do_it($name,$new_name){
		if(!file_exists($name)){
			throw new Exception("Файл ".$name." не найден",E_ERROR);
		}
		if(file_exists($new_name)){
			throw new Exception("Файл ".$new_name." уже существует. Файл не был переименован.",E_ERROR);
		}
		
		$query="
			SELECT
				COUNT(*) AS 'in_base'
			FROM
				file_collector
			WHERE
				name='".$name."'
		";
		if(!($result=mysql_query($query,$this->connector))){
			throw new Exception(mysql_error(),E_ERROR);
		}
		if(!($row=mysql_fetch_assoc($result))){
			throw new Exception(mysql_error(),E_ERROR);
		}
		if($row['in_base']==0){
			throw new Exception("Запись о файле ".$name." не найдена в базе данных.",E_ERROR);
		}
		
		if(!rename($name,$new_name)){
			throw new Exception("Не удалось переименовать файл ".$name." в ".$new_name,E_ERROR);
		}
		$query="
			UPDATE
				file_collector
			SET
				name='".$new_name."'
			WHERE
				name='".$name."'
			LIMIT 1
		";
		if(!mysql_query($query,$this->connector)){
			throw new Exception(mysql_error(),E_ERROR);
		}		
	}	
}

set_exception_handler("err_overwork");
set_error_handler("redirect_err",E_ALL);


$renamer = new db_renamer('user','pass','localhost','renamer_db');
try{
	$renamer->do_it('some_file.txt','some_other_file.txt');
}catch(Exception $error){
	//Специфический обработчик ошибки
}

restore_exception_handler();
restore_error_handler();

function err_overwork($error){
	//общий обработчик ошибки
	echo "<pre>";
	var_dump($error);
    die("</pre>");
}
function redirect_err($errno, $errstr, $errfile, $errline, $errcontext){
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    return true;
}
?>
На исключениях с пользовательскими исключениями:
PHP:
<?php
header("content-type: text/html; charset=utf-8");
class FSError extends Exception{};
class DBError extends Exception{};
class ArgError extends Exception{};

class db_renamer{
	//! Переменная, содержащая указатель для работы с базой данных
	private $connector;
	//! Конструктор. Инициирует класс для работы с SMS.
	/*!
		@param user - имя пользователя
		@param pass - пароль
		@param host - хост
		@param dbname - имя базы данных
		@return false в случае ошибки и true в противном
	*/
	public function __construct($user,$pass,$host,$dbname){
		if(
			empty($user)||
			empty($pass)||
			empty($host)||
			empty($dbname)
		){
			throw new ArgError("Не все данные заданы",E_ERROR);
		}
		//Насколько следующие строки c обработкой ошибок имеют смысл, 
если в случае ошибки с mysql_connect в любом случае интерпретатором будет сгенерирован Warning, 
который перехватывается через redirect_err?
		if(!($this->connector=mysql_connect($host,$user,$pass))){
			throw new DBError(mysql_error(),E_ERROR);
		}
		if(!mysql_select_db($dbname,$this->connector)){
			throw new DBError(mysql_error(),E_ERROR);
		}
	}
	//! Метод переименует имя файла и изменяет его вбазе данных
	/*!
		@param name - имя файла
		@param new_name - новое имя файла
	*/
	public function do_it($name,$new_name){
		if(!file_exists($name)){
			throw new FSError("Файл ".$name." не найден",E_ERROR);
		}
		if(file_exists($new_name)){
			throw new FSError("Файл ".$new_name." уже существует. Файл не был переименован.",E_ERROR);
		}
		
		$query="
			SELECT
				COUNT(*) AS 'in_base'
			FROM
				file_collector
			WHERE
				name='".$name."'
		";
		if(!($result=mysql_query($query,$this->connector))){
			throw new DBError(mysql_error(),E_ERROR);
		}
		if(!($row=mysql_fetch_assoc($result))){
			throw new DBError(mysql_error(),E_ERROR);
		}
		if($row['in_base']==0){
			throw new FSError("Запись о файле ".$name." не найдена в базе данных.",E_ERROR);
		}
		
		if(!rename($name,$new_name)){
			throw new FSError("Не удалось переименовать файл ".$name." в ".$new_name,E_ERROR);
		}
		$query="
			UPDATE
				file_collector
			SET
				name='".$new_name."'
			WHERE
				name='".$name."'
			LIMIT 1
		";
		if(!mysql_query($query,$this->connector)){
			throw new DBError(mysql_error(),E_ERROR);
		}		
	}	
}

set_exception_handler("err_overwork");
set_error_handler("redirect_err",E_ALL);

try{
	$renamer = new db_renamer('user','pass','localhost','renamer_db');
}catch(DBError $error){
	echo 'Специфический обработчик DBError';
	throw $error;
}catch(ArgError $error){
	echo 'Специфический обработчик ArgError';
	//Специфический обработчик ошибки
}

try{
	$renamer->do_it('some_file.txt','some_other_file.txt');
}catch(FSError $error){
	echo 'Специфический обработчик FSError';
	//Специфический обработчик ошибки
}catch(DBError $error){
	echo 'Специфический обработчик DBError';
	//Специфический обработчик ошибки
}

restore_exception_handler();
restore_error_handler();

function err_overwork($error){
	//общий обработчик ошибки
	echo 'Общий обработчик';
	echo "<pre>";
	var_dump($error);
    die("</pre>");
}
function redirect_err($errno, $errstr, $errfile, $errline, $errcontext){
	echo 'Ошибка сгенерирована интерпретатором и перенаправлена<br/>';
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    return true;
}
?>
Да. В принципе конструкция
try{}catch(){}
По объему не многим хуже конструкции
if(){}
Зато не надо сбрасывать ошибку, добавлять класс, возвращать false или делать проверку флага что бы поднятся вверх по стеку на каждом уровне.

Меня интересуют следующие вопросы:

1. Когда надо пользоватся наследованием класса Exception?
Это нужно для создания общих классов ошибок, насколько я понимаю.
Как широко надо это применять?
Насколько это применимо по вашему опыту?
И где держать определение наследующих классов?
В отдельном скрипте и рекваерить?

2. Если переопределяется ф-я обработки ошибок, сгенерированных интерпретатором, то стоит ли писать конструкции генерирующие пользовательское исключение?
(см. пример 3. Конструктор. Комментарий.)
Наверное стоит, что бы не зависеть от того переопределен перехватчик ошибок генерируемых интерпретатором или нет.
Что вы думаете?

3. Писать в каждом скрипте
PHP:
set_exception_handler("err_overwork");
set_error_handler("redirect_err",E_ALL);
restore_exception_handler();
restore_error_handler();
function redirect_err($errno, $errstr, $errfile, $errline, $errcontext){
	echo 'Ошибка сгенерирована интерпретатором и перенаправлена<br/>';
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    return true;
}
Довольно некрасиво, как мне кажется.
Имеет ли смысл что бы этого избежать сократить свой класс до:
PHP:
//!Класс работы с ошибками
/*!
    @ingroup error
*/
class error_class{
	//! Флаг замены стандартной функции обработки ошибок
	private $err_handler=false;
	
	//! Конструктор
	/*!
		@param callback - CallBack функция, которая будет обрабатывать исключения.
		@param err_redirect - Если задана callback функция и err_redirect установлен в true, 
то пользовательской функции будет передана так же обработка ошибок, генерируемых интерпретатором. По умолчанию true.
		@param level - определяет, какие генерируемые интерпретатором ошибки будут перехватыватся. 
Возможные значения: [url]http://ua.php.net/manual/ru/errorfunc.constants.php[/url]
	*/
	public function __construct($callback=null,$err_redirect=true,$level=E_ALL){
		if($callback==null){
			throw new Exception("Не определена callback функция.",E_ERROR);
		}
		set_exception_handler($callback);
		if($err_redirect){
			$this->err_handler = true;
			set_error_handler(array("error_class","redirect_err"),$level);
		}
	}
	
	//! Метод, который перехватывает ошибку, сгенерированую интерпретатором и 
вызывает исключение, которое будет обработано пользовательской функцией.
	/*!
		@param errno - уровень сгенерированой ошибки
		@param errstr - текстовое описание ошибки
		@param errfile - имя файла, содержащего код, вызвавший ошибку
		@param errline - номер строки, которая содержит код, вызвавший ошибку
		@param errcontext - массив, указывающий на активную символьную таблицу 
в месте где возникла ошибка. Другими словами массив, содержащий все переменные, 
которы существовали в тот момент, как была сгенерирована ошибка. 
Пользовательская ф-я обработки ошибок не должна изменять контекст.
	*/
	public function redirect_err($errno, $errstr, $errfile, $errline, $errcontext){
		throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
		return true;
	}

	//! Деструктор функции, который возвращает обработчики исключений и ошибок, 
в предидущее их состояние, если они были изменены при запуске конструктора.
	public function __destruct(){
		restore_exception_handler();
		if($this->err_handler){
			restore_error_handler();
		}
	}
}
Что бы инициировать все это простым:
PHP:
new error_class('err_overwork');
function err_overwork($error){	
}
Что вы посоветуете? Как лучше сделать? Мне кажется что если это типовая операция, то сократить - стоит.


2atv:
Спасибо что подтолкнули меня к написанию примеров.
Раньше уже использовал исключения в "чистом" варианте... Но вот сел писать недавно и почему то стукнуло в голову, что try ужасно усложняют структуру кода и надо попробовать что то придумать. Придумал...

2zerkms:
Спасибо за статью и потраченное на спор со мной время. Это подтолкнуло меня перечитать еще раз http://ua.php.net/exceptions и хорошенько подумать

2whirlwind:
Ошибки не надо обрабатывать. Ошибки надо не допускать.
Это не всегда возможно, или я не прав? Если не прав - поясните пожалуйста.

Все делается гораздо изящнее: без программирования ради программирования. Пишется декоратор делегирующий экземпляру класса БД в случае если кеш не содержит данных.
Честно говоря - просто не понял что вы сказали. Приведите пожалуйста пример. Или хотя бы rtfmните меня.

P.S.
Еще раз спасибо всем за то что потратили время на пояснения и комментарии.
Буду очень благодарен вам, если вы поможете мне окончательно разобратся в теме.
 

whirlwind

TDD infected, paranoid
Да. В принципе конструкция
try{}catch(){}
По объему не многим хуже конструкции
if(){}
Зато не надо сбрасывать ошибку, добавлять класс, возвращать false или делать проверку флага что бы поднятся вверх по стеку на каждом уровне.
У исключений есть два преимущества:
1) Это состояние, то есть объект, со всеми вытекающими. Можно добавлять атрибуты и обработку данных, чего нельзя сделать в твоем классе, если например понадобится указать при соединении с какой базой произошла ошибка. В случае с твоим классом ты просто навязывашь приложению формат ошибки.
2) Форсирование стека, то есть обработчик может быть через несколько вызовов от места возникновения исключения. Обработчик исключения находится там, где программа знает как обрабатывать этого вида исключения. Нет необходимости выполнять if (error) на уровнях между обработчиком и местом возникновения. В твоем случае придется проверять if ( error ) на каждом уровне. Если ты этого не сделаешь то поведение твоей программы будет неопределено.

Вот теперь можешь сравнивать объемы.

Это не всегда возможно, или я не прав? Если не прав - поясните пожалуйста.
Надо различать ошибки и ситуации предусмотренные алгоритмом. Например, когда в программу интегрирован сторонний сервис, то могут возникать ошибки связанные с функционированием этого сервиса или ошибки транспортного уровня. Это не ошибки твоей программы, но ты не можешь гарантировать что они не возникнут. Если речь идет например о платежном сервисе то тут без обработки этих сторонних ошибок нельзя. Но и тут с исключениями удобнее. Низкоуровневые классы бросают исключения, остается просто написать обработчики этих исключений, формат которых тебе известен.

А ошибок именно в коде быть не должно. Надо к этому стремиться.

просто не понял что вы сказали. Приведите пожалуйста пример. Или хотя бы rtfmните меня.
декоратор
 

Бочонок

http://frontender.info
Вот теперь можешь сравнивать объемы.
Уже. Я понял, что был не прав и механизм исключений достаточен для того, что бы реализовать обработку ошибок.

А ошибок именно в коде быть не должно. Надо к этому стремиться.
Понял что вы имеете в виду и полностью с вами согласен.

Спасибо за ссылку. Пошел читать.

p.s. теперь бы разобратся как и в каких случаях лучше использовать "пользовательские типы исключений" полученные наследованием от класса Exceptions.
 

cDLEON

Онанист РНРСlub
Мда...
А при чём здесь проверка данных в форме к исключениям? о_О
Валидацией формы, ИМХО, должен заниматься отдельный класс валидатор. Т.е. что то вроде
PHP:
$validate=new FormValidate();
$validate->is_string($_POST['string']);
$validate->is_num($_POST['num']);
if(!$validate->complete()) {
 $view->set_block('err',$validate->get_errors());
}
ese {
//....
}
Исключения нужны для отлова системных ошибок!
А не какие то конченные формы валидировать...
Зато не надо сбрасывать ошибку, добавлять класс, возвращать false или делать проверку флага что бы поднятся вверх по стеку на каждом уровне.
Мде...Иди ещё почитай про исключения....
 

Бочонок

http://frontender.info
2cDLEON:
1. Вы правы. Обаботкой полученных скриптом данных может (должен?) заниматся отдельный класс.
2. Вы не правы. Исключения могут быть использованы в качестве механизма обработки ошибок внутри класса обработки входных данных. Почему не стоит использовать исключения для обработки ошибок в случае проверки входных данных?

Мде...Иди ещё почитай про исключения....
Пожалуйста, поясните что вы имеете в виду и чем именно вызвано это замечание.
 

atv

Новичок
Бочонок, как раз главное в том примере что я просил сделать ты и упустил :), а главное это строчки:
PHP:
try{
    $renamer->do_it('some_file.txt','some_other_file.txt');
}catch(Exception $error){
    //Специфический обработчик ошибки
}
а точнее строчка "//Специфический обработчик ошибки".

Бросить исключение, это ещё не обработка ошибки, это просто сигнализация об ошибке. А в обработке нужно было отменить транзакцию (предварительно запущенную в блоке try), переименовать файл обратно, если нужно, в зависимости от того, какое исключение было перехвачено.

И достаточно было псевдокода. Дополни теперь свой пример обработкой ошибок, опять же, в двух вариантах, и тогда ты окончательно разберёшся в исключениях.
 

cDLEON

Онанист РНРСlub
Вы не правы. Исключения могут быть использованы в качестве механизма обработки ошибок внутри класса обработки входных данных. Почему не стоит использовать исключения для обработки ошибок в случае проверки входных данных?
Здесь ключевое слово "могут". Если вам не чем заняться, можете и дальше пытаться найти рак прямой кишки, начиная поиски через горло. Или зубы через задницу выдирайте, как вам угодно...
Пожалуйста, поясните что вы имеете в виду и чем именно вызвано это замечание.
Вам до этого 4 аратора указывали на ваши ошибки. А вы до сих пор не можете понять????
Ну что ж...Выделю...

Форсирование стека, то есть обработчик может быть через несколько вызовов от места возникновения исключения. Обработчик исключения находится там, где программа знает как обрабатывать этого вида исключения. Нет необходимости выполнять if (error) на уровнях между обработчиком и местом возникновения. В твоем случае придется проверять if ( error ) на каждом уровне. Если ты этого не сделаешь то поведение твоей программы будет неопределено.
Бросить исключение, это ещё не обработка ошибки, это просто сигнализация об ошибке. А в обработке нужно было отменить транзакцию (предварительно запущенную в блоке try), переименовать файл обратно, если нужно, в зависимости от того, какое исключение было перехвачено.
И так на протяжение всего топика. Нужные слова вы просто игнорируете.
 

korchasa

LIMB infected
Автор оригинала: cDLEON ...
Исключения нужны для отлова системных ошибок!
А не какие то конченные формы валидировать...
...
Исключения нужны для отлова исключительных ситуаций. Ошибкой является только исклчительная ситуация, которую никто не знает как нормально обработать.

Попытка сохранить невалидный объект это вполне себе исключительная ситуация.

Валидация вообще не обязательно подразумевает формы, это просто один из способов заполнения данных.
 

Бочонок

http://frontender.info
Извините за задержку, решил пока суть да дело еще статейку написать, точнее перевести. Про типографику..


2atv:
Спасибо за замечание. Посмотрите что у меня получилось?
Я сделал 3 варианта.
В первом - вообще без пользовательских классов наследующих исключения.
Во втором - с пользовательскими классами используемыми для типизации ошибок.
В третьем - с пользовательским классом, которорый обозначает необходимость определенного действия. Например - rollback'а. Но таким образом мы обозначаем логику обработки ошибок в классе. А это не хорошо.
Еще можно было бы для каждой ошибки определить свой пользовательский класс. Но пример решил не писать, так как количество таких классов-наследников Exception стало бы просто неприличным и мне показалось что эта идея - бред сумасшедшего.

Честно говоря, я не очень сейчас понимаю как правильно и лучше сделать. Наставьте меня, пожалуйста, на путь истинный.

Вариант 1:
PHP:
<?php
header("content-type: text/html; charset=utf-8");
class db_renamer{
    //! Переменная, содержащая указатель для работы с базой данных
    private $connector;
    //! Конструктор. Инициирует класс для работы с SMS.
    /*!
        @param user - имя пользователя
        @param pass - пароль
        @param host - хост
        @param dbname - имя базы данных
        @return false в случае ошибки и true в противном
    */
    public function __construct($user,$pass,$host,$dbname){
        if(
            empty($user)||
            empty($pass)||
            empty($host)||
            empty($dbname)
        ){
            throw new Exception("Не все данные заданы",E_ERROR);
        }
        if(!($this->connector=mysql_connect($host,$user,$pass))){
            throw new Exception(mysql_error(),1);
        }
        if(!mysql_select_db($dbname,$this->connector)){
            throw new Exception(mysql_error(),2);
        }
    }
    //! Метод переименует имя файла и изменяет его вбазе данных
    /*!
        @param name - имя файла
        @param new_name - новое имя файла
    */
    public function do_it($name,$new_name){
        if(!file_exists($name)){
            throw new Exception("Файл ".$name." не найден",3);
        }
        if(file_exists($new_name)){
            throw new Exception("Файл ".$new_name." уже существует. Файл не был переименован.",4);
        }
        
        $query="
            SELECT
                COUNT(*) AS 'in_base'
            FROM
                file_collector
            WHERE
                name='".$name."'
        ";
        if(!($result=mysql_query($query,$this->connector))){
            throw new Exception(mysql_error(),5);
        }
        if(!($row=mysql_fetch_assoc($result))){
            throw new Exception(mysql_error(),6);
        }
        if($row['in_base']==0){
            throw new Exception("Запись о файле ".$name." не найдена в базе данных.",7);
        }
        
        if(!rename($name,$new_name)){
            throw new Exception("Не удалось переименовать файл ".$name." в ".$new_name,8);
        }
        $query="
            UPDATE
                file_collector
            SET
                name='".$new_name."'
            WHERE
                name='".$name."'
            LIMIT 1
        ";
        if(!mysql_query($query,$this->connector)){
            throw new Exception(mysql_error()+" Будет проведен откат файла к предыдущему состоянию.",9);
        }        
    }    
}

set_exception_handler("err_overwork");
set_error_handler("redirect_err",E_ALL);

$renamer = new db_renamer('user','pass','localhost','renamer_db');
try{
    $renamer->do_it('some_file.txt','some_other_file.txt');
}catch(Exception $error){
    switch($error->getCode()){
		case 9:
			if(!rename('some_other_file.txt','some_file.txt')){
			    throw new Exception("Произошла ошибка: ".$error->getMessage()."; 
Во время отката к предидущему состоянию не удалось переименовать файл ".$new_name." обратно в ".$new,8);
			}
			break;
	}
	throw $error;
}

restore_exception_handler();
restore_error_handler();

function err_overwork($error){
    //общий обработчик ошибки
    echo "<pre>";
    var_dump($error);
    die("</pre>");
}
function redirect_err($errno, $errstr, $errfile, $errline, $errcontext){
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    return true;
}
?>
Вариант2:
PHP:
<?php
header("content-type: text/html; charset=utf-8");
class FSError extends Exception{};
class DBError extends Exception{};
class ArgError extends Exception{};

class db_renamer{
    //! Переменная, содержащая указатель для работы с базой данных
    private $connector;
    //! Конструктор. Инициирует класс для работы с SMS.
    /*!
        @param user - имя пользователя
        @param pass - пароль
        @param host - хост
        @param dbname - имя базы данных
        @return false в случае ошибки и true в противном
    */
    public function __construct($user,$pass,$host,$dbname){
        if(
            empty($user)||
            empty($pass)||
            empty($host)||
            empty($dbname)
        ){
            throw new ArgError("Не все данные заданы",1);
        }

        if(!($this->connector=mysql_connect($host,$user,$pass))){
            throw new DBError(mysql_error(),1);
        }
        if(!mysql_select_db($dbname,$this->connector)){
            throw new DBError(mysql_error(),2);
        }
    }
    //! Метод переименует имя файла и изменяет его вбазе данных
    /*!
        @param name - имя файла
        @param new_name - новое имя файла
    */
    public function do_it($name,$new_name){
        if(!file_exists($name)){
            throw new FSError("Файл ".$name." не найден",1);
        }
        if(file_exists($new_name)){
            throw new FSError("Файл ".$new_name." уже существует. Файл не был переименован.",1);
        }
        
        $query="
            SELECT
                COUNT(*) AS 'in_base'
            FROM
                file_collector
            WHERE
                name='".$name."'
        ";
        if(!($result=mysql_query($query,$this->connector))){
            throw new DBError(mysql_error(),3);
        }
        if(!($row=mysql_fetch_assoc($result))){
            throw new DBError(mysql_error(),4);
        }
        if($row['in_base']==0){
            throw new FSError("Запись о файле ".$name." не найдена в базе данных.",3);
        }
        
        if(!rename($name,$new_name)){
            throw new FSError("Не удалось переименовать файл ".$name." в ".$new_name,4);
        }
        $query="
            UPDATE
                file_collector
            SET
                name='".$new_name."'
            WHERE
                name='".$name."'
            LIMIT 1
        ";
        if(!mysql_query($query,$this->connector)){
            throw new DBError(mysql_error()+" Будет проведен откат файла к предыдущему состоянию.",5);
        }        
    }    
}

set_exception_handler("err_overwork");
set_error_handler("redirect_err",E_ALL);

$renamer = new db_renamer('user','pass','localhost','renamer_db');
try{
    $renamer->do_it('some_file.txt','some_other_file.txt');
}catch(DBError $error){
    switch($error->getCode()){
		case 5:
			if(!rename('some_other_file.txt','some_file.txt')){
			    throw new FSError("Произошла ошибка: ".$error->getMessage()."; 
Во время отката к предидущему состоянию не удалось переименовать файл ".$new_name." обратно в ".$new,5);
			}
			break;
	}
	throw $error;
}

restore_exception_handler();
restore_error_handler();

function err_overwork($error){
    //общий обработчик ошибки
    echo 'Общий обработчик';
    echo "<pre>";
    var_dump($error);
    die("</pre>");
}
function redirect_err($errno, $errstr, $errfile, $errline, $errcontext){
    echo 'Ошибка сгенерирована интерпретатором и перенаправлена<br/>';
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    return true;
}
?>
Вариант3:
PHP:
<?php
header("content-type: text/html; charset=utf-8");
class rollBack extends Exception{};

class db_renamer{
    //! Переменная, содержащая указатель для работы с базой данных
    private $connector;
    //! Конструктор. Инициирует класс для работы с SMS.
    /*!
        @param user - имя пользователя
        @param pass - пароль
        @param host - хост
        @param dbname - имя базы данных
        @return false в случае ошибки и true в противном
    */
    public function __construct($user,$pass,$host,$dbname){
        if(
            empty($user)||
            empty($pass)||
            empty($host)||
            empty($dbname)
        ){
            throw new Exception("Не все данные заданы",E_ERROR);
        }

        if(!($this->connector=mysql_connect($host,$user,$pass))){
            throw new Exception(mysql_error(),E_ERROR);
        }
        if(!mysql_select_db($dbname,$this->connector)){
            throw new Exception(mysql_error(),E_ERROR);
        }
    }
    //! Метод переименует имя файла и изменяет его вбазе данных
    /*!
        @param name - имя файла
        @param new_name - новое имя файла
    */
    public function do_it($name,$new_name){
        if(!file_exists($name)){
            throw new Exception("Файл ".$name." не найден",E_ERROR);
        }
        if(file_exists($new_name)){
            throw new Exception("Файл ".$new_name." уже существует. Файл не был переименован.",E_ERROR);
        }
        
        $query="
            SELECT
                COUNT(*) AS 'in_base'
            FROM
                file_collector
            WHERE
                name='".$name."'
        ";
        if(!($result=mysql_query($query,$this->connector))){
            throw new Exception(mysql_error(),E_ERROR);
        }
        if(!($row=mysql_fetch_assoc($result))){
            throw new Exception(mysql_error(),E_ERROR);
        }
        if($row['in_base']==0){
            throw new Exception("Запись о файле ".$name." не найдена в базе данных.",E_ERROR);
        }
        
        if(!rename($name,$new_name)){
            throw new Exception("Не удалось переименовать файл ".$name." в ".$new_name,E_ERROR);
        }
        $query="
            UPDATE
                file_collector
            SET
                name='".$new_name."'
            WHERE
                name='".$name."'
            LIMIT 1
        ";
        if(!mysql_query($query,$this->connector)){
            throw new rollBack(mysql_error()+" Будет проведен откат файла к предыдущему состоянию.",E_ERROR);
        }        
    }    
}

set_exception_handler("err_overwork");
set_error_handler("redirect_err",E_ALL);

$renamer = new db_renamer('user','pass','localhost','renamer_db');

try{
    $renamer->do_it('some_file.txt','some_other_file.txt');
}catch(rollBack $error){
    if(!rename('some_other_file.txt','some_file.txt')){
	    throw new Exception("Произошла ошибка: ".$error->getMessage()."; 
Во время отката к предидущему состоянию не удалось переименовать файл ".$new_name." обратно в ".$new,5);
	}
	throw $error;
}

restore_exception_handler();
restore_error_handler();

function err_overwork($error){
    //общий обработчик ошибки
    echo 'Общий обработчик';
    echo "<pre>";
    var_dump($error);
    die("</pre>");
}
function redirect_err($errno, $errstr, $errfile, $errline, $errcontext){
    echo 'Ошибка сгенерирована интерпретатором и перенаправлена<br/>';
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
    return true;
}
?>
 

atv

Новичок
Немного не в том направлении ты пошёл, поэтому приведу псевдокод на try...catch, а ты попробуй написать такой псевдокод на колбеках.

Итак
PHP:
try {
    $db->beginTransaction();

    $db_file->rename($newname);
    $file->rename($newname);

    $db->commit();
} catch (Exception $e) {
    $db->rollback();
    throw new RenameException('Не удалось переименовать файл "'.$newname.'": '.$e->getMessage());
}
Разбор полётов:
Во первых, в блоке try, как правило, бывает больше одной строчки. Если в какой нибудь из них произойдёт исключение, то остальные строчки после неё выполняться НЕ БУДУТ, управление сразу перейдёт к блоку catch.

Например, если исключение произойдёт при операции с БД (в строчке $db_file->rename($newname);), то попытки переименовать файл на диске не будет (строчка $file->rename($newname);).

В твоём варианте придётся в каждой строке добавлять условие проверки, и прерывать выполнение.

Во вторых, ты так и не использовал транзакции.

Например, если исключение произойдёт при переименовании файла на диске (строчка $file->rename($newname);), то транзакция не закончиться (строчка $db->commit();), и изменения в БД откатятся строчкой $db->rollback();

Наконец, в блоке catch, как минимум, мы должны обработать данное исключение, откатив транзакцию. Если логикой приложения предусмотрены ещё какие-то действия, выполняем их. При этом у нас доступны все локальные переменные такие как $db, $db_file, $file и $newname. В случае использования колбеков, эти переменные не будут доступны.

С другой стороны, если в данном блоке catch, не определена логика обработки исключения, мы можем отправить исключение дальше, причём изменив контекст этого исключения так, чтобы он был понятен на более высоких уровнях приложения.

Например, в приложении может быть много файлов, с которыми может возникнуть одно и тоже исключение (например FileException), но для каждого файла определена своя логика обработки исключения, соответственно, тебе нужно различать эти исключения. Это удобно делать, меняя объект исключения. В моём псевдокоде я бросил другое исключение RenameException. А где-то, на НЕСКОЛЬКО УРОВНЕЙ више, может стоять блок catch (RenameException $e), в котором определена логика обработки конкретно этого исключения.

решил пока суть да дело еще статейку написать
А ты, всё-таки, графоман :D

Может направишь свои силы в более полезное русло. Есть, например, много проектов, нуждающихся в хороших туториалах, документации и т.д.
 
Сверху