Возвращение рекурсии и разделение ответственностей алгоритма удаления директорий


Исходный код

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 возвращает только дочерние идентификаторы, без родительских.

Попутно была сделана хорошая оптимизация, уменьшающая количество запросов к базе данных; причём это было сделано без ущерба для читаемости кода.

Теория