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
requireorinstantiateis set.Set to
0to disable even whenrequireis 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$/
follow_symlinks
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 firstsearch_pathentry, 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