Transfer Object
From Patterns For PHP
Contents |
Introduction
The intuitive naming of Patterns gives a clue as to the meaning of a Transfer Object. In short a Transfer Object bundles data into a single, easy to transport object. The Transfer Object can sometimes be referred to as the Data Transfer Object (as in POEAA). Both names refer to the same Design Pattern.
There are a number of reasons why a Transfer Object is useful. By grouping related sets of data in a single object, it reduces the complexity of passing such data into methods by removing long parameter lists. Long parameter lists are often termed a "code smell" since they can be confusing and parameter order must be maintained even if a value is not used. As a return value from a method, a Transfer Object also enables multiple values be returned.
Of course this can be easily accomplished in PHP by an array. While a Transfer Object may resemble an array, it has other features that ultimately make it more useful:
- A Transfer Object has a class type which can help enforce an interface by using PHP5's Type Hinting.
- It's preset properties will always be available (with default values). One can even prevent the setting of new public properties.
- It is possible to perform additional actions in a Transfer Object upon setting a new value via PHP5's Magic Methods.
Definition
Definition: The Transfer Object allows the encapsulation of multiple known values in a single object that supports basic data storage and retrieval functionality.
Implementation for PHP5
Implementing a Transfer Object is quite simple. A distinction should however be made between a Transfer Object and the very similar Value Object. The values in a Transfer Object are not immutable, i.e. they can be changed via a public interface (getters and setters) or by defining the Transfer Object's properties as public or using Magic Methods to simulate public properties in the interface. Often the two Pattern titles are used interchangeably although there are differences in their respective goals.
An example of a Transfer Object might be an object which stores the data associated with a row in a database table. Rather than passing each database value individually to other objects' methods, one need only pass the representative Transfer Object. This simplifies operations quite a bit and removes a lot of complexity from a class interface if used wisely.
Moreover, since a Transfer Object has a specific class type. An accepting method can restrict a parameter to only accept the expected Transfer Object by using Type Hinting.
A simple PHP5 Transfer Object:
Note that the properties are defined as public. For this Transfer Object, the client code can use the properties directly without accessors. Since a Transfer Object by definition is not designed to hold arbirary properties (created by client code), the Registry Pattern is more suited for such a purpose or one can even fall back on an array. While a Registry is agnostic to its contents, a Transfer Object's property names (or alternatively the matching setters/getters) form part of its testable interface.
A Unit Test (using SimpleTest) for the above simple Transfer Object would contain:
It's worth noting that the most common alternative to a Transfer Object is a simple array. An array and a simple Transfer Object share similar characteristics. A Transfer Object however has a class type and will always contain specific properties (optionally with default values). Additionally a Transfer Object can perform it's own private internal operations on incoming and outgoing data by using the PHP5 Magic Methods: __set() and __call() to overload the public properties.
Let's consider our previous User Row example. As the User Row Transfer object is passed around our application and other classes may alter the original data. This changed data will need to be stored to the database. To prevent unnecessary SQL update operations, we would like if the Transfer Object could inform the application whether it's data has been changed (i.e. dirty) or unchanged (i.e. clean).
For the example below we'll make use of PHP5's Magic Methods in order to control the value of an additional private property - isDirty. Other objects will be able to retrieve the value of isDirty by calling the isDirty() method.
The differences from our simple prior example are not all that complex assuming you are familiar with PHP5's overloading[1] magic methods. The use of the Magic Methods sets up access to private member properties (the private $data array) while upholding the interface that properties are publicly accessible.
Overloading with magic methods lets us use the class in the same way as the first example only this time the Transfer Object can check whether it's default values have changed by setting the value of the isDirty property to TRUE. There are no public properties since magic functions cannot overload a property which is already defined - hence they are moved to a private array. As a last step we add a public getter method to retrieve the isDirty value.
The use of magic methods which rely solely on a defined private array of possible values also has an additional effect: it prevents the setting of any new public properties by client code. In effect the magic methods version is limited to the defined properties - no new ones can be set.
In both cases, the classes can be used in the following way.
Output:
Padraic
mypassword
myemail
mywebsite
As with our simple Transfer Object, we can verify that the complex version follows an identical interface as the simple version by writing a Unit Test to verify the expected behaviour of the class. The Unit Test is identical to the last version, except that we additionally ensure the isDirty property value is set to TRUE when a value is changed from the defaults (set via the constructor when a User_Row object is instantiated.
As we previously noted. A Transfer Object has a class type - an array has no specific type except the generic "array" (from PHP 5.1). This difference is one further advantage of using this pattern instead of an array. If you take the following client code, any attempt to pass a parameter which is not an object of the type User_Row to the constructor will result in a fatal error. This safety net feature ensures client code adheres to this class's interface and is not capable of passing other object types or arrays. For example, our User_Row_Printer class below only knows how to work with User_Row objects - giving it any other object might result in unexpected output or a fatal error.
Implementation for PHP4
Implementing a PHP4 Transfer Object is not as effective as in PHP5. The lack of property visibility keywords (private, protected and public) mean that property access cannot be restricted. This rules out effective getters and setters which might replace the PHP5 use of the magic methods __get() and __set(). Getters and setters can be implemented, but without access keywords the client is free to ignore them and access the object properties directly.
Still, the basic structure remains the same and type hinting (if desireable) can be implemented within any method by making an is_a() check against the lowercased class type of the relevant Transfer Object being passed as a parameter to the method.
A simple Transfer Object example for PHP4:
Unit Testing of the PHP4 version is largely similar to the PHP5 Unit Tests (minus the PHP5 syntax differences).
Conclusion
The Transfer Object is a simple Design Pattern. At first glance it can easily be mistaken for a glorified array but there are a number of important differences. Unlike an array, a Transfer Object has a specific type which allows a class's method be restricted to only accepting a specific Transfer Object through Type Hinting. While an array can have any number of additional fields appended, a Transfer Object can have a fixed number of properties enforced (see PHP5 complex example code). As a result, Transfer Objects of the same type can always be handled in the exact same way. Another difference is that a Transfer Object can perform its own internal operations when values are set. The PHP5 examples above illustrate how a User_Row object can track whether it's default values have changed since the object was created thereby allowing client code (perhaps a Data Access class) tell when the database row the Transfer Object represents requires updating.
On a par with an array, a Transfer Object by bundling multiple values in a single entity can help alleviate the long parameter list "code smell" when refactoring and enable methods to return multiple values encapsulated in an object. This is not its primary purpose, but the resemblance to a Parameter Object is close.


