Curtis "Ovid" Poe

NAME

Class::Meta::Declare - Deprecated in favor of Class::Meta::Express

VERSION

Version 0.04

SYNOPSIS

This was a first attempt at making a saner interface for Class::Meta. It is nicer, but Class::Meta::Express is nicer still. Go use that one.

 package MyApp::Thingy;
 use Class::Meta::Declare ':all';
 use Data::UUID;

 Class::Meta::Declare->new(
     meta       => [
         key       => 'thingy',
         accessors => $ACC_SEMI_AFFORDANCE,
     ],
     attributes => [
         pi => {
             context => $CTXT_CLASS,
             authz   => $AUTHZ_READ,
             default => 3.1415927,
         },
         id => {
             authz   => $AUTHZ_READ,
             type    => $TYPE_STRING,
             default => sub { Data::UUID->new->create_str },
         },
         name => {
             required => 1,
             type     => $TYPE_STRING,
             default  => 'No Name Supplied',
         },
         age => { type => $TYPE_INTEGER, },
     ],
     methods => [
         some_method => {
             view => $VIEW_PUBLIC,
             code => sub {
                 my $self = shift;
                 return [ reverse @_ ];
             },
         }
     ]
 );

 my $object = MyApp::Thingy->new;
 print MyApp::Thingy->pi;  # prints 3.1415927
 print $object->name;      # prints "No Name Supplied';
 $object->set_name("bob");
 print $object->name;      # prints "bob"

DESCRIPTION

This class provides an alternate interface for Class::Meta.

Class::Meta is a useful module which allows one to create Perl classes which support introspection (also known as reflection). Typically Perl classes, when created, don't supply a lot of metadata. Imported helper functions show up when you call $object->can($method). Private, protected and trusted methods are not readily supported. Fetching a list of attributes or methods is a haphazard affair. Class::Meta overcomes these shortcomings by building the classes for you and allowing you to fetch a class object:

  my $class_object = $object->my_class;

  foreach my $attribute ( $class_object->attributes ) {
      print $attribute->name, "\n";
  }
  foreach my $method ( $class_object->methods ) {
      print $method->name, "\n";
  }

If you've set up your class correctly, these properties are now easy to discover.

Unfortunately, many find the Class::Meta interface to be a bit clumsy. As an alternative, Class::Meta::Declare allows you to declare your entire class in a single argument list to the constructor and have the class built for you automatically. Further, reasonable defaults are provided for just about everything.

IMPORTANT: You want this class or Class::Meta if you need an introspection API for your classes. If you do not need introspection or dynamic class generation, these modules are overkill.

COMPARISON TO CLASS::META

Consider the Class::Meta::Declare example from the SYNOPSIS:

 package MyApp::Thingy;
 use Class::Meta::Declare ':all';
 use Data::UUID;

 Class::Meta::Declare->new(
     meta       => [
        key       => 'thingy',
        accessors => $ACC_SEMI_AFFORDANCE,
     ],
     attributes => [
         pi => {
             context => $CTXT_CLASS,
             authz   => $AUTHZ_READ,
             default => 3.1415927,
         },
         id => {
             authz   => $AUTHZ_READ,
             type    => $TYPE_INTEGER,
             default => sub { Data::UUID->new->create_str },
         },
         name => {
             required => 1,
             type     => $TYPE_STRING,
             default  => 'No Name Supplied',
         },
         age => { type => $TYPE_INTEGER, },
     ],
     methods => [
         some_method => {
             view => $VIEW_PUBLIC,
             code => sub {
                 my $self = shift;
                 return [ reverse @_ ];
             },
         }
     ]
 );

Here's the equivalent Class::Meta code:

 package MyApp::Thingy;
 use Class::Meta;
 use Class::Meta::Types::String 'semi-affordance';
 use Class::Meta::Types::Numeric 'semi-affordance';
 use Data::UUID;

 my $cm = Class::Meta->new( key => 'thingy' );

 $cm->add_constructor(
     name   => 'new',
     create => 1,
 );

 $cm->add_attribute(
     name    => 'pi',
     context => Class::Meta::CLASS,
     authz   => Class::Meta::READ,
     type    => 'whole',
     default => 3.1415927,
 );

 $cm->add_attribute(
     name    => 'id',
     authz   => Class::Meta::READ,
     type    => 'integer',
     default => sub { Data::UUID->new->create_str },
 );

 $cm->add_attribute(
     name     => 'name',
     required => 1,
     type     => 'string',
     default  => 'No Name Supplied',
 );

 $cm->add_attribute(
     name => 'age',
     type => 'integer',
 );

 sub some_method {
     my $self = shift;
     return [ reverse @_ ];
 }

 $cm->add_method(
     name => 'some_method',
     view => Class::Meta::PUBLIC,
 );

 $cm->build;

As you can see, the Class::Meta code is longer. The larger and more complicated the class, the longer it gets. Class::Meta::Declare offers the following advantages:

  • Shorter code

  • Compile-time failures for many mistyped attribute values

  • Less duplication of information (for example, see add_method)

  • Helper classes for included Class::Meta types are autoloaded

  • Sensible defaults for many entries

CLASS METHODS

new

  Class::Meta::Declare->new(%options);

The new method allows you to build an entire class, with reflective capabilities, just like Class::Meta. However, the syntax is shorter, hopefully clearer, and it builds everything in one go.

See CONSTRUCTOR OPTIONS for details on %options.

create

  my $declare = Class::Meta::Declare->create(\%options);
  my $class_meta = $declare->cm;
  # more Class::Meta stuff
  $class_meta->build;

This constructor is exactly the same as new except it does not call Class::Meta's build method. Use this constructor if you have more stuff you need to do prior to build being called.

INSTANCE METHODS

cm

 my $cm = $declare->cm;

Returns the Class::Meta object used to build the the class.

installed_types

 my @types = $declare->installed_types;
 if ($declare->installed_types('Class::Meta::Type::Numeric')) { ... }

Returns a list of data types used. If passed a data type, returns a boolean value indicating whether or not that type was used.

package

  my $package = $declare->package;

Returns the package for which the Class::Meta code was declared.

CONSTRUCTOR OPTIONS

The constructor takes an even-sized list of name/value declarations. Each name should be one of meta, constructors, attributes or methods. Each declaration should be an array reference of with key/value pairs in it (in other words, it's like a hashref but because it's in an array reference, we preserve the element order). Each key is optional, but supplying no keys pretty much means you have an empty class (though you will get a default constructor).

The following lists the key/value options for each declaration.

meta

Note that all keys for meta are optional.

  • key

    This specifies the "class key" underwhich you may fetch a new instance of a class object:

     my $class_object = Class::Meta->for_key($key);

    See Class::Meta's "for_key".

  • package

    Building a class assumes the class is to be built in the current package. You may override this with a package parameter.

     meta => [
       key     => 'foo',
       package => 'Foo',
     ]
  • accessors

    This key specifies the getter/setter style which will be built for attributes. Perl-style getter/setters look like this:

     my $name = $object->name;
     $object->name('Bob');

    You may also specify "semi-affordance" style accessors with $ACC_SEMI_AFFORDANCE:

     my $name = $object->name;
     $object->set_name('Bob');

    You may also specify "affordance" style accessors with $ACC_AFFORDANCE:

     my $name = $object->get_name;
     $object->set_name('Bob');

    This meta declaration thus might look like this:

     meta => [
       accessors => $ACC_SEMI_AFFORDANCE
     ]

    Note that the accessors parameter has no value on data types not supplied by Class::Meta unless they have been written to recognize them.

  • use

    By default, we assume that Class::Meta is the build class. If you have subclassed Class::Meta (or done something really bizarre like creating an alternative with an identical interface), you may specify that class with the use key:

     meta => [
       use => "Class::Meta::Subclass",
     ]

    Note that Class::Meta::Declare is an alternate interface, not a subclass of Class::Meta.

meta defaults

  • accessors

    $ACC_PERL

  • key

    Defaults to value of package key.

  • package

    Calling package.

  • use

    Class::Meta

constructors

By default, a new constructor is created for you. If you pass a constructors declaration, the default constructor will not be built and all constructor creation will be up to you.

Each constructor must have a key which specifies the name of the constructor and point to a hashref containing additional information about the constructor. An empty hashref will simply create a constructor with the given name, so the default constructor which is provided by Class::Meta::Declare in the absense of a constructors declaration is simply:

 constructors => [
    new => {}
 ]

The values of the hashref should match the values identified in the Class::Meta "add_constructor" documentation. name is not required (and will be ignored if supplied) as name is taken from the hashref key. view should be on of the values listed in the EXPORT ":view" section of this documentation.

The actual body of the constructor, if supplied, should be provided with the code key.

So to create factory constructor, one might do this (the following example assumes that the two factory classes listed are subclasses of the current class):

 package MyClass;
 use Class::Meta::Declare;
 Class::Meta::Declare->new(
   constructors => [
     new     => {}, # we can have multiple constructors
     factory => {
       view => $VIEW_PUBLIC, # optional as this is the default
       code => sub {
         my ($class, $target) = @_;
         $class = $target eq 'foo'
           ? 'Subclass::Foo'
           : 'Subclass::Bar';
         return bless {}, $class;
       }
     }
   ]

And later you'll be able to do this:

 my $object = MyClass->new;
 print ref $object; # MyClass

 $object = MyClass->factory('foo');
 print ref $object; # Subclass::Foo

constructors defaults:

  • view

    $VIEW_PUBLIC.

  • create

    If code is provided, false, otherwise true.

    Note that if you supply a create slot, its value will be ignored in favor of the "default" create value.

attributes

Each attribute must have a key which specifies the name of the attribute and point to a hashref containing additional information about the attribute. An empty hashref will create a simple scalar attribute with the given name, so a basic getter/setter with no validation is simply:

 attributes => [
    some_attribute => {}
 ]

The values of the hashref should match the values identified in the Class::Meta "add_attribute" documentation. name is not required (and will be ignored if supplied) as name is taken from the hashref key. view should be on of the values listed in the EXPORT ":view" section of this documentation.

The type should be one of the datatypes specified in the EXPORT ":type" section. Note that unlike Class::Meta, you do not have to load the type class. Class::Meta::Declare will infer the type class from the type you provide and handle this for you.

The authz and create values should be one of their corresponding values in the EXPORT section of this document.

The context key indicates whether this is a class or instance attribute. It's value should be either $CTXT_CLASS or $CTXT_OBJECT.

attributes defaults:

  • name

    Set to the value of the "key" for the attribute:

     rank => { # name will be set to 'rank'
       default => 'private',
     }
  • type

    $TYPE_SCALAR.

  • required

    False.

  • once

    False.

  • label

    None.

  • desc

    None.

  • view

    $VIEW_PUBLIC.

  • authz

    $AUTHZ_RDWR.

  • create

    Value corresponding to value in authz slot.

  • context

    $CTXT_OBJECT.

  • default

    None (Ironic, eh?)

  • override

    False.

Custom Accessors

If you wish to provide custom attribute accessors, the actual body of the accessor should be provided with the code key. If this is done, the create value will automatically be set to $CREATE_NONE. This tells Class::Meta to not create attribute accessor for you, but to use the code you have supplied.

There are two ways to create custom attribute code depending on the accessor style you have chosen. If you are using regular "perl style" accessors (the default), then code should point to a code reference or an anonymous sub:

 password => { # insecure code for demonstration purposes only
   code => sub {
     my $self = shift;
     return $self->{password} unless @_;
     my $password = shift;
     if (length $password < 5) {
       croak "Password too short";
     }
     $self->{password} = $password;
     return $self;
   }
 }

However, if you are using $ACC_SEMI_AFFORDANCE or $ACC_AFFORDANCE style accessors, then you'll have separate get and set methods. code should then point to a hash reference with get and set as the keys and their values pointing to their corresponding methods.

 meta => [
   accessors => $ACC_SEMI_AFFORDANCE,
 ],
 attributes => [
   password => { # insecure code for demonstration purposes only
     code => {
       get => sub { shift->{password} },
       set => sub {
         my ($self, $password) = @_;
         if (length $password < 5) {
           croak "Password too short";
         }
         $self->{password} = $password;
         return $self;
       }
     }
   }
 ]

For the code above, you may then access the attribute via $object->password and $object->set_password($password).

Custom Types

You may find the built-in list of types insufficient for your needs. For example, you may wish to create an accessor which only accepts types of class Customer. In this case, Customer should be a Class::Meta or Class::Meta::Declare class and should be loaded prior to new being called. type should then point to the Customer key.

 Class::Meta::Declare->new(
     meta => [
        key     => 'customer',
        package => 'Customer',
     ],
     @customer_attributes,
     @customer_methods
 );

And later:

 Class::Meta::Declare->new(
     meta => [
         key     => 'some_key',
         package => 'Some::Package',
     ],
     attributes => [
         cust => {
             type => 'customer',
         }
     ]
 );

methods

Each method must have a key which specifies the name of the method and point to a hashref containing additional information about the method. Each hashref should contain, at minimum, a code key which points to a subref of anonymous subroutine which defines the method:

 methods => [
   reverse_name => sub {
     my $self = shift;
     return scalar reverse $self->name;
   }
 ]

The values of the hashref should match the values identified in the Class::Meta "add_method" documentation. name is not required (and will be ignored if supplied) as name is taken from the hashref key. view should be on of the values listed in the EXPORT ":view" section of this documentation.

The context key indicates whether this is a class or instance method. It's value should be either $CTXT_CLASS or $CTXT_OBJECT. The default is $CTXT_OBJECT.

The actual body of the method, if supplied, should be provided with the code key. If it's not supplied, it is assumed the the method will still be available at runtime. This is if the method is declared elsewhere or will be provided via AUTOLOAD or similar functionality.

methods defaults:

  • name

    Set to the value of the "key" for the method:

     rank => { # name will be set to 'rank'
       code => \&some_method,
     }
  • label

    None.

  • desc

    None.

  • view

    $VIEW_PUBLIC.

  • context

    $CTXT_OBJECT.

  • caller

    None.

  • args

    None.

  • returns

    None.

EXPORT

Class::Meta::Declare exports a number of constants on demand. These constants are used to provide a simpler interface for Class::Meta use.

See CONSTRUCTOR OPTIONS for details on where to use these.

:acc

Foreach each class, you can specify the type of attribute accessors created. Defaults to "perl-style" accessors.

See the "Accessors" section for Class::Meta.

See also:

Class::Meta::AccessorBuilder

Class::Meta::AccessorBuilder::Affordance

Class::Meta::AccessorBuilder::SemiAffordance

  • $ACC_PERL

  • $ACC_AFFORDANCE

  • $ACC_SEMI_AFFORDANCE

:authz

Sets the authorization for each attribute, determining whether people can read or write to a given accessor. Defaults to Class::Meta::RDWR.

See authz.

  • $AUTHZ_READ

  • $AUTHZ_WRITE

  • $AUTHZ_RDWR

  • $AUTHZ_NONE

:create

Indicates what type of accessor or accessors are to be created for the attribute. Generally sets a sensible default based upon the authz setting.

See the "create" section under add_attribute.

  • $CREATE_GET

  • $CREATE_SET

  • $CREATE_GETSET

  • $CREATE_NONE

:ctxt

For each attribute, you may specify if it is a class or instance attribute.

See the "context" section under add_attribute.

  • $CTXT_CLASS

  • $CTXT_OBJECT

:type

Sets the data type for each attribute. Setting an attribute to an illegal data type is a fatal error. This list of data types covers all that are supplied with Class::Meta. If you use others, you'll have to specify them explicitly.

See Data Types.

  • $TYPE_SCALAR

  • $TYPE_SCALARREF

  • $TYPE_ARRAY

  • $TYPE_ARRAYREF

  • $TYPE_HASH

  • $TYPE_HASHREF

  • $TYPE_CODE

  • $TYPE_CODEREF

  • $TYPE_CLOSURE

  • $TYPE_STRING

  • $TYPE_BOOLEAN

  • $TYPE_BOOL

  • $TYPE_WHOLE

  • $TYPE_INTEGER

  • $TYPE_DECIMAL

  • $TYPE_REAL

  • $TYPE_FLOAT

:view

Sets the "visibility" of a constructor, attribute, or method.

See the "view" section under add_constructor.

  • $VIEW_PUBLIC

  • $VIEW_PRIVATE

  • $VIEW_TRUSTED

  • $VIEW_PROTECTED

AUTHOR

Curtis "Ovid" Poe, <ovid@cpan.org>

BUGS

Please report any bugs or feature requests to bug-class-meta-declare@rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Class-Meta-Declare. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

ACKNOWLEDGEMENTS

Thanks to Kineticode, Inc, http://www.kineticode.com for sponsoring this work.

SEE ALSO

Class::Meta

DEPENDENCIES

Class::BuildMethods

Class::Meta

Exporter::Tidy

Readonly

COPYRIGHT & LICENSE

Copyright 2005 Curtis "Ovid" Poe, all rights reserved.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.