PHP –
Programación
Guidelines for dessigning classes in PHP
Artículo copiado de
http://www.phppatterns.com/docs/design/guidelines_for_designing_classes_in_php
Autor:
Object oriented programming takes some getting used to, quite often meaning a radical shift in the way we design our code and applications. Initially, when making the step up from procedural programming, it's often easy to regard classes as being not much more than a nice way to group a set of functions. Taking this view misses the real benefit of OOP, though, which is how it helps you deal with the inevitable: change.
In this article we'll look at a few essential tips which may help in designing classes, to help in writing flexible and
re-usable code. The idea is to lay some foundations as a platform for taking advantage of design patterns.
One of the best examples of object oriented design in PHP, IMO, is Vincent Oostind's
Eclipse Library (Extensible Class Library of PHP Software Engineers). It's well worth studying the code and
API documentation as source to get ideas from. This article will be referring to Eclipse.
Design a Solid API
Perhaps
the most important part of designing a class is constructing a solid API. API stands for application program interface. In terms of a classes, what we're talking about is the methods we provide for client code to use our class.
The API is the part of our class client code relies on. Behind the API we can effectively do whatever we like so long as we don't change the API. That means the names of the methods we provide should not change as well as the arguments they accept and the values they return. The objective is to avoid breaking the client code despite the changes we've made behind the scenes.
With PHP this is particularly important, as client code (in PHP 4.x) has access to everything, including class member variables. It's important, therefore, that we put together an API that provides a client with everything it needs rather than leaving it to take shortcuts directly to class variables which me may at a later date rename or modify.
As a general rule it's bad practice to access a classes member variables from outside the class. This should always be done via a class method, even if the only purpose of that method is to return the value on a single member variable. Looking at Eclipse, we see that some methods are documented as being ACCESSORS – these are the methods which make up the API for Eclipse.
In general if we need to radically alter our class methods, one way we can do this, without breaking earlier code, is to create a child class which over writes it's parent's methods. Older client code can continue working with the parent while new code can use the child (we shouldn't go crazy here though – see Using Inheritance below).
On a practical note, it's often useful to start by writing the blank class as s template that only contains the names of methods but no code. This helps us to concentrate on the overall design of the API without getting bogged down in the specifics of hacking.
The better we get at designing APIs, the easier it will become to modify our code at a microscopic level (within a single class, for example) without breaking the application as a whole.
It's worth investigating some of the UML tools available to help with class design. There are some (
*deprecated link* as well a short discussion of UML and PHP here.
Documentation
Writing documentation is something which goes hand in hand with API design. The purpose of documenting a class is not to describe the specifics of a classes internals (which we may do for our own notes) but rather to make a class useful to other developers. The exercise of writing this sort of documentation often helps in the general process of design so it's worth it even if we don't for see anyone else using our class.
The type of information we're aiming to provide is an overall description of what the class does along with a description of the purpose of each method, the arguments a method takes and the values it returns (in terms of variables types). Most important is making this information useful to other developers. They don't necessarily need to the specific of what we're doing behind the scenes but they do need to know how to use the API of the class.
There are a number of tools around which we can use to generate human readable output from the documentation we place in our classes. Devshed has a good article which explains
Writing Self Documenting Code. Some of the tools around with support for PHP include
Doxygen,
PHPDoc (written in PHP, this is the utility discussed in the Devshed article, now part of PEAR), the alternative
PHP Documentor (also written in PHP and has itself got excellent documentation), another tool called
PHPDoc (used to generate the Eclipse API documentation, this is a Java utility which uses Sun's Javadoc) and
eZ phpdoc (written in Perl; it's worth noting the eZ systems have switched to Doxygen for eZ publish version 3.0).
Avoid 'GOD' Classes
This goes back to the idea that classes are more that just a useful container for functions. Many of the more esoteric descriptions of OOP talk about objects representing ideas. Put in a more down to earth sense, it's a good idea to focus a class on dealing with a specific problem rather than dealing with many hundreds of problems (a single class acting like GOD for our application).
A class which connects to a database, for example, probably shouldn't be rendering HTML as well.
A more in depth example that springs to mind comes from looking at some of the
XML-RPC and SOAP clients available in PHP (this also applies to some of the RSS clients). On the one hand it is good that they offer off the shelf functionality which is easy to use. But many combine the tasks of handling
XML-RPC / SOAP messages with HTTP client functionality, using something like a single method to perform an HTTP request.
A few of the problems that arise from this; how complete is the HTTP client? Can it deal with proxy servers that require passwords? Can it connect to SSL servers? Does it understand sessions and cookies? Can it handling caching is respect to the content expiry headers a web server can send? To try and handle all that within a single class method is going to cause a lot of problems. What's more SOAP is designed not only for transfer over HTTP but also via SMTP (
e-mail). Tieing a web service implementation to HTTP limits it's use.
It would be better if the HTTP client was a separate class which could be passed to the SOAP/
XML-RPC (or RSS) client as an object. And if we're really going the distance of making our network connection classes
re-usable, we might think about building a
Network Client class (not tied to any specific layer 4 protocol), an HTTPClient_1_0 and an HTTPClient_1_1 (for dealing with HTTP version 1.0 and 1.1 which have enough significant differences to be treated as separate problems) class and and SMTPClient class. They all fit together something like this;
Unknown action "design:WebServices.png?200x68"
Now a SOAP client can use SMTP or HTTP. If the API design of the TCPClient classes is good, it should be possible to start simple then as additional functionality is required, such as dealing with proxy servers, this can be added to the clients without modifying any of the web services clients.
Going back to Eclipse, one nice element is how it separates database results from database connections. It becomes easy to pass results around to other classes rather that passing a bulky object contain database connection logic as well (an Eclipse
Database Result object has the database connection as a property).
Keep it Simple
Easy to say but often hard to do. Generally speaking the objective is to keep class methods minimal (Eclipse again demonstrates this point nicely). It makes code both easy to read and understand (should that be necessary) and makes future modifications a breeze.
If we find ourselves writing methods which accept hundreds of parameters we're probably doing something wrong. The same may apply if we've got methods with complex logic control structures (e.g. countless if / else's or giant switch statements).
If a method is getting too big, consider making some other methods which handle some of this complexity or perhaps even making a new class to which the work can be delegated.
Focus on writing human readable code
One important aspect which seems to be overlooked in many discussions of OOP is a good design should ultimately be very humble. It should'nt demand a rocket scientist to figure out what a class actually does. It should simply make sense so that when using a class in some client code, it reads very much like natural human languages.
Going back to the web services example, good might be if using the
Network Client results in code something like;
<code>
$httpClient=new HTTPClient_1_0();
$networkClient=new
Network Client($httpClient);
$networkClient->connect();
$networkClient->send();
$response=$networkClient->receive();
$networkClient->disconnect();
</code>
Hopefully reading when that code, even without comments, it's pretty clear what's going on.
Don't Output Directly
This is more of an aside but in general if a class constructs things that will ultimately end up being sent as output to a web browser, don't have the class echo() (or otherwise) directly but instead have it simply return the output on demand and leave the job of displaying it to some other code.
Where this is particularly important for web applications is there is a particular order in which output must be delivered to a browser, beginning with HTTP headers. We probably all know that Cannot add header information message that PHP will hand us if you try starting a session after we've begun sending the HTML content for a page. What's more if we have a class that renders a table and outputs the content directly to the browser, it by be the developer using the class wants to construct the table before they've even sent the <head /> section of the page.
Constants and Globals
Aside from the weird behaviour that can result from use global variables in classes and functions, when some other code declares a variable of the same name, it's generally better to avoid them when building classes and rather fetch the kind of data, which constants and globals provide a short cut to, via the classes API.
The reason is simply constants can globals place requirements on the code outside the class to provide the correct environment. To someone using our class in a different context, it may not be clear to them exactly what environment they need to provide. Worse still it may force them into a particular design that is unsuitable for their application.
Layering Applications
It's useful to think in terms of layers when building applications (as discussed in A Quick Intro to
N-Tier). Although
N-Tier is a subject not directly related to the specifics of a class, it does help when trying to decide what the role of a class is (particularly when trying to avoid 'GOD' classes). It helps to have an overall framework in mind into which the class will fit.
[Rant Warning...]
There are many open source PHP projects out there, worthy of high praise but a common problem seems to be that many tie Presentation Logic tier tasks with Data Access tier tasks, making it hard to
re-use elements of the application outside of the context they were designed for.
It's frustrating, for example, that most of the forum applications written in PHP are so difficult to build upon. In principle they make the perfect basis for building online applications, most having excellent user authentication systems, internal messaging systems and a whole load of other great features that could be put to excellent use elsewhere in a website, to build powerful community and groupware sites. Using a Facade pattern (a subject we'll look at another time on this site) the first stage of any PHP based website could be simply to install an existing forum application to provide fundamental functionality needed for all sorts of special features the site will provide.
As an aside, another area that it seems the current generation of PHP forum applications will probably miss out on, hampered by current design issues, is taking advantage of web services. Imagine if we could provide a client which would allow a remote web site to
co-host part of the forum we run on our own site? All authentication, logic, queries etc. is still handled by our site but forum members can access the user interface from a completely remote web site.
And what if PHP forums could use distributed authentication? When you consider the sheer number of people signed up to a version of vBulletin or phpBB on the entire Internet, the opportunity is there to build an authentication system where users can login to other boards using an account they created on another website. If this took a distributed form (i.e. no single site owns all the user accounts) and some kind of trust system was evolved, we'd be talking about something which completely dissolves the need for corporate owned system like Passport and Liberty.
There's endless possibilities here. Aside from the blogs out there, the first truly pioneering application of these kind of ideas I've seen is
Phuse – the PHP Unified Search Engine.
[Rant over]
The overall idea with layering an application is it makes it easy to replace layers to insert new layers to an existing application, for example placing a web services layer between the business logic tier and the presentation logic tier allows us to run the presentation logic both on a local web site and on a remote one.
Using Inheritance
This is basically an extension of the Avoiding GOD Classes argument. As programmers, when we first discover what that magic keyword extends actually does, it's easy to go overboard and use inheritance everywhere as an easy way to get access to another classes methods. The problem here though is it effectively builds one giant class, where it's hard to take a child on it's own and use it in a different environment.
It's important to remember that intention of inheritance is to group families of classes which perform more or less the same job. For example
Tree Menu and
Vertical Menu might both be children of a base class called Menu, in the same way as Eagle and Pidgeon might both be children of a class Bird.
A general rule of thumb that seems to work well is to avoid going beyond two generations of inheritance. The great grandchildren have probably mutated so much they no longer bear any resemblance to the original parent.
Consider aggregation and composition instead. Other developers can then use an Adapter Pattern if they want to use their own classes with one of yours.
It's particularly tempting to overuse inheritance when trying to deliver off the shelf functionality. A better alternative is probably an Abstract Factory method (coming soon[ish]) as with PEAR::DB for example. This allows us to package a complex relationship of classes in a friendlier API.
And finally...
Get into design patterns. It probably is true to say design is dead. When we run into those situations when coding where we find ourselves thinking There's got to be a better way to do this. Three months from know I'm going to have to change this code weep... there's probably a design pattern that can help.
As a subject they're probably
over-hyped right now, particularity with Sun and MS pushing their frameworks; a subject for endless intellectual debate. But, in essence, design patterns are simply down to earth; good common sense. A collection of the best solutions to problems that software designers encounter again and again in their daily work. Given a little thought, design patterns are really an expression of the obvious, about which there's not much room for debate (hence explainations on phpPatterns tend to be minimal, leaving it to the code to speak for itself).
Personally I've found design patterns the best way to learn the real value of OOP.
Anyway – enough tips for the time being. There's probably loads I've missed or points to debate so please feel free to add your thoughts...