4.3. Изменение уровня узла
Перемещение узла на уровень вверх - не особо сложная процедура: нужно просто определить родительский узел, и установить перемещаемый узел рядом, на одном уровне. Перемещение на уровень вниз по дереву - так же несложно, для того, что бы это сделать нужно определить, соседний узел (я использую вышестоящий по списку), и переместить узел в его подчинение. Это, правда, совсем не значит, что нам нужно будет использовать два предыдущих метода. Наша задача - определить, в первом случае - правый ключ родителя и его уровень, во втором - тот же правый ключ соседнего узла и его уровень (правда, при этом, мы будем использовать значения на единицу меньшие), итак:
Для того, что бы переместить узел на уровень вверх - вниз нужны следующие данные:
- параметры перемещаемого узла;
- параметр перемещения узла - уровень вверх или уровень вниз.
- порядок перемещения - в начало или конец списка
И произвести следующие действия:
- определить параметры перемещаемого узла (если они еще не определены);
- определить параметры родительского или соседнего узла, в зависимости от параметра перемещения;
- передать полученные данные процедуре _move_unit
sub set_unit_level {
# Получаем данные: объект, перемещаемый узел, место перемещения, порядок перемещения
my ($self, %common)= @_;
# перемещаемый узел
my $unit = $common{'unit'} || undef;
# место перемещения
my $move = $common{'move'} || undef;
return 0 unless $move;
# порядок перемещения (top - в начало, иначе - в конец списка)
my $order = $common{'order'} || undef;
# объявляем переменную, которую будем передавать процедуре перемещения
my $data;
# определяем данные перемещаемого узла
if ($unit) {$self = &select_unit($self, $unit)}
elsif (!$self->{'unit'}->{'id'}) {croak("NestedSets failed: Your must first select unit, for moving it!!!")}
# если на уровень вверх
if ($move eq 'up') {
# определяем данные места перемещения - узла, рядом с которым
# будет располагаться перемещаемый узел
my $sql = 'SELECT '.
$self->{'right'}.' AS rk, '.
$self->{'level'}.' AS lv FROM '. $self->{'table'}.
' WHERE '.
$self->{'left'}.' < '.$self->{'unit'}->{'left'}.' AND '.
$self->{'right'}.' > '.$self->{'unit'}->{'right'}.' AND '.
$self->{'level'}.' = '.$self->{'unit'}->{'level'}.' - 1 '.
($self->{'type'} eq 'M' ?
' AND '.$self->{'multi'}.'= \''.$self->{'unit'}->{'multi'}.'\'' : '');
my $sth = $self -> {'DBI'} -> prepare($sql); $sth -> execute();
my $row = $sth -> fetchrow_hashref();
$sth -> finish();
if ($row) {
$data->{'near'} = $$row{'rk'};
$data->{'level_new'} = $$row{'lv'}
} else {return 0}
# если на уровень вниз
} elsif ($move eq 'down') {
# определяем данные места перемещения - узла, новый родитель
my $sql = 'SELECT '.
$self->{'right'}.' AS rk, '.
$self->{'left'}.' AS lk, '.
$self->{'level'}.' AS lv FROM '.$self->{'table'}.
' WHERE '.
$self->{'right'}.' = '.$self->{'unit'}->{'left'}.' - 1'.
($self->{'type'} eq 'M' ?
' AND '.$self->{'multi'}.'= \''.$self->{'unit'}->{'multi'}.'\'' : '');
my $sth = $self -> {'DBI'} -> prepare($sql); $sth -> execute();
my $row = $sth -> fetchrow_hashref();
$sth -> finish();
if ($row && (($order && $order eq 'top') || $self->{'order'} eq 'T')) {
$data->{'near'} = $$row{'lk'};
$data->{'level_new'} = $$row{'lv'} + 1
} elsif ($row) {
$data->{'near'} = $$row{'rk'} - 1;
$data->{'level_new'} = $$row{'lv'} + 1
} else {return 0}
} else {return 0}
# перебрасываем из объекта данные о перемещаемом узле
$data->{'left'} = $self->{'unit'}->{'left'};
$data->{'right'} = $self->{'unit'}->{'right'};
$data->{'level'} = $self->{'unit'}->{'level'};
$data->{'multi'} = $self->{'unit'}->{'multi'} || undef;
$self->{'unit'} = undef;
# перемещаем узел
&_move_unit($self, $data);
return 1
}
Вызов данного метода производится так:
...
my $unit = ... # Определяем идентификатор узла
...
use MyModule::NestedSets;
my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh};
$nested->set_unit_level(unit=>$unit, move=>'up', order=>'top');
...
или так:
...
my $unit = ... # Определяем идентификатор узла
...
use MyModule::NestedSets;
my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh};
$nested->select_unit($unit);
$nested->set_unit_level(move=>'down', order=>'top');
...
4.4. Изменение порядка узла
Не смотря на то, что у нас есть метод перемещения узла в точку, рядом с другим. Часто требуется простое перемещение узла по порядку вверх или вниз. Используя вышесказаный метод, нам будет нужно определить этот соседний узел, что приводит к лишним операциям, поэтому целесообразно описать отдельную процедуру для управления порядком, в пределах одного подчинения. Данные, которые нам понадобятся:
- параметры перемещаемого узла;
- параметры перемещения (вверх - вниз)
Действия, точно такие же как и в предыдущих методах.
sub set_unit_order {
# Получаем данные: объект, перемещаемый узел, порядок перемещения
my ($self, %common)= @_;
# перемещаемый узел
my $unit = $common{'unit'} || undef;
# место перемещения
my $move = $common{'move'} || undef;
return 0 unless $move;
# объявляем переменную, которую будем передавать процедуре перемещения
my $data;
# определяем данные перемещаемого узла
if ($unit) {$self = &select_unit($self, $unit)}
elsif (!$self->{'unit'}->{'id'}) {croak("NestedSets failed: Your must first select unit, for moving it!!!")}
# определяем данные места перемещения - узла, за которым
# будет располагаться перемещаемый узел
if ($move eq 'up') {
my $sql = 'SELECT '.
$self->{'left'}.' AS lk '.
' FROM '.$self->{'table'}.
' WHERE '.
$self->{'right'}.' = '.$self->{'unit'}->{'left'}.' - 1 '.
($self->{'type'} eq 'M' ?
' AND '.$self->{'multi'}.'= \''.$self->{'unit'}->{'multi'}.'\'' : '');
my $sth = $self -> {'DBI'} -> prepare($sql); $sth -> execute();
my $row = $sth -> fetchrow_hashref();
$sth -> finish();
if ($row) {$data->{'near'} = $$row{'lk'} - 1} else {return 0}
} elsif ($move eq 'down') {
my $sql = 'SELECT '.
$self->{'right'}.' AS rk '.
' FROM '.$self->{'table'}.
' WHERE '.
$self->{'left'}.' = '.$self->{'unit'}->{'right'}.' + 1'.
($self->{'type'} eq 'M' ?
' AND '.$self->{'multi'}.'= \''.$self->{'unit'}->{'multi'}.'\'' : '');
my $sth = $self->{'DBI'}->prepare($sql); $sth->execute();
my $row = $sth->fetchrow_hashref();
$sth -> finish();
if ($row) {$data->{'near'} = $$row{'rk'}} else {return 0}
}
# перебрасываем из объекта данные о перемещаемом узле
$data->{'left'} = $self->{'unit'}->{'left'};
$data->{'right'} = $self->{'unit'}->{'right'};
$data->{'level'} = $self->{'unit'}->{'level'};
# Так как работаем в перделах одного подчинения, то уровень не меняется
$data->{'level_new'} = $self->{'unit'}->{'level'};
$data->{'multi'} = $self->{'unit'}->{'multi'} || undef;
$self->{'unit'} = undef;
# перемещаем узел
&_move_unit($self, $data);
return 1
}
Вызов данного метода, как всегда:
...
my $unit = ... # Определяем идентификатор узла
...
use MyModule::NestedSets;
my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh};
$nested->set_unit_order(unit=>$unit, move=>'up');
...
или так:
...
my $unit = ... # Определяем идентификатор узла
...
use MyModule::NestedSets;
my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh};
$nested->select_unit($unit);
$nested->set_unit_order(move=>'down');
...
5. Вернемся к созданию объекта
В процессе, написания модуля, мы правили несколько раз процедуру new, теперь стоит посмотреть, что из нее получилось:
sub new {
# Получаем ссылку на переменную и входные параметры
my ($self, $common) = @_;
# Описываем переменную, как ссылку на хеш хешей
$self = {
id => 'id', # имя поля таблицы - идентификатор
left => 'left_key', # имя поля таблицы - левый ключ
right => 'right_key', # имя поля таблицы - правый ключ
level => 'level', # имя поля таблицы - уровень
multi => 'class', # имя поля таблицы - идентификатор дерева
table => undef, # имя таблицы
DBI => undef, # подключение к базе данных
type => 'N', # мультидерево или нет
order => 'B', # порядок вставки, перемещения
unit => { # текущий (выбранный) элемент
id => undef, # идентификатор элемента
left => undef, # левый ключ элемента
right => undef, # правый ключ элемента
level => undef, # уровень элемента
multi => undef, # идентификатор дерева элемента
},
};
# Обработка входных параметров
$self->{'type'} = $$common{'type'} && $$common{'type'} eq 'multi' ? 'M' : 'N';
$self->{'order'} = $$common{'order'} && $$common{'order'} eq 'top' ? 'T' : 'B';
$self->{'left'} = $$common{'left'} if $$common{'left'};
$self->{'right'} = $$common{'right'} if $$common{'right'};
$self->{'level'} = $$common{'level'} if $$common{'level'};
$self->{'multi'} = $$common{'multi'} if $$common{'multi'};
$self->{'table'} = $$common{'table'} if $$common{'table'};
$self->{'DBI'} = $$common{'DBI'} if $$common{'DBI'};
# "благословление" объекта на работу ;-)
bless $self;
return $self;
}
|