NAME
Module::Generic::Dynamic - Dynamic Object Class
SYNOPSIS
package My::Module;
use parent qw( Module::Generic::Dynamic );
# Then, instantiating an object
my $object = My::Module->new({
name => 'Test Product',
quantity => 20,
metadata => { sku => 'ABC123', price => 99.99 },
tags => [qw( product entrepreneurship capital )],
created => '2023-11-18T10:30:00',
is_active => 1,
url => 'https://example.com',
file_path => './config.ini',
});
# Accessing methods
print $object->name; # Test Product
print $object->metadata->sku; # ABC123
print $object->tags->join(','); # product,entrepreneurship,capital
print $object->created->year; # 2023
# Dynamic method creation via AUTOLOAD
$object->version('1.2.3');
print $object->version->stringify; # 1.2.3
VERSION
v1.3.0
DESCRIPTION
Module::Generic::Dynamic
provides a framework for dynamically creating classes and methods based on an input hash reference. It automatically generates accessor methods for each key-value pair, inferring the appropriate data type and handling method creation at runtime using AUTOLOAD
.
This module is particularly useful for creating flexible, data-driven objects where the structure is not known in advance. It supports a wide range of data types, including scalars, hashes, arrays, booleans, URIs, UUIDs, IP addresses, versions, files, code references, globs, and custom objects.
For more granular control over the method to be used for each data key-value, use "_set_get_class" in Module::Generic
METHODS
new
Provided with a hash reference of data, this creates a new object and dynamically generates a class based on the calling package name. For each key-value pair in the hash, it creates an accessor method and assigns an appropriate handler based on the data type:
array
If the array contains hash references, creates a method using "_set_get_object_array_object" in Module::Generic with a new dynamic class for each hash element. Otherwise, uses "_set_get_array_as_object" in Module::Generic to return a Module::Generic::Array object.
boolean
For keys matching
is_
,has_
,enable
,active
,valid
,disabled
, orinactive
(case-insensitive) with values0
,1
,undef
, or empty string, uses "_set_get_boolean" in Module::Generic to return a Module::Generic::Boolean object.code
For code references, uses "_set_get_code" in Module::Generic to store and execute the code.
file
For keys matching
file
orpath
with a valid file path (e.g.,./config.ini
,~/docs
), uses "_set_get_file" in Module::Generic to return a Module::Generic::File object.glob
For glob references (e.g., filehandles), uses "_set_get_glob" in Module::Generic.
hash
Creates a method using "_set_get_object" in Module::Generic and a new dynamic class (e.g.,
My::Module::KeyName
) for nested hash data.integer
For keys matching
count
,size
, orquantity
with an integer value, uses "_set_get_number" in Module::Generic to return a Module::Generic::Number object.IP address
For keys matching
ip
,ip_addr
, orip_address
with a valid IPv4 or IPv6 address, uses "_set_get_ip" in Module::Generic to return a Module::Generic::Scalar object.number
For numeric values (integer or floating-point), uses "_set_get_number" in Module::Generic to return a Module::Generic::Number object.
object
For blessed objects (e.g., JSON::PP::Boolean, Module::Generic::File, custom classes), uses the appropriate handler (e.g., "_set_get_boolean" in Module::Generic for booleans) or "_set_get_object" in Module::Generic for others, preserving the object’s class.
string
Creates a method using "_set_get_scalar_as_object" in Module::Generic, returning a Module::Generic::Scalar object.
URI
For keys matching
uri
orurl
or values starting withhttp://
orhttps://
, uses "_set_get_uri" in Module::Generic to return a URI object. If Regexp::Common::URI is available, it validates URIs more precisely.UUID
For keys matching
id
,_id
, or ending withid
with a valid UUID format, uses "_set_get_uuid" in Module::Generic to return a Module::Generic::Scalar object.version
For keys matching
version
with a valid version string (e.g.,1.2.3
), uses "_set_get_version" in Module::Generic to return a version object.
If a key results in an invalid method name (e.g., starting with digits), it is sanitized by removing non-alphanumeric characters and leading digits.
Example:
my $obj = Module::Generic::Dynamic->new({
name => 'Test',
is_active => 1,
url => 'https://example.com',
file => './config.ini',
});
print $obj->name; # Test
print $obj->is_active; # 1 (Module::Generic::Boolean)
print $obj->url->host; # example.com
print $obj->file->basename; # config.ini
AUTOLOAD
Dynamically creates accessor methods for undefined method calls based on the method name and the first argument’s type (if provided). It uses the same type-guessing logic as new
, inferring handlers from:
Method name patterns (e.g.,
is_active
for boolean,url
for URI). =item * Argument types (e.g., JSON::PP::Boolean, hash, array, scalar). =item * Value formats (e.g., UUID, IP address, file path).
If no arguments are provided (getter call), the method is created based on the method name. For example:
my $obj = Module::Generic::Dynamic->new;
$obj->url('https://example.com');
print $obj->url->host; # example.com
print $obj->is_active; # Creates boolean accessor, returns undef
Invalid inputs (e.g., non-UUID values for id
fields) fall back to "_set_get_scalar_as_object" in Module::Generic.
SERIALISATION
Serialisation is supported for CBOR, Sereal, Storable::Improved, and Storable. The module implements FREEZE
, THAW
, STORABLE_freeze
, STORABLE_thaw
, and TO_JSON
to handle object serialization and deserialization, preserving data types and structure.
Example:
my $obj = Module::Generic::Dynamic->new({ name => 'Test', tags => [qw( product test )] });
my $data = $obj->serialise( $obj, serialiser => 'Storable::Improved' );
my $new_obj = Module::Generic->new->deserialise( data => $data, serialiser => 'Storable::Improved' );
print $new_obj->name; # Test
print $new_obj->tags->join(','); # product,test
THREAD SAFETY WARNING
This module is not thread-safe.
Module::Generic::Dynamic
dynamically creates packages and injects methods into symbol tables at runtime using eval
. In multi-threaded environments (Perl ithreads), this can cause:
Race conditions between threads attempting to create the same symbol.
Corrupted symbol tables if a method is installed while another thread is reading or writing from the same class.
Recommended Usage
Use this module only during application initialization, before any threads are spawned.
Avoid invoking
new
or triggeringAUTOLOAD
from within a thread.In persistent environments (e.g., mod_perl), precompile structures at startup.
ERROR HANDLING
Errors in new
and AUTOLOAD
, such as invalid parameters or failed method creation, return undef
with an error object accessible via $obj->error
. Check for errors to handle failures gracefully:
my $obj = Module::Generic::Dynamic->new({ name => 'Test' });
die $obj->error if !defined $obj;
Invalid method names (e.g., starting with digits) are sanitised by removing non-alphanumeric characters and leading digits, or ignored if no valid name remains.
LIMITATIONS
Invalid method names (e.g.,
123invalid
) are sanitised or ignored, which may lead to unexpected behavior if not checked.Type guessing is conservative to avoid false positives. For example, a string like
/api/v1
is treated as a scalar unless the key name suggests a file (e.g.,file_path
).Ambiguous inputs (e.g., a number that could be a version or integer) may default to a less specific handler (e.g., "_set_get_number" in Module::Generic).
Users can override type guessing by explicitly using "_set_get_class" in Module::Generic for more control.
AUTHOR
Jacques Deguest <jack@deguest.jp>
COPYRIGHT & LICENSE
Copyright (c) 2000-2024 DEGUEST Pte. Ltd.
You can use, copy, modify and redistribute this package and associated files under the same terms as Perl itself.