our
$VERSION
=
"0.47"
;
my
%all_params
;
sub
_constructor {
my
(
$class
,
%params
) =
@_
;
foreach
my
$param_name
(
qw( role_name name varref state )
) {
Carp::croak(
"$param_name is a required param"
)
unless
exists
$params
{
$param_name
};
}
$all_params
{
$params
{role_name}}->{
$params
{name}} =
bless
\
%params
,
$class
;
}
sub
new {
my
$class
=
shift
;
return
$class
->_constructor(
@_
,
state
=>
'unbound'
);
}
sub
TIESCALAR {
my
$class
=
shift
;
return
$class
->_constructor(
@_
,
state
=>
'bound'
);
}
sub
name {
shift
->{name} }
sub
role_name {
shift
->{role_name} }
sub
varref {
shift
->{varref} }
sub
state {
shift
->{state} }
sub
FETCH {
my
$self
=
shift
;
my
$param_name
=
$self
->name;
my
$role_instance
=
$self
->_search_for_invocant_role_instance();
unless
(
$role_instance
) {
Carp::confess(
"Role param '$param_name' is not bound to a value in this call frame"
);
}
my
$params
=
$role_instance
->role_params();
return
$params
->{
$param_name
};
}
sub
STORE {
my
$self
=
shift
;
my
$name
=
$self
->name;
Carp::croak(
"Role param '$name' is read-only"
);
}
sub
_search_for_invocant_role_instance {
my
$self
=
shift
;
local
$@;
for
(
my
$frame
= 1; ;
$frame
++) {
my
(
$role_package
,
$invocant
) =
do
{
my
@caller
=
caller
(
$frame
);
last
unless
$caller
[3];
my
(
$function_package
) =
$caller
[3] =~ m/^(.*)::\w+$/;
next
unless
$function_package
;
eval
{ (
$function_package
,
$DB::args
[0]) };
};
my
$invocant_class
= blessed(
$invocant
) || (!
ref
(
$invocant
) &&
$invocant
);
next
unless
$invocant_class
;
my
$role_instance
= UR::Role::Instance->get(
role_name
=>
$role_package
,
'class_name isa'
=>
$invocant_class
);
return
$role_instance
if
$role_instance
;
}
return
;
}
sub
clone_self {
return
$_
[0];
}
sub
param_names_for_role {
my
(
$class
,
$role_name
) =
@_
;
return
keys
(%{
$all_params
{
$role_name
} });
}
sub
replace_unbound_params_in_struct_with_values {
my
(
$class
,
$struct
,
@role_objects
) =
@_
;
my
%role_params
=
map
{
$_
->
role_name
=>
$_
->role_params }
@role_objects
;
my
$replacer
=
sub
{
my
$ref
=
shift
;
my
$self
=
$$ref
;
my
$role_params
=
$role_params
{
$self
->role_name};
$$ref
=
$role_params
->{
$self
->name};
my
$role_param_ref
=
$self
->varref;
unless
(
tied
(
$$role_param_ref
)) {
tie
$$role_param_ref
,
'UR::Role::Param'
,
name
=>
$self
->name,
role_name
=>
$self
->role_name,
varref
=>
$self
->varref;
}
};
_visit_params_with_values_in_struct(
$struct
,
$replacer
);
}
sub
_is_unbound_param {
my
$val
=
shift
;
return
(blessed(
$val
) &&
$val
->isa(__PACKAGE__) &&
$val
->state eq
'unbound'
);
}
sub
_visit_params_with_values_in_struct {
my
(
$struct
,
$cb
) =
@_
;
return
unless
my
$reftype
=
ref
(
$struct
);
if
(
$reftype
eq
'HASH'
) {
while
(
my
(
$key
,
$val
) =
each
%$struct
) {
if
(_is_unbound_param(
$val
)) {
$cb
->(\
$struct
->{
$key
});
}
else
{
_visit_params_with_values_in_struct(
$val
,
$cb
);
}
}
}
elsif
(
$reftype
eq
'ARRAY'
) {
for
(
my
$i
= 0;
$i
<
@$struct
;
$i
++) {
my
$val
=
$struct
->[
$i
];
if
(_is_unbound_param(
$val
)) {
$cb
->(\
$struct
->[
$i
]);
}
else
{
_visit_params_with_values_in_struct(
$val
,
$cb
);
}
}
}
elsif
(
$reftype
eq
'SCALAR'
) {
_visit_params_with_values_in_struct(
$struct
,
$cb
);
}
}
1;
=pod
=head1 NAME
UR::Role::Param - Role parameters as
package
variables
=head1 SYNOPSIS
our
$logging_object
: RoleParam(logging_obejct);
role ProjectNamespace::SomeParameterizedRole { };
sub
log
{
my
(
$self
,
$message
) =
@_
;
$logging_object
->
log
(
$message
);
}
my
$logger
= create_a_logging_object();
class ThingThatLogs {
roles
=> [ ProjectNamespace::SomeParameterizedRole->create(
logging_object
=>
$logger
) ],
};
=head1 DESCRIPTION
Roles can be configured by declaring variables
with
the C<RoleParam> attribute.
These variables acquire
values
by calling C<create()> on the role's name and
giving
values
for
all the role's parameters. More information about declaring
and using these parameters is described in the
"Parameterized Roles"
section of
L<UR::Role>.
When the variables are initially declared, their value is initialized to a
reference to a UR::Role::Param. This represents a placeholder value to be
filled in later. The value may be used in a role definition or in any
subroutine.
When the role is composed into a class, the placeholder
values
are replaced
with
the actual
values
given
in the C<create()> call on the role's name. The
original RoleParam variable is then
tied
to the UR::Role::Param class; it's
C<FETCH> method returns the proper value by searching the call stack
for
the
first method whose invocant class
has
composed the role where the FETCH
originated from. It returns the value
given
when
the role was composed
into the class.
These role param variables are
read
-only.
Each variable
with
the RoleParam attribute becomes a required argument
when
the role is instantiated .
=head1 SEE ALSO
L<UR::Role>, L<UR::Role::Prototype>, L<UR::Role::Instance>