Sub-classes of Object allow you to attach methods to a class dynamically at run-time. Once a method has been attached all instances of that class in existance, or instantiated thereafter, will have the method available. Attached methods also show up with reflection when using the RecessReflectionClass. Example usages of attached methods are: the implementation of relationship methods on models, and wrappable methods. To attach a method to a class you must first define the target method on another class. The following detailed example demonstrates attaching a method to a class.


1

The class we will attach a method to is MyRecessObject, it is important that it extends Recess' Object class.

2

The class AttachedBehavior contains the method we will attach to MyRecessObject: targetMethod. Attached methods must be defined within a class because attaching requires an instance of the class as well as the method name.

3

Notice that attached methods always take an instance of the class they are attached to as their first parameter. Subsequent parameters are the parameters to be passed by the attached method's call (6).

4

We've created an instance of the AttachedBehavior class and given it some state. The ability to provide the method with some context is key to being able to do interesting things with attached methods on a class-by-class basis. For example, when a !HasMany or !BelongsTo annotation expands it attaches multiple methods to the class the annotation is defined on. Each of these target methods are defined on an instance of HasManyRelationship or BelongsToRelationship that have additional state, such as the relationship name, related table, foreign key, etc, that are vital state for the attached methods. One way to think of this is a poor man's closure.

5

The call to the static method attachMethod is where the magic mapping happens. Object defines attachedMethod which has four parameters. The first is an unfortunate one: the string classname the method is being attached to[1]. The second parameter is the name we are assigning to the new method on our class. So, in this example the method's name will be attachedMethod(). Next we provide the target the attached method will call. We specify this by passing an instance of an object, and the string name of the method being attached.

6

We can now call attachedMethod on an instance of MyRecessObject and it will map to targetMethod on our AttachedBehavior instance.

Underneath the covers there is some simple indirection taking place in Object's __call method that maps the attached method call to the target method based on entries in the ClassDescriptor. If methods are attached during the process which sub-classes of Object expand annotations and build-up ClassDescriptors then the target instance, its state, and the mapping will be automatically cached in production mode. For more information on ClassDescriptor, see the section called “Hooks in Object while expanding Annotations and shaping ClassDescriptor



[1] This parameter a result of PHP < 5.3's lack of late static binding. Even though the static method is invoked on the class we are attempting to attach the method to, MyRecessObject, until PHP 5.3 there is no means for determining the sub-class it was called on. After PHP5.3 this limitation has been fixed.