Steven Haryanto

NAME

Module::Patch - Patch package with a set of patches

VERSION

version 0.18

SYNOPSIS

To use Module::Patch directly:

 # patching DBI modules so that calls are logged

 use Module::Patch qw(patch_package);
 use Log::Any '$log';
 my $handle = patch_package(['DBI', 'DBI::st', 'DBI::db'], [
     {action=>'wrap', mod_version=>':all', sub_name=>':public', code=>sub {
         my $ctx = shift;

         $log->tracef("Entering %s(%s) ...", $ctx->{orig_name}, \@_);
         my $res;
         if (wantarray) { $res=[$ctx->{orig}->(@_)] } else { $res=$ctx->{orig}->(@_) }
         $log->tracef("Returned from %s", $ctx->{orig_name});
         if (wantarray) { return @$res } else { return $res }
     }},
 ]);

 # restore original
 undef $handle;

To create a patch module by subclassing Module::Patch:

 # in your patch module

 package Some::Module::Patch::YourCategory;
 use parent qw(Module::Patch);

 sub patch_data {
     return {
         v => 3,
         patches => [...], # $patches_spec
         config => { # per-patch-module config
             a => {
                 default => 1,
             },
             b => {},
             c => {
                 default => 3,
             },
         },
     };
 }
 1;

 # using your patch module

 use Some::Module::Patch::YourCategory
     -force => 1, # optional, force patch even if target version does not match
     -config => {a=>10, b=>20}, # optional, set config value
 ;

 # accessing per-patch-module config data

 print $Some::Module::Patch::YourCategory::config->{a}; # 10
 print $Some::Module::Patch::YourCategory::config->{c}; # 3, default value

 # unpatch, restore original subroutines
 no Some::Module::Patch::YourCategory;

DESCRIPTION

Module::Patch is basically a convenient way to define and bundle a set of patches. Actual patching is done by Monkey::Patch::Action, which provides lexically scoped patching.

There are two ways to use this module:

  • subclass it

    This is used for convenient bundling of patches. You create a patch module (a module that monkey-patches other module by adding/replacing/wrapping/deleting subroutines of target module) by subclassing Module::Patch and providing the patches specification in patch_data() method.

    Patch module should be named Some::Module::Patch::YourCategory. YourCategory should be a keyword or phrase (verb + obj) that describes what the patch does. For example, HTTP::Daemon::Patch::IPv6, LWP::UserAgent::Patch::LogResponse.

    Patch module should be use()'d, or require()'d + import()'ed instead of just require()'d, because the patching is done in import().

  • require/import it directly

    Module::Patch provides patch_package which is the actual routine to do the patching.

FUNCTIONS

import()

If imported directly, will export @exports as arguments and export requested symbols.

If imported from subclass, will take %opts as arguments and run patch_package() on caller package. %opts include:

  • -load_target => BOOL (default 1)

    Load target modules. Set to 0 if package is already defined in other files and cannot be require()-ed.

  • -warn_target_loaded => BOOL (default 1)

    If set to false, do not warn if target modules are loaded before the patch module. By default, it warns to prevent users making the mistake of importing subroutines from target modules before they are patched.

  • -force => BOOL

    Will be passed to patch_package's \%opts.

patch_package($package, $patches_spec, \%opts) => HANDLE

Patch target package $package with a set of patches.

$patches_spec is an arrayref containing a series of patches specifications. Each patch specification is a hashref containing these keys: action (string, required; either 'wrap', 'add', 'replace', 'add_or_replace', 'delete'), mod_version (string/regex or array of string/regex, can be ':all' to mean all versions; optional; defaults to ':all'). sub_name (string/regex or array of string/regex, subroutine(s) to patch, can be ':all' to mean all subroutine, ':public' to mean all public subroutines [those not prefixed by _], ':private' to mean all private), code (coderef, not required if action is 'delete').

Die if there is conflict with other patch modules, for example if target module has been patched 'delete' and another patch wants to 'wrap' it.

Known options:

  • force => BOOL (default 0)

    Force patching even if target module version does not match. The default is to warn and skip patching.

None are exported by default, but they are exportable.

FAQ

This module does not work! The target module does not get patched!

It probably does. Some of the common mistakes are:

  • Not storing the handle

    You do this:

     patch_package(...);

    instead of this:

     my $handle = patch_package(...);

    Since the handle is used to revert the patch, if you do not store $handle, you are basically patching and immediately reverting the patch.

  • Importing before patching

    If Target::Module exports symbols, and you patch one of the default exports, the users need to patch before importing. Otherwise he/she will get the unpatched version. For example, this won't work:

     use Target::Module; # by default export foo
     use Target::Module::Patch::Foo; # patches foo
    
     foo(); # user gets the unpatched version

    While this does:

     use Target::Module::Patch::Foo; # patches foo
     use Target::Module; # by default export foo
    
     foo(); # user gets the patched version

    Since 0.16, Module::Patch already warns this (unless -load_target or -warn_target_loaded is set to false).

SEE ALSO

Monkey::Patch::Action

Pod::Weaver::Plugin::ModulePatch

Some examples of patch modules that use Module::Patch by subclassing it: Net::HTTP::Methods::Patch::LogRequest, LWP::UserAgent::Patch::HTTPSHardTimeout.

Some examples of modules that use Module::Patch directly: Log::Any::For::Class.

HOMEPAGE

Please visit the project's homepage at https://metacpan.org/release/Module-Patch.

SOURCE

Source repository is at https://github.com/sharyanto/perl-Module-Patch.

BUGS

Please report any bugs or feature requests on the bugtracker website https://rt.cpan.org/Public/Dist/Display.html?Name=Module-Patch

When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature.

AUTHOR

Steven Haryanto <stevenharyanto@gmail.com>

COPYRIGHT AND LICENSE

This software is copyright (c) 2013 by Steven Haryanto.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.




Hosting generously
sponsored by Bytemark