The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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++.

Playing with objects as if they were pure Perl hashes

The order in which you defined your properties in the class is maintained. So you can to slice assignments and group operations with these objects as if they where an ordered hash.

For example:

Copying all properties and their values from $obj1 to $obj2

    %{$obj2} = ${$obj1};
    
    @$obj2{ keys %$obj1 } = @$obj1{ keys %$obj 1 };

Copying all properties and their values that belong to group 'SomeGroup' from $obj1 to $obj2

    @$obj2{ $obj1->pGroup('SomeGroup') } = @$obj1{ $obj1->pGroup('SomeGroup') };
    
    %$obj2 = map { $_ => $obj1->{ $_ } } $obj1->pGroup('SomeGroup');

Copying all properties and their values that belong to groups 'SomeGroup1' and 'Somegroup2'

    %$obj2 = map { $_ => $obj1->{ $_ } } $obj1->pGroup('SomeGroup1','SomeGroup2');
    

Let's say that we have a hash '%args' which contains parameters from a form submission and you only want to set those properties that belong to group 'FormStep1' and disregards all other args.

    @$obj{ $obj->pGroup('FormStep1') } = @args{ $obj->pGroup('FormStep1') };
    
    # now we check for data validation errors
    
    if ($obj->pErrors)
    {
        warn "We have the following errors: ",Dumper($obj->pGetErrors),"\n";
        # now do something with your errors like rendering the form again and
        # showing the user the errors.
    }

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 overridden 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, additionally 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 enumerated 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 failure 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 failure 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 failure of size will result in error code 131.

minsize

The minimum size or length allowed. Data validation failure of minsize will result in error code 132.

maxsize

The maximum size or length allowed. Data validation failure of maxsize will result in error code 133.

min

The minimum numeric value allowed. Data validation failure of min will result in error code 141.

max

The maximum numeric value allowed. Data validation failure 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 infinite 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 controlled 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 tuned.

Public

Methods and Properties marked as Public can be access by the defining class, its children and the outside world. This is very much how standard Perl subroutines work, therefore using the function attribute Public is merely a formalization. Method and Properties not marked with any access modifiers will default to Public

Protected

Methods and Properties marked as Protected will be accessible 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 accessible 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

By default POOF will not allow you override methods or properties in child classes, only those methods or properties declared as Virtual can be overriden, the only exception is Private methods or properties that are not inheritable thus really private, but will alway be accesible by its definer class.

Utility Methods and Functions

POOF provides a series utility methods and functions listed below, all but new (the constructor) and _init the instance initialization function are prefixed with a "p" in order not to pollute 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..

    sub _init
    {
        my $obj = shift;
        # make sure we call super so it does its thing
        my %args = $obj->SUPER::_init(@_);
        
        # do something here ...
        
        # 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;
    }

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:

file: ColorTest.pm

    package ColorTest;
    use base qw(POOF);
    sub Color : Property Public Virtual
    {
        {
            'type' => 'enum',
            'options' => [qw(red green blue)]
        }
    }
    1;
    

file: 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 the error and the offending value that cause the error.

For example:

file: ColorTest.pm

    package ColorTest;
    use base qw(POOF);
    sub Color : Property Public Virtual
    {
        {
            'type' => 'enum',
            'options' => [qw(red green blue)]
        }
    }
    1;
    

file: test.pl

    #!/usr/bin/perl -w
    
    use strict;
    use ColorTest;
    use Data::Dumper;
    
    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.

file: 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;
    

file: Engine.pm

    package Engine;
    
    use strict;
    use base qw(POOF);
    
    sub State : Property Public
    {
        {
            'type' => 'enum',
            'options' => ["running","stoped"],
        }
    }
    1;
    

file: 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.

    my @groupnames = $obj->pGetGroups;
    

Here we traverse and entire class and create a hash containing property names and values based on their group membership.

    foreach my $group ($obj->pGetGroups )
    {
        $group_and_members->{ $group } = { $obj->pGetPropertiesOfGroup($group) };
    }
    

or short hand like this

    map { $group_and_members->{$_} = { $obj->pGetPropertiesOfGroup($_) } } $obj->pGetGroups;
    

pGetPropertiesOfGroups

Returns a hash indexed by the property names and containing the properties values. Everything is always returned in the order it was defined. Even though the core of the POOF object behaves like a hash the order is always maintained.

on a class with properties that belong to a group called 'SomeGroup' and this group has members 'prop1', 'prop2' and 'prop3';

    %prop_copy = $obj->pGetPropertiesOfGroups('SomeGroup');
    

same as

    @prop_copy{ $obj->pGroup('SomeGroup') } = @$obj{ $obj->pGroup('SomeGroup') };

same as

    @prop_copy{ qw(prop1 prop2 prop3) } = @$obj{ qw(prop1 prop2 prop3) };
    

same as

    @prop_copy{ $obj->pGetNamesOfGroup('SomeGroup') } = @$obj{ $obj->pGetValuesOfGroup('SomeGroup') };

same as

    @prop_copy{ $obj->pGroup('SomeGroup') } = @$obj{ $obj->pGetValuesOfGroup('SomeGroup') };

same as

    forech my $prop ($obj->pGroup('SomeGroup))
    {
        $prop_copy{$prop} = $obj->{$prop};
    }

pGetNamesOfGroup

Returns the names of the properties belonging to the specified group in the order in which they were defined.

For example:

    @prop_names = $obj->pGetNamesOfGroup('somegroupname');
    

same as

    @prop_names = $obj->pGroup('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' that belongs to group 'CoolProperties'

    print $obj->pGetGroupEncoded('CoolProperties');
    
    # 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.

For example:

    @$obj2{ $obj1->pGroup('SomeGroup) } = $obj1->pGetValuesOfGroup('SomeGroup');

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');

same as

    $refObj->{'Engine'}->{'State'} = 'stopped';

This method along with its counterpart below are useful for traversing complex object structures without having to use eval when generating code automatically and when using introspection. The Encoder class uses them to take "-" encoded properties from form fields to populate your application. There are three other POOF modules that I will publish very soon "POOF-Application-Simple", "POOF-HTML-Form" and "POOF-Webservices" that make use of these facilities.

pGetPropertyDeeply

This the counterpart to pSetPropertyDeeply, but it gets the value instead.

For example:

    $obj->pGetPropertyDeeply($refObj,'Engine','State');

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 objects that contain 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 itself pre-populated 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.

For example:

    $prop_options = $obj->pPropertyEnumOptions('SomeProp');

same as

    @prop_options = @{ $obj->pPropertyEnumOptions('SomeProp') };
    

pPropertyDefinition

Takes in a property name and returns the definition hash ref for this property.

    $prop_definition = $obj->pPropertyDefinition('SomeProp');

same as

    %prop_definition = %{ $obj->pPropertyDefinition('SomeProp') };

If you wanted a hash containing the definintions of all the props in class

    %prop_definitions = map { $_ => $obj->pPropertyDefinition($_) } keys %{$obj};

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.

Special Thank You

I'd would like to take this opportunity to thank my friends, Buddy Burden, Alain Avakian, John Callender, Diane Xu, Matthew Silvera and Dave Trischuk for their support and valuable input which has help shape the direction of this module.

You guys rock!

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.