1. Trang chủ
  2. » Công Nghệ Thông Tin

PHP in Action phần 2 pptx

55 301 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 55
Dung lượng 611,08 KB

Nội dung

30 CHAPTER 2 OBJECTS IN PHP break; } } In real life, obviously, we would do something more sophisticated than just echoing a string. But what if we want to handle only one of our exception subtypes here, and handle the other type somewhere else? It's simple: we can rethrow it so it can be caught by a different method or object: case ConfigException::SQL_SYNTAX_ERROR: throw $e; break; It's a good idea to name exception classes based on what went wrong rather than where it occurred. The ConfigException class in the previous examples is intended to convey the idea that they are exceptions that are typically caused by misconfiguration or bugs in the application. 2.2.4 Replacing built-in PHP fatal errors with exceptions Once we’re using exceptions, it’s a bit irritating that errors from PHP are reported as PHP 4-style errors rather than as exceptions. But it is possible to build a bridge from the old error-handling system to the new. Although this will not catch all errors (fatal runtime errors such as calling a nonexistent method on an object will not be reported), it will make error handling more consistent. The first things we need are an exception class to distinguish the PHP errors from other exceptions and a simple error handler to receive a PHP error and throw an excep- tion instead: class ErrorFromPHPException extends Exception {} function PHPErrorHandler($errno, $errstr, $errfile, $errline) { throw new ErrorFromPHPException($errstr,$errno); } Now we can set the error handler. If we proceed to try to open a nonexistent file, we will get an exception instead of the old-fashioned error: $oldHandler = set_error_handler('PHPErrorHandler'); fopen('/tmp/non-existent','r'); And if for some reason we want to return to the ordinary way of handling these errors, we can do this: set_error_handler($oldHandler); 2.2.5 Don’t overdo exceptions We want to avoid cluttering our code with too much error handling, and exceptions help us do that, since the catch statements can be fewer than error handling condi- OBJECT REFERENCES IN PHP 4 AND PHP 5 31 tionals that have to test the return codes from every method call. But even with exceptions, there is no reason to check for every conceivable problem. As Wirfs-Brock and McKean say: Defensive collaborations—designing objects to take precautions before and after calling on a collaborator—are expensive and error-prone. Not every object should be tasked with these responsibilities. Fortunately, PHP never forces you to check anything. Exception handling is one of the most important of the new features that were introduced in PHP 5. An even more important change was the new way of handling object references. This change is crucial in enabling object-oriented design. 2.3 OBJECT REFERENCES IN PHP 4 AND PHP 5 When the police are looking for a wanted criminal or a missing person, it helps to have a photograph of the individual. A good photograph can make it easy to recog- nize a person, but it only shows how he looked at a particular instant. People change clothes, put on or remove makeup, cut or change their hair, shave, grow beards, put on sunglasses, even undergo plastic surgery. Sooner or later (sooner if it’s a criminal working hard to avoid recognition) it becomes hard to recognize the person from the photograph. Even more obvious and fundamental is the fact that doing something to the pho- tograph won’t affect the person. Putting the picture in a jail cell is futile. Unless you believe in voodoo, you have to live with the fact that the image and the person are physically separate. So there are limits to what you can do if you have only the pho- tograph available. It’s nothing like having the person present. PHP 4 object handling is similar. PHP 4 creates a copy of an object every time you use an assignment or return an object from a function or method. So you get a “snap- shot” that looks deceptively like the original, but is actually a different object and doesn’t reflect or cause changes in the original. This creates some of the same problems as a photograph. In object-oriented programming, an object typically represents an entity, real or abstract, that cannot simply be changed by proxy. Changing a copy of a document won’t help if the original is the one that’s saved to the database. Changing an object representing the title of an HTML page won’t help if the original is the one that’s shown in the browser. But unlike a photograph, a copy of an object has all the bulk and weight of the orig- inal. If the original object contains two megabytes of data, the copy does, too, so now you have four megabytes in all. So copying objects make the program consume more memory than necessary. That’s why PHP 4-style object handling is universally recognized as a Bad Thing. It seemed like a good idea at the time it was implemented, but it wasn’t. The people who developed PHP did not passionately desire that kind of object behavior. It just happened to be easier given the way PHP had been implemented. Object orientation 32 CHAPTER 2 OBJECTS IN PHP was not used a lot in PHP at the time. But as it turned out, object-oriented program- ming in PHP became quite popular. It eventually became obvious that the PHP way of handling objects was a liability. So it became an urgent priority to change the default behavior of objects in PHP to use references instead of copies. This has happened with PHP 5. Object orientation in PHP 5 now works the same way as in most other object- oriented languages. PHP 4 has references, too, but they are different from the object references in most object-oriented languages. They can be used—and have been used—for object-ori- ented programming in PHP 4. But it’s hard to understand how they work, and they sometimes do things you might not expect them to do. Their behavior is counterin- tuitive. PHP 5 objects, on the other hand, behave most of the time in a way that’s useful and natural. Trying to use references in PHP 4 tends to cause headaches. In PHP 5, you can usually ignore the fact that the objects you pass around are actually references and focus your attention on making the code work. This section starts out by explaining how object references work and what hap- pened when “normal” object-oriented references were introduced with PHP 5. Then we found out why they are more useful than the earlier type of reference. They aren’t always, though, and we’ll take a closer look at that aspect as well. 2.3.1 How object references work In PHP 4, when you create an object and assign it to another variable, the entire object and all its content is copied. In PHP 5, the variable contains a reference to the object, and only the reference is copied. The following example will have different effects in the two versions: $user = new User; $user->email = 'lou@example.com'; $sameuser = $user; $user->email = 'barefoot@example.com'; In PHP 4, $sameuser->email is lou@example.com. In PHP 5, it has changed to barefoot@example.com. That's because in PHP 5, there is only one object. $user and $sameuser are both references to the same object. If you know references in PHP 4, you will realize that you can do this: $user = new User; $sameuser = &$user; $user->email = 'someoneelse@example.com'; Now the same thing happens in PHP 4 and PHP 5. $sameuser->email changes. But there is a difference. As the manual will tell you, the & operator produces a sym- bol table alias, which is a different name for the same content. That is not the same thing as a reference. The preceding code means that $user and $sameuser have the same content. In the PHP 5 object reference example, we copy the content of the vari- OBJECT REFERENCES IN PHP 4 AND PHP 5 33 able, which just happens to be an object reference. With the PHP 4-style reference, we just give the same content a different name. Most of the time, PHP 5 references are superior to the PHP 4 aliases. But there are uses for aliases, too. For example, if you have a large data structure that is not object- oriented (normally, I would not recommend that, but there’s a lot of legacy code in the world), using an alias can still save you from copying all that content, just like in PHP 4. 2.3.2 The advantages of object references As I’ve mentioned, object references help improve performance by preventing objects from being copied and consuming excessive memory space. In PHP 4 applications, many efforts were made to avoid this overhead by explicitly copying objects by refer- ence. This makes sense if you have a lot of objects or if they are very large. (Try dumping a PEAR DB object and you will see what I mean by large objects. On the other hand, if you keep your design simple, it will help keep your objects smaller, too.) In PHP 5, these efforts are no longer necessary. But having objects represented by references also has advantages for object-oriented design. It makes it easier to build and manipulate complex object structures. You put one object $dog inside object $doghouse, and then you modify the object $dog and you want that to be reflected on the inside of $doghouse. GUIs typically have this kind of complex structure. In web programming, we work with HTML docu- ments, but let’s say we are representing the elements in an HTML document as objects. We might do something like this: $checkbox = new Checkbox; $form = new Form; $document = new Document; $document->add($form); $form->add($checkbox); Now what happens if we change one of the inner elements? $checkbox->setChecked(); In PHP 4, this is practically useless, since the checkbox inside the form inside the doc- ument won’t change. In PHP 5, it will change, and when we generate the HTML code from the Document object, it will have a checked checkbox. This is obviously what we want, and it illustrates what I mean when I say that the behavior of PHP 5 objects is mostly intuitive, useful, and natural. 2.3.3 When references are not so useful Object references may be wonderfully intuitive most of the time, but at other times we actively want objects to be copied rather than passed around by reference. This is the case with the kinds of objects known as value objects. If we represent dates, money 34 CHAPTER 2 OBJECTS IN PHP amounts, and the like as objects, it will be more natural to copy them, because they have no identity. To copy objects in PHP 5, use the clone keyword. We will deal with this in detail in later chapters. After references, we will deal with one more feature that was introduced in PHP 5: the ability to intercept method calls and transform them before they are executed. 2.4 INTERCEPTING METHOD CALLS AND CLASS INSTANTIATION In PHP 5, a feature was introduced called overloadable method calls. In practice, the feature allows us to intercept, re-route, and redefine method calls. It’s like stealing someone’s mail and opening it. Then we can send it to someone else, change the con- tents, or even throw it into the wastebasket. This means that we can change the usual way methods respond and even respond to nonexistent methods. We will start this section by clarifying the official term overloadable method calls and how it relates to the idea of intercepting method calls. Then we’ll see a couple of exam- ples of how this can be used: Java-style method overloading, and a general logging mechanism for method calls. Finally, we’ll take a peek at a related subject: how to use the autoload feature to control what happens when a class is instantiated. 2.4.1 What is “method overloading”? “Method overloading” may be a slightly confusing term, since it means something spe- cific in other languages. In Java and C ++ , method overloading means writing different methods that have the same name, but different numbers or types of arguments, and which method is executed depends on what arguments you supply. This is particularly useful in statically typed languages (such as Java and C ++ ). Without method overload- ing, you might need two differently-named methods just to handle arguments of differ- ent types (for example, a date specified as a string or a numerical timestamp). Overloadable method calls in PHP 5 are more general. You can overload method calls, but you have to define the overloading yourself. It works like this: if you try to call a method that’s not defined, PHP 5 will call a method called __call() instead. Then you can do whatever you want with the “failed” method call. You can execute another method, possibly on another object, or you can give an error message that’s different from the usual one. You can even do nothing; that will cause PHP to disregard failed method calls instead of generating a fatal error. That could be useful occasion- ally, but in general, be careful with anything that reduces the level of error checking and allows bugs to go unnoticed. This behavior is not method overloading, but it does allow you to define method overloading, so it does make method calls overloadable. The term overloading means that the same element (in this case, a method name) can have different meanings depending on context. And, since __call() lets us INTERCEPTING METHOD CALLS AND CLASS INSTANTIATION 35 check the context and respond according to it, method overloading is one of the things we can do with it. 2.4.2 Java-style method overloading in PHP Sometimes it’s convenient to be able to call the same method with a variable number of arguments. PHP makes this possible through its ability to define optional argu- ments with default values. But sometimes, you need the method to have significantly different behaviors depending on the argument list. In languages that don’t have method overloading, this means adding conditional logic to the beginning of the method. If you can use method overloading instead, you can skip the conditional logic and the code will be cleaner. It’s possible to implement this kind of method overloading using __call() in PHP 5. Let’s look at an example. We’re assuming that we will reuse the overloading behavior, so let’s put it in an abstract parent class: abstract class OverloadableObject { function __call($name,$args) { $method = $name."_".count($args); if (!method_exists($this,$method)) { throw new Exception("Call to undefined method ". get_class($this)."::$method"); } return call_user_func_array(array($this,$method),$args); } } Most of the behavior of this class is defined by the one line in bold. If an undefined method is called, the __call() method generates a new method name consisting of the original method and the number of arguments, separated by an underscore char- acter. Then it calls the method with the newly generated name, passing the original arguments along. Now if we want to make an overloaded method called multiply that can be called with one or two arguments and will multiply them in either case, we make two methods called multiply_2 and multiply_3, respectively: class Multiplier extends OverloadableObject { function multiply_2($one,$two) { return $one * $two; } function multiply_3($one,$two,$three) { return $one * $two * $three; } } To use this, we just call the multiply method with two or three arguments: $multi = new Multiplier; echo $multi->multiply(5,6)."\n"; echo $multi->multiply(5,6,3)."\n"; 36 CHAPTER 2 OBJECTS IN PHP This is still not quite the same as method overloading in Java and C ++ , since we’re only checking the number of arguments, not their types. However, we could use type information as well. On the other hand, as we’ve seen, having the behavior depend on argument types is less important in PHP than in statically typed languages. We’ve looked at how overloadable method calls work. For an example of how they can be put to use, let’s see how they can be used to log method calls. 2.4.3 A near aspect-oriented experience: logging method calls Aspect-oriented programming is a relatively new-fangled way of doing some things that are not entirely elegant in plain object-oriented programming. For instance, consider the problem of logging the start and finish of all method calls in an application. To do this in plain OOP, we have to add code to every single method. We can work to make this additional code minimal, but it will certainly add substantial clutter to our classes. Logging is typically the kind of problem addressed by aspect-oriented program- ming. These problems, called crosscutting concerns, touch different modules or sub- systems and are hard to isolate in separate classes. Another example would be checking whether the current user is authorized to use the method. Aspect-oriented programming is typically done by defining aspects—class-like con- structs that are inserted into the code during a code-generation process. Here, we will do something much simpler and more primitive using __call() in PHP 5. We use the PEAR Log class and control the logging process from the __call() method in a parent class, as shown in listing 2.1. class LoggingClass { function __call($method,$args) { $method = "_$method"; if (!method_exists($this,$method)) throw new Exception("Call to undefined method " .get_class($this)."::$method"); $log = Log::singleton('file', '/tmp/user.log', 'Methods', NULL, LOG_INFO); $log->log("Just starting method $method"); $return = call_user_func_array(array($this,$method),$args); $log->log("Just finished method $method"); return $return; } } This is similar to our method overloading example, in that the actual method has a slightly different name than the name we call from the client code. The method we call from the client code doesn’t exist, so __call() intercepts it, logs the beginning, calls the real method, and logs the end. Listing 2.1 Parent class for classes in which we want to log method calls INTERCEPTING METHOD CALLS AND CLASS INSTANTIATION 37 To use it, we need to extend LoggingClass and give the methods names that start with an underscore. (There’s no compelling reason why it has to be an underscore; you can use anything that makes the names unique.) Listing 2.2 is a simplified class for handling dates and times: class DateAndTime extends LoggingClass { private $timestamp; function __construct($timestamp=FALSE) { $this->init($timestamp); } protected function _init($timestamp) { $this->timestamp = $timestamp ? $timestamp : time(); } function getTimestamp() { return $this->timestamp; } protected function _before(DateAndTime $other) { return $this->timestamp < $other->getTimestamp(); } } The init() and before() methods will be logged; the getTimestamp() method won’t, since the name doesn’t start with an underscore character. I’ve added the init() method to allow the construction of the object to be logged as well. The __call() method is not normally triggered during construction. That’s not sur- prising, since a class is not required to have a constructor. The loggable methods are declared protected. That means they cannot be called from client code except through the __call() mechanism. They are protected rather than private because the __call() method is in a parent class. Now let’s try the class and see what happens. We make two different DateAndTime objects and then compare them: $now = new DateAndTime; $nexthour = new DateAndTime(time() + 3600); print_r(array($now,$nexthour)); if ( $now->before($nexthour) ) { echo "OK\n"; } The method calls are logged like this: May 04 15:20:08 Methods [info] Just starting method _init May 04 15:20:08 Methods [info] Just finished method _init May 04 15:20:08 Methods [info] Just starting method _init May 04 15:20:08 Methods [info] Just finished method _init May 04 15:20:08 Methods [info] Just starting method _before Listing 2.2 DateAndTime class with methods that can be logged 38 CHAPTER 2 OBJECTS IN PHP May 04 15:20:08 Methods [info] Just finished method _before It’s far from aspect-oriented programming (AOP) in a specialized AOP language. And in practice, if you want to log method calls, you may be looking for a profiling tool. There seems to be a potential for useful applications, though. Overloadable method calls are a kind of magic that lets us define what will happen whenever a method—any method—is called. Autoloading classes is a similar concept: we can define what happens whenever we try to use an undefined class—any unde- fined class. 2.4.4 Autoloading classes To use a class in PHP 4, you have to include or require the file that contains the class. PHP 5 has a way to avoid this by automating the process of loading classes. You can define a function called __autoload() that will be run each time you try to instantiate a class that is not defined. That function can then include the appropriate class file. Listing 2.3 shows an example that is slightly more sophisticated than the standard example. function __autoload($className) { include_once __autoloadFilename($className); } function __autoloadFilename($className) { return str_replace('_','/',$className).".php"; } The __autoloadFilename() function generates the name of the file to include. (There is a separate function for this just so it would be easier to test. We can run a test on the __autoloadFilename() function and check that its return value is correct. Checking that a file has been included is more difficult than just checking the return value.) The str_replace function replaces all underscores with slashes. So if the class name is HTML_Form, the __autoload() function will include the file HTML/ Form.php. This makes it easy to sort classes into different directories in the PEAR stan- dard way. If you have very small classes (there are some of them in this book), you might find it convenient to keep more than one class in a file. You can combine that with autoloading by making a link in the file system. Say you have a Template class and a Redirect class and they are both in a file called Template.php. In Linux or UNIX, you could do this: ln -s Template.php Redirect.php Listing 2.3 Autoloading class files SUMMARY 39 Now if you use the Redirect class, the __autoload() function will include the Redirect.php file, which happens to be a link to Template.php, which in turn con- tains the Redirect class. 2.5 SUMMARY Object-oriented programming in PHP is a natural way to work, especially with the enhancements that were introduced in version 5. Some features are common to nearly all object-oriented languages. You can define classes that allow you to create objects with the behavior you want; you can use constructors to control what hap- pens when the object is created; and you can use inheritance to create variations on a class. Exceptions provide more flexible and readable error handling. Being able to handle objects by reference makes life much easier in PHP 5 than in PHP 4, particularly when dealing with complex object-oriented structures. The ability to call a method on the result of a method call is convenient in the same circumstances. The ability to intercept method calls and access instance variables allows us to solve several different problems in a more elegant way. We can make the first step in the direction of aspect-oriented programming, using overloading to insert code before or after all method calls (or a selection of them) without having to duplicate all that code. We are moving gradually from programming syntax toward application design. In the next chapter, we will take a look at some PHP features that act as tools for object- oriented design. Among them are visibility restrictions, class methods, abstract classes, and interfaces. [...]... weak and indirect one, since we can be sure to avoid this kind of error if we make sure not to use any methods that are not defined in the Template interface Anyway, since there is no such checking in PHP, we can always use a tag interface in place of a “real” one INTERFACES 63 3.5.5 Interfaces in PHP 5 versus Java One notable difference between Java and PHP interfaces has already been mentioned: PHP type... complain Your average dynamically typed language has no interface construct Interestingly, PHP 5 does But how useful are they really, and for what purposes? Let’s investigate We’ll start by seeing how interfaces work and discuss whether they are needed at all in PHP Then we’ll see a couple uses for interfaces: making design clearer, and improving type hints Finally, we’ll see how interfaces in PHP differ... particular kind of complexity worth the trouble Using type hints can be troublesome if you’re tied to using a specific class name, since changing the class name requires you to change all the type hints One way to get more freedom in using type hints is to use interfaces 3.5 INTERFACES The word interface has a semantic meaning and in some programming languages— a syntactic one as well In object-oriented... obvious In both Java and PHP, an interface can extend another interface using the extends keyword And in both, you can add the abstract keyword to methods in interface, but it’s not required, since all method declarations in an interface are abstract anyway Table 3.3 summarizes these differences and similarities Table 3.3 Comparing the interface construct in PHP 5 and Java PHP 5 Java ✓ Overlapping methods... method or instance variable is visible to any class in the same package Protected methods and variables are available to child and descendant classes and to other classes in the same package By contrast, in PHP 5, default visibility is public; this makes it possible to program in PHP 5 in the same way as in PHP 4 If we do not indicate visibility, everything is publicly visible, as in PHP 4 PHP 5 also... defined the Response interface like this: interface Response {} In Java programming, this kind of interface is sometimes called a tag interface But there is an important difference between Java and PHP: when you name an interface as the type of a method argument in Java, the compiler will not allow you to call methods on that object that are not defined in the interface The PHP type hint is just a simple... TYPE HINTS 59 abstract class StringHolder { protected $string; public function construct($string) { $this->string = $string; } public function getString() { return $this->string; } } class SqlStatement extends StringHolder {} I am not advocating this approach; I’m simply pointing out the possibility It would clearly be overkill for general use, but there may be circumstances in which type checking is... also used in a different meaning—to define class constants PHP 5 uses const instead The similarities and differences between PHP and Java are summarized in table 3 .2 Table 3 .2 The final keyword in PHP 5 versus Java Java final classes cannot be extended by child classes final methods cannot be overridden by child classes Syntax for class constants ✓ ✓ static final PHP 5 ✓ ✓ const We’ve discussed visibility... of this kind of thing: $this->setDate(Date $date); Now if we change the name of the Date class to DateMidnight, we will have to change all those type hints Programmers who are used to dealing with this kind of thing in Java may tell you that you should type hint on an interface to avoid this, but finding out what interface you need is far from trivial Type hints are more likely to be useful in constructors... while internally we control the accesses from accessor methods Listing 3 .2 shows how to do this 2 46 In the official documentation, this is called overloading, though this doesn’t quite fit the standard definition of overloading But it’s not entirely unreasonable to use the term overloadable, since we can use it to implement something similar to Java-style method overloading C H A PT E R 3 USING PHP . 15 :20 :08 Methods [info] Just finished method _init May 04 15 :20 :08 Methods [info] Just starting method _init May 04 15 :20 :08 Methods [info] Just finished method _init May 04 15 :20 :08 Methods [info]. crucial in enabling object-oriented design. 2. 3 OBJECT REFERENCES IN PHP 4 AND PHP 5 When the police are looking for a wanted criminal or a missing person, it helps to have a photograph of the individual Methods [info] Just starting method _before Listing 2. 2 DateAndTime class with methods that can be logged 38 CHAPTER 2 OBJECTS IN PHP May 04 15 :20 :08 Methods [info] Just finished method _before It’s

Ngày đăng: 12/08/2014, 21:21

TỪ KHÓA LIÊN QUAN