NAME

Module::Pluggable::Object - the underlying plugin-finding object used by Module::Pluggable

SYNOPSIS

package MyClass;
use Module::Pluggable::Object;

my $finder = Module::Pluggable::Object->new(
    package     => __PACKAGE__,
    search_path => ['MyClass::Plugin'],
    require     => 1,
);

my @plugins = $finder->plugins;
print "Found: ", join(", ", @plugins), "\n";

DESCRIPTION

Module::Pluggable::Object is the engine underneath Module::Pluggable. It walks a set of search paths (namespaces and directories) looking for .pm files, maps their paths to package names, and optionally requires or instantiates them.

Most of the time you should use Module::Pluggable directly, which wraps this class and exports a plugins() method into your namespace. Use this class directly when you need more control:

  • You want to create multiple independent finders in the same package.

  • You want to subclass or decorate the finder object.

  • You want to store the finder and call plugins() on it explicitly rather than through an exported method.

  • You are building your own higher-level abstraction on top of the plugin-discovery machinery.

CONSTRUCTOR

new(%opts)

my $finder = Module::Pluggable::Object->new(%opts);

Creates and returns a new finder object. All options are passed as a flat hash; they are stored on the object and consulted by "plugins". See "OPTIONS" below for the full list.

METHODS

plugins

my @plugins = $finder->plugins;        # list of class names
my @objects = $finder->plugins(@args); # instances when 'instantiate' is set

The main entry point. Walks search_path across search_dirs (and @INC) and returns either a sorted list of fully-qualified package names or, if instantiate is set, a list of objects created by calling the named method on each plugin class.

Arguments passed to plugins() are forwarded to the instantiate method on each plugin class.

The full search is repeated on every call, which allows plugins installed after startup to be discovered at run time. If you do not need dynamic discovery and want to pay the I/O cost only once, memoize the result yourself:

our @PLUGINS;
sub plugins { @PLUGINS ||= shift->_finder->plugins }

search_directories(@dirs)

my @plugins = $finder->search_directories(@INC);

Iterates over @dirs and delegates to "search_paths" for each one. Normally called internally by "plugins", but exposed so subclasses can override the top-level directory loop.

search_paths($dir)

my @plugins = $finder->search_paths($dir);

For a single base directory, iterates over /search_path namespaces, locates .pm files under the corresponding subdirectory, converts file paths to package names, and calls "handle_finding_plugin" for each candidate.

Handles case-insensitive filesystems by reading the package declaration out of the file when the file name is all-lower or all-upper case.

handle_finding_plugin($plugin, \@plugins, $no_require)

$finder->handle_finding_plugin('MyApp::Plugin::Foo', \@plugins);

Called for each candidate package name discovered during the search. Applies the only/except/min_depth/max_depth filters, invokes before_require and after_require triggers, optionally requires the module, and pushes it onto \@plugins if everything succeeds.

Pass a true value for $no_require to skip the require step (used when the module is known to already be loaded, e.g. for inner packages).

find_files($search_path)

my @files = $finder->find_files($dir);

Recursively finds all files matching file_regex (default qr/\.pm$/) under $dir using File::Find. Respects the follow_symlinks option.

handle_inc_hooks($search_path, @dirs)

my @plugins = $finder->handle_inc_hooks($path, @SEARCHDIR);

Handles the case where an entry in @INC is a blessed object with a files() method (as used by App::FatPacker and PAR). Calls files() on each such object, filters by $search_path, and delegates to "handle_finding_plugin".

handle_innerpackages($search_path)

my @plugins = $finder->handle_innerpackages('MyApp::Plugin');

Uses Devel::InnerPackage to find any package declared inside an already- loaded file whose name falls under $search_path. This is how Something::Plugin::Bar is discovered when it is defined inside Something/Plugin/Foo.pm. Set inner => 0 to disable this behaviour.

OPTIONS

All options are passed to new as a flat hash and may be set or overridden directly on the object before calling plugins.

package

The package name used to build the default search_path. When using Module::Pluggable this is set automatically to the calling package. When using Module::Pluggable::Object directly you should set it explicitly.

search_path

An array ref (or a single string, which is promoted to an array ref) of namespace prefixes to search. Defaults to ["${package}::Plugin"].

search_path => ['MyApp::Plugin', 'MyApp::Extension']

Alternatively, you can pass a hash ref where each key is a namespace prefix and each value is a hash ref of option overrides for that path. The per-path options are merged on top of the global options, so you only need to specify what differs.

search_path => {
    'MyApp::Plugin'    => { max_depth => 2 },
    'MyApp::Extension' => { max_depth => 3, instantiate => 'new' },
}

As another alternative, if you want plugins to be in a particular order, use an array ref of mixed plain strings and single-key hash refs. Order is preserved; only the hash ref entries carry per-path option overrides.

search_path => [
    'MyApp::Plugin',
    { 'MyApp::Extension' => { max_depth => 3, instantiate => 'new' } },
]

Results from all paths are combined and returned in a single list. Class names are deduplicated across paths; if any path uses instantiate its objects are included as-is alongside any class names from other paths.

search_dirs

An array ref (or a single string) of filesystem directories to search before @INC. Use this when plugins live outside the normal include path.

search_dirs => ['/opt/myapp/plugins']

search_dirs_strict

When true, only the directories in search_dirs are searched; @INC is ignored entirely. Requires search_dirs to also be set.

search_dirs_strict => 1

instantiate

Name of the method to call on each plugin class to create an instance. Typically 'new'. When set, plugins() returns a list of objects instead of class names. Arguments passed to plugins() are forwarded to this method.

instantiate => 'new'

require

When true, each plugin module is required but not instantiated. Overrides instantiate. Also enables inner-package discovery (see "inner").

require => 1

inner

Controls whether inner packages (secondary package declarations inside a single .pm file) are discovered.

  • Defaults to true when require or instantiate is set.

  • Set to 0 to disable even when require is set.

inner => 0

only

Restricts the returned plugins to those matching this value. May be:

  • A string - only the exact named plugin is returned.

  • An array ref of strings - only those exact plugins are returned.

  • A compiled regex - only plugins whose name matches are returned.

only => qr/^MyApp::Plugin::Safe/

except

Excludes matching plugins from the results. Accepts the same forms as only.

except => ['MyApp::Plugin::Broken', 'MyApp::Plugin::Deprecated']

min_depth / max_depth

Restrict discovery to plugins at a certain namespace depth (number of ::-separated components).

For example, MyApp::Plugin::Foo has depth 3 and MyApp::Plugin::Foo::Bar has depth 4.

max_depth => 3   # only MyApp::Plugin::Foo, not MyApp::Plugin::Foo::Bar
min_depth => 4   # only MyApp::Plugin::Foo::Bar and deeper

file_regex

A compiled regex used to identify plugin files. Defaults to qr/\.pm$/.

file_regex => qr/\.plugin$/

Whether to follow symbolic links when walking the filesystem. Defaults to 1 (follow symlinks) on all platforms except Windows with File::Find ≥ 1.39, where it defaults to 0 to preserve the historical behaviour.

follow_symlinks => 0

include_editor_junk

By default, files that look like editor artefacts are silently skipped:

  • Files ending in ~ (Emacs / generic backup files).

  • Files beginning with .# (Emacs lock files).

  • Files matching ^[._].*\.s[a-w][a-z]$ (Vim swap files).

Set include_editor_junk to a true value to disable this filtering and include every file that matches file_regex.

include_editor_junk => 1

force_search_all_paths

By default, when the module is loaded from inside a blib/ tree (i.e. during make test), only @INC entries that themselves contain blib are searched. Set this option to search all of @INC regardless.

force_search_all_paths => 1

sort_results

Controls the order plugin names (or objects) are returned in. One of:

  • 'alpha' (the default) - sorted alphabetically by package name.

  • 'path' - returned in search-path discovery order: all plugins under the first search_path entry, then the second, and so on. Within a single path, order follows the underlying filesystem traversal.

  • 0 - no sort is applied at all. Fastest option, for callers that will sort or otherwise order the results themselves.

sort_results => 'path'

TRIGGERS

Triggers are callbacks passed as options to new. If a trigger returns 0 (or any false value), the plugin is dropped at that point in the pipeline.

before_require

before_require => sub { my ($plugin) = @_; ... }

Called with the plugin package name before it is required. Return 0 to skip both the require and all subsequent steps for this plugin.

on_require_error

on_require_error => sub { my ($plugin, $err) = @_; ... }

Called when require raises an exception. The default handler carps the error and returns 0 (dropping the plugin). Return a true value to keep the plugin in the list despite the error.

after_require

after_require => sub { my ($plugin) = @_; ... }

Called after a successful require. Return 0 to exclude the plugin from results even though it has already been loaded.

before_instantiate

before_instantiate => sub { my ($plugin) = @_; ... }

Called with the plugin package name before the instantiate method is invoked. Return 0 to skip instantiation for this plugin.

on_instantiate_error

on_instantiate_error => sub { my ($plugin, $err) = @_; ... }

Called when the instantiate method raises an exception. The default handler carps the error and returns 0.

after_instantiate

after_instantiate => sub { my ($plugin, $obj) = @_; return $obj }

Called after successful instantiation with the plugin name and the newly created object. The return value is used as the plugin entry in the results list; return a false value to discard the plugin entirely. Use this to wrap or decorate plugin objects transparently.

BEHAVIOUR UNDER TEST ENVIRONMENTS

When blib.pm is present in %INC and the calling file's path contains blib/, only @INC entries that themselves contain blib are searched. This prevents installed (non-blib) versions of plugins from interfering with tests. See force_search_all_paths to override this.

@INC HOOKS AND App::FatPacker

If an entry in @INC is a blessed object with a files() method, its files() list is searched for plugin modules. This is how App::FatPacker (from version 0.10.0 onwards) integrates with Module::Pluggable::Object, and how PAR can be made to do the same. See t/26inc_hook.t for a working example.

AUTHOR

Simon Wistow <simon@thegestalt.org>

COPYING

Copyright, 2006-2026 Simon Wistow

Distributed under the same terms as Perl itself.

SEE ALSO

Module::Pluggable, Devel::InnerPackage, Module::Runtime, App::FatPacker, PAR