————————package
Class::Meta::Attribute;
=head1 NAME
Class::Meta::Attribute - Class::Meta class attribute introspection
=head1 SYNOPSIS
# Assuming MyApp::Thingy was generated by Class::Meta.
my $class = MyApp::Thingy->my_class;
my $thingy = MyApp::Thingy->new;
print "\nAttributes:\n";
for my $attr ($class->attributes) {
print " o ", $attr->name, " => ", $attr->get($thingy), $/;
if ($attr->authz >= Class::Meta::SET && $attr->type eq 'string') {
$attr->get($thingy, 'hey there!');
print " Changed to: ", $attr->get($thingy) $/;
}
}
=head1 DESCRIPTION
An object of this class describes an attribute of a class created by
Class::Meta. It includes meta data such as the name of the attribute, its data
type, its accessibility, and whether or not a value is required. It also
provides methods to easily get and set the value of the attribute for a given
instance of the class.
Class::Meta::Attribute objects are created by Class::Meta; they are never
instantiated directly in client code. To access the attribute objects for a
Class::Meta-generated class, simply call its C<my_class()> method to retrieve
its Class::Meta::Class object, and then call the C<attributes()> method on the
Class::Meta::Class object.
=cut
##############################################################################
# Dependencies #
##############################################################################
use
strict;
##############################################################################
# Package Globals #
##############################################################################
our
$VERSION
=
'0.66'
;
##############################################################################
# Private Package Globals #
##############################################################################
my
%type_pkg_for
= (
map
( {
$_
=>
'Boolean'
}
qw(bool boolean)
),
map
( {
$_
=>
'Numeric'
}
qw(whole integer int decimal dec real float)
),
map
(
{
$_
=>
'Perl'
}
qw(scalar scalarref array arrayref hash hashref code coderef closure)
),
string
=>
'String'
,
);
##############################################################################
# Constructors #
##############################################################################
=head1 INTERFACE
=head2 Constructors
=head3 new
A protected method for constructing a Class::Meta::Attribute object. Do not
call this method directly; Call the
L<C<add_attribute()>|Class::Meta/"add_attribute"> method on a Class::Meta
object, instead.
=cut
sub
new {
my
$pkg
=
shift
;
my
$class
=
shift
;
# Check to make sure that only Class::Meta or a subclass is constructing a
# Class::Meta::Attribute object.
my
$caller
=
caller
;
Class::Meta->handle_error(
"Package '$caller' cannot create $pkg "
.
"objects"
)
unless
UNIVERSAL::isa(
$caller
,
'Class::Meta'
)
|| UNIVERSAL::isa(
$caller
, __PACKAGE__);
# Make sure we can get all the arguments.
$class
->handle_error(
"Odd number of parameters in call to new() when "
.
"named parameters were expected"
)
if
@_
% 2;
my
%p
=
@_
;
# Validate the name.
$class
->handle_error(
"Parameter 'name' is required in call to new()"
)
unless
$p
{name};
# Is this too paranoid?
$class
->handle_error(
"Attribute '$p{name}' is not a valid attribute "
.
"name -- only alphanumeric and '_' characters "
.
"allowed"
)
if
$p
{name} =~ /\W/;
# Grab the package name.
$p
{
package
} =
$class
->{
package
};
# Set the required and once attributes.
for
(
qw(required once)
) {
$p
{
$_
} =
$p
{
$_
} ? 1 : 0;
}
# Make sure the name hasn't already been used for another attribute
$class
->handle_error(
"Attribute '$p{name}' already exists in class '"
.
$class
->{attrs}{
$p
{name}}{
package
} .
"'"
)
if
!
delete
$p
{
override
} &&
exists
$class
->{attrs}{
$p
{name}};
# Check the view.
if
(
exists
$p
{view}) {
$p
{view} = Class::Meta::_str_to_const(
$p
{view});
$class
->handle_error(
"Not a valid view parameter: '$p{view}'"
)
unless
$p
{view} == Class::Meta::PUBLIC
or
$p
{view} == Class::Meta::PROTECTED
or
$p
{view} == Class::Meta::TRUSTED
or
$p
{view} == Class::Meta::PRIVATE;
}
else
{
# Make it public by default.
$p
{view} = Class::Meta::PUBLIC;
}
# Check the authorization level.
if
(
exists
$p
{authz}) {
$p
{authz} = Class::Meta::_str_to_const(
$p
{authz});
$class
->handle_error(
"Not a valid authz parameter: '$p{authz}'"
)
unless
$p
{authz} == Class::Meta::NONE
or
$p
{authz} == Class::Meta::READ
or
$p
{authz} == Class::Meta::WRITE
or
$p
{authz} == Class::Meta::RDWR;
}
else
{
# Make it read/write by default.
$p
{authz} = Class::Meta::RDWR;
}
# Check the creation constant.
if
(
exists
$p
{create}) {
$p
{create} = Class::Meta::_str_to_const(
$p
{create});
$class
->handle_error(
"Not a valid create parameter: '$p{create}'"
)
unless
$p
{create} == Class::Meta::NONE
or
$p
{create} == Class::Meta::GET
or
$p
{create} == Class::Meta::SET
or
$p
{create} == Class::Meta::GETSET;
}
else
{
# Rely on the authz setting by default.
$p
{create} =
$p
{authz};
}
# Check the context.
if
(
exists
$p
{context}) {
$p
{context} = Class::Meta::_str_to_const(
$p
{context});
$class
->handle_error(
"Not a valid context parameter: '$p{context}'"
)
unless
$p
{context} == Class::Meta::OBJECT
or
$p
{context} == Class::Meta::CLASS;
}
else
{
# Put it in object context by default.
$p
{context} = Class::Meta::OBJECT;
}
# Check the type.
$p
{type} =
delete
$p
{is}
if
exists
$p
{is};
$p
{type} ||=
$class
->default_type;
$class
->handle_error(
"No type specified for the '$p{name}' attribute"
)
unless
$p
{type};
unless
(
eval
{ Class::Meta::Type->new(
$p
{type}) } ) {
my
$pkg
=
$type_pkg_for
{
$p
{type} }
or
$class
->handle_error(
"Unknown type: '$p{type}'"
);
eval
"require Class::Meta::Types::$pkg"
;
$class
->handle_error(
"Unknown type: '$p{type}'"
)
if
$@;
"Class::Meta::Types::$pkg"
->
import
;
}
# Check the default.
if
(
exists
$p
{
default
}) {
# A code ref should be executed when the default is called.
$p
{_def_code} =
delete
$p
{
default
}
if
ref
$p
{
default
} eq
'CODE'
;
}
# Create and cache the attribute object.
$class
->{attrs}{
$p
{name}} =
bless
\
%p
,
ref
$pkg
||
$pkg
;
# Index its view.
push
@{
$class
->{all_attr_ord} },
$p
{name};
if
(
$p
{view} > Class::Meta::PRIVATE) {
push
@{
$class
->{prot_attr_ord}},
$p
{name}
unless
$p
{view} == Class::Meta::TRUSTED;
if
(
$p
{view} > Class::Meta::PROTECTED) {
push
@{
$class
->{trst_attr_ord}},
$p
{name};
push
@{
$class
->{attr_ord}},
$p
{name}
if
$p
{view} == Class::Meta::PUBLIC;
}
}
# Store a reference to the class object.
$p
{class} =
$class
;
# Let 'em have it.
return
$class
->{attrs}{
$p
{name}};
}
##############################################################################
# Instance Methods #
##############################################################################
=head2 Instance Methods
=head3 name
my $name = $attr->name;
Returns the name of the attribute.
=head3 type
my $type = $attr->type;
Returns the name of the attribute's data type. Typical values are "scalar",
"string", and "boolean". See L<Class::Meta|Class::Meta/"Data Types"> for a
complete list.
=head3 is
if ($attr->is('string')) {
# ...
}
A convenience method for C<< $attr->type eq $type >>.
=head3 desc
my $desc = $attr->desc;
Returns a description of the attribute.
=head3 label
my $label = $attr->label;
Returns a label for the attribute, suitable for use in a user interface. It is
distinguished from the attribute name, which functions to name the accessor
methods for the attribute.
=head3 required
my $req = $attr->required;
Indicates if the attribute is required to have a value.
=head3 once
my $once = $attr->once;
Indicates whether an attribute value can be set to a defined value only once.
=head3 package
my $package = $attr->package;
Returns the package name of the class that attribute is associated with.
=head3 view
my $view = $attr->view;
Returns the view of the attribute, reflecting its visibility. The possible
values are defined by the following constants:
=over 4
=item Class::Meta::PUBLIC
=item Class::Meta::PRIVATE
=item Class::Meta::TRUSTED
=item Class::Meta::PROTECTED
=back
=head3 context
my $context = $attr->context;
Returns the context of the attribute, essentially whether it is a class or
object attribute. The possible values are defined by the following constants:
=over 4
=item Class::Meta::CLASS
=item Class::Meta::OBJECT
=back
=head3 authz
my $authz = $attr->authz;
Returns the authorization for the attribute, which determines whether it can be
read or changed. The possible values are defined by the following constants:
=over 4
=item Class::Meta::READ
=item Class::Meta::WRITE
=item Class::Meta::RDWR
=item Class::Meta::NONE
=back
=head3 class
my $class = $attr->class;
Returns the Class::Meta::Class object that this attribute is associated
with. Note that this object will always represent the class in which the
attribute is defined, and I<not> any of its subclasses.
=cut
sub
name {
$_
[0]->{name} }
sub
type {
$_
[0]->{type} }
sub
desc {
$_
[0]->{desc} }
sub
label {
$_
[0]->{label} }
sub
required {
$_
[0]->{required} }
sub
once {
$_
[0]->{once} }
sub
package
{
$_
[0]->{
package
} }
sub
view {
$_
[0]->{view} }
sub
context {
$_
[0]->{context} }
sub
authz {
$_
[0]->{authz} }
sub
class {
$_
[0]->{class} }
sub
is {
$_
[0]->{type} eq
$_
[1] }
##############################################################################
=head3 default
my $default = $attr->default;
Returns the default value for a new instance of this attribute. Since the
default value can be determined dynamically, the value returned by
C<default()> may change on subsequent calls. It all depends on what was
passed for the C<default> parameter in the call to C<add_attribute()> on the
Class::Meta object that generated the class.
=cut
sub
default
{
if
(
my
$code
=
$_
[0]->{_def_code}) {
return
$code
->();
}
return
$_
[0]->{
default
};
}
##############################################################################
=head3 get
my $value = $attr->get($thingy);
This method calls the "get" accessor method on the object passed as the sole
argument and returns the value of the attribute for that object. Note that it
uses a C<goto> to execute the accessor, so the call to C<set()> itself
will not appear in a call stack trace.
=cut
sub
get {
my
$self
=
shift
;
my
$code
=
$self
->{_get} or
$self
->class->handle_error(
q{Cannot get attribute '}
,
$self
->name,
q{'}
);
goto
&$code
;
}
##############################################################################
=head3 set
$attr->set($thingy, $new_value);
This method calls the "set" accessor method on the object passed as the first
argument and passes any remaining arguments to assign a new value to the
attribute for that object. Note that it uses a C<goto> to execute the
accessor, so the call to C<set()> itself will not appear in a call stack
trace.
=cut
sub
set {
my
$self
=
shift
;
my
$code
=
$self
->{_set} or
$self
->class->handle_error(
q{Cannot set attribute '}
,
$self
->name,
q{'}
);
goto
&$code
;
}
##############################################################################
=head3 build
$attr->build($class);
This is a protected method, designed to be called only by the Class::Meta
class or a subclass of Class::Meta. It takes a single argument, the
Class::Meta::Class object for the class in which the attribute was defined,
and generates attribute accessors by calling out to the C<make_attr_get()> and
C<make_attr_set()> methods of Class::Meta::Type as appropriate for the
Class::Meta::Attribute object.
Although you should never call this method directly, subclasses of
Class::Meta::Constructor may need to override its behavior.
=cut
sub
build {
my
(
$self
,
$class
) =
@_
;
# Check to make sure that only Class::Meta or a subclass is building
# attribute accessors.
my
$caller
=
caller
;
$self
->class->handle_error(
"Package '$caller' cannot call "
.
ref
(
$self
) .
"->build"
)
unless
UNIVERSAL::isa(
$caller
,
'Class::Meta'
)
|| UNIVERSAL::isa(
$caller
, __PACKAGE__);
# Get the data type object and build any accessors.
my
$type
= Class::Meta::Type->new(
$self
->{type});
$self
->{type} =
$type
->key;
my
$create
=
delete
$self
->{create};
$type
->build(
$class
->{
package
},
$self
,
$create
)
if
$create
!= Class::Meta::NONE;
# Create the attribute object get code reference.
if
(
$self
->{authz} >= Class::Meta::READ) {
$self
->{_get} =
$type
->make_attr_get(
$self
);
}
# Create the attribute object set code reference.
if
(
$self
->{authz} >= Class::Meta::WRITE) {
$self
->{_set} =
$type
->make_attr_set(
$self
);
}
}
1;
__END__
=head1 SUPPORT
This module is stored in an open L<GitHub
repository|http://github.com/theory/class-meta/>. Feel free to fork and
contribute!
Please file bug reports via L<GitHub
Issues|http://github.com/theory/class-meta/issues/> or by sending mail to
L<bug-Class-Meta@rt.cpan.org|mailto:bug-Class-Meta@rt.cpan.org>.
=head1 AUTHOR
David E. Wheeler <david@justatheory.com>
=head1 SEE ALSO
Other classes of interest within the Class::Meta distribution include:
=over 4
=item L<Class::Meta|Class::Meta>
=item L<Class::Meta::Class|Class::Meta::Class>
=item L<Class::Meta::Method|Class::Meta::Method>
=item L<Class::Meta::Constructor|Class::Meta::Constructor>
=item L<Class::Meta::Type|Class::Meta::Type>
=back
=head1 COPYRIGHT AND LICENSE
Copyright (c) 2002-2011, David E. Wheeler. Some Rights Reserved.
This module is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut