如何访问应用我的 Moose 角色的模块的元类? [英] How can I access the meta class of the module my Moose role is being applied to?

查看:54
本文介绍了如何访问应用我的 Moose 角色的模块的元类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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;

推荐答案

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

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;

<小时>

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


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;

<小时>

附录 2:我进一步发现,如果角色被应用到一个对象实例,而不是一个类,那么 $target_meta 实际上是执行消费的对象的类:


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!

这篇关于如何访问应用我的 Moose 角色的模块的元类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆