Применение ролей во время выполнения из модификатора метода


У меня есть роль, которая предоставляет модификатор метода так:

package MyApp::Role::MenuExtensionRed;

use Moose::Role;
requires 'BuildMenu';

after 'BuildMenu' => sub {...};

Из-за требований в другом месте мне нужно применить ряд ролей во время выполнения, например:

package MyApp::MainMenu

before 'BuildMenu' => sub {
    my ( $self, $args ) = @_;

    my $roles  = $args->{menu_extensions};
    apply_all_roles($self,@$roles);
};

sub BuildMenu {...}
Однако модификатор метода "after" никогда не вызывается. Очевидно, я нарушаю какое-то правило, но я действительно хотел бы понять, почему это не работает!

Это работает, если вместо применения ролей перед 'BuildMenu', я применяю их в методе сборки. Но, к сожалению, мой список ролей menu_extension не является в данный момент он свободен, так что мне придется подождать.

Любые альтернативные решения будут оценены!

EDIT интересно, что after 'BuildMenu' вызывается, но только при последующих вызовах к BuildMenu. Таким образом, модификаторы метода не могут быть изменены изнутри другого из его собственных модификаторов. Является ли это ожидаемым поведением? Есть ли способ добавить в" список " модификаторов во время выполнения?

1 4

1 ответ:

Это побочный эффект того, как его реализовали.

Когда вы используете модификатор метода, они в основном заменяют существующий метод новым, и новый метод содержит ссылку на старый.

Итак, когда вы видите

before foo => sub {
}

Что происходит во время составления роли:

my $orig = *package::foo;
*package::foo = sub {
     $beforetrigger->( @args );
     return $orig->( @args );
}

Более или менее.

Итак, представьте, что у вас есть 2 подмена," A", версия BuildMenu, которая вызывается перед применением роли, и" B", версия BuildMenu, которая вызывается после применения роли.

Итак, что происходит, ваш порядок вызова выглядит следующим образом:

First Call ->
   A ->  
    before ->
         apply roles ->
           replace A with B
    <-- return from before
    A body   ->
    <-- return from A Body
Subsequent Call 
   B -> something

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

 around foo => sub { 
    my ( $orig, $self , @args ) = @_;
    # apply role to $self here
    # this gets the sub that will be resolved *after* the role is applied
    my $wrapped_sub = $self->can('foo');
    my $rval = $wrapped_sub->( $self, @args );      
    return $wrapped_sub->( $self, @args );
 }

Обратите внимание, что есть потенциал для того, чтобы этот код имел забавную вещь, где $wrapped_sub-это sub, который я только что написал, чтобы применить роли, ... Я еще не выяснил, что именно там произойдет, но вам, возможно, придется поставить некоторые условия, чтобы предотвратить собственн-призвание-суб смерти петля от происходить.

Но суть проблемы по существу сводится к тому, что вы не можете заменить суб, который выполняется, на новый суб, из самого Суба, и ожидать, что он будет автоматически цепляться, потому что заменяемый суб не осознает, что он заменяется, и потому что "модификаторы метода" сводятся к замене субов новыми, которые цепляются к старым=)