The 2003 Perl Advent Calendar
[about] | [news] | [rss] | [mirrors] | [links]
[2000] | [2001] | [2002] | [2003] | [2004]

On the 7th day of Advent my True Language brought to me..
Attribute::Handlers

Attributes are a method of attaching out of band information onto variables or functions in modern perls. This is very useful for storing metadata - data about the data or code you are creating.

They can be used for a variety of tasks, and if used properly can be used to add extra functionality or constraints to your code without overly complicating the code layout.

Attribute::Handlers allows you to write your own attributes in Perl that your scripts can use.

Using (as opposed to writing) attributes is simple. For example, the Attribute::TieClasses module allows us an alternative interface to tie.

  #!/usr/bin/perl
  # turn on the safety features
  use strict;
  use warnings;
  # load the module that defines our attributes
  use Attribute::TieClasses;
  # create a variable and stick the magical 'Toggle' attribute on it
  my $foo : Toggle;
  # print out $foo four times.
  for (1..4)
  {
    if ($foo)
     { print "it's true!\n" }
    else
     { print "it's false!\n" }
  }

The Toggle note tells Perl that the variable should be tied with the Tie::Toggle class, meaning every time it's read it alternates between being true and false. Hence, when we run this code we get:

  it's false!
  it's true!
  it's false!
  it's true!

Attributes can also be attached to subroutines. For example, the Attribute::Attempts module allows you to put a little note on a subroutine that says that it an exception is thrown within that routine Perl should simply wait a bit and run the subroutine again.

  # alter db will try three times before failing
  sub alter_db : attempts(tries => 3, delay => 2)
  {
    # connect to the database or throw an exception
    my $dbh = DBI->connect("DBD::Mysql:foo", "mark", "opensaysme")
      or die "Can't connect to database";
    # tell the dbi to throw an exception if there's a problem
    local $dbh->{RaiseException} = 1;
    # try doing some sql
    $dbh->do("alter table items change pie pies int(10)");
  }

Note how the attempts attribute itself takes arguments (in this case the number attempts Perl should make to run the subroutine before giving up, and the seconds it should wait between attempts.) As with all attributes these must be all on the same line as the sub declaration itself.

Writing your own attributes

Attribute::Handlers works by defining a new attribute ATTR that itself can be used to create new attributes by applying it to approperately named subroutines.

For example, we can write a module that defines a Monitored attribute that when applied to a variable indicates that the contents of the variable should be monitored and printed out whenever the report method is called.

We do this by defining a class with a Monitored subroutine in it and marking this subroutine with the ATTR attribute.

  package Attribute::Monitored;
  # turn on perl's safety features
  use strict;
  use warnings;
  # setup the ATTR attribute
  use Attribute::Handlers;
  use Data::Dumper;
  # our list of variables that we're monitoring
  our @monitoring;
  # declare an attribute 'Monitored' that can be attached to scalars
  sub Monitored : ATTR(SCALAR)
  {
    my ($package, $symbol, $referent, $attr, $data, $phase) = @_;
    # referent contains a reference to the variable that the attribute
    # was written next to.  We simply record it.
    push @monitoring, $referent;
  }
  sub report
  {
    # go through each of the variables that we're storing.
    foreach my $var (@monitoring)
    {
      # print out the variable
      print Dumper ${ $var };
    }
  }
  # return true to keep Perl happy
  1;

This attribute is now accessible to any class that inherits from our class. package Fred; use base qw(Attribute::Monitored);

  # declare a variable and make it monitored
  my $foo : Monitored = "bar";
  # a subroutine to alter $foo
  sub foo { $foo = shift }
  1;

We can now get reports like so:

  use Fred;
  Fred->report;
  Fred->foo("baz");
  Fred->report;

Which prints out:

  $VAR1 = 'bar';
  $VAR1 = 'Fred';

Moving on further.

Though I've written quite a few attribute handlers, the code tends to be hard to follow and quite verbose - neither of which make ideal content for the advent calendar, even if the module deserves a mention.

The complexity comes from the wealth of information you're passed in each of your attribute subroutines each time the attribute is applied.

With all these options you should hopefully get an idea of what exactly is possible. With a reference to a variable you can tie the underlying variable to do 'interesting things' With a reference to a symbol table you can modify subroutines by wrapping them in external subroutines.

  • Attribute::TieClasses
  • Tie::Toggle
  • Attribute::Attempts