如何访问我的 Moose 角色所应用的模块的元类?

发布于 2024-08-11 15:11:42 字数 1070 浏览 7 评论 0原文

我正在使用 Moose roles 在类中的某些访问器方法周围应用一些包装器行为。我想将此角色应用于多个模块,每个模块都有一组不同的属性,我想包装其访问器。有没有办法从角色内部访问正在应用的模块的元类?即像这样:

package My::Foo;
use Moose;
with 'My::Role::X';

has [ qw(attr1 attr2) ] => (
    is => 'rw', # ...
);

has 'fields' => (
    is => 'bare', isa => 'ArrayRef[Str]',
    default => sub { [qw(attr1 attr2) ] },
);
1;

package My::Role::X;
use Moose::Role;

# this should be a Moose::Meta::Class object
my $target_meta = '????';

# get Class::MOP::Attribute object out of the metaclass
my $fields_attr = $target_meta->find_attribute_by_name('fields');

# extract the value of this attribute - should be a coderef
my $fields_to_modify = $fields_attr->default;

# evaluate the coderef to get the arrayref
$fields_to_modify = &$fields_to_modify if ref $fields_to_modify eq 'CODE';

around $_ => sub {
    # ...
} for @$fields_to_modify;
1;

I'm using Moose roles to apply some wrapper behaviour around some accessor methods in a class. I want to apply this role to a number of modules, each of which have a different set of attributes whose accessors I want to wrap. Is there a way to access the meta class of the module being applied to, from within the role? i.e. something like this:

package My::Foo;
use Moose;
with 'My::Role::X';

has [ qw(attr1 attr2) ] => (
    is => 'rw', # ...
);

has 'fields' => (
    is => 'bare', isa => 'ArrayRef[Str]',
    default => sub { [qw(attr1 attr2) ] },
);
1;

package My::Role::X;
use Moose::Role;

# this should be a Moose::Meta::Class object
my $target_meta = '????';

# get Class::MOP::Attribute object out of the metaclass
my $fields_attr = $target_meta->find_attribute_by_name('fields');

# extract the value of this attribute - should be a coderef
my $fields_to_modify = $fields_attr->default;

# evaluate the coderef to get the arrayref
$fields_to_modify = &$fields_to_modify if ref $fields_to_modify eq 'CODE';

around $_ => sub {
    # ...
} for @$fields_to_modify;
1;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

如梦 2024-08-18 15:11:42

看起来 MooseX::Role::Parameterized 可以解决问题:

普通角色可能要求其使用者具有特定的方法名称列表。由于参数化角色可以直接访问其使用者,因此您可以检查它并在使用者不满足您的需求时抛出错误。 (链接)

角色专业化的详细信息是避免班级被扩大;它甚至不需要传递任何参数,它只需要知道将哪些参数(要包装的字段列表)传递给角色即可。唯一的关键是角色必须在类上定义相关属性之后使用。

因此,所使用的类和角色的定义如下:

package My::Foo;
use Moose;

my @fields = qw(attr1 attr2);

has \@fields => (
    is => 'rw', # ...
);

has 'fields' => (
    is => 'bare', isa => 'ArrayRef[Str]',
    default => sub { \@fields },
);

with 'My::Role::X' => {};

1;

package My::Role::X;
use MooseX::Role::Parameterized;

role {
    my $p = shift;

    my %args = @_;

    # this should be a Moose::Meta::Class object
    my $target_meta = $args{consumer};

    # get Class::MOP::Attribute object out of the metaclass
    my $fields_attr = $target_meta->find_attribute_by_name('fields');

    # extract the value of this attribute - should be a coderef
    my $fields_to_modify = $fields_attr->default;

    # evaluate the coderef to get the arrayref
    $fields_to_modify = &$fields_to_modify if ref $fields_to_modify eq 'CODE';

    around $_ => sub {
        # ...
    } for @$fields_to_modify;
};

1;

附录:我发现,如果一个参数化角色使用另一个参数化角色,则嵌套角色中的 $target_meta 将实际上是父角色的元类(isa MooseX::Role::Parameterized::Meta::Role::Parameterized),而不是消费类的元类(isa <代码>Moose::Meta::Class)。为了派生正确的元类,您需要显式地将其作为参数传递。我已将此作为“最佳实践”模板添加到我的所有参数化角色中:

package MyApp::Role::SomeRole;

use MooseX::Role::Parameterized;

# because we are used by an earlier role, meta is not actually the meta of the
# consumer, but of the higher-level parameterized role.
parameter metaclass => (
    is => 'ro', isa => 'Moose::Meta::Class',
    required => 1,
);

# ... other parameters here...

role {
    my $params = shift;
    my %args = @_;

    # isa a Moose::Meta::Class
    my $meta = $params->metaclass;

    # class name of what is consuming us, om nom nom
    my $consumer = $meta->name;

    # ... code here...

}; # end role
no Moose::Role;
1;

附录 2:我进一步发现,如果将角色应用于对象实例,与类相反,那么角色中的 $target_meta 实际上将是执行消费的对象的类:

package main;
use My::Foo;
use Moose::Util;

my $foo = My::Foo->new;
Moose::Util::apply_all_roles($foo, MyApp::Role::SomeRole, { parameter => 'value' });

package MyApp::Role::SomeRole;
use MooseX::Role::Parameterized;
# ... use same code as above (in addendum 1):

role {
    my $meta = $args{consumer};
    my $consumer = $meta->name;     # fail! My::Foo does not implement the 'name' method

因此,在参数化角色开始时提取元类时,此代码是必要的:

role {
    my $params = shift;
    my %args = @_;

    # could be a Moose::Meta::Class, or the object consuming us
    my $meta = $args{consumer};
    $meta = $meta->meta if not $meta->isa('Moose::Meta::Class');   # <-- important!

It looks like MooseX::Role::Parameterized will do the trick:

Ordinary roles can require that its consumers have a particular list of method names. Since parameterized roles have direct access to its consumer, you can inspect it and throw errors if the consumer does not meet your needs. (link)

The details of the role specialization is kept from the class being augmented; it doesn't even need to pass any parameters all it needs to know is what parameters (the list of fields to wrap) to pass to the role. The only key is that the role must be used after the relevant attributes have been defined on the class.

Therefore, the consumed class and the role become defined like so:

package My::Foo;
use Moose;

my @fields = qw(attr1 attr2);

has \@fields => (
    is => 'rw', # ...
);

has 'fields' => (
    is => 'bare', isa => 'ArrayRef[Str]',
    default => sub { \@fields },
);

with 'My::Role::X' => {};

1;

package My::Role::X;
use MooseX::Role::Parameterized;

role {
    my $p = shift;

    my %args = @_;

    # this should be a Moose::Meta::Class object
    my $target_meta = $args{consumer};

    # get Class::MOP::Attribute object out of the metaclass
    my $fields_attr = $target_meta->find_attribute_by_name('fields');

    # extract the value of this attribute - should be a coderef
    my $fields_to_modify = $fields_attr->default;

    # evaluate the coderef to get the arrayref
    $fields_to_modify = &$fields_to_modify if ref $fields_to_modify eq 'CODE';

    around $_ => sub {
        # ...
    } for @$fields_to_modify;
};

1;

Addendum: I have discovered that if a parameterized role consumes another parameterized role, then $target_meta in the nested role will actually be the meta-class of the parent role (isa MooseX::Role::Parameterized::Meta::Role::Parameterized), rather than the meta-class of the consuming class (isa Moose::Meta::Class). In order for the proper meta-class to be derived, you need to explicitly pass it as a parameter. I have added this to all my parameterized roles as a "best practice" template:

package MyApp::Role::SomeRole;

use MooseX::Role::Parameterized;

# because we are used by an earlier role, meta is not actually the meta of the
# consumer, but of the higher-level parameterized role.
parameter metaclass => (
    is => 'ro', isa => 'Moose::Meta::Class',
    required => 1,
);

# ... other parameters here...

role {
    my $params = shift;
    my %args = @_;

    # isa a Moose::Meta::Class
    my $meta = $params->metaclass;

    # class name of what is consuming us, om nom nom
    my $consumer = $meta->name;

    # ... code here...

}; # end role
no Moose::Role;
1;

Addendum 2: I have further discovered that if the role is being applied to an object instance, as opposed to a class, then $target_meta in the role will actually be the class of the object doing the consuming:

package main;
use My::Foo;
use Moose::Util;

my $foo = My::Foo->new;
Moose::Util::apply_all_roles($foo, MyApp::Role::SomeRole, { parameter => 'value' });

package MyApp::Role::SomeRole;
use MooseX::Role::Parameterized;
# ... use same code as above (in addendum 1):

role {
    my $meta = $args{consumer};
    my $consumer = $meta->name;     # fail! My::Foo does not implement the 'name' method

Therefore, this code is necessary when extracting the meta-class at the start of the parameterized role:

role {
    my $params = shift;
    my %args = @_;

    # could be a Moose::Meta::Class, or the object consuming us
    my $meta = $args{consumer};
    $meta = $meta->meta if not $meta->isa('Moose::Meta::Class');   # <-- important!
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文