- PHP 5 CMS Framework Development / 2nd Edition
- Martin Brampton
- 2268字
- 2021-04-13 17:21:59
Before we look at specific solutions, we can explore various helpful aspects of PHP and also look at the pattern known as Singleton.
We want to automate the loading of code and PHP provided a tool to do exactly this with the launch of PHP5. It was the __autoload
function. When PHP came across a class it did not know about, it called __autoload
, passing the class name as parameter. It was up to the user to code a suitable body for the function.
This was a good start, and in the first edition it was the solution to the problem. However, PHP has moved on and improved the handling of autoloading. The obvious problem with __autoload
was that there could only be one such function. Someone writing, say, a library of PHP code for some particular purpose will not know about all the possible uses for the library, but may well want to use autoloading. So there really needs to be a system that supports multiple spheres of autoloading. PHP adapted to this need by providing spl_autoload_register
in version 5.1.2, which can be used multiple times, permitting the registration of a number of autoload functions (which can now be methods).
Classes in PHP are globally visible. The same is true of functions, although mostly I avoid the use of functions in favor of building classes and methods. Because classes are global names, large projects have often put a prefix on their class names to reduce the risk of name clashes. For example, most classes in Aliro have names that start with "aliro".
PHP 5.3 introduces a feature that was first slated as part of PHP 6, which is the ability to write code within namespaces. At the time of writing, it is not feasible to take advantage of this in a project that is developing code for general distribution, since it will be some time before PHP 5.3 is widely deployed in web hosting. If you are writing for a controlled environment and have chosen to use PHP 5.3 then it is possible to take advantage of namespaces.
There is widespread disappointment that the separator chosen to go between a namespace and a class name is the backslash. (It is also used between different namespace names when they are built into a hierarchy.)
The basic principle of namespaces is that code can be declared to be within a namespace, and the full names of classes and functions are deemed to be the coded names, preceded by the namespace and a backslash. Within the namespace, it is only necessary to use the coded name, but outside the namespace, the full name is required. It is thus possible to create classes with natural sounding names, such as "user", without fear of name clashes.
In a system like Aliro, where class names are provided for extensions in a context such as the packaging XML, the fully qualified name will need to be given so that references can be guaranteed to be unique. With this proviso, it is expected that existing code will continue to work.
It may be that as namespaces become more familiar their appeal grows. At present, I must admit to being lukewarm about them. Where code is being brought together from a variety of sources, each of which operates in ignorance of the others, as happens with a CMS that supports extensions, there will be a requirement to use fully qualified names on many occasions. These seem no better than long names that incorporate prefixes. Having grown used to always referring to classes that have prefixed names, I am not really troubled by them. But time may alter perceptions.
The class to be discussed later handles the management of the code files in the system, and is the first example we will encounter of the Singleton Pattern. There are also elements of the Factory Method Pattern. Aliro makes extensive use of singleton classes. Some people are critical of the Singleton Pattern, and the objections will be considered once the basic mechanisms have been explained.
Singletons arise naturally in designing systems where a range of related services is needed. Sometimes, these are provided by creating a class that consists only of static methods. But that is both inefficient, and inflexible. Dealing with an instance of a class is generally more efficient than dealing with static methods, and in many cases a singleton object will know quite a lot of potentially complex information. Since a class of this kind is a manager or handler, there should never be a need for more than a single instance of the class, hence the name singleton.
Class names are global (subject to the namespace considerations mentioned earlier), and singleton classes implement a static method, conventionally called getInstance
. If the single instance were created outside the singleton class by using the new
keyword, there would be no way to stop multiple instances being created. In PHP5, the use of new
can be completely blocked from outside the class by making the constructor method private or protected. The other route to creating more than one object of a particular class is to clone the object, and that is blocked by having the __clone
method private or protected, even though it may do nothing. In the case of classes that inherit from cachedSingleton
the protected __clone
method is inherited from the parent class.
With these mechanisms in place, the only way to obtain the one and only instance of the class is to use the static getInstance
method. This can be done in any PHP module as long as you know the name of the required class. Within the class, the single instance of the class can be held in a class variable, and a simple convention is to always call it self::$instance
, as shown in this rudimentary example of a singleton class and a test:
class mySingleton { private static $instance = null; private function __construct () { } public static function getInstance () { return self::$instance instanceof self ? self::$instance : self::$instance = new self(); } } $testobj = mySingleton::getInstance(); var_dump($testobj) $testobj = mySingleton::getInstance(); var_dump($testobj)
Running the previous code will show a dump of the same object twice, the sole instance of mySingleton
that is created only once. The logic is that if our instance variable is already an instance of the class, we can return it without further ado. Otherwise, we assign to it a new instance of the class before returning it. Note that the getInstance
method could be put into any class that uses the conventional name for the instance variable, as it contains no code that is specific to the class.
In fact, the code used in the getInstance
method used for class mapping is rather more complex, as we will see in a later section.
In the discussion of patterns in Chapter 1, CMS Architecture it was pointed out that slavish devotion to patterns can be damaging. So it is certainly possible to misapply patterns, including Singleton. I am keen to advocate well thought out designs, and prepared to sacrifice a certain amount to principle. All the same, design is often a question of trade offs between one benefit and another. In my view, blanket disdain for the Singleton Pattern is an extreme view. But it is worth considering the complaints that are made.
One objection is to the appearance of the word "global" in the description of the pattern's intention by the GoF: "Ensure a class only has one instance, and provide a global point of access to it". Now the problems with global data were discussed in Chapter 1, CMS Architecture and although we must recognize that there are pitfalls to be avoided, it is unwise to unconditionally abhor globals. It has to be borne in mind that some entities have more need to be global than others. The names of classes and functions are global in most programming languages. So it is possible to say that the code new myClass()
provides a global mechanism for obtaining an instance of myClass
. This is not something we can avoid. (In fact it is a weakness of many languages, including PHP, that there is no way to limit a class or method so that it can only be used within a cooperative group of classes.)
In the case of some classes, including the one that is described in this chapter, it is reasonable to urge that there should be as few references to them as possible. In fact, in the whole of Aliro, there is only one reference to the getInstance
method of smartClassMapper
, and that is only required because of quirks of initialization. But in other cases, it is hard to see that there is anything wrong with widespread use of a singleton class, such as aliroMenuManager
, which knows about all the menu entries in the whole CMS. Earlier systems typically found applications repeatedly reading from the menu table in the database. Now the database, as we noted in Chapter 1, can actually be a rather pernicious kind of global data. It is highly desirable to have a programmed interface between an application and a key data resource, such as the menu items. It is both more flexible and more efficient than direct access to the database. Given that any application may want access to information such as the menus, if it is not offered through a globally known service, such as aliroMenuManager
, the alternative is a long string of parameters passed to every application (the CMS does not know in advance what applications will be installed or what they will do). In my view, long strings of parameters that may not be used are rather worse than global access to core functions. Indeed, they totally undermine the goal that objectors to singletons profess to seek, which is to make dependencies explicit. It is not just in one place that this consideration applies; in a loosely coupled system, it will occur in many places.
Another objection that has been made to singletons is that they tie code tightly to a particular class implementation, making it difficult to substitute alternatives. This objection also runs up against the problem that it tends to demand long lists of parameters to avoid the code simply knowing where to get a service. The objection also runs counter to another important object principle: objects should not need to know more than necessary about the workings of other objects. In a tightly coupled system, objects may have to know quite a lot about each other, but in a loosely coupled system, it may be an extremely onerous requirement to have to provide an object with all the resources it needs.
Also, to the extent that this objection is brought against singletons implemented in a language with at least as much flexibility as PHP, it takes a too narrow view of singletons. Although the basic Singleton Pattern returns an object of the class whose getInstance
method has been called, this is by no means required by the language. It is perfectly possible to incorporate ideas from the Factory Method Pattern into the getInstance
method, so that it returns an object from one of a framework of classes. This approach is often used in Aliro, including in the class described in this chapter. The smartClassMapper
is one of a number of classes that return different objects depending on whether the current request is inside or outside the interface that is exclusively available to administrators. There is much code in Aliro that can operate in the same way irrespective of who is using it, relying on other classes to instantiate themselves appropriately.
A variation on this objection is the claim that subclassing singletons creates difficulties. This has already been partially answered. It might be true in some languages, but using PHP Aliro plainly demonstrates in a number of areas that quite complex frameworks of singleton classes can be created (as, for instance, in the classes that represent the various kinds of extension that can be added to a CMS). The objection just does not stand up.
The final objection to be considered is that singletons carry persistent state. Now this objection might have more force in a language that did not work by processing individual HTTP requests to completion, usually in less than a second, and then ending. Certainly, it would be unwise to have a singleton that simply behaved as global storage, allowing data to be stored and retrieved arbitrarily. But bad uses do not make a pattern universally bad. The typical application for a singleton is to provide a point of access to data that is fixed, at least for the duration of a request, and typically only modified infrequently by an administrator. Often, in Aliro, when such modification occurs, the related singleton object is effectively destroyed. The flow of data is mostly only out of the singleton objects (with the exception of helper methods that are used to confine knowledge of database structures to one or a few classes).
All in all, I continue to believe that judicious use of the Singleton Pattern, sometimes combined with elements of the Factory Method Pattern, is helpful to creating a strongly OO, loosely coupled CMS framework. One curious outcome, worth mentioning here, is that the singleton class to be described later actually serves to limit knowledge of classes, so that code that is not running in relation to the administrator interface has no knowledge of classes that are exclusive to administrators.