Wrappable methods are special methods on an Object class whose invocations be dynamically 'wrapped' with new behavior before and/or after the invocation of the wrapped method. For Python coders method wrappers are similar to method decorators, for Aspect-Oriented folks this is a poor man's join-point, for everyone else wrappable methods are a flexible extensibility point where you can flexibly inject new behavior.

Consider inheritance in traditional object-oriented programming. By overriding a method in a sub-class you can effectively wrap behavior around the parent class' method call. Here is some example code:


1

MyBaseClass defines a method foo that will be 'wrapped' using inheritance.

2

foo is overridden by MySubClass and first echos a message.

3

Now, we call the 'wrapped', or in this case overridden, parent class' foo method.

4

Finally, let's print an after message once the call to MyBaseClass' foo has returned.

In this simple example we've effectively wrapped MyBaseClass' foo method with some new behavior: we print 'Before!' and 'After!' foo is called on instances of MySubClass. What is so bad about pure inheritance?

Other languages and programming paradigms have a fairly simple solution to these limitations, as mentioned Python's decorators and AOP's join-points, that Recess looked towards for inspiration. Recess' solution is called 'Wrappable' methods. A !Wrappable method can be wrapped by, (or decorated with), unbounded IWrappers that can be composed dynamically at runtime. Let's take a look at how we could make foo a wrapped method:


1

The !Wrappable annotation can be used on methods to create a 'wrappable' method. Notice that foo follows !Wrappable, this indicates that the wrappable method will be invoked with the name foo.

2

The actual name of the method is wrappedFoo. The only restriction on naming the wrapped method is that it can not have the same name as the wrappable method. By convention Recess prefixes wrapped methods with wrapped. I.E. foo() wraps wrappedFoo()

3

We can now invoke a method foo() on instances of MyBaseClass. Underneath the covers wrappable methods are surfaced using attached methods, for more info on attached methods see the section called “Attached Methods”.

Great! We have a wrappable foo method, now let's wrap it with printing behavior. All we need to do is create a PrintWrapper class that implements the IWrapper interface.


PrintWrapper implements the three methods defined in the IWrapper interface. Let's discuss each:

1

before is called before the wrapped method is called. It is passed a reference to the object the wrappable method has been called on, as well as an array of the arguments passed to the wrappable method. The arguments are passed by reference so that wrappers can transform what is eventually passed to the wrapped method. Not shown here, but if a wrapper's before method returns a non-null value it will short-circuit the call and return the value immediately to the callee without calling the wrapped method.

2

after is called when the wrapped method returns. It is passed a reference to the object the wrappable method has been called on, and the return value of the wrapped method. The value after returns is passed to the calle so after has an opportunity to transform or replace the value returned from the wrapped method.

3

combine is an optimization method that will be discussed shortly.

4

Here we use the static method wrapMethod to wrap a PrintWrapper instance on the wrappable method foo on MyBaseClass.

5

By invoking foo we first work our way through PrintWrapper's before(), then call MyBaseClass' wrappedFoo(), and finally back out to PrintWrapper's after().

6

For the sake of being thorough we show that you can still call wrappedFoo directly. Because wrapped methods are implemented at the Recess Core library level and not directly in PHP there is no way around this.

We now have the same printing behavior as in our Inheritanc example, but without using inheritance. The power and flexibility of wrappable methods and wrappers is that multiple wrappers can wrap a wrappable method. So we could create a logging wrapper that also wraps foo and easily flip either wrapper on or off, dynamically at run-time, without having to reorganize our class hierarchy. For more detail see the section called “The Mechanics of Wrapped Methods and Wrappers”. Lets point out exactly how wrappable methods address the downsides of inheritance:

  • Wrappable methods avoid the inflexibility of inheritance-based overriding because methods can be wrapped dynamically. Wrapping methods with new functionality does not affect the type system or class hierarchy.

  • Wrappable methods encapsulate a behavior around a method call. This presents a new way for PHP programs to package functionality to fight code duplication. To use Aspect-Oriented Programming terminology you could think of wrappers as a means for separating crosscutting concerns. Cross-cutting concerns are tasks like logging/printing debug messages as shown in the example above, authorization and access control, etc.

Given these two specific characteristics wrappable methods and wrappers provide a natural extensibility model. Plugin-developers can implement IWrappers that application-developers can easily incorporate in their projects because there is no need to modify a class heirarchy and the plugin's wrapper behavior is encapsulated in a simple class. For application-developers, instantiating wrappers and applying them with the Object::wrapMethod API can be awkward and cumbersome. This is where Recess Core's annotations come to the rescue, annotations provide the perfect vehicle for making declarative statements about a class or method which then employ wrappers and attached methods to do the leg work under the covers. For more information on annotations, see the section called “Annotations”

Where in the code base are wrapped methods implemented? What is the exact logic for processing methods wrapped with multiple wrappers? The answers to these questions are the focus of this section.

The implementation of wrapped methods can be thought of as a combination of the Observer and Strategy design patterns with specific semantics. Wrappers are observers of wrapped methods who are notified before and after the wrapped method is called. The before and after aren't vanilla notifications, though, and can return values that affect the logic of the call similar to a strategy. The logic of wrapped method invocation is implemented in the recess.lang.WrappedMethod class. The addWrappedMethod in recess.lang.ClassDescriptor brings wrapped methods onto a class' descriptor, and, finally the recess.lang.WrappableAnnotation abstracts away the pattern of making a plain-old class method a wrappable method.

When a wrapped method is invoked, the following process occurs:

  1. Statement S invokes wrapped method M on object O with arguments A*.

  2. Each wrapper's before method is invoked in the reverse order that the wrappers were added[2](LIFO). before is passed, by reference, O and an array of A*. The wrapper, thus, has an opportunity to get or set public state from O or any argument in A*. If a wrapper's before does not return a value or returns the value null then the next wrapper's before is called until all wrapper's before methods have been called. If a wrapper's before returns a non-null value this value does not pass go and does not collect two hundred dollars, it short-circuits the wrapper call-chain and is immediately returned to statement S.

  3. The call to the wrapped method M is made using the (potentially transformed) arguments A*. M returns value R.

  4. Each wrapper's after method is invoked in the order that the wrappers were added (FIFO). after is passed arguments O and R (M's return value). If the call to a wrapper's after returns a non-null value then this return value, R', will override R in the remaining wrapper's calls to after, else R' is set to R.

  5. The value R' is returned to S.

Warning

While nothing will stop you as an IWrapper author from writing the following at design-time, it should be noted that these practices will most likely cause errors and headaches at run-time and are considered really bad practice:

  • In before: changing the types of arguments in A*, or changing the number of arguments in A*. A* must remain such that using its elements to call method M will result in a valid method call with the arguments M expects.

  • In before: returning a value of any type other than M could be expected to return.

  • In after: returning a value R' of any type other than M could be expected to return.

At runtime each wrapper is an instantiated object. In production mode these objects are deserialized on every request. Reducing the number-of wrappers is a boost to performance in time (extra method calls are expensive) and space so Recess gives IWrapper authors a simple way to combine similar wrappers. Imagine you've just created a !Required annotation that application developers can place on properties of a Model to denote they are required for insert and update. Beyind the scenes you've written a RequiredWrapper that takes the name of a property and in the before method checks to make sure the property's value is non-null. Each annotation would thus expand to wrap insert and update with a new instance of RequiredWrapper for every property on the model. That could mean a lot of IWrapper objects to call before and after on to check requiredness! (It would also mean you couldn't check more than one field for requiredness because of short-circuit returns!)

When wrappers are applied to a WrappedMethod using addWrapper the WrappedMethod first iterates through each of the existing wrappers and calls their combine method, passing in the new wrapper. If the existing wrapper determines it can combine its state with the new wrapper's state it will do so and return true which indicates to the WrappedMethod "do not add this new wrapper to your list, I've taken on its duties". If all existing wrapper's combine method returns false the new wrapper will be added to the list of registered wrappers. Let's take a look at an example:


1

We'll store the list of required property names in $this->properties.

2

Here we short-circuit return false, so that the wrapped method will not get called. In this naive wrapper we're simply printing the error message in code.

3

Returning null is not necessary, it is the same as not returning, shown to illustrate that if all required fields are non-null the call will pass through to the wrapped call just fine.

4

Here we combine the state of two RequiredWrappers after checking for type sameness.

5

If we can combine we return true, and $that is not added to the list of wrappers because we have combined its state with $this.

6

If we cannot combine we return false.

The result of this code is that there will be two RequiredWrapper instances, one for insert and the other for update. The RequiredWrapper for insert will contain two properties in its $properties array. It is important to note that new instances of wrappers should be wrapped for each method, for example, if the same instance of a new RequiredWrapper('fieldB') had been wrapped around insert and update then fieldA would be required for update as well because of combine. (Note: This doesn't pass the sniff test and exposes too much guts. Maybe we could change the implementation of addWrapper in WrappedMethod to clone the Wrapper before adding it.)



[2] IWrapper implementations must not depend on the order in which they are applied to a wrappable method.