Tom Leete > Tie-Constrained > Tie::Constrained

Download:
Tie-Constrained-0.03.tar.gz

Dependencies

Annotate this POD

Related Modules

Params::Validate
Regexp::Common
SOAP::WSDL
Class::DBI
CGI::Application
HTML::Template
XML::Xerces
HTML::FillInForm
Tie::Hash::Sorted
Tie::Scalar
more...
By perlmonks.org

CPAN RT

Open  0
View/Report Bugs
Module Version: 0.03   Source  

NAME ^

Tie::Constrained - Auto pre-validation of changes to variables

SYNOPSIS ^

Following the usual convention for optional arguments,

  use Tie::Constrained
      [qw/[:all] [:dummy] [:diagnostic] [:error] [subname ...]/];

Tie::Constrained aggregates a tied scalar variable with a validation test and a failure handler. The general syntax for the tie is:

  [$var_ctl =] tie $var, 'Tie::Constrained'[,
        [ \&validator [, $initval [, \&fail_handler]]]
      | [ $hashref ]
      | [ $TC_obj ]];

A constructor is available for unbound Tie::Constraint objects

  my $TC_obj = Tie::Constrained->new (
        [ \&validator [, $initval [, \&fail_handler]]]
      | [ $hashref ]
      | [ $TC_obj ] );

There is a concise wrapper for the tie,

  [$var_ctl =] constrain( $var,
        [ \&validator [, $initval [, \&fail_handler]]]
      | [ $hashref ]
      | [ $TC_obj ] );

Validity tests should expect a single argument, the proposed new value for the tied variable. They should return true if the value is to be accepted, false if the failure handler is to be called. The passed value is modifiable.

Failure handlers should expect three arguments -- a reference to the validator which failed, the value it failed with, and an error number which is assigned to $!. If they return at all, they should return false if the value is to be rejected, true if it is now to be accepted. The passed value is modifiable.

DESCRIPTION ^

Tie::Constrained is a simple tie class for constraining the values a variable tied to it may take. The commonest use for that is to validate data. The tied interface provides the unique ability to wedge the validation test into assignment and mutator operations, prior to changing the variable's value. The effect is to ensure that the tied variable is always validated by the associated test.

In the tie statement,

  $foo_ctl = tie $foo, 'Tie::Constrained',
      \&validator, $initval, \&fail_handler;

The validator function should be designed to return true on success, false otherwise. It should expect the value to be tested as its first argument. If $initval is given, it will be tested by validator($initval) before the value is committed. The fail_handler() is the action to be taken when validator() returns false. If the failure handler returns, its value should be true if the proposed new value is to be accepted, false if it is to be ignored. Fail handlers are called with three arguments. The first is a coderef to the test which failed. The second is the value it tested. The third is an error number which is assigned to $! in the handler. User tests and handlers may make whatever use thay wish of the arguments, but $! should be set to the third argument. Value arguments are modifiable through their alias in @_, allowing tests and handlers to modify them before committal. That capability should be treated with respect, it is prone to high obfuscation. Avoid setting $var_ctl unless you really need to modify the tie on the fly. Testing the return value for logical truth is sufficient to check for success of the tie. Since a Tie::Constrained object may be used as an initializer in the tie call, it is convenient to be able to produce them independent of any binding. That is provided by the new class method.

Philosophy

Tie::Constrained is a low level module, and most of the interface is designed to remind the user of that fact. That design choice is deliberate. It is also designed to be relentlessly Object Oriented, though not in the way that equates library files and modules and classes and types. The tie mechanism constructs an object instance which, to the user, is simply a variable. The interface to the object is all the universe of perl operators and functions which may be applied to that variable. The underlying object aggregates the value of the variable, a validity test, and a handler for failure of the test. That is the Tie::Constrained object proper. The STORE and FETCH methods of the Tie::Constrained object are sufficient to hook into every use of the tied variable by perl. That allows us to place our own conditions on what uses we will accept. Perl itself does all the rest of the work. To restrict assignment or a mutator operator, we don't need to overload the operators or write code around each use of them. Our tied wedge into perl is sufficient to make it all happen automatically.

To use Tie::Constrained effectively, you should understand how it works. That is the subject of the next section,

TieGuts

The STORE() method of a tied class is called when perl has a value to be stored in the tied variable. STORE() must take care of storing the value where the tied object expects it, and must return a value which perl sees as the value of the operation. That value may be passed along in chained assignments or returned from a sub, among other possibilities.

&Tie::Constrained::STORE does not automatically store the value. It first tests the value with its validator function. If that returns true, the value is stored. If it returns false, the object's failure function is called. That function may not return at all if it throws. If it returns true, the value is stored anyway (fail may have modified it). If it returns false, no value is stored and the tied variable remains unmodified. In pseudocode,

  sub STORE {
      return storage = test or fail ? value : storage;
  }

That simple code is capable of many effects since both test and fail are capable of modifying value, and fail has enough information to retest.

Similarly, perl calls the tied class's FETCH() method when it wishes to know the value of the tied variable. By default, Tie::Constrained does not test the value in storage. That is so a tie may be left uninitialized and yet accept a value from a mutator (mutators call FETCH() first to get the value they are to work on). If you want a more stringent tie, where the value must always pass the test, you may set the $STRICT flag and every FETCH() will apply the test or fail before returning the value. In pseudocode, FETCH() looks like this:

  sub FETCH {
      fail if strict and not test storage;
      return storage;
  }

The TIESCALAR class method is the constructor for the tie class. After sorting out what kind of arguments it is given, its behavior is similar to STORE() as far as the application of test or fail to the initial value is concerned. The new class method for constructing an unbound Tie::Constrained is just an alias to TIESCALAR. Unbound objects are used as templates for initializing ties as arguments to TIESCALAR, new, or the constrain wrapper.

Tie::Constrained does not make use of DESTROY or UNTIE. Subclasses should do whatever they need with those.

Subclassing Tie::Constrained

Tie::Constrained is intended as a fully usable class with an interface which is low-level enough to provide a flexible way of highjacking the mutation of variables. If users wish to have a more specialized and restrictive constraint class, Tie::Constrained is constructed to work as a base class, providing the framework for that.

The default fail handler dies loud in Tie::Constrained. If by default you would prefer to silently ignore bad mutations, you can subclass Tie::Constrained like this:

  package Tie::Constrained::Quiet;
  use Tie::Constrained;
  use vars qw/@ISA/;
  @ISA = qw/Tie::Constrained/;
  *failure = \&ignore;
  1;

If you just wish to ensure that your constraints are always strict and can't be subverted without notice by tie object tricks,

  package Tie::Constrained::Strict;
  use Tie::Constrained;
  use vars qw/@ISA/;
  @ISA = ('Tie::Constrained');
  *STRICT = \1;
  1;

Similarly, $VERBOSE may be set for a debugging environment.

The valid or failure functions may be overridden with user code to replace the default test or fail handlers.

Tests and Handlers

<Tie::Constrained>'s conventions for tests and fail handlers are as follows.

For tests:

For fail handlers:

Good taste and good sense should prevail in designing a constrained variable. Simple handlers of the kind shown below in the examples are robust and predictable. Clearly, this mechanism has room for plenty of exotic behaviors, though. There is lots of room to experiment.

Included Tests and Handlers

There are a few pre-packaged tests and failure handlers available to Tie::Constrained. They are accessible by importing through the arguments to use Tie::Constrained. The functions are listed in the "FUNCTIONS" section, and the export tags in "EXPORTS"

FUNCTIONS ^

Class Methods

VERSION()

Reports the version number of Tie::Constrained.

new(LIST)

Constructor for an unbound Tie::Constructor object. The arguments follow the same syntax as those following the package name in the tie call. Typical usage,

  my $all_vowels =
      Tie::Constrained->new(sub {$_[0] !~ /[^aeiouy]/});

The returned Tie::Constrained object may be used to initialize multiple vowel strings. The constructors perform a deep copy of the object, so subsequent changes are not reflected in earlier uses.

constrain( $var, $tc_obj)

A convenience function which wraps a Tie::Constrained binding. Ties $var to the Tie::Constrained object. Given a bound or unbound Tie::Constrained object, constrain admits the nice syntax,

  constrain( my $word, $all_vowels) or die $!;

Stock Validators

Validators take one argument and return true if the argument is to be committed to the tied variable. A false return triggers the fail handler. Values written to $_[0] will be seen and used by the caller. Treat that with delicacy, forget it, or have fun with it. Tie::Constrained includes two dummy validators described below.

notest

Null validator. Every modification is accepted. The fail handler is never called. This is the default test.

deny

No modification is accepted with this validator. The fail handler is always called.

detaint

This is not a validator itself, but is included as a useful componemt of validators. Called on the caller's argument list, it will detaint the first argument. detaint always returns true. Example validator for an http URI from a tainted source:

  use Regexp::Common 'URI';
  sub is_http { $_[0] =~ /^$RE{URI}{HTTP}$/ and &detaint, 1 }

Stock Fail Handlers

Fail handlers are called with three arguments. The first is a reference to the test which triggered the failure. It may be used to test repair attempts, select actions, or whatever else your imagination can devise. The second argument is the value which failed. As with tests, and with the same caveats, that value is modifiable through $_[1] and changes to it will be effective in the caller. The third argument is an error number. The convention is to take them by name from Errno.pm, and assign them to $! in the handler. Tie::Constrained is equipped with four stock fail handlers. The default, death, dies loudly through &Carp::croak or &Carp::confess. That is intended to support an exception style of error handling.

death

Croaks with an error message from the lowest level caller. If $VERBOSE is true, the message contains a full stack trace (confess). The default fail handler, failure, is an alias to death for its effect as an exception with respect to eval.

warning

Issues warn with respect to the loweat level caller. If $VERBOSE is true, warns with a full stack trace.

ignore

Any modification passed to this handler is silently ignored. The old value of the tied variable is retained. In chained assignments, the old value will be passed along to the left.

allow

This handler overrides any validation test and allows the tied variable to take the proposed value.

eraser

Responds to a failed test by clearing error and undefining value of the tied variable.

valid($value)

This is the default validity test for Tie::Constrained and those subclasses which honor its tie conventions. In the base class, where it is expected that each tie binding will carry its own test, it is an alias to the notest function.

failure( $test, $value, $error)

The default fail handler for Tie::Constrained and its faithful subclasses. In Tie::Constrained, it is an alias to death, which throws a loud exception.

EXPORTS

All the tests and fail handlers listed above are ordinary functions, not class or instance methods. They may all be imported from Tie::Constrained by name. There are no default exports.

There are a few export tags which identify groups of functions. They are:

:diagnostic

death, warning,

:dummy

notest, deny, ignore, allow

:all
    C<EINVAL>,  C<EDOM>, C<ERANGE>, C<notest>, C<deny>, C<death>, C<warning>, C<ignore>, C<allow>, C<eraser>, C<constrain>

Everything in the @EXPORT_OK list

:error

EINVAL, EDOM, ERANGE (all imported to Tie::Constrained from Errno)

EXAMPLES ^

Here are a few examples of Tie::Constrained at work.

We'll first look at a few cases where we want a string to contain nothing but vowels.

  tie my $vowels, Tie::Constrained =>
      sub { not $_[0] =~ /[^aeiouy]/ },
      'ioyou'
      sub { $! = undef };

In that example, the fail handler squashes errors and returns false, causing invalid values to be silently ignored. The tied variable $vowels will retain its old value.

The argument to a validation function is modifiable, opening the way for something more than validation.

  tie my $cons_killer, Tie::Constrained =>
      sub { $_[0] =~ tr/aeiouy//cd; 1 };
  $cons_killer = "googleplex";

which results in $cons_killer taking the value ooee, much to the confusion of some future maintainer. Increment operators have an interesting effect on $cons_killer. Those tricks may best be left out of unobfuscated code.

A case where argument modifiability is more defensible:

  tie my $pristine, Tie::Constrained =>
      sub { $_[0] !~ /[^aeiouy]/ and &detaint };

There, we modify the taint property of a copy of the data, not the value itself. The detaint function is exportable from Tie::Constrained. Note that the old-style sub call is intended here, though detaint(@_) would have done as well.

Fail handlers are also capable of modifying the proposed value for a tied variable:

  tie my $all_or_nothing, Tie::Consrained =>
      sub { $_[0] =~ /$re/ },
      undef,
      sub { $_[0] = undef; 1 };

With that, a failed assignment or mutation will leave the tied variable undefined.

Other modules are a rich source of tests. Suppose we obtain what is supposed to be an http URI from an untrusted source. Drawing on Regexp::Common, we say,

  use Tie::Constrained qw/detaint/;
  use Regexp::Common qw/URI/;
  tie my $address, Tie::Constrained =>
      sub { $_[0] =~ /^$RE{URI}{HTTP}$/ and &detaint }
      undef,
      sub { $_[0] = undef; 1 };

Tie::Constrained is not limited to the values of ordinary scalars. Here is an example where variable is constrained to be a CGI query object. This usage also does the error handling for the CGI constructor.

  use CGI;
  tie my $query, Tie::Constrained =>
      sub { $_[0]->isa('CGI') },
      CGI->new;

That is error-handling that keeps on protecting. Later assignment to any value not a CGI instance will carry the death penalty.

PREREQUISITES ^

In use, Tie::Constrained depends on

Carp
Errno
Exporter

All are from the perl core.

Pre-installation testing demands:

Test::Harness
Test::More
Test::Simple

Some tests use other modules. Those tests will be skipped if the needed modules are not available.

TODO ^

I am uneasy about the design of FETCH under $STRICT. Its current behavior should not be regarded as a stable api yet. The version 1.0 release will not happen until that is resolved.

$STRICT should be a property of each object, not a global flag. That is another goal for version 1.0.

I would like to expand the "EXAMPLES" section and split it off to its own cookbook pod, with an accompanying directory of example code..

AUTHOR ^

Zaxo, <zaxo@cpan.org>

BUGS ^

Please report any bugs or feature requests to bug-tie-constrained@rt.cpan.org, or through the web interface at http://rt.cpan.org. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

ACKNOWLEDGEMENTS ^

Joshua ben Jore (<jjore@cpan.org>), for the initial packaging and test suite, testing and patches for compatibility with older perl, thanks!

The Monks at the Monastery, http://perlmonks.org, who saw it first.

COPYRIGHT & LICENSE ^

Copyright Zaxo (Tom Leete), 2004,2005, All Rights Reserved.

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

syntax highlighting: