The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Class::Injection - Injects methods to other classes

DESCRIPTION

The Injection class is a elegant way to manipulate existing classes without editing them. It is done during runtime. It is a good way to write plugins without creating special plugins technologies.

SYNOPSIS

 # Here an original class
 # Imagine you want to overwrite the test() method.

 package Foo::Target;

 sub test{
   my $this=shift;
 
   print "this is the original method.\n";
   
 }

In a simple Perl file you can use that class:

 use Foo::Target;

 my $foo = Foo::Target->new();

 my $foo = Over->new();

 $foo->test(); # outout is: 'this is the original method'

So far nothing happened

If you want to change the test() method without editing the original code, you can use Class::Injection. First create a new class, like this:

 package Bar::Over;


 use Class::Injection qw/Foo::Target/; # define the target


 sub test {
   my $this=shift;

   print "this is the new method\n";
  
 }

To define the class which should be overwritten, you set the name after the 'use Class::Injection' call, here Foo::Target.

In the calling Perl script to need to initialize that:

 use Foo::Target;
 use Bar::Over1;

 Class::Injection::install(); # installs the new methods from Bar::Over

 my $foo = Foo::Target->new();
 
 $foo->test(); # Output is: 'this is the new method'

  

The example above uses the default copymethod 'replace', which just replaces the methods. Class::Injection can do more complicated things, depending on your need you can stack methods to run several different variations e.g. of a test(). You can also define the way of returning a value.

ADD A METHOD

The simplest way to add a method to an existing one you can see in the example below. To add a method means to execute the original method and the new method.

 package Over;

 use Class::Injection qw/Target add/;

 sub test { ... };

This example overwrites a class 'Target'. You can see the second value after the target class is the copymethod. Here it is 'add'. It is equivalent to 'append'.

RETURN TYPES

You can configure the return types of a method. Please be aware of the its behaviours:

1. It is a class-wide configuration.

2. If you use more than one overwriting class, The last called, defines the overwriting rules.

At first have a look into the following example how to set complex parameters:

 use Class::Injection 'Target', {
                                    'how'           =>  'add',
                                    'priority'      =>  4,
                                    'returnmethod'  =>  'collect',
                                };

The first parameter is still the target class, but then follows, seperated by a comma, a hashref with some values.

how - defines the way of adding the method. Default is 'replace'. You can also use 'add' (same as 'append') or 'insert'.

copymethod - same as 'how'.

priority - please see the chapter PRIORITY for that.

returns - defines the return-type. (see below)

returntype - same as 'returns'.

returnmethod - defines which method(s) return values are used. (see below)

debug - enables the debug mode and prints some infos as well as the virtual commands. use 'true|yes|1' as value.

replace - will cause the original method not to be used anymore, it will completely replaced by the injection methods. use 'true|yes|1' as value.

The returntype is currently set to 'array' for any type. What means it is not further implemented to returns something else. I have to see if there changes are neccessary during praxis. So far it looks like a return of array is ok.

The returntype is currently more automatically defined by context! It means if you e.g. call a

 my @x = test();

It gives you an array, and if you do

 my $x = test();

It gives you an scalar. But it depends on the used 'returnmethod' what exaclty you will get! With 'collect' it returns an arrayref, with anything else it will be the first value, if in scalar context.

RETURNMETHOD

The returnmethod can have the following values:

last, all, original, collect.

Before you start dealing with returnmethods, please note, that it might get compilcated, because you are changing the way of returning values of the original method. If you just use 'replace' you dont change the returnmethods. It can be used to build a plugin system and handling the results of several parallel classes.

If you want to manipulate values with the methods (functions), I recommend using references as a part of the given parameters and not the output of a method. For example:

 # not good:
 my $string = 'abcdefg';
 my $new_string = change_text($stgring)

The example above will make trouble if you use 'collect' as returnmethod.

 # better:
 my $string = 'abcdefg';
 change_text(\$stgring)

Here each new installed method just takes the reference and can change the text. No return values needed.

The default is 'last', what means the last called method's return values are used. This is the most save way to handle, because this method is usually used somewhere already and a specific returntype is expected. If you just change it, maybe the code stops working.

Also save is 'orginal' that will just return the original's method value.

With 'all' it merges all return values into an array, what must be handled in context. If you previosly used that call:

 my $x = test();

It will give you now only the first value, what is maybe not what you want. Expect with 'all' an array as return value and handle it:

 my @x = test();

PRIORITY

You can add more than one method. To finetune the position, you can set as a third value a priority. The original class has the priority 0. Every positive number comes later and negative numbers before.

 package Over;

 use Class::Injection qw/Target add -5/;

 sub test { ... };

If you dont care about a priority, but just want the same order like it is listed, you can use 'insert' (before) or 'append'.

 use Class::Injection qw/Target append/;
 ...
 use Class::Injection qw/Target insert/;

Inserted class's method will be called before the appended class's method.

PLUGINS

How to use Class::Injection to build a plugin system?

I see two type of plugins here:

1. Just replacing existing methods and returning ONE value, like the original method.

2. The caller expects plugins, what means he may handle different return values, that can occour when e.g. used 'collect' as a copymethod.

For both types you will need to scan a folder for perl modules and 'use' them. Of course I assume they have the Class::Injection in use.

If the calller expects plugins, I recommend using an abstract class as a skelleton, which the caller uses to instantiate the class. The methods of the abstract class should already return an arrayref. And in the plugins use the key " replace => 'true' " in the Class::Injection line. That will overwrite the abstract class's methods.

    package Abstract;

    sub test{
    my $this=shift;


    return [];
    }

    1;

Here a plugin:

 package Plugin1;
 
 use base 'Abstract';
 
 use Class::Injection 'Abstract', {
                                   'how'           => 'add',
                                   'returnmethod'  => 'collect',
                                   'replace'       => 'true',
                                  };
 
 sub test{
   my $this=shift;
 
   return "this is plugin 1";
 }
 
 1;

The main script:

    eval('use Plugin1;'); # only to show it might be dynamically loaded

    use Class::Injection;
    use Abstract;

    Class::Injection::install();

    my $foo = Abstract->new();

FUNCTIONS

import

 Class::Injection::import();

The import function is called by Perl when the class is included by 'use'. It takes the parameters after the 'use Class::Injection' call. This function stores your intention of injection in the static collector variable. Later you can call install() function to overwrite or append the methods.

break

Breakig in a method

 Class::Injection::break();

sets a flag to break after current method. No further methods used in the method stack. You can use that in your new method.

If you want to break and makes the current method to the last one used, you can set a break flag by calling a static method:

    sub test{
     my $this=shift;

     Class::Injection::break;

     return "this is plugin 1";
    }

After this method, nur further method is called. Make sure you use the break on the very and of a method, because it could be that further, deeper methods you call, also are injected. That would cause a break for them.

lastvalue

Value of last method

If you want to work with the result of the former injected method in a method, you can get the result, as a arrayref, with the static method lastvalue:

  Class::Injection::lastvalue

info

 Class::Injection::info();

returning infos about the installed methods as a hash.

show_replacement_matrix

 Class::Injection::show_replacement_matrix();

Prints an easy to read matrix like that:

  ----------------------------------------------------------------------------------------------------
  Local::Abstract::test                      <-     Local::Plugin2::test                      injected
  Local::Abstract::test                      <-     Local::Abstract::test                     original
  Local::Abstract::test                      <-     Local::Plugin1::test                      injected
  ----------------------------------------------------------------------------------------------------

to show what happens to the classes and methods.

install

 Class::Injection::install();

Installs the methods to existing classes. Do not try to call this method in a BEGIN block, the needed environment does not exist so far.

REQUIRES

Data::Dumper

Class::Inspector

AUTHOR

Andreas Hernitscheck ahernit(AT)cpan.org

LICENSE

You can redistribute it and/or modify it under the conditions of LGPL and Artistic (What means to keep the original author in the source code).