Исходный код
function drop_dir ( $dir_id )
{
$query = mysql_query ( "SELECT `id` FROM `dirs` WHERE `subid`=" . $dir_id );
while ( $row = mysql_fetch_array ( $query ) )
{
$n_query = mysql_query ( "SELECT `id` FROM `dirs` WHERE `subid`=" . $row['id'] );
while ( $n_row = mysql_fetch_array ( $query ) )
{
$n2_query = mysql_query ( "SELECT `id` FROM `dirs` WHERE `subid`=" . $n_row['id'] );
while ( $n2_row = mysql_fetch_array ( $n2_query ) )
{
mysql_query ( "DELETE FROM `dirs` WHERE `id`=" . $n2_row['id'] );
}
// Неужто блядь кто то дальше вложит
mysql_query ( "DELETE FROM `dirs` WHERE `id`=" . $n_row['id'] );
}
mysql_query ( "DELETE FROM `dirs` WHERE `id`=" . $row['id'] );
}
mysql_query ( "DELETE FROM `dirs` WHERE `id`=" . $dir );
RETURN TRUE;
}
Что не так в исходном коде
Очевидно, что тут имеет место линейная реализация рекурсивного по сути алгоритма удаления узлов дерева. Исправление алгоритма будет заключаться в возвращении ему рекурсивности.
Второй недочёт реализации — смешение двух ответственностей: получение идентификаторов всех директорий, вложенных в данную, и собственно удаление.
Вариант рефакторинга исходного кода
function drop_dir( $dir_id )
{
$dir_ids = array_merge ( [ $dir_id ], subdir_ids_rec( [ $dir_id ] ) );
$dir_ids_str = implode ( ',', $dir_ids );
mysql_query ( "DELETE FROM `dirs` WHERE `id` IN ($dir_ids_str)" );
return true;
}
function subdir_ids_rec( $dir_ids )
{
$dir_ids_next = subdir_ids( $dir_ids );
if ( empty ( $dir_ids_next ) ) {
return []; <em>// выход из рекурсии</em>
}
$dir_ids_next_rec = subdir_ids_rec( $dir_ids_next );
return array_merge ( $dir_ids_next, $dir_ids_next_rec );
}
function subdir_ids( $dir_ids )
{
$subdir_ids = [];
$query = mysql_query ( "SELECT `id` FROM `dirs` WHERE `subid` IN (" . implode ( ',', $dir_ids ) . ")" );
while ( $row = mysql_fetch_array ( $query ) )
{
$subdir_ids[] = $row['id'];
}
return $subdir_ids;
}
В алгоритме рекурсивного получения идентификаторов узлов я тоже разделил ответственности: отдельно получение всех дочерних директорий одного уровня, и отдельно — дочерних директорий всех уровней.
По опыту могу сказать, что всегда полезно иметь список всех дочерних узлов, не включающий родительский узел: всегда найдётся какая-нибудь операция, касающаяся только дочерних узлов и не затрагивающая текущий узел; поэтому subdir_ids_rec
возвращает только дочерние идентификаторы, без родительских.
Попутно была сделана хорошая оптимизация, уменьшающая количество запросов к базе данных; причём это было сделано без ущерба для читаемости кода.
Теория
- Принцип единственного знания
- Рефакторинг «извлечение метода»