На первой полосе | Авто (мир автомобилей) | Hi-Tech | Деньги. Инвестиции. Кредиты | Работа и карьера | Семья и дом | Спорт, туризм и отдых | Я ль на свете всех милее ? | Мужикам всех стран... | Недвижимость | Наука и образование | Исследования и обзоры | Странности и причуды | ТОПы (супер-рейтинг) | С юмором по жизни...
01.03.2006 Дерево каталогов Nested Sets (вложенные множества) ч.2 (1)В предыдущей статье мы рассмотрели теорию управления Nested Sets. Теперь попробуем собрать на её основе модуль Perl для работы. Для начала, определим, сам объект: package MyModule::NestedSets;
Где:
Пока все тривиально и просто, в объекте описаны имена полей и таблицы, в которой хранится наше дерево каталогов. Теперь нужно определить какие методы мы будем применять к объекту.
Так же, существует реальная потребность в том, что бы хранить несколько деревьев в одной таблице (далее по тексту: "мультидерево"). Например, если существует два и более раздельных каталогов товаров. Для определения того, одно или несколько разных деревьев в таблице добавим в наш объект еще два свойства: ...
Где:
Объявление объекта можно производить так: ...
Или, дабы упростить объявление: ...
Но при этом в процедуре - new модуля, нужно дополнительно обработать данные: sub new {
Несмотря, на то, что возможно переопределение имен полей таблицы (правый - левый ключ, идентификатор узла и идентификатор дерева), я стараюсь использовать одни и те же имена для всех таблиц, что бы не запутаться, тогда определять, по сути, нужно будет только имя таблицы. Сама таблица будет выглядеть так: CREATE TABLE `catalog_category` (
Соответственно, если в таблице будет только лишь одно дерево, то поле class - не нужно. Теперь можно перейти непосредственно к методам нашего объекта: 1. Создание узла Как показывает практика, иногда требуется создавать узел в начале списка, а иногда - в конце. Причем данный параметр может распространяться как на все дерево, так и непосредственно только на конкретную операцию создания (перемещения). Поэтому добавим еще одно свойство объекта, которое мы будем определять во время его объявления, а так же сделаем возможность указывать данный параметр, во время операции. Изменяем процедуру new модуля: sub new {
... $self = { ... order => 'B', # T - (top) начало списка, B - (bottom) конец списка }; ... $self->{'order'} = $$common{'order'} && $$common{'order'} eq 'top' ? 'T' : 'B'; ... } Для того, что бы создать узел, нам нужны следующие данные:
Действия, которые мы должны произвести во время создания:
sub insert_unit {
# Получаем объект, идентификатор родителя и идентификатор дерева my ($self, %common)= @_; # Инициализируем идентификатор дерева my $catalog = $common{'tree'} || 1; # Инициализируем идентификатор родителя my $under = $common{'under'} || 'root'; # Определяем порядок создания (место в списке) my $order = $common{'order'} || undef; # Объявляем локальные переменные my ($key, $level); # Если родитель корень дерева if ($under eq 'root') { # если вставка в конец списка левый ключ создаваемого выбирается как # максимальный правый ключ дерева + 1, уровень узла - 1 if (($order && $order eq 'top') || ($self->{'order'} eq 'T')) { $level = 1; $key = 1 } else { my $sql = 'SELECT MAX('.$self->{'right'}.') + 1 FROM '.$self->{'table'}. ($self->{'type'} eq 'M' ? ' WHERE '.$self->{'multi'}.'= \''.$catalog.'\'' : ''); my $sth = $self->{'DBI'}->prepare($sql); $sth->execute(); $key = $sth->fetchrow_arrayref()->[0]; $sth->finish(); $level = 1; $key = $key || 1 } # Если родитель определен, то левый ключ создаваемого узла будет равным # правому ключу родительского узла, уровень - родительский + 1 } else { my $sql = 'SELECT '.$self->{'right'}.', '.$self->{'left'}.', '.$self->{'level'}. ($self->{'type'} eq 'M' ? ', '.$self->{'multi'} : ''). ' FROM '.$self->{'table'}.' WHERE '.$self->{'id'}.' = \''.$under.'\''; my $sth = $self->{'DBI'}->prepare($sql); $sth->execute(); my $row = $sth->fetchrow_arrayref(); $sth->finish(); $key = ($order && $order eq 'top') || ($self->{'order'} eq 'T') ? $$row[1] + 1: $$row[0]; $level = $$row[2] + 1; # Если у нас мультидерево, то переопределяем идентификатор дерева # относительно родительского узла $catalog = $$row[3] || undef; } # Обновляем ключи дерева для создания пустого промежутка $self->{'DBI'}->do('UPDATE '.$self->{'table'}.' SET '. $self->{'right'}.' = '.$self->{'right'}.' + 2, '. $self->{'left'}.' = IF('.$self->{'left'}.' >= '.$key.', '.$self->{'left'}. ' + 2, '.$self->{'left'}.') WHERE '.$self->{'right'}.' >= '.$key. ($self->{'type'} eq 'M' ? ' AND '.$self->{'multi'}.'= \''.$catalog.'\'' : '')); # Создаем новый узел $self->{'DBI'}->do('INSERT INTO '.$self->{'table'}.' SET '. $self->{'left'}.' = '.$key.', '.$self->{'right'}.' = '.$key.' + 1, '. $self->{'level'}.' = '.$level. ($self->{'type'} eq 'M' ? ', '.$self->{'multi'}.'= \''.$catalog.'\'' : '')); # Получаем идентификатор созданного узла и возвращаем его в качестве результата my $sth = $self->{'DBI'}->prepare('SELECT LAST_INSERT_ID()'); $sth->execute(); my $id = $sth->fetchrow_arrayref()->[0]; $sth->finish(); return $id } Вызов данного метода производится так: ...
my $under = ... ; # Определяем родителя my $tree = ... ; # Определяем идентификатор дерева ... use MyModule::NestedSets; my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh}; my $new_unit = $nested->insert_unit(under=>$under, tree=>$tree, order=>'top'); ... 2. Определение узла Все дальнейшие действия мы производим уже над существующими узлами. В большинстве случаев мы обычно знаем, только лишь идентификатор редактируемого узла, но этих данных мало, поэтому, создадим метод определения узла: sub select_unit {
# Получаем объект, идентификатор узла my $self = shift; $self->{'unit'}->{'id'} = shift; # Производим выборку данных узла* my $sql = 'SELECT '.$self->{'left'}.' AS lk, '. $self->{'right'}.' AS rk, '. $self->{'level'}.' AS lv '. ($self->{'type'} eq 'M' ? ', '.$self->{'multi'}.' AS cl' : ''). ' FROM '.$self->{'table'}. ' WHERE '.$self->{'id'}.' = \''.$self->{'unit'}->{'id'}.'\''; my $sth = $self->{'DBI'}->prepare($sql); $sth->execute(); my $row = $sth -> fetchrow_hashref(); $sth -> finish(); # Если узел существует, то передаем данные в объект if ($row) { $self->{'unit'}->{'left'} = $row->{'lk'}; $self->{'unit'}->{'right'} = $row->{'rk'}; $self->{'unit'}->{'level'} = $row->{'lv'}; $self->{'unit'}->{'multi'} = $row->{'cl'} if $row->{'cl'}; return $self } else {croak("NestedSets failed: Your cann't select this unit, because unit is not exist!!!")} } Хочу обратить внимание на то, что всем полям при выборке мы объявляем псевдонимы, потому как имена полей в таблице могут быт разные. Полученные данные, мы сохраняем в тот же объект $self, поэтому, добавим в описание объекта дополнительные свойства: sub new {
... $self = { ... unit => { id => undef, left => undef, right => undef, level => undef, multi => undef, }, }; ... } Вызов данного метода производится так: ...
my $unit = ... # Определяем идентификатор узла ... use MyModule::NestedSets; my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh}; $nested->select_unit($unit); ... 3. Удаление узла Во время удаления узла нам нужны данные только удаляемого узла, для определения которых мы воспользуемся вышеописанной процедурой. Действия, которые мы будем производить:
sub delete_unit {
# Получаем данные: объект и идентификатор удаляемого узла my ($self, $unit) = @_; # получаем параметры узла if ($unit) {$self = &select_unit($self, $unit)} elsif (!$self->{'unit'}->{'id'}) {croak("NestedSets failed: Your must first select unit, for detete it!!!")} # Определяем смещение ключей после удаления my $skew = $self->{'unit'}->{'right'} - $self->{'unit'}->{'left'} + 1; # Удаляем узел $self->{'DBI'}->do('DELETE FROM '.$self->{'table'}.' WHERE '. $self->{'left'}.' >= '.$self->{'unit'}->{'left'}. ' AND '.$self->{'right'}.' <= '.$self->{'unit'}->{'right'}. ($self->{'type'} eq 'M' ? ' AND '.$self->{'multi'}.'= \''.$self->{'unit'}->{'multi'}.'\'' : '') ); # Обновляем ключи дерева относительно смещения $self->{'DBI'}->do('UPDATE '.$self->{'table'}. ' SET '. $self->{'left'}.' = IF('.$self->{'left'}.' > '.$self->{'unit'}->{'left'}.', '.$self->{'left'}.' - '.$skew.', '.$self->{'left'}.'), '. $self->{'right'}.' = '.$self->{'right'}.' - '.$skew. ' WHERE '. $self->{'right'}.' > '.$self->{'unit'}->{'right'}.' AND '. ($self->{'type'} eq 'M' ? $self->{'multi'}.'= \''.$self->{'unit_select'}->{'multi'}.'\'' : '') ); return 1 } Вызов данного метода производится так: ...
my $unit = ... # Определяем идентификатор узла ... use MyModule::NestedSets; my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh}; $nested->delete_unit($unit); ... или так: ...
my $unit = ... # Определяем идентификатор узла ... use MyModule::NestedSets; my $nested = new MyModule::NestedSets {table=>'catalog_category', type=>'M', DBI=>$dbh}; $nested->select_unit($unit); $nested->delete_unit; ...
|