NAME
POOF - Perl extension that provides stronger typing, encapsulation and inheritance.
SYNOPSIS
package MyClass;
use base qw(POOF);
# class properties
sub Name : Property Public
{
{
'type' => 'string',
'default' => '',
'regex' => qr/^.{0,128}$/,
}
}
sub Age : Property Public
{
{
'type' => 'integer',
'default' => 0,
'min' => 0,
'max' => 120,
}
}
sub marritalStatus : Property Private
{
{
'type' => 'string',
'default' => 'single',
'regex' => qr/^(?single|married)$/
'ifilter' => sub
{
my $val = shift;
return lc $val;
}
}
}
sub spouse : Property Private
{
{
'type' => 'string',
'default' => 'single',
'regex' => qr/^.{0,64}$/,
'ifilter' => sub
{
my $val = shift;
return lc $val;
}
}
}
sub opinionAboutPerl6 : Property Protected
{
{
'type' => 'string',
'default' => 'I am so worried, I don\'t sleep at night.'
}
}
# class methods
sub MarritalStatus : Method Public
{
my ($obj,$requester) = @_;
if ($requester eq 'nefarious looking stranger')
{
return 'non of your business';
}
else
{
return $obj->{'marritalStatus'}
}
}
sub GetMarried : Method Public
{
my ($obj,$new_spouse) = @_;
$obj->{'spouse'} = $new_spouse;
if ($obj->pErrors)
{
my $errors = $obj->pGetErrors;
if (exists $errors->{'spouse'})
{
die "Problems, the marrige is off!! $errors->{'spouse'}\n";
return 0;
}
}
else
{
$obj->{'marritalStatus'} = 'married';
return 1;
}
}
sub OpinionAboutPerl6 : Method Public Virtual
{
my ($obj) = @_;
return "Oh, great, really looking forward to it. It's almost here :)";
}
sub RealPublicOpinionAboutPerl6 : Method Public
{
my ($obj) = @_;
return $obj->OpinionAboutPerl6;
}
DESCRIPTION
This module attempts to give Perl a more formal OO implementation framework. Providing a distinction between class properties and methods with three levels of access (Public, Protected and Private). It also restricts method overriding in children classes to those properties or methods marked as "Virtual", in which case a child class can override the method but only from its own context. As far as the parent is concern the overridden method or property still behaves in the expected way from its perspective.
Take the example above:
Any children of MyClass can override the method "OpinionAboutPerl6" as it is marked "Virtual":
# in child
sub OpinionAboutPerl6 : Method Public
{
my ($obj) = @_;
return "Dude, it's totally tubular!!";
}
However if the public method "RealPublicOpinionAboutPerl6" it's called then it would in turn call the "OpinionAboutPerl6" method as it was defined in MyClass, because from the parents perspective the method never changed. I believe this is crucial behavior and it goes along with how the OO principles have been implemented in other popular languages like Java, C# and C++.
Property declaration
Class properties are defined by use of the "Property" function attribute. Properties like methods have three levels of access (see. Access Levels) which are Public, Protected and Private. In addition to the various access levels properties can be marked as Virtual, which allows them to be overriden in sub-clases and gives them visibility through the entire class hierarchy.
Property
The "Property" keyword is used as a function attribute to declare a class property. Property and Method are mutually exclusive and should never be used on the same function.
Sample usage:
The minimum requirement of a property is be declared with the Property function attribute and for it to return a valid hash ref describing at least its basic type. More about types in the type sub-section.
sub FirstName : Property
{
{
'type' => 'string'
}
}
Here we combine the Property attribute with the Public access modified. Note that order does not matter when combining POOF function attributes.
sub Color : Public Property
{
{
'type' => 'enum',
'options' => [qw(red blue green)]
}
}
Here we combine the Property attribute with the Protected and Virtual modifiers.
sub Height : Property Protected Virtual
{
{
'type' => 'float',
'min' => 0,
'max' => 8,
}
}
WRONG: You should never combine Property with Method.
sub Bad : Property Method
{
{
'type' => 'boolean'
}
}
Property definition hash
Properties are declared by the Property function attribute and defined by their definition hash. The definition has allows for various definitions such as type, default value, min value, etc. See the section below for a list of possible definitions.
type
As the name implies defines the type of the property. POOF has several built in basic types, additionaly you can set type to be the namespace (package name) of the object you intend to store in the property.
For example:
# Property that will store a DBI object
sub Dbh
{
{
'type' => 'DBI::db'
}
}
If you defined the type to be a namespace (like above) and you try to store an object other than 'DBI::db' and error code 101 will be generated.
Below is a list of built-in basic types currently supported by POOF:
- string
-
Basic string like in C++ and other strong typed languages. Defaults to ''.
- integer
-
Basic integer signed integer where the sign is optional with with up to 15 digits and must pass the /^-?[0-9]{1,15}$/ regex. Defaults to 0.
- char
-
Basic char as in a single character. Defaults to ''.
- binary
-
This will hold anything. Defaults to ''.
- float
-
Basic float and must pass /^(?:[0-9]{1,11}|[0-9]{0,11}\.[0-9]{1,11})$/ regex. Defaults to '0.0'.
- boolean
-
Basic boolean 1 or 0. Defaults to 0.
- blob
-
This will hold anything like the binary type. Defaults to undef.
- enum
-
Basic enumarated type, its valid options are defined with the additional options definition. See options.
- hash
-
Basic Perl hash ref. Defaults to {}.
- array
-
Basic Perl array ref. Defaults to [].
- code
-
Basic Perl code ref. Defaults to undef.
regex
Regular expression that needs to return true for value to be considered valid.
The regular expression should be in the form of:
sub Color : Property Public
{
{
'type' => 'string',
'regex' => qr/^(?:red|green|blue)$/i
}
}
The qr operator allows the regex to be pre-compiled and you can specify additional switches like in the case above i for non-case sensitive. Data validation faulure of regex will result in error code 121.
null
Defines the property to be nullable and allows it to be set to undef. Data validation faulure of null will result in error code 111.
default
The default value this property should return if it has not be set.
size
The size as in length or number of characters (Same as minsize). Data validation faulure of size will result in error code 131.
minsize
The minimum size or length allowed. Data validation faulure of minsize will result in error code 132.
maxsize
The maximum size or length allowed. Data validation faulure of maxsize will result in error code 133.
min
The ninimum numeric value allowed. Data validation faulure of min will result in error code 141.
max
The maximum numeric value allowed. Data validation faulure of max will result in error code 142.
format
A format to use on output as you would use in printf or sprintf.
groups
The optional list of groups the property belongs to. Although it takes an array ref for the list it is normally specified in-line as in the example below:
sub Age : Property Public
{
{
'type' => 'integer',
'min' => 0,
'max' => 120,
'groups' => [qw(PersonalInfo Profile)]
}
}
In the above example the property "Age" belongs to two groups "PersonalInfo" and "Profile" and would be return if either of the to groups are called.
For example:
my @profile_prop_names = $obj->GetNamesOfGroup('PersonalInfo');
my @personal_prop_names = $obj->GetNamesOfGroup('Profile');
options
This is used to define all valid elements or options for the enumerated typed property.
For example:
sub Relationship : Property Public
{
{
'type' => 'enum',
'options' => [qw(Parent Self Spouse Children)]
}
}
ifilter
ifilter is a callback hook in the form of code ref that executes when a property is assigned a value and before we attempt to validate the data. The code ref can be an anonymous subroutine defined in-line or a a ref to a named sub-routine. The filter gets a reference to the object in which they exists along with the value that is being set. The value can be manipulated within the filter and/or validated by calling die if one desires to reject the value before it's set.
Note that because you have a reference to the object you also have access to other properties from the filter to both set and get, however you must be careful to no create an infinit loop by setting or getting values to a property that calls the same filter.
For example:
sub FirstName : Property Public Virtual
{
{
'type' => 'string',
'ifilter' => sub
{
my ($obj,$val) = @_;
if ($val)
{
# remove end of line char if any
chomp($val);
# trim leading and trailing white spaces
$val =~ s/^\s*|\s*$//;
# make all chars lower case
$val = lc($val);
# make first char upper case
$val =~ s/^([a-z])/uc($1)/e;
# reject it if val contains profanity
die "Failed profanity filter"
if $obj->ContainsPropfanity($val);
}
return $val;
}
}
}
Obviously the example above assumes with have a method "ContainsPropfanity" that will check the contents of $val and returns true if profanity is detected. See the section on Errors and Exceptions handling for more information on data validation violation and handling of die within filters.
ofilter
This is basically the same as the ifilter except it get executed when the someone tries to get the value from the property.
For example:
my $name = $obj->{'FirstName'};
Method declaration
See Method below.
Method
Methods are declared using the Method function attribute. Subroutines marked with the Method attribute will be deleted from the symbol table right after compilation and their access will be controled by the framework. All three access modifiers (Public, Protected and Private) work with methods as well as the Virtual modifier. See Access Modifiers for more information.
For example:
sub SayHello : Method Public
{
my ($obj) = @_;
print "Hello world!";
}
Access modifiers
These modifiers control access to both methods and properties. POOF currently supports Public, Protected and Private. Some additional modifiers are in the works so stay tunned.
Public
Methods and Properties marked as Public can be access by the defining class, its children and the outsite world. This is very much how standard Perl subroutines work, therefore using the function attribute <BPublic> is mearly a formalization. Method and Properties not marked with any access modifiers will default to Public
Protected
Methods and Properties marked as Protected will be accesible by the defining class and its decendants (children, grand children, etc.) but not from outside the class hierarchy.
Private
Methods and Properties marked as Private will be accesible only by the defining class. In fact, you can define a Private method in one class, inherit from that class and define another Private method in the child class and not have a conflict. Each class will use its version of the method. Very useful when you want to control what gets inherited.
Other declarative modifiers
Virtual
This is a bit more tricky, documentation comming soon.
Utility Methods and Functions
POOF provides a series utility methods and fuctions listed below, all but new (the constructor) and _init the instance inititalization function are prefixed with a "p" in order not to polute your namespace too much. This allows you to have your own "Errors" method as the POOF one is called "pErrors" and so son...
Note: Currently some of these methods also exist in POOF without the "p" and I'm working to removed then in a timely manner.
new
The standard Perl constructor, a bit of processing is done in the constructor so you should not override it. If you need to do things in the constructor override the _init method instead. You've been warned :)
_init
This is a safe place where you can do things right after the object has been instantiated. Things normally done here include processing constructor arguments, setting of default values, etc..
pErrors
Returns and integer representing the number of errors or exceptions currently in the exception tree. Whenever data validation on a property fails an exception or error is stored for the instance in question and the number of such errors can be retrieved by calling this method.
For example:
#-------------------------------
# on ColorTest.pm
package ColorTest;
use base qw(POOF);
sub Color : Property Public Virtual
{
{
'type' => 'enum',
'options' => [qw(red green blue)]
}
}
1;
#-------------------------------
# on test.pl
#!/usr/bin/perl -w
use strict;
use ColorTest;
my $obj = ColorTest->new;
$obj->{'Color'} = 'orange';
print "We have " . $obj->pErrors . " error\n";
The above example should print "We have 1 error";
pGetErrors
Returns a hash indexed by the name of the property that generated the error, an error code, a description string for th error and the offending value that cause the error.
For example:
#-------------------------------
# on ColorTest.pm
package ColorTest;
use base qw(POOF);
sub Color : Property Public Virtual
{
{
'type' => 'enum',
'options' => [qw(red green blue)]
}
}
1;
#-------------------------------
# on test.pl
#!/usr/bin/perl -w
use strict;
use ColorTest;
my $obj = ColorTest->new;
$obj->{'Color'} = 'orange';
print "Errors: ",Dumper($obj->pGetErrors);
# this should print something like this:
Errors: $VAR1 = {
'Color' => {
'value' => 'orange',
'description' => 'Not a valid options for this enumerated property',
'code' => 151
}
};
pGetAllErrors
Like pGetErrors above this method returns errors with their property name, code and description but it does so recursively across any contained objects.
For example:
class Car contains class engine stored in the Engine property.
#-------------------------------
# in Car.pm
package Car;
use strict;
use base qw(POOF);
use Engine;
sub _init
{
my $obj = shift;
# make sure we call super so it does its thing
my %args = $obj->SUPER::_init(@_);
$obj->{'Engine'} = Engine->new;
$obj->{'Engine'}->{'State'} = 'stoped';
# make sure we return %args to if some inherits from us
# and they call super to get their args they get what its expected.
return %args;
}
sub Engine : Property Public
{
{
'type' => 'Engine'
}
}
1;
#-------------------------------
# in Engine.pm
package Engine;
use strict;
use base qw(POOF);
sub State : Property Public
{
{
'type' => 'enum',
'options' => ["running","stoped"],
}
}
1;
#-------------------------------
# in test.pl
#!/usr/bin/perl -w
use strict;
use Car;
use Data::Dumper;
my $car = Car->new;
$car->{'Engine'}->{'State'} = 'please stop';
print "Errors: ",Dumper($car->pGetAllErrors);
# should print something like this:
Errors: $VAR1 = {
'Engine-State' => {
'value' => 'please stop',
'description' => 'Not a valid options for this enumerated property',
'code' => 151
}
};
Note: property names are encoded with a dash to depict the containment level. In the example above "Engine-State" is "Engine" the name of the property containing the object and "State" the name of the property containing the error condition.
pGetGroups
Returns a list of all the property groups defined.
pGetPropertiesOfGroups
Returns a hash indexed by the property names and containing the properties values.
pGetNamesOfGroup
Returns the names of the properties belonging to the specified group in the order in which they where defined.
For example:
my @prop_names = $obj->pGetNamesOfGroup('somegroupname');
pGroup
Same as pGetNamesOfGroup. I use this method quite often so I made a shortcut :).
pGroupEncoded
Same as pGetNamesOfGroup but the names are encoded using the namespace of the class defining the property.
For example:
# in a class named 'MyClasses::ThisClass' with a property named 'MyProp'
print $obj->pGetGroupEncoded;
# should print something like this:
MyClass-ThisClass-MyProp
This is useful in some scenarios, more about this type of stuff some other time.
pPropertyNamesEncoded
Takes a in a reference of a POOF object and a list of property names and returns a list of the names encoded as in pGroupEncoded.
pGetValuesOfGroup
Takes in a group name and returns a list of the values of all the properties that belong to that group.
pValidGroupName
Takes a group name and returns 1 if it's valid and 0 if it's not.
pSetPropertyDeeply
Takes in a reference to a POOF object, a value and a list of levels normally property names. It will use traverse the object model recursively and set the leaf node to the value.
For example:
$obj->pSetPropertyDeeply($refObj,'stopped','Engine','State');
# which is the same as:
$refObj->{'Engine'}->{'State'} = 'stopped';
pGetPropertyDeeply
This the counter part to pSetPropertyDeeply, but it gets the value instead.
For example:
$obj->pGetPropertyDeeply($refObj,'Engine','State');
# which is the same as:
my $value = $refObj->{'Engine'}->{'State'};
pIntantiate
Takes in a property name and returns an new instance of the property's contained object. Useful for traversing object that countain other objects, currently used by the Encoder class.
pReInstantitateSelf
Takes in an optional hash to use as constructor arguments and returns a new instance of its self prepopulated with the args.
pPropertyEnumOptions
Takes in a property name and returns an array ref with their valid options. It will blow up calling confess if you pass it a property name that does not exist.
pPropertyDefinition
Takes in a property name and returns the definition hash for this property.
EXPORT
None.
TODO
Many, many things, but first we need to finished the documentation and tutorials of the existing classes. Beyond that better diagnostics is next on the list.
SEE ALSO
Although this framework is currently being used in production environments, I cannot accept responsibility for any damages cause by this framework, use only at your own risk.
Documentation for this module is a work in progress. I hope to be able to dedicate more time and created a more comprehensive set of docs in the near future. Anyone interested in helping with the documentation, please contact me at bmillares@cpan.org.
AUTHOR
Benny Millares <bmillares@cpan.org>
COPYRIGHT AND LICENSE
Copyright (C) 2007 by Benny Millares
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.8 or, at your option, any later version of Perl 5 you may have available.