NAME
Object::Properties - minimal-ceremony class builder
SYNOPSIS
package SomeClass;
use Object::Properties qw( foo +bar ), '+baz' => \&_check_baz;
sub _check_baz {
my ( $self, $value ) = @_;
ref $value ? croak 'SomeClass->baz must not be a ref' : $value;
}
Meanwhile, elsewhere:
my $obj = SomeClass->new( foo => 7 );
say $obj->foo; # outputs 7
$obj->foo = 42; # dies -- cannot be assigned to
$obj->bar = 42; # no problemo; and any value goes
say $obj->bar; # outputs 42
$obj->baz = ''; # no problemo encore -- except:
$obj->baz = \1; # nope, _check_baz croaks
DESCRIPTION
This is a class builder with a minimal API that can be used as a drop-in upgrade for Object::Tiny. It adds support support for field validation and read-write fields, realised as lvalue methods. Validation for lvalue writes will be efficient in XS-capable environments but will still function, slowly, in other situations.
INTERFACE
Declaring properties
The module's import
method accepts a list of field names and sets up an accessor for each of them in the package it was invoked from:
use Object::Properties qw( foo bar );
Fields are read-only by default but you can request read-write fields by preceding them with a plus sign:
use Object::Properties qw( readonly +readwrite );
By default, fields accept any value whatsoever, but any field name (read-only or read-write) may be followed by a reference to a validation function:
use Object::Properties '+hostname' => \&_munge_hostname;
Any write to such a field will invoke its validation function, with the object instance and the new value for the field as its arguments. The return value of this function will then be stored as the field value:
sub _munge_hostname { lc $_[1] }
You can return an empty list from a validation function, in which case nothing will be stored at all. This allows you to take over the entire handling of the value if needed. (You can also return more than one value, in which case only the first value will be stored, so that's generally a silly thing to do.)
Constructing instances
If you declare any validated fields, a method called PROPINIT
will be added to your package along with the accessors. When called, it clears the values of whatever validated fields may already have been stored in the instance, then sets them from a hashref it expects to be passed as its only parameter. It also deletes every key it has used from this hashref:
my $arg = { page => 5, max_page => 20 };
$self->PROPINIT( $arg );
# now $arg is {} and $self->page == 5 and $self->max_page == 20
It is valid to pass the instance itself as its parameter hash:
my $self = bless { page => 5, max_page => 20 }, $class;
$self->PROPINIT( $self );
You will probably want to call all the PROPINIT
s in your inheritance chain from your constructor (with aid from NEXT):
sub new {
my $class = shift;
my $self = bless { @_ }, $class;
$self->EVERY::LAST::PROPINIT( $self );
return $self;
}
But if that is all your constructor would do, you will not need to write it: Object::Properties::Base
contains such a new
method and will be added to your @ISA
as your superclass if that is empty.
Inter-field depencencies
NOTE that you have to take care of data dependencies. During construction, validated fields will be set in the order you declare them, so be sure that none of your validation functions depend on validated fields listed after them in the declaration order:
use Object::Properties
'+max_page' => \&_munge_max_page,
'+page' => \&_munge_page;
# make sure it's always a number
sub _munge_page {
my ( $self, $value ) = @_;
no warnings 'numeric';
$value > $self->max_page ? croak 'Cannot go past last page' : 0+$value;
}
sub _munge_max_page {
my ( $self, $value ) = @_;
{ no warnings 'numeric'; $value = 0+$value; }
croak 'Cannot shrink below current page' if $value < ( $self->page // 0 );
$value;
}
In this example, max_page
is declared first and the function is written to deal with the case of page
being uninitialized. Then, page
can just depend on max_page
already being initialized.
Reversing the order of declarations here would make it impossible to properly construct a new object with an initial page
value – or to construct one at all, almost. It would always cause a warning due to max_page
being undefined during the comparison in _munge_page
, and any value other than 0 would trigger the croak
.
SEE ALSO
AUTHOR
Aristotle Pagaltzis <pagaltzis@gmx.de>
COPYRIGHT AND LICENSE
This software is copyright (c) 2016 by Aristotle Pagaltzis.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.