NAME
Object::InsideOut - Comprehensive inside-out object support module
VERSION
This document describes Object::InsideOut version 1.39
SYNOPSIS
package My::Class; {
use Object::InsideOut;
# Numeric field with combined get+set accessor
my @data :Field('Accessor' => 'data', 'Type' => 'NUMERIC');
# Takes 'DATA' (or 'data', etc.) as a manatory parameter to ->new()
my %init_args :InitArgs = (
'DATA' => {
'Regex' => qr/^data$/i,
'Mandatory' => 1,
'Type' => 'NUMERIC',
},
);
# Handle class specific args as part of ->new()
sub init :Init
{
my ($self, $args) = @_;
$self->set(\@data, $args->{'DATA'});
}
}
package My::Class::Sub; {
use Object::InsideOut qw(My::Class);
# List field with standard 'get_X' and 'set_X' accessors
my @info :Field('Standard' => 'info', 'Type' => 'LIST');
# Takes 'INFO' as an optional list parameter to ->new()
# Value automatically added to @info array
# Defaults to [ 'empty' ]
my %init_args :InitArgs = (
'INFO' => {
'Type' => 'LIST',
'Field' => \@info,
'Default' => 'empty',
},
);
}
package main;
my $obj = My::Class::Sub->new('Data' => 69);
my $info = $obj->get_info(); # [ 'empty' ]
my $data = $obj->data(); # 69
$obj->data(42);
$data = $obj->data(); # 42
$obj = My::Class::Sub->new('INFO' => 'help', 'DATA' => 86);
$data = $obj->data(); # 86
$info = $obj->get_info(); # [ 'help' ]
$obj->set_info(qw(foo bar baz));
$info = $obj->get_info(); # [ 'foo', 'bar', 'baz' ]
DESCRIPTION
This module provides comprehensive support for implementing classes using the inside-out object model.
This module implements inside-out objects as anonymous scalar references that are blessed into a class with the scalar containing the ID for the object (usually a sequence number). For Perl 5.8.3 and later, the scalar reference is set as readonly to prevent accidental modifications to the ID. Object data (i.e., fields) are stored within the class's package in either arrays indexed by the object's ID, or hashes keyed to the object's ID.
The virtues of the inside-out object model over the blessed hash object model have been extolled in detail elsewhere. See the informational links under "SEE ALSO". Briefly, inside-out objects offer the following advantages over blessed hash objects:
Encapsulation
Object data is enclosed within the class's code and is accessible only through the class-defined interface.
Field Name Collision Avoidance
Inheritance using blessed hash classes can lead to conflicts if any classes use the same name for a field (i.e., hash key). Inside-out objects are immune to this problem because object data is stored inside each class's package, and not in the object itself.
Compile-time Name Checking
A common error with blessed hash classes is the misspelling of field names:
$obj->{'coment'} = 'Say what?'; # Should be 'comment' not 'coment'
As there is no compile-time checking on hash keys, such errors do not usually manifest themselves until runtime.
With inside-out objects, text hash keys are not used for accessing field data. Field names and the data index (i.e., $$self) are checked by the Perl compiler such that any typos are easily caught using
perl -c
.$coment[$$self] = $value; # Causes a compile-time error # or with hash-based fields $comment{$$slef} = $value; # Also causes a compile-time error
This module offers all the capabilities of other inside-out object modules with the following additional key advantages:
Speed
When using arrays to store object data, Object::InsideOut objects are as much as 40% faster than blessed hash objects for fetching and setting data, and even with hashes they are still several percent faster than blessed hash objects.
Threads
Object::InsideOut is thread safe, and thoroughly supports sharing objects between threads using threads::shared.
Flexibility
Allows control over object ID specification, accessor naming, parameter name matching, and more.
Runtime Support
Supports classes that may be loaded at runtime (i.e., using
eval { require ...; };
). This makes it usable from within mod_perl, as well. Also supports dynamic creation of object fields during runtime.Perl 5.6 and 5.8
Tested on Perl v5.6.0 through v5.6.2, v5.8.0 through v5.8.8, and v5.9.3.
Exception Objects
As recommended in Perl Best Practices, Object::InsideOut uses Exception::Class for handling errors in an OO-compatible manner.
Object Serialization
Object::InsideOut has built-in support for object dumping and reloading that can be accomplished in either an automated fashion or through the use of class-supplied subroutines. Serialization using Storable is also supported.
Foreign Class Inheritance
Object::InsideOut allows classes to inherit from foreign (i.e., non-Object::InsideOut) classes, thus allowing you to sub-class other Perl class, and access their methods from your own objects.
Class Declarations
To use this module, your classes will start with use Object::InsideOut;
:
package My::Class: {
use Object::InsideOut;
...
}
Sub-classes inherit from base classes by telling Object::InsideOut what the parent class is:
package My::Sub {
use Object::InsideOut qw(My::Parent);
...
}
Multiple inheritance is also supported:
package My::Project {
use Object::InsideOut qw(My::Class Another::Class);
...
}
Object::InsideOut acts as a replacement for the base
pragma: It loads the parent module(s), calls their import
functions, and sets up the sub-class's @ISA array. Therefore, you should not use base ...
yourself, or try to set up @ISA
arrays.
If a parent class takes parameters (e.g., symbols to be exported via Exporter), enclose them in an array ref (mandatory) following the name of the parent class:
package My::Project {
use Object::InsideOut 'My::Class' => [ 'param1', 'param2' ],
'Another::Class' => [ 'param' ];
...
}
Field Declarations
Object data fields consist of arrays within a class's package into which data are stored using the object's ID as the array index. An array is declared as being an object field by following its declaration with the :Field
attribute:
my @info :Field;
Object data fields may also be hashes:
my %data :Field;
However, as array access is as much as 40% faster than hash access, you should stick to using arrays. (See "Object ID" concerning when hashes may be required.)
(The case of the word Field does not matter, but by convention should not be all lowercase.)
Object Creation
Objects are created using the ->new()
method which is exported by Object::InsideOut to each class:
my $obj = My::Class->new();
Classes do not (normally) implement their own ->new()
method. Class-specific object initialization actions are handled by :Init
labeled methods (see "Object Initialization").
Parameters are passed in as combinations of key => value
pairs and/or hash refs:
my $obj = My::Class->new('param1' => 'value1');
# or
my $obj = My::Class->new({'param1' => 'value1'});
# or even
my $obj = My::Class->new(
'param_X' => 'value_X',
'param_Y' => 'value_Y',
{
'param_A' => 'value_A',
'param_B' => 'value_B',
},
{
'param_Q' => 'value_Q',
},
);
Additionally, parameters can be segregated in hash refs for specific classes:
my $obj = My::Class->new(
'foo' => 'bar',
'My::Class' => { 'param' => 'value' },
'Parent::Class' => { 'data' => 'info' },
);
The initialization methods for both classes in the above will get 'foo' => 'bar'
, My::Class
will also get 'param' => 'value'
, and Parent::Class
will also get 'data' => 'info'
. In this scheme, class-specific parameters will override general parameters specified at a higher level:
my $obj = My::Class->new(
'default' => 'bar',
'Parent::Class' => { 'default' => 'baz' },
);
My::Class
will get 'default' => 'bar'
, and Parent::Class
will get 'default' => 'baz'
.
Calling new
on an object works, too, and operates the same as calling new
for the class of the object (i.e., $obj->new()
is the same as ref($obj)->new()
).
NOTE: You cannot create objects from Object::InsideOut itself:
# This is an error
# my $obj = Object::InsideOut->new();
In this way, Object::InsideOut is not an object class, but functions more like a pragma.
Object Cloning
Copies of objects can be created using the ->clone()
method which is exported by Object::InsideOut to each class:
my $obj2 = $obj->clone();
When called without arguments, ->clone()
creates a shallow copy of the object, meaning that any complex data structures (i.e., refs) stored in the object will be shared with its clone.
Calling ->clone()
with a true argument:
my $obj2 = $obj->clone(1);
creates a deep copy of the object such that no internal data is shared between the objects.
Deep cloning can also be controlled at the field level. See "Field Cloning" below for more details.
Object Initialization
Object initialization is accomplished through a combination of an :InitArgs
labeled hash (explained in detail in the next section), and an :Init
labeled subroutine.
The :InitArgs
labeled hash specifies the parameters to be extracted from the argument list supplied to the ->new()
method. These parameters are then sent to the :Init
labeled subroutine for processing:
package My::Class; {
my @my_field :Field;
my %init_args :InitArgs = (
'MY_PARAM' => qr/MY_PARAM/i,
);
sub _init :Init
{
my ($self, $args) = @_;
if (exists($args->{'MY_PARAM'})) {
$self->set(\@my_field, $args->{'MY_PARAM'});
}
}
}
package main;
my $obj = My::Class->new('my_param' => 'data');
(The case of the words InitArgs and Init does not matter, but by convention should not be all lowercase.)
This :Init
labeled subroutine will receive two arguments: The newly created object requiring further initialization (i.e., $self
), and a hash ref of supplied arguments that matched :InitArgs
specifications.
Data processed by the subroutine may be placed directly into the class's field arrays (hashes) using the object's ID (i.e., $$self
):
$my_field[$$self] = $args->{'MY_PARAM'};
However, it is strongly recommended that you use the ->set() method:
$self->set(\@my_field, $args->{'MY_PARAM'});
which handles converting the data to a shared format when needed for applications using threads::shared.
Object Initialization Argument Specifications
The parameters to be handled by the ->new()
method are specified in a hash that is labeled with the :InitArgs
attribute.
The simplest parameter specification is just a tag:
my %init_args :InitArgs = (
'DATA' => '',
);
In this case, if a key => value
pair with an exact match of DATA
for the key is found in the arguments sent to the ->new()
method, then 'DATA' => value
will be included in the argument hash ref sent to the :Init
labeled subroutine.
- Parameter Name Matching
-
Rather than counting on exact matches, regular expressions can be used to specify the parameter:
my %init_args :InitArgs = ( 'Param' => qr/^PARA?M$/i, );
In this case, the argument key could be any of the following: PARAM, PARM, Param, Parm, param, parm, and so on. If a match is found, then
'Param' => value
is sent to the:Init
subroutine. Note that the:InitArgs
hash key is substituted for the original argument key. This eliminates the need for any parameter key pattern matching within the:Init
subroutine.If additional parameter specifications (described below) are used, the syntax changes, and the regular expression is moved inside a hash ref:
my %init_args :InitArgs = ( 'Param' => { 'Regex' => qr/^PARA?M$/i, }, );
- Mandatory Parameters
-
Mandatory parameters are declared as follows:
my %init_args :InitArgs = ( # Mandatory parameter requiring exact matching 'INFO' => { 'Mandatory' => 1, }, # Mandatory parameter with pattern matching 'input' => { 'Regex' => qr/^in(?:put)?$/i, 'Mandatory' => 1, }, );
If a mandatory parameter is missing from the argument list to
new
, an error is generated. - Default Values
-
For optional parameters, defaults can be specified:
my %init_args :InitArgs = ( 'LEVEL' => { 'Regex' => qr/^lev(?:el)?|lvl$/i, 'Default' => 3, }, );
- Type Checking
-
The parameter's type can also be specified:
my %init_args :InitArgs = ( 'LEVEL' => { 'Regex' => qr/^lev(?:el)?|lvl$/i, 'Default' => 3, 'Type' => 'Numeric', }, );
Available types are:
- Numeric
-
Can also be specified as
Num
orNumber
. This uses Scalar::Util::looks_like_number to test the input value. - List
-
This type permits a single value (that is then placed in an array ref) or an array ref.
- A class name
-
The parameter's type must be of the specified class, or one of its sub-classes (i.e., type checking is done using
->isa()
). For example,My::Class
. - Other reference type
-
The parameter's type must be of the specified reference type (as returned by ref()). For example,
CODE
.
The first two types above are case-insensitive (e.g., 'NUMERIC', 'Numeric', 'numeric', etc.); the last two are case-sensitive.
The
Type
keyword may also be paired with a code reference to provide custom type checking. The code ref can either be in the form of an anonymous subroutine, or it can be derived from a (publicly accessible) subroutine. The result of executing the code ref on the initializer should be a boolean value.package My::Class; { use Object::InsideOut; # For initializer type checking, the subroutine can NOT be made 'Private' sub is_int { my $arg = $_[0]; return (Scalar::Util::looks_like_number($arg) && (int($arg) == $arg)); } my @level :Field; my @comment :Field; my %init_args :InitArgs = ( 'LEVEL' => { 'Field' => \@level, # Type checking using a named subroutine 'Type' => \&is_int, }, 'COMMENT' => { 'Field' => \@comment, # Type checking using an anonymous subroutine 'Type' => sub { $_[0] ne '' } }, ); }
- Automatic Processing
-
You can specify automatic processing for a parameter's value such that it is placed directly into a field array/hash, and not sent to the
:Init
subroutine:my @hosts :Field; my %init_args :InitArgs = ( 'HOSTS' => { # Allow 'host' or 'hosts' - case-insensitive 'Regex' => qr/^hosts?$/i, # Mandatory parameter 'Mandatory' => 1, # Allow single value or array ref 'Type' => 'List', # Automatically put the parameter into @hosts 'Field' => \@hosts, }, );
In this case, when the host parameter is found, it is automatically put into the
@hosts
array, and a'HOSTS' => value
pair is not sent to the:Init
subroutine. In fact, if you specify fields for all your parameters, then you don't even need to have an:Init
subroutine! All the work will be taken care of for you. - Parameter Preprocessing
-
You can specify a subroutine for a parameter that will be called on that parameter prior to any of the other parameter actions described above being taken:
package My::Class; { use Object::InsideOut; my @data :Field; my %init_args :InitArgs = ( 'DATA' => { 'Preprocess' => \&my_preproc, 'Field' => \@data, 'Type' => 'Numeric', 'Default' => 99, }, ); sub my_preproc { my ($class, $param, $spec, $obj, $value) = @_; # Preform parameter preprocessing ... # Return result return ...; } }
As the above illustrates, the parameter preprocessing subroutine is sent five arguments:
The name of the class associated with the parameter
This would be
My::Class
in the example above.The name of the parameter
This would be
DATA
in the example above.A hash ref of the parameter's specifiers
The hash ref paired to the
DATA
key in the:InitArgs
hash.The object being initialized
The parameter's value
This is the value assigned to the parameter in the
->new()
method's argument list. If the parameter was not provided to->new()
, thenundef
will sent.
The return value of the preprocessing subroutine will then be assigned to the parameter.
Be careful about what types of data the preprocessing subroutine tries to make use of
external
to the arguments supplied. For instance, because the order of parameter processing is not specified, the preprocessing subroutine cannot rely on whether or not some other parameter is set. Such processing would need to be done in the:Init
subroutine. It can, however, make use of object data set by classes higher up in the class hierarchy. (That is why the object is provided as one of the arguments.)Possible uses for parameter preprocessing include:
Overriding the supplied value (or even deleting it by returning
undef
)Providing a dynamically-determined default value
(In the above, Regex may be Regexp or just Re, Default may be Defaults or Def, and Preprocess may be Preproc or Pre. They and the other specifier keys are case-insensitive, as well.)
Getting Data
In class code, data can be fetched directly from an object's field array (hash) using the object's ID:
$data = $field[$$self];
# or
$data = $field{$$self};
Setting Data
Object::InsideOut automatically exports a method called set
to each class. This method should be used in class code to put data into object field arrays/hashes whenever there is the possibility that the class code may be used in an application that uses threads::shared.
As mentioned above, data can be put directly into an object's field array (hash) using the object's ID:
$field[$$self] = $data;
# or
$field{$$self} = $data;
However, in a threaded application that uses data sharing (i.e., uses threads::shared
), $data
must be converted into shared data so that it can be put into the field array (hash). The ->set()
method handles all those details for you.
The ->set()
method, requires two arguments: A reference to the object field array/hash, and the data (as a scalar) to be put in it:
$self->set(\@field, $data);
# or
$self->set(\%field, $data);
To be clear, the ->set()
method is used inside class code; not application code. Use it inside any object methods that set data in object field arrays/hashes.
In the event of a method naming conflict, the ->set()
method can be called using its fully-qualified name:
$self->Object::InsideOut::set(\@field, $data);
Automatic Accessor Generation
As part of the "Field Declarations", you can optionally specify the automatic generation of accessor methods.
- Accessor Naming
-
You can specify the generation of a pair of standard-named accessor methods (i.e., prefixed by get_ and set_):
my @data :Field('Standard' => 'data');
The above results in Object::InsideOut automatically generating accessor methods named
get_data
andset_data
. (The keywordStandard
is case-insensitive, and can be abbreviated toStd
.)You can also separately specify the get and/or set accessors:
my @name :Field('Get' => 'name', 'Set' => 'change_name'); # or my @name :Field('Get' => 'get_name'); # or my @name :Field('Set' => 'new_name');
For the above, you specify the full name of the accessor(s) (i.e., no prefix is added to the given name(s)). (The
Get
andSet
keywords are case-insensitive.)You can specify the automatic generation of a combined get/set accessor method:
my @comment :Field('Accessor' => 'comment');
which would be used as follows:
# Set a new comment $obj->comment("I have no comment, today."); # Get the current comment my $cmt = $obj->comment();
(The keyword
Accessor
is case-insensitive, and can be abbreviated toAcc
or can be specified asget_set
orCombined
orCombo
orMutator
.) - Set Accessor Return Value
-
For any of the automatically generated methods that perform set operations, the default for the method's return value is the value being set (i.e., the new value).
The
Return
keyword allows you to modify the default behavior. The other options are to have the set accessor return the old (previous) value (orundef
if unset):my @data :Field('Set' => 'set_data', 'Return' => 'Old');
or to return the object itself:
my @data :Field('Set' => 'set_data', 'Return' => 'Object');
Returning the object from a set method allows it to be chained to other methods:
$obj->set_data($data)->do_something();
If desired, you can explicitly specify the default behavior of returning the new value:
my @data :Field('Set' => 'set_data', 'Return' => 'New');
(
Return
may be abbreviated toRet
;Previous
,Prev
andPrior
are synonymous withOld
; andObject
may be abbreviated toObj
and is also synonymous withSelf
. All these are case-insensitive.) - Accessor Type Checking
-
You may, optionally, direct Object::InsideOut to add type-checking code to the set/combined accessor:
my @level :Field('Accessor' => 'level', 'Type' => 'Numeric');
Available types are:
- Numeric
-
Can also be specified as
Num
orNumber
. This uses Scalar::Util::looks_like_number to test the input value. - List or Array
-
This type permits the accessor to accept multiple values (that are then placed in an array ref) or a single array ref.
- Array_ref
-
This specifies that the accessor can only accept a single array reference. Can also be specified as
Arrayref
. - Hash
-
This type allows multiple
key => value
pairs (that are then placed in a hash ref) or a single hash ref. - Hash_ref
-
This specifies that the accessor can only accept a single hash reference. Can also be specified as
Hashref
. - A class name
-
The accessor will only accept a value of the specified class, or one of its sub-classes (i.e., type checking is done using
->isa()
). For example,My::Class
. - Other reference type
-
The accessor will only accept a value of the specified reference type (as returned by ref()). For example,
CODE
.
The types above are case-insensitive (e.g., 'NUMERIC', 'Numeric', 'numeric', etc.), except for the last two.
The
Type
keyword can also be paired with a code reference to provide custom type checking. The code ref can either be in the form of an anonymous subroutine, or a fully-qualified subroutine name. The result of executing the code ref on the input argument should be a boolean value.package My::Class; { use Object::InsideOut; # For accessor type checking, the subroutine can be made 'Private' sub positive :Private { return (Scalar::Util::looks_like_number($_[0]) && ($_[0] > 0)); } # Code ref is an anonymous subroutine # (This one checks that the argument is a SCALAR) my @data :Field('Accessor' => 'data', 'Type' => sub { ! ref($_[0]) } ); # Code ref using a fully-qualified subroutine name my @num :Field('Accessor' => 'num', 'Type' => \&My::Class::positive); }
Note that it is an error to use the
Type
keyword by itself, or in combination with only theGet
keyword.Due to limitations in the Perl parser, you cannot use line wrapping with the
:Field
attribute:# This doesn't work # my @level :Field('Get' => 'level', # 'Set' => 'set_level', # 'Type' => 'Num'); # Must be all on one line my @level :Field('Get' =>'level', 'Set' => 'set_level', 'Type' => 'Num');
Weak Fields
Frequently, it is useful to store weakened references to data or objects in a field. Such a field can be declared as weak
so that data (i.e., references) set via automatically generated accessors, :InitArgs
, the ->set()
method, etc., will automatically be weakened after being stored in the field array/hash.
my @data :Field('Weak' => 1);
NOTE: If data in a weak field is set directly (i.e., the ->set()
method is not used), then weaken() must be invoked on the stored reference afterwards:
$field[$$self] = $data;
Scalar::Util::weaken($field[$$self]);
Field Cloning
Object cloning can be controlled at the field level such that only specified fields are deep copied when ->clone()
is called without any arguments. This is done by adding another specifier to the :Field
attribute:
my @data :Field('Clone' => 'deep');
# or
my @data :Field('Copy' => 'deep');
# or
my @data :Field('Deep' => 1);
As usual, the keywords above are case-insensitive.
Object ID
By default, the ID of an object is derived from a sequence counter for the object's class hierarchy. This should suffice for nearly all cases of class development. If there is a special need for the module code to control the object ID (see Math::Random::MT::Auto as an example), then an :ID
labeled subroutine can be specified:
sub _id :ID
{
my $class = $_[0];
# Determine a unique object ID
...
return ($id);
}
The ID returned by your subroutine can be any kind of regular scalar (e.g., a string or a number). However, if the ID is something other than a low-valued integer, then you will have to architect all your classes using hashes for the object fields.
Within any class hierarchy only one class may specify an :ID
subroutine.
Object Replication
Object replication occurs explicitly when the ->clone()
method is called on an object, and implicitly when threads are created in a threaded application. In nearly all cases, Object::InsideOut will take care of all the details for you.
In rare cases, a class may require special handling for object replication. It must then provide a subroutine labeled with the :Replicate
attribute. This subroutine will be sent three arguments: The parent and the cloned objects, and a flag:
sub _replicate :Replicate
{
my ($parent, $clone, $flag) = @_;
# Special object replication processing
if ($clone eq 'CLONE') {
# Handling for thread cloning
...
} elsif ($clone eq 'deep') {
# Deep copy of the parent
...
} else {
# Shallow copying
...
}
}
In the case of thread cloning, $flag
will be set to 'CLONE'
, and the $parent
object is just an un-blessed anonymous scalar reference that contains the ID for the object in the parent thread.
When invoked via the ->clone()
method, $flag
may be either an empty string which denotes that a shallow copy is being produced for the clone, or $flag
may be set to 'deep'
indicating a deep copy is being produced.
The :Replicate
subroutine only needs to deal with the special replication processing needed by the object: Object::InsideOut will handle all the other details.
Object Destruction
Object::InsideOut exports a DESTROY
method to each class that deletes an object's data from the object field arrays (hashes). If a class requires additional destruction processing (e.g., closing filehandles), then it must provide a subroutine labeled with the :Destroy
attribute. This subroutine will be sent the object that is being destroyed:
sub _destroy :Destroy
{
my $obj = $_[0];
# Special object destruction processing
}
The :Destroy
subroutine only needs to deal with the special destruction processing: The DESTROY
method will handle all the other details of object destruction.
Cumulative Methods
Normally, methods with the same name in a class hierarchy are masked (i.e., overridden) by inheritance - only the method in the most-derived class is called. With cumulative methods, this masking is removed, and the same named method is called in each of the classes within the hierarchy. The return results from each call (if any) are then gathered together into the return value for the original method call. For example,
package My::Class; {
use Object::InsideOut;
sub what_am_i :Cumulative
{
my $self = shift;
my $ima = (ref($self) eq __PACKAGE__)
? q/I was created as a /
: q/My top class is /;
return ($ima . __PACKAGE__);
}
}
package My::Foo; {
use Object::InsideOut 'My::Class';
sub what_am_i :Cumulative
{
my $self = shift;
my $ima = (ref($self) eq __PACKAGE__)
? q/I was created as a /
: q/I'm also a /;
return ($ima . __PACKAGE__);
}
}
package My::Child; {
use Object::InsideOut 'My::Foo';
sub what_am_i :Cumulative
{
my $self = shift;
my $ima = (ref($self) eq __PACKAGE__)
? q/I was created as a /
: q/I'm in class /;
return ($ima . __PACKAGE__);
}
}
package main;
my $obj = My::Child->new();
my @desc = $obj->what_am_i();
print(join("\n", @desc), "\n");
produces:
My top class is My::Class
I'm also a My::Foo
I was created as a My::Child
When called in a list context (as in the above), the return results of cumulative methods are accumulated, and returned as a list.
In a scalar context, a results object is returned that segregates the results from the cumulative method calls by class. Through overloading, this object can then be dereferenced as an array, hash, string, number, or boolean. For example, the above could be rewritten as:
my $obj = My::Child->new();
my $desc = $obj->what_am_i(); # Results object
print(join("\n", @{$desc}), "\n"); # Dereference as an array
The following uses hash dereferencing:
my $obj = My::Child->new();
my $desc = $obj->what_am_i();
while (my ($class, $value) = each(%{$desc})) {
print("Class $class reports:\n\t$value\n");
}
and produces:
Class My::Class reports:
My top class is My::Class
Class My::Child reports:
I was created as a My::Child
Class My::Foo reports:
I'm also a My::Foo
As illustrated above, Cumulative methods are tagged with the :Cumulative
attribute (or :Cumulative(top down)
), and propagate from the top down through the class hierarchy (i.e., from the base classes down through the child classes). If tagged with :Cumulative(bottom up)
, they will propagated from the object's class upwards through the parent classes.
Chained Methods
In addition to :Cumulative
, Object::InsideOut provides a way of creating methods that are chained together so that their return values are passed as input arguments to other similarly named methods in the same class hierarchy. In this way, the chained methods act as though they were piped together.
For example, imagine you had a method called format_name
that formats some text for display:
package Subscriber; {
use Object::InsideOut;
sub format_name {
my ($self, $name) = @_;
# Strip leading and trailing whitespace
$name =~ s/^\s+//;
$name =~ s/\s+$//;
return ($name);
}
}
And elsewhere you have a second class that formats the case of names:
package Person; {
use Lingua::EN::NameCase qw(nc);
use Object::InsideOut;
sub format_name {
my ($self, $name) = @_;
# Attempt to properly case names
return (nc($name));
}
}
And you decide that you'd like to perform some formatting of your own, and then have all the parent methods apply their own formatting. Normally, if you have a single parent class, you'd just call the method directly with $self-
SUPER::format_name($name)>, but if you have more than one parent class you'd have to explicitly call each method directly:
package Customer; {
use Object::InsideOut qw(Person Subscriber);
sub format_name {
my ($self, $name) = @_;
# Compress all whitespace into a single space
$name =~ s/\s+/ /g;
$name = $self->Subscriber::format_name($name);
$name = $self->Person::format_name($name);
return $name;
}
}
With Object::InsideOut you'd add the :Chained
attribute to each class's format_name
method, and the methods will be chained together automatically:
package Subscriber; {
use Object::InsideOut;
sub format_name :Chained {
my ($self, $name) = @_;
# Strip leading and trailing whitespace
$name =~ s/^\s+//;
$name =~ s/\s+$//;
return ($name);
}
}
package Person; {
use Lingua::EN::NameCase qw(nc);
use Object::InsideOut;
sub format_name :Chained {
my ($self, $name) = @_;
# Attempt to properly case names
return (nc($name));
}
}
package Customer; {
use Object::InsideOut qw(Person Subscriber);
sub format_name :Chained {
my ($self, $name) = @_;
# Compress all whitespace into a single space
$name =~ s/\s+/ /g;
return ($name);
}
}
So passing in someone's name to format_name
in Customer
would cause leading and trailing whitespace to be removed, then the name to be properly cased, and finally whitespace to be compressed to a single space. The resulting $name
would be returned to the caller.
The default direction is to chain methods from the base classes at the top of the class hierarchy down through the child classes. You may use the attribute :Chained(top down)
to make this more explicit.
If you label the method with the :Chained(bottom up)
attribute, then the chained methods are called starting with the object's class and working upwards through the class hierarchy, similar to how :Cumulative(bottom up)
works.
Unlike :Cumulative
methods, :Chained
methods return a scalar when used in a scalar context; not a results object.
Automethods
There are significant issues related to Perl's AUTOLOAD
mechanism that cause it to be ill-suited for use in a class hierarchy. Therefore, Object::InsideOut implements its own :Automethod
mechanism to overcome these problems
Classes requiring AUTOLOAD
-type capabilities must provided a subroutine labeled with the :Automethod
attribute. The :Automethod
subroutine will be called with the object and the arguments in the original method call (the same as for AUTOLOAD
). The :Automethod
subroutine should return either a subroutine reference that implements the requested method's functionality, or else undef
to indicate that it doesn't know how to handle the request.
Using its own AUTOLOAD
subroutine (which is exported to every class), Object::InsideOut walks through the class tree, calling each :Automethod
subroutine, as needed, to fulfill an unimplemented method call.
The name of the method being called is passed as $_
instead of $AUTOLOAD
, and does not have the class name prepended to it. If the :Automethod
subroutine also needs to access the $_
from the caller's scope, it is available as $CALLER::_
.
Automethods can also be made to act as "Cumulative Methods" or "Chained Methods". In these cases, the :Automethod
subroutine should return two values: The subroutine ref to handle the method call, and a string designating the type of method. The designator has the same form as the attributes used to designate :Cumulative
and :Chained
methods:
':Cumulative' or ':Cumulative(top down)'
':Cumulative(bottom up)'
':Chained' or ':Chained(top down)'
':Chained(bottom up)'
The following skeletal code illustrates how an :Automethod
subroutine could be structured:
sub _automethod :Automethod
{
my $self = shift;
my @args = @_;
my $method_name = $_;
# This class can handle the method directly
if (...) {
my $handler = sub {
my $self = shift;
...
return ...;
};
### OPTIONAL ###
# Install the handler so it gets called directly next time
# no strict refs;
# *{__PACKAGE__.'::'.$method_name} = $handler;
################
return ($handler);
}
# This class can handle the method as part of a chain
if (...) {
my $chained_handler = sub {
my $self = shift;
...
return ...;
};
return ($chained_handler, ':Chained');
}
# This class cannot handle the method request
return;
}
Note: The OPTIONAL code above for installing the generated handler as a method should not be used with :Cumulative
or :Chained
Automethods.
Object Serialization
- my $array_ref = $obj->dump();
- my $string = $obj->dump(1);
-
Object::InsideOut exports a method called
dump
to each class that returns either a Perl or a string representation of the object that invokes the method.The Perl representation is returned when
->dump()
is called without arguments. It consists of an array ref whose first element is the name of the object's class, and whose second element is a hash ref containing the object's data. The object data hash ref contains keys for each of the classes that make up the object's hierarchy. The values for those keys are hash refs containingkey => value
pairs for the object's fields. For example:[ 'My::Class::Sub', { 'My::Class' => { 'data' => 'value' }, 'My::Class::Sub' => { 'life' => 42 } } ]
The name for an object field (data and life in the example above) can be specified as part of the field declaration using the
NAME
keyword:my @life :Field('Name' => 'life');
If the
NAME
keyword is not present, then the name for a field will be either the tag from the:InitArgs
array that is associated with the field, its get method name, its set method name, or, failing all that, a string of the formARRAY(0x...)
orHASH(0x...)
.When called with a true argument,
->dump()
returns a string version of the Perl representation using Data::Dumper.Note that using Data::Dumper directly on an inside-out object will not produce the desired results (it'll just output the contents of the scalar ref). Also, if inside-out objects are stored inside other structures, a dump of those structures will not contain the contents of the object's fields.
In the event of a method naming conflict, the
->dump()
method can be called using its fully-qualified name:my $dump = $obj->Object::InsideOut::dump();
- my $obj = Object::InsideOut->pump($data);
-
Object::InsideOut-
pump()> takes the output from the->dump()
method, and returns an object that is created using that data. If$data
is the array ref returned by using$obj->dump()
, then the data is inserted directly into the corresponding fields for each class in the object's class hierarchy. If If$data
is the string returned by using$obj->dump(1)
, then it iseval
ed to turn it into an array ref, and then processed as above.If any of an object's fields are dumped to field name keys of the form
ARRAY(0x...)
orHASH(0x...)
(see above), then the data will not be reloadable usingObject::InsideOut-
pump()>. To overcome this problem, the class developer must either addName
keywords to the:Field
declarations (see above), or provide a:Dumper
/:Pumper
pair of subroutines as described below. :Dumper
Subroutine Attribute-
If a class requires special processing to dump its data, then it can provide a subroutine labeled with the
:Dumper
attribute. This subroutine will be sent the object that is being dumped. It may then return any type of scalar the developer deems appropriate. Most likely this would be a hash ref containingkey => value
pairs for the object's fields. For example,my @data :Field; sub _dump :Dumper { my $obj = $_[0]; my %field_data; $field_data{'data'} = $data[$$obj]; return (\%field_data); }
Just be sure not to call your
:Dumper
subroutinedump
as that is the name of the dump method exported by Object::InsideOut as explained above. :Pumper
Subroutine Attribute-
If a class supplies a
:Dumper
subroutine, it will most likely need to provide a complementary:Pumper
labeled subroutine that will be used as part of creating an object from dumped data usingObject::InsideOut::pump()
. The subroutine will be supplied the new object that is being created, and whatever scalar was returned by the:Dumper
subroutine. The corresponding:Pumper
for the example:Dumper
above would be:sub _pump :Pumper { my ($obj, $field_data) = @_; $data[$$obj] = $field_data->{'data'}; }
- Storable
-
Object::InsideOut also supports object serialization using the Storable module. There are two methods for specifying that a class can be serialized using Storable. The first method involves adding Storable to the Object::InsideOut declaration in your package:
package My::Class; { use Object::InsideOut qw(Storable); ... }
and adding
use Storable;
in your application. Then you can use the->store()
and->freeze()
methods to serialize your objects, and theretrieve()
andthaw()
subroutines to deserialize them.package main; use Storable; use My::Class; my $obj = My::Class->new(...); $obj->store('/tmp/object.dat'); ... my $obj2 = retrieve('/tmp/object.dat');
The other method of specifying Storable serialization involves setting a
::storable
variable (inside aBEGIN
block) for the class prior to its use:package main; use Storable; BEGIN { $My::Class::storable = 1; } use My::Class;
Dynamic Field Creation
Normally, object fields are declared as part of the class code. However, some classes may need the capability to create object fields on-the-fly, for example, as part of an :Automethod
. Object::InsideOut provides a class method for this:
# Dynamically create a hash field with standard accessors
Object::InsideOut->create_field($class, '%'.$fld, "'Standard'=>'$fld'");
The first argument is the class to which the field will be added. The second argument is a string containing the name of the field preceeded by either a @
or %
to declare an array field or hash field, respectively. The third argument is a string containing key => value
pairs used in conjunction with the :Field
attribute for generating field accessors.
Here's a more elaborate example used in inside an :Automethod
:
package My::Class; {
use Object::InsideOut;
sub auto :Automethod
{
my $self = $_[0];
my $class = ref($self) || $self;
my $method = $_;
# Extract desired field name from get_/set_ method name
my ($fld_name) = $method =~ /^[gs]et_(.*)$/;
if (! $fld_name) {
return; # Not a recognized method
}
# Create the field and its standard accessors
Object::InsideOut->create_field($class, '@'.$fld_name,
"'Standard'=>'$fld_name'");
# Return code ref for newly created accessor
no strict 'refs';
return *{$class.'::'.$method}{'CODE'};
}
}
Restricted and Private Methods
Access to certain methods can be narrowed by use of the :Restricted
and :Private
attributes. :Restricted
methods can only be called from within the class's hierarchy. :Private
methods can only be called from within the method's class.
Without the above attributes, most methods have public access. If desired, you may explicitly label them with the :Public
attribute.
You can also specify access permissions on automatically generated accessors:
my @data :Field('Standard' => 'data', 'Permission' => 'private');
my @info :Field('Set' => 'set_info', 'Perm' => 'restricted');
my @internal :Field('Acc' => 'internal', 'Private' => 1);
my @state :Field('Get' => 'state', 'Restricted' => 1);
Such permissions apply to both of the get and set accessors created on a field. If different permissions are required on an accessor pair, then you'll have to create the accessors yourself, using the :Restricted
and :Private
attributes when applicable:
# Create a private set method on the 'foo' field
my @foo :Field('Set' => 'set_foo', 'Priv' => 1);
# Read access on the 'foo' field is restricted
sub get_foo :Restrict
{
return ($foo[${$_[0]}]);
}
# Create a restricted set method on the 'bar' field
my %bar :Field('Set' => 'set_bar', 'Perm' => 'restrict');
# Read access on the 'foo' field is public
sub get_bar
{
return ($bar{${$_[0]}});
}
Hidden Methods
For subroutines marked with the following attributes:
Object::InsideOut normally renders them uncallable (hidden) to class and application code (as they should normally only be needed by Object::InsideOut itself). If needed, this behavior can be overridden by adding the PUBLIC
, RESTRICTED
or PRIVATE
keywords following the attribute:
sub _init :Init(private) # Callable from within this class
{
my ($self, $args) = @_;
...
}
NOTE: A bug in Perl 5.6.0 prevents using these access keywords. As such, subroutines marked with the above attributes will be left with public access.
NOTE: The above cannot be accomplished by using the corresponding attributes. For example:
# sub _init :Init :Private # Wrong syntax - doesn't work
Object Coercion
Object::InsideOut provides support for various forms of object coercion through the overload mechanism. For instance, if you want an object to be usable directly in a string, you would supply a subroutine in your class labeled with the :Stringify
attribute:
sub as_string :Stringify
{
my $self = $_[0];
my $string = ...;
return ($string);
}
Then you could do things like:
print("The object says, '$obj'\n");
For a boolean context, you would supply:
sub as_string :Boolify
{
my $self = $_[0];
my $true_or_false = ...;
return ($true_or_false);
}
and use it in this manner:
if (! defined($obj)) {
# The object is undefined
....
} elsif (! $obj) {
# The object returned a false value
...
}
The following coercion attributes are supported:
Coercing an object to a scalar (:Scalarify
) is not supported as $$obj
is the ID of the object and cannot be overridden.
FOREIGN CLASS INHERITANCE
Object::InsideOut supports inheritance from foreign (i.e., non-Object::InsideOut) classes. This means that your classes can inherit from other Perl class, and access their methods from your own objects.
One method of declaring foreign class inheritance is to add the class name to the Object::InsideOut declaration inside your package:
package My::Class; {
use Object::InsideOut qw(Foreign::Class);
...
}
This allows you to access the foreign class's static (i.e., class) methods from your own class. For example, suppose Foreign::Class
has a class method called foo
. With the above, you can access that method using My::Class->foo()
instead.
Multiple foreign inheritance is supported, as well:
package My::Class; {
use Object::InsideOut qw(Foreign::Class Other::Foreign::Class);
...
}
- $self->inherit($obj, ...);
-
To use object methods from foreign classes, an object must inherit from an object of that class. This would normally be done inside a class's
:Init
subroutine:package My::Class; { use Object::InsideOut qw(Foreign::Class); sub init :Init { my ($self, $args) = @_; my $foreign_obj = Foreign::Class->new(...); $self->inherit($foreign_obj); } }
Thus, with the above, if
Foreign::Class
has an object method calledbar
, you can call that method from your own objects:package main; my $obj = My::Class->new(); $obj->bar();
Object::InsideOut's
AUTOLOAD
subroutine handles the dispatching of the->bar()
method call using the internally held inherited object (in this case,$foreign_obj
).Multiple inheritance is supported, as well: You can call the
->inherit()
method multiple times, or make just one call with all the objects to be inherited from.->inherit()
is a restricted method. In other words, you cannot use it on an object outside of code belonging to the object's class tree (e.g., you can't call it from application code).In the event of a method naming conflict, the
->inherit()
method can be called using its fully-qualified name:$self->Object::InsideOut::inherit($obj);
- my @objs = $self->heritage();
- my $obj = $self->heritage($class);
- my @objs = $self->heritage($class1, $class2, ...);
-
Your class code can retrieve any inherited objects using the
->heritage()
method. When called without any arguments, it returns a list of any objects that were stored by the calling class using the calling object. In other words, if classMy::Class
uses object$obj
to store foreign objects$fobj1
and$fobj2
, then later on in classMy::Class
,$obj->heritage()
will return$fobj1
and$fobj2
.->heritage()
can also be called with one or more class name arguments. In this case, only objects of the specified class(es) are returned.In the event of a method naming conflict, the
->heritage()
method can be called using its fully-qualified name:my @objs = $self->Object::InsideOut::heritage();
- $self->disinherit($class [, ...])
- $self->disinherit($obj [, ...])
-
The
->disinherit()
method disassociates (i.e., deletes) the inheritance of foreign object(s) from an object. The foreign objects may be specified by class, or using the actual inherited object (retrieved via->heritage()
, for example).The call is only effective when called inside the class code that established the initial inheritance. In other words, if an inheritance is set up inside a class, then disinheritance can only be done from inside that class.
In the event of a method naming conflict, the
->disinherit()
method can be called using its fully-qualified name:$self->Object::InsideOut::disinherit($obj [, ...])
NOTE: With foreign inheritance, you only have access to class and object methods. The encapsulation of the inherited objects is strong, meaning that only the class where the inheritance takes place has direct access to the inherited object. If access to the inherited objects themselves, or their internal hash fields (in the case of blessed hash objects), is needed outside the class, then you'll need to write your own accessors for that.
LIMITATION: You cannot use fully-qualified method names to access foreign methods (when encapsulated foreign objects are involved). Thus, the following will not work:
my $obj = My::Class->new();
$obj->Foreign::Class::bar();
Normally, you shouldn't ever need to do the above: $obj->bar()
would suffice.
The only time this may be an issue is when the native class overrides an inherited foreign class's method (e.g., My::Class
has its own ->bar()
method). Such overridden methods are not directly callable. If such overriding is intentional, then this should not be an issue: No one should be writing code that tries to by-pass the override. However, if the overriding is accidently, then either the native method should be renamed, or the native class should provide a wrapper method so that the functionality of the overridden method is made available under a different name.
use base
and Fully-qualified Method Names
The foreign inheritance methodology handled by the above is predicated on non-Object::InsideOut classes that generate their own objects and expect their object methods to be invoked via those objects.
There are exceptions to this rule:
- 1. Foreign object methods that expect to be invoked via the inheriting class's object, or foreign object methods that don't care how they are invoked (i.e., they don't make reference to the invoking object).
-
This is the case where a class provides auxiliary methods for your objects, but from which you don't actually create any objects (i.e., there is no corresponding foreign object, and
$obj->inherit($foreign)
is not used.)In this case, you can either:
a. Declare the foreign class using the standard method (i.e.,
use Object::InsideOut qw(Foreign::Class);
), and invoke its methods using their full path (e.g.,$obj->Foreign::Class::method();
); orb. You can use the base pragma so that you don't have to use the full path for foreign methods.
package My::Class; { use Object::InsideOut; use base 'Foreign::Class'; ... }
The former scheme is faster.
- 2. Foreign class methods that expect to be invoked via the inheriting class.
-
As with the above, you can either invoke the class methods using their full path (e.g.,
My::Class->Foreign::Class::method();
), or you canuse base
so that you don't have to use the full path. Again, using the full path is faster.Class::Singleton is an example of this type of class.
- 3. Class methods that don't care how they are invoked (i.e., they don't make reference to the invoking class).
-
In this case, you can either use
use Object::InsideOut qw(Foreign::Class);
for consistency, or useuse base qw(Foreign::Class);
if (slightly) better performance is needed.
If you're not familiar with the inner workings of the foreign class such that you don't know if or which of the above exceptions applies, then the formulaic approach would be to first use the documented method for foreign inheritance (i.e., use Object::InsideOut qw(Foreign::Class);
). If that works, then I strongly recommend that you just use that approach unless you have a good reason not to. If it doesn't work, then try use base
.
THREAD SUPPORT
For Perl 5.8.1 and later, this module fully supports threads (i.e., is thread safe), and supports the sharing of Object::InsideOut objects between threads using threads::shared.
To use Object::InsideOut in a threaded application, you must put use threads;
at the beginning of the application. (The use of require threads;
after the program is running is not supported.) If object sharing it to be utilized, then use threads::shared;
should follow.
If you just use threads;
, then objects from one thread will be copied and made available in a child thread.
The addition of use threads::shared;
in and of itself does not alter the behavior of Object::InsideOut objects. The default behavior is to not share objects between threads (i.e., they act the same as with use threads;
alone).
To enable the sharing of objects between threads, you must specify which classes will be involved with thread object sharing. There are two methods for doing this. The first involves setting a ::shared
variable (inside a BEGIN
block) for the class prior to its use:
use threads;
use threads::shared;
BEGIN {
$My::Class::shared = 1;
}
use My::Class;
The other method is for a class to add a :SHARED
flag to its use Object::InsideOut ...
declaration:
package My::Class; {
use Object::InsideOut ':SHARED';
...
}
When either sharing flag is set for one class in an object hierarchy, then all the classes in the hierarchy are affected.
If a class cannot support thread object sharing (e.g., one of the object fields contains code refs [which Perl cannot share between threads]), it should specifically declare this fact:
package My::Class; {
use Object::InsideOut ':NOT_SHARED';
...
}
However, you cannot mix thread object sharing classes with non-sharing classes in the same class hierarchy:
use threads;
use threads::shared;
package My::Class; {
use Object::InsideOut ':SHARED';
...
}
package Other::Class; {
use Object::InsideOut ':NOT_SHARED';
...
}
package My::Derived; {
use Object::InsideOut qw(My::Class Other::Class); # ERROR!
...
}
Here is a complete example with thread object sharing enabled:
use threads;
use threads::shared;
package My::Class; {
use Object::InsideOut ':SHARED';
# One list-type field
my @data :Field('Accessor' => 'data', 'Type' => 'List');
}
package main;
# New object
my $obj = My::Class->new();
# Set the object's 'data' field
$obj->data(qw(foo bar baz));
# Print out the object's data
print(join(', ', @{$obj->data()}), "\n"); # "foo, bar, baz"
# Create a thread and manipulate the object's data
my $rc = threads->create(
sub {
# Read the object's data
my $data = $obj->data();
# Print out the object's data
print(join(', ', @{$data}), "\n"); # "foo, bar, baz"
# Change the object's data
$obj->data(@$data[1..2], 'zooks');
# Print out the object's modified data
print(join(', ', @{$obj->data()}), "\n"); # "bar, baz, zooks"
return (1);
}
)->join();
# Show that changes in the object are visible in the parent thread
# I.e., this shows that the object was indeed shared between threads
print(join(', ', @{$obj->data()}), "\n"); # "bar, baz, zooks"
SPECIAL USAGE
Usage With Exporter
It is possible to use Exporter to export functions from one inside-out object class to another:
use strict;
use warnings;
package Foo; {
use Object::InsideOut 'Exporter';
BEGIN {
our @EXPORT_OK = qw(foo_name);
}
sub foo_name
{
return (__PACKAGE__);
}
}
package Bar; {
use Object::InsideOut 'Foo' => [ qw(foo_name) ];
sub get_foo_name
{
return (foo_name());
}
}
package main;
print("Bar got Foo's name as '", Bar::get_foo_name(), "'\n");
Note that the BEGIN
block is needed to ensure that the Exporter symbol arrays (in this case @EXPORT_OK
) get populated properly.
Usage With require
and mod_perl
Object::InsideOut usage under mod_perl and with runtime-loaded classes is supported automatically; no special coding is required.
Singleton Classes
A singleton class is a case where you would provide your own ->new()
method that in turn calls Object::InsideOut's ->new()
method:
package My::Class; {
use Object::InsideOut;
my $singleton;
sub new {
my $thing = shift;
if (! $singleton) {
$singleton = $thing->Object::InsideOut::new(@_);
}
return ($singleton);
}
}
DIAGNOSTICS
This module uses Exception::Class
for reporting errors. The base error class for this module is OIO
. Here is an example of the basic manner for trapping and handling errors:
my $obj;
eval { $obj = My::Class->new(); };
if (my $e = OIO->caught()) {
print(STDERR "Failure creating object: $e\n");
exit(1);
}
I have tried to make the messages and information returned by the error objects as informative as possible. Suggested improvements are welcome. Also, please bring to my attention any conditions that you encounter where an error occurs as a result of Object::InsideOut code that doesn't generate an Exception::Class object. Here is one such error:
- Invalid ARRAY/HASH attribute
-
This error indicates you forgot the following in your class's code:
use Object::InsideOut qw(Parent::Class ...);
BUGS AND LIMITATIONS
You cannot overload an object to a scalar context (i.e., can't :SCALARIFY
).
You cannot use two instances of the same class with mixed thread object sharing in same application.
Cannot use attributes on subroutine stubs (i.e., forward declaration without later definition) with :Automethod
:
package My::Class; {
sub method :Private; # Will not work
sub _automethod :Automethod
{
# Code to handle call to 'method' stub
}
}
Due to limitations in the Perl parser, you cannot use line wrapping with the :Field
attribute.
If a set accessor accepts scalars, then you can store any inside-out object type in it. If its Type
is set to HASH
, then it can store any blessed hash object.
It is possible to hack together a fake Object::InsideOut object, and so gain access to another object's data:
my $fake = bless(\do{my $scalar}, 'Some::Class');
$$fake = 86; # ID of another object
my $stolen = $fake->get_data();
Why anyone would try to do this is unknown. How this could be used for any sort of malicious exploitation is also unknown. However, if preventing this sort of security issue a requirement, then do not use Object::InsideOut.
There are bugs associated with threads::shared that may prevent you from using foreign inheritance with shared objects, or storing objects inside of shared objects.
For Perl 5.6.0 through 5.8.0, a Perl bug prevents package variables (e.g., object attribute arrays/hashes) from being referenced properly from subroutine refs returned by an :Automethod
subroutine. For Perl 5.8.0 there is no workaround: This bug causes Perl to core dump. For Perl 5.6.0 through 5.6.2, the workaround is to create a ref to the required variable inside the :Automethod
subroutine, and use that inside the subroutine ref:
package My::Class; {
use Object::InsideOut;
my %data;
sub auto :Automethod
{
my $self = $_[0];
my $name = $_;
my $data = \%data; # Workaround for 5.6.X bug
return sub {
my $self = shift;
if (! @_) {
return ($$data{$name});
}
$$data{$name} = shift;
};
}
}
For Perl 5.8.1 through 5.8.4, a Perl bug produces spurious warning messages when threads are destroyed. These messages are innocuous, and can be suppressed by adding the following to your application code:
$SIG{__WARN__} = sub {
if ($_[0] !~ /^Attempt to free unreferenced scalar/) {
print(STDERR @_);
}
};
A better solution would be to upgrade threads and threads::shared from CPAN, especially if you encounter other problems associated with threads.
For Perl 5.8.4 and 5.8.5, the "Storable" feature does not work due to a Perl bug. Use Object::InsideOut v1.33 if needed.
View existing bug reports at, and submit any new bugs, problems, patches, etc. to: http://rt.cpan.org/NoAuth/Bugs.html?Dist=Object-InsideOut
REQUIREMENTS
Perl 5.6.0 or later
Exception::Class v1.22 or later
Scalar::Util v1.10 or later. It is possible to install a pure perl version of Scalar::Util, however, it will be missing the weaken() function which is needed by Object::InsideOut. You'll need to upgrade your version of Scalar::Util to one that supports its XS
code.
Test::More v0.50 or later (for installation)
SEE ALSO
Object::InsideOut Discussion Forum on CPAN: http://www.cpanforum.com/dist/Object-InsideOut
Annotated POD for Object::InsideOut: http://annocpan.org/~JDHEDDEN/Object-InsideOut-1.39/lib/Object/InsideOut.pm
Inside-out Object Model: http://www.perlmonks.org/?node_id=219378, http://www.perlmonks.org/?node_id=483162, http://www.perlmonks.org/?node_id=515650, Chapters 15 and 16 of Perl Best Practices by Damian Conway
ACKNOWLEDGEMENTS
Abigail <perl AT abigail DOT nl> for inside-out objects in general.
Damian Conway <dconway AT cpan DOT org> for Class::Std.
David A. Golden <dagolden AT cpan DOT org> for thread handling for inside-out objects.
Dan Kubb <dan.kubb-cpan AT autopilotmarketing DOT com> for :Chained
methods.
AUTHOR
Jerry D. Hedden, <jdhedden AT cpan DOT org>
COPYRIGHT AND LICENSE
Copyright 2005, 2006 Jerry D. Hedden. All rights reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.