Module::Pluggable::Object(3)

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 "require"s 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 "require"s 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 "require"d 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$/

  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 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 "require"d. 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 "carp"s 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 "carp"s 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