Item 52: Create invisible interfaces with tied variables.


Item 52: Create invisible interfaces with tied variables .

Let's end our discussion of object-oriented programming with an example. A tied variable in Perl is a variable that has been given magical properties via the tie operator. A tied variable is bound to a Perl object. All of the operations on that variable, such as assignment to it, reading its value(s), iterating over its values, and so on, are translated into method calls for that object. Perl supports tying the following types of variables:

  • Scalars Tied scalars let you create your own magic variables along the lines of $! .

  • Arrays The implementation for arrays is incomplete as of this writing, but it is somewhat usable in its current form.

  • Hashes The implementation for hashes is complete and robust, largely because the first tied variables were Perl 4 DBM hashes.

  • Filehandles Recent versions of Perl support tied filehandles.

Let's just dive right in and look at an example. I've tried to explain it reasonably thoroughly on the fly, but this isn't intended to be a complete description of how to write a tied class. For that you should consult the perltie man page.

A tied hash

We will create a class called FileProp that allows you to access certain properties of a file by accessing the elements of a hash. To keep things simple, let's support just the following:

  • name (read/write) the name of the file

  • contents (read/write) the contents of the file

  • size (readonly) the size of the file

  • mtime (read/write) the modification time of the file, in Unix seconds since the epoch

  • ctime (readonly) the "change time" of the file

There are nine methods to write for a tied hash: TIEHASH , FETCH , STORE , DELETE , CLEAR , EXISTS , FIRSTKEY , NEXTKEY , and DESTROY . You don't have to write all of them if you will be using only some of the functionality of a hash (see Item 58 for an example of this), but you should go the full Monty if you are writing a tied hash class for general use.

The first step is to create a constructorthe TIEHASH methodfor the FileProp class:

The FileProp prologue and constructor

 #!/usr/local/bin/perl -w  package FileProp;  use Carp; 

We'll put everything in one file for convenience.

The Carp module adds the croak function, which reports errors at the point our package was called, rather than from within the package.

 my %PROPS = (    name => 1, size => 0, mtime => 1,    contents => 1, ctime => 0  );  my @KEYS = keys %PROPS; 

The PROPS hash contains property names and a flag indicating whether they are read/write.

KEYS will help with iterators.

 sub TIEHASH {    my ($pkg, $name) = @_;    unless (-e $name) {      local *FH;      open FH, ">$name" or        croak "can't create $name";      close FH;    } 

The TIEHASH method constructs a new "shadow object" that underlies the tied variable.

 bless {      NAME => $name, INDEX => 0    }, $pkg;  } 

Our shadow object is a hash containing the filename and a numeric iterator index.

You tie variables to the FileProp class using the tie operator:

 tie %data, FileProp, "new.data"; 

Then tie makes a class method call to TIEHASH :

 FileProp::TIEHASH "FileProp", "new.data"; 

TIEHASH returns a Perl object (in this case, a blessed hash ref), which tie magically binds to the tied variable ( %data , above). Now, all accesses to the variable %data will invoke FileProp methods. Let's write some of those methods.

The FETCH method is called when a value is retrieved from the tied variable:

The FileProp FETCH method

 sub FETCH {    my ($self, $key) = @_;    my $name = $self->{NAME};    unless (exists $PROPS{$key}) {      croak "no property $key for $name";    } 

FETCH is called when a value is read from the tied hash.

Get filename for convenience.

Do we grok this property?

 if ($key eq 'size') {      -s $name    } elsif ($key eq 'name') {      $name    } elsif ($key eq 'mtime') {      (stat $name)[9]    } elsif ($key eq 'ctime') {      (stat $name)[10] 

File size in bytes.

Filename.

Mod time, seconds since the epoch.

Change time, seconds since the epoch.

 } elsif ($key eq 'contents') {      local $/, *FH;      open FH, $name;      my $contents = <FH>;      close FH;      $contents;    }  } 

Contents of the file. Open it and read it in.

With the FETCH method in place, you can say:

 print "size of data = $data{size}\n"; 

and the size of the file "new.data" will be displayed. Next up is the STORE method. The STORE method is called when a value is assigned to the tied variable:

The FileProp STORE method

 sub STORE {    my ($self, $key, $value) = @_;    my $name = $self->{NAME};    unless ($PROPS{$key} and -w $name) {      croak "can't set prop $key for $name";    } 

Called whenever a value is stored into the tied hash.

Can we write this property (and write to this file)?

 if ($key eq 'name') {      croak "file $key exists" if -e $key;      rename $name => $key;      $self->{NAME} = $key;    } elsif ($key eq 'mtime') {      utime((stat $name)[8], $value, $name);    } elsif ($key eq 'contents') {      local *FH;      open FH, ">$name" or die;      print FH $value;      close FH;    }  } 

Change filename.

Safety feature.

Rename the file.

Update internal filename.

Change mod time.

Change only mtime.

Change contents.

With the STORE method working, you can say:

 $data{contents} = "Testing one two three\n"; 

and the contents of the file will be overwritten with the string "Testing one two three\n" . Or, perhaps you would like to change the modification time to two minutes ago:

 $data{mtime} = time - 120; 

It gets simpler from here. Let's define the methods used to test and iterate over keys:

The FileProp EXISTS , FIRSTKEY , and NEXTKEY methods

 sub EXISTS {    my ($self, $key) = @_;    exists $PROPS{$key};  } 

Called when exists is used on a key of the tied hash.

 sub FIRSTKEY {    my $self = shift;    $self->{INDEX} = 0;    $KEYS[$self->{INDEX}++];  } 

Called by keys and each to get the first key from the tied hash.

We have to maintain some sort of index of where we are on a per-object basis, thus the INDEX member.

 sub NEXTKEY {    my $self = shift;    my $key = $KEYS[$self->{INDEX}++];    $self->{INDEX} = 0 unless defined $key;    $key;  } 

Called by keys and each to get succeeding keys from the tied hash.

Now you can find out what properties are supported by FileProp :

 print "properties: ", join(" ", keys %data), "\n"; 

which will give you:

 name ctime size mtime contents 

Finally, although they don't make a lot of sense for our application, we have to define methods for deleting and clearing the tied hash. We will just have them produce errors:

The FileProp DELETE and CLEAR methods

 sub DELETE {    croak "can't delete properties"  } 

Called when delete is used on a key of the tied hash.

 sub CLEAR {    croak "can't clear properties"  } 

Called when the hash is cleared, as when assigned an empty list.

A destructor (the DESTROY method) is not necessary for this example.

Let's demonstrate the completed class. First, some driver code (just add it to the end of the file):

 package main;  tie %data, FileProp, "new.data";  $data{contents} = "Demo data";  foreach (sort keys %data) {    print "$_: $data{$_}\n";  } 

When run, this should produce output similar to the following:

 %  tryme  contents: Demo data  ctime: 873187477  mtime: 873187477  name: new.data  size: 9 

That's all there is to it!

For another tied variable example, see Item 58.



Effective Perl Programming. Writing Better Programs with Perl
Effective Perl Programming: Writing Better Programs with Perl
ISBN: 0201419750
EAN: 2147483647
Year: 1996
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net