Object-Configure

 view release on metacpan or  search on metacpan

lib/Object/Configure.pm  view on Meta::CPAN

      # Inherits retries: 3 and log_level: info from parent

    # Result: Child class gets timeout=60, retries=3, log_level=info

Parent configuration files are optional.
If a parent class's configuration file doesn't exist, the module simply skips it and continues up the inheritance chain.
All discovered configuration files are tracked in the C<_config_files> array for hot reload support.

=head3 UNIVERSAL CONFIGURATION

All Perl classes implicitly inherit from C<UNIVERSAL>.
C<Object::Configure> takes advantage of this to provide a mechanism for universal configuration settings
that apply to all classes by default.

If you create a configuration file named C<universal.yml> (or C<universal.conf>, C<universal.json>, etc.)
in your configuration directory,
the settings in its C<UNIVERSAL> section will be inherited by all classes that use C<Object::Configure>,
unless explicitly overridden by class-specific configuration files.

This is particularly useful for setting application-wide defaults such as logging levels,
timeout values,
or other common parameters that should apply across all modules.

Example C<~/.conf/universal.yml>:

    ---
    UNIVERSAL:
      timeout: 30
      retries: 3
      logger:
        level: info

With this universal configuration file in place,
all classes will inherit these default values.
Individual classes can override any of these settings in their own configuration files:

Example C<~/.conf/my-special-class.yml>:

    ---
    My__Special__Class:
      timeout: 120
      # Inherits retries: 3 and logger.level: info from UNIVERSAL

The universal configuration is loaded first in the inheritance chain,
followed by parent class configurations,
and finally the specific class configuration,
with later configurations overriding earlier ones.

=head2 CHANGING BEHAVIOUR AT RUN TIME

=head3 USING A CONFIGURATION FILE

To control behavior at runtime, C<Object::Configure> supports loading settings from a configuration file via L<Config::Abstraction>.

A minimal example of a config file (C<~/.conf/local.conf>) might look like:

   [My__Module]
   logger.file = /var/log/mymodule.log

The C<configure()> function will read this file,
overlay it onto your default parameters,
and initialize the logger accordingly.

If the file is not readable and no config_dirs are provided,
the module will throw an error.
To be clear, in this case, inheritance is not followed.

This mechanism allows dynamic tuning of logging behavior (or other parameters you expose) without modifying code.

More details to be written.

=head3 USING ENVIRONMENT VARIABLES

C<Object::Configure> also supports runtime configuration via environment variables,
without requiring a configuration file.

Environment variables are read automatically when you use the C<configure()> function,
thanks to its integration with L<Config::Abstraction>.
These variables should be prefixed with your class name, followed by a double colon.

For example, to enable syslog logging for your C<My::Module> class,
you could set:

    export My__Module__logger__file=/var/log/mymodule.log

This would be equivalent to passing the following in your constructor:

     My::Module->new(logger => Log::Abstraction->new({ file => '/var/log/mymodule.log' });

All environment variables are read and merged into the default parameters under the section named after your class.
This allows centralized and temporary control of settings (e.g., for production diagnostics or ad hoc testing) without modifying code or files.

Note that environment variable settings take effect regardless of whether a configuration file is used,
and are applied during the call to C<configure()>.

More details to be written.

=head2 HOT RELOAD

Hot reload is not supported on Windows.

=head3 Basic Hot Reload Setup

    package My::App;
    use Object::Configure;

    sub new {
        my $class = shift;
        my $params = Object::Configure::configure($class, @_ ? \@_ : undef);
        my $self = bless $params, $class;

        # Register for hot reload
        Object::Configure::register_object($class, $self) if $params->{_config_file};

        return $self;
    }

    # Optional: Define a reload hook
    sub _on_config_reload {
        my ($self, $new_config) = @_;
        print "My::App config was reloaded!\n";

lib/Object/Configure.pm  view on Meta::CPAN

				"${dir}/${class_file}.json",
			);
		}
	}

	# Return the first file that exists and is readable
	foreach my $pattern (@patterns) {
		if (-r $pattern && -f $pattern) {
			return $pattern;
		}
	}

	return undef;
}

# Helper function to get the inheritance chain for a class
sub _get_inheritance_chain {
	my ($class) = @_;
	my @chain = ();
	my %seen = ();

	_walk_isa($class, \@chain, \%seen);

	return @chain;
}

# Recursive function to walk the @ISA hierarchy
sub _walk_isa {
	my ($class, $chain, $seen) = @_;

	return if $seen->{$class}++;

	# Get the @ISA array for this class
	no strict 'refs';
	my @isa = @{"${class}::ISA"};
	use strict 'refs';

	# Recursively process parent classes first
	foreach my $parent (@isa) {
		# Skip common base classes that won't have configs
		# next if $parent eq 'Exporter';
		# next if $parent eq 'DynaLoader';
		# next if $parent eq 'UNIVERSAL';

		_walk_isa($parent, $chain, $seen);
	}

	# If this class has no parents and isn't UNIVERSAL itself,
	# explicitly add UNIVERSAL as a parent
	if (!@isa && $class ne 'UNIVERSAL') {
		_walk_isa('UNIVERSAL', $chain, $seen);
	}

	# Add current class to chain (after parents)
	push @$chain, $class;
}

# Deep merge two hash references
# Second hash takes precedence over first
sub _deep_merge {
	my ($base, $overlay) = @_;

	return $overlay unless ref($base) eq 'HASH';
	return $base unless ref($overlay) eq 'HASH';

	my $result = { %$base };

	foreach my $key (keys %$overlay) {
		if (ref($overlay->{$key}) eq 'HASH' && ref($result->{$key}) eq 'HASH') {
			$result->{$key} = _deep_merge($result->{$key}, $overlay->{$key});
		} else {
			$result->{$key} = $overlay->{$key};
		}
	}

	return $result;
}


=head2 instantiate($class,...)

Create and configure an object of the given class.
This is a quick and dirty way of making third-party classes configurable at runtime.

=cut

sub instantiate
{
	my $params = Params::Get::get_params('class', @_);

	my $class = $params->{'class'};
	$params = configure($class, $params);

	my $obj = $class->new($params);

	# Register object for hot reload if config file is used
	if ($params->{_config_file}) {
		register_object($class, $obj);
	}

	return $obj;
}

=head1 HOT RELOAD FEATURES

=head2 enable_hot_reload

Enable hot reloading for configuration files.

    Object::Configure::enable_hot_reload(
        interval => 5,  # Check every 5 seconds (default: 10)
        callback => sub { print "Config reloaded!\n"; }  # Optional callback
    );

=cut

sub enable_hot_reload {
	my %params = @_;

	my $interval = $params{interval} || 10;
	my $callback = $params{callback};

	# Don't start multiple watchers
	return if %_config_watchers;

	# Fork a background process to watch config files
	if (my $pid = fork()) {
		# Parent process - store the watcher PID
		$_config_watchers{pid} = $pid;
		$_config_watchers{callback} = $callback;
		return $pid;
	} elsif (defined $pid) {



( run in 1.796 second using v1.01-cache-2.11-cpan-39bf76dae61 )