xp-framework / rfc

One of the major deficiencies in the development of many projects is that there is no roadmap or strategy available other than in the developers' heads. The XP team publishes its decisions by documenting change requests in form of RFCs.
2 stars 1 forks source link

Invocation utilities package #109

Closed thekid closed 9 years ago

thekid commented 13 years ago

Scope of Change

A new package invoke will be added.

Rationale

Method invocation utilities.

Functionality

Invocation chain

Example: Prerequisites-check:

<?php
  class Account extends Object {

    public function transfer($amount) {
      // TBI
    }
  }

  class SecurityInterceptor extends Object implements InvocationInterceptor {
    protected $level;

    public function __construct($level) {
      $this->level= $level;
    }

    public function invoke(InvocationChain $chain) {
      if (
        'transfer' == $chain->method->getName() && 
        $chain->parameters[0] > 10000 &&
        $this->level < 4
      ) {
        throw new IllegalAccessException(sprintf(
          'Clearance level IV needed to transfer %d EUR', 
          $chain->parameters[0]
        );

        return $chain->proceed();
      }
    }
  }

  $account= new Account();

  // Use invocation chain to invoke transfer() instead of calling it directly
  $chain= new InvocationChain();
  $chain->addInterceptor(new SecurityInterceptor(3));

  // Will throw an exception because of unmet prerequisites
  $result= $chain->invoke(
    $account, 
    $account->getClass()->getMethod('transfer'), 
    array(120000)
  );
?>

Example: Logging:

<?php
  class LoggingInterceptor extends Object implements InvocationInterceptor {
    protected $cat;

    public function __construct($cat) {
      $this->cat= $cat;
    }

    public function invoke(InvocationChain $chain) {
      $this->cat->debug('Invoking', $chain->method, '(', $chain->parameters, ')');
      try {
        $result= $chain->proceed();
      } catch (Throwable $e) {
        $this->cat->warn($e);
        throw $e;
      }
      $this->cat->info('Result', $result);
      return $result;
    }
  }
?>

Lazy initialization

The util.AbstractDeferredInvokationHandler class will be moved to invoke and be renamed to AbstractDeferredInvocationHandler (invocation is spelled with a "c", while invoke is spelled with a "k").

Caller

Finding out a method's caller:

<?php
  class Account extends Object {

    public function deposit($amount) {
      $this->cat->debug('Called from', InvocationContext::getCaller());

      // TBI
    }

    public function withdraw($amount) {
      $this->cat->debug('Called from', InvocationContext::getCaller());

      // TBI
    }
  }

  class MonetaryTransaction extends Object {

    public function transfer(Account $origin, Account $destination, $amount) {
      $origin->withdraw($amount);
      $destination->deposit($amount);
    }
  }

  $t= new MonetaryTransaction();
  $t->transfer(Account::getByAccountNumber(1), Account::getByAccountNumber(2), 100);
?>

This will print somthing like:

  [15:55:19 29540 debug] Called from invoke.Invocation@{
    [instance  ] com.example.banking.MonetaryTransaction{}
    [class     ] lang.XPClass<com.example.banking.MonetaryTransaction>
    [method    ] public transfer($origin, $destination, $amount)
    [arguments ] [
      0 => com.example.banking.Account<#1>
      1 => com.example.banking.Account<#2>
      2 => 100
    ]
  }

Another example of using the invocation class:

<?php
  $repr= $instance->getClass()->getMethod('toString')->invoke($instance);

  $i= new Invocation($instance, 'toString');
  $repr= $i->invoke();  
?>

Invocation comparison

Instead of manually comparing class name, method and parameter counts in interceptors, one can make use of the PointCutExpression class.

<?php
  if (
    'com.example.banking.Account' == $chain->instance->getClassName() &&
    'transfer' == $chain->method->getName() && 
    1 == sizeof($chain->parameters) &&
    $chain->parameters[0] > 10000 &&
    $this->level < 4
  ) {
    // ...
  }

  $pc= new PointCutExpression('call:com.example.banking.Account::transfer(1)');
  if (
    $pc->matches($chain->invocation()) && 
    $chain->parameters[0] > 10000 && 
    $this->level < 4
  ) {
    // ...
  }
?>

Syntax that can be used in forName():

  call:[class]::[method]([narguments])
  new:[class]([narguments])

Where:

n/a

Speed impact

n/a

Dependencies

n/a

Related documents

thekid commented 13 years ago

Reset to draft - this needs some more thought

friebe, Wed, 10 Oct 2007 21:46:14 +0200

thekid commented 9 years ago

Can be done inside a library.