NAME
Params::Validate::Dependencies::Extending
DESCRIPTION
How to extend Params::Validate::Dependencies, and a discussion of its internals.
PRE-REQUISITES
Before even thinking about extending this module you should understand references, in particular code-references and closures, and also understand objects in perl.
WHAT THE *_of FUNCTIONS REALLY DO
If you've read the documentation for Params::Validate::Dependencies and Data::Domain::Dependencies, then you'd think that they return a code-ref which is a closure over the original arguments, and that they're implemented something like this:
sub any_of {
my @options = @_;
return sub {
my $hashref = shift;
... do stuff with @options and $hashref ...
}
}
In version 1, that was exactly what was happening. But then I wanted to make them self-documenting.
Now, they still return a code-ref, but that code-ref is blessed into a class - Params::Validate::Dependencies::any_of in the case of the closures generated by any_of
- so that I can attach a little bit of extra metadata. They now look something like this:
sub any_of {
my @options = @_;
bless sub {
... some extra stuff ...
my $hashref = shift;
... do stuff with @options and $hashref ...
}, 'Params::Validate::Dependencies::any_of';
}
There are four such classes, one for each of the *_of functions.
HOW THE AUTO-DOCUMENTATION WORKS
Each of those four classes is a sub-class of Params::Validate::Dependencies::Documenter. When you call Params::Validate::Dependencies::document() and pass it one of those objects, it calls the object's _document
method. That puts P::V::D into "documentation mode" by setting a global variable to the object itself, and then executes the object's underlying code-ref.
In the example above, where it says "... some extra stuff ...", we actually have this:
if($Params::Validate::Dependencies::DOC) {
return $$Params::Validate::Dependencies::DOC->_doc_me(list => \@options);
}
so, now that <$DOC> has a value, the code-ref doesn't bother validating anything, it instead calls its _doc_me
method, and passes it the options that the closure was originally created with.
Finally, _doc_me
, which now has both the data that was originally closed over, and the object itself, it can construct a useful string of documentation. To do this it gets the name of the original factory function by calling the object's name
method - in the case of a Params::Validate::Dependencies::any_of object this returns 'any_of' - and, so that it can use 'and' or 'or' when constructing lists, it gets that by calling the join_with
method.
It then iterates over the original list of options, scalars first followed by code-reffy objects, recursing into objects and getting them to document themselves.
The end result if that, given a code-ref created thus:
any_of(
qw(alpha beta),
all_of(
qw(foo bar),
none_of('barf')
),
one_of(qw(quux garbleflux))
)
You will get back documentation like this:
any of ('alpha', 'beta', all of ('foo', 'bar' and none of ('barf')) or one of ('quux' or 'garbleflux'))
which is admittedly not perfect but is better than having to write the blasted stuff yourself.
ADDING YOUR OWN VALIDATORS WITHOUT DOCUMENTATION
If you can't be bothered with making your validators self-documenting, then you can just have them return a closure and be done with it:
sub two_of {
my @options = @_;
return sub {
my $hashref = shift;
my $count = 0;
foreach my $option (@options) {
$count++ if(
(!ref($option) && exists($hashref->{$option})) ||
(ref($option) && $option->($hashref))
);
}
return ($count == 2);
}
}
There is an example of this in t/05-extra-validator-without-doco.t. In fact it's this very example.
ADDING YOUR OWN VALIDATORS WITH AUTO-DOCUMENTATION
If you want to do the full-fat implementation complete with auto-doc, then you will need to implement one teeny-tiny class, as well as modify your factory function to return an object of that class and to look out for "documentation mode" being turned on. The example above would be modifed thus:
sub two_of {
my @options = @_;
return bless sub {
if($Params::Validate::Dependencies::DOC) {
return $Params::Validate::Dependencies::DOC->_doc_me(list => \@options);
}
...
}, 'Params::Validate::Dependencies::two_of'
}
and the corresponding class would look like:
package Params::Validate::Dependencies::two_of;
use base qw(Params::Validate::Dependencies::Documenter);
sub join_with { return 'or'; }
sub name { return 'two_of'; }
It is suggested that you combine class and function together, and have it export the function, as can be seen in the example in t/lib/Params/Validate/Dependencies/two_of.pm, which is used in t/06-extra-validator-with-doco.t.
SEE ALSO
Params::Validate::Dependencies
SOURCE CODE REPOSITORY
git://github.com/DrHyde/perl-modules-Params-Validate-Dependencies.git
https://github.com/DrHyde/perl-modules-Params-Validate-Dependencies/
COPYRIGHT and LICENCE
Copyright 2011 David Cantrell <david@cantrell.org.uk>
This documentation is free-as-in-speech software. It may be used, distributed, and modified under the terms of the Creative Commons Attribution-Share Alike 2.0 UK: England & Wales License, whose text you may read at http://creativecommons.org/licenses/by-sa/2.0/uk/.
CONSPIRACY
This documentation is also free-as-in-mason.