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

Practical Web 2.0 Applications with PHP phần 10 doc

52 465 1

Đ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 52
Dung lượng 2,39 MB

Nội dung

Deployment and Maintenance So far in this book we have developed a somewhat complete web application. Although features can be added to an application, there is an old saying that the last 10 percent of the development of an application takes 90 percent of the time. What this typically refers to are all the little details in making the application something that can be used reliably by many people. In this chapter, I will cover some of the details that this refers to, such as handling errors and notifying the user accordingly that something went wrong, deploying the application on a production server, using application logs, and backing up the application. Application Logging In Chapter 2 we set up logging capabilities for our web application, meaning we can record when various events occur. Although the only events we actually recorded were related to user logins and blog indexing, the idea was that we put a foundation in place that can easily be used anywhere in the application whenever required. Having said that, a logging system isn’t much use if there’s no way to use the log. In the following sections, I will talk more about the logging system and show how it can be extended and used. The reason for looking at the logging system first in this chapter is that the changes we will make later in this chapter for handling site errors rely partly on the features added here. E-mailing Critical Errors to an Administrator Zend_Log provides the ability to have multiple writers for a single logger. A writer is a class that is used to output log messages, be it to a file (as we have done so far), a database, or an e-mail. The Zend Framework doesn’t ship with a writer that can e-mail log messages, but we can easily write our own by extending the Zend_Log_Writer_Abstract class. We then register the new writer with the logger so critical errors can be sent to the e-mail address we will add to the application configuration. Creating the Log Writer The main log writers that come with the Zend Framework are the stream writer (for writing to files) and the database writer. Both of these writers record the message to their target locations as soon as the message is logged. If we were to do the same thing for our e-mail writer, then a new e-mail would be sent for every recorded message. Since a single request could result in 519 CHAPTER 14 9063Ch14CMP2 11/13/07 8:20 PM Page 519 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com several log messages, we must accumulate log messages and send them in one e-mail mes- sage at the completion of the request. Thankfully, Zend_Log simplifies this by allowing us to define a method called shutdown(), which is automatically called (using PHP 5 class deconstructors) when the request completes. The shutdown() function we create will use Zend_Mail to send an e-mail to the nominated address. We will call this class EmailLogger, which we store in ./include/EmailLogger.php. Listing 14-1 shows the constructor for this class. We create an array in which to hold the log messages until they are ready to be sent. Additionally, we use Zend_Validator to ensure a valid e-mail address has been included in the constructor arguments. By implementing the setEmail() function, we can easily change the target e-mail address during runtime if required. The other key line of code to note here is the final statement where we instantiate Zend_ Log_Formatter_Simple. With Zend_Log you can create a custom formatter, used to define how a log message appears in the writer’s output. We will use the built-in Zend_Log_Formatter_Simple class (you saw an example of its output in Chapter 2), but you could also use the built-in XML formatter or create your own. ■Note If you want to use a different formatter, you would typically call the setFormatter() method on the writer once it has been instantiated rather than changing the code in the writer. Listing 14-1. Initializing the EmailLogger Class (EmailLogger.php) <?php class EmailLogger extends Zend_Log_Writer_Abstract { protected $_email; protected $_events = array(); public function __construct($email) { $this->_formatter = new Zend_Log_Formatter_Simple(); $this->setEmail($email); } public function setEmail($email) { $validator = new Zend_Validate_EmailAddress(); if (!$validator->isValid($email)) throw new Exception('Invalid e-mail address specified'); $this->_email = $email; } CHAPTER 14 ■ DEPLOYMENT AND MAINTENANCE520 9063Ch14CMP2 11/13/07 8:20 PM Page 520 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Next we must create the _write() method, shown in Listing 14-2. This is an abstract method that is called internally when a new log message is recorded. In this method we sim- ply need to write the message to the $_events array. As noted earlier, this array is used to hold the messages that are to be e-mailed until the e-mail is actually sent. Before the message is written to the array, we must format the message using the format- ter. If you prefer, you can write the raw message to the array here and format it when generating the e-mail body in shutdown(). Listing 14-2. Formatting the Log Message and Then Accumulating It for Later Use (EmailLogger.php) protected function _write($event) { $this->_events[] = $this->_formatter->format($event); } Finally, we implement the shutdown() method. As mentioned, this method is called automatically when the request ends. Our objective in this method is to generate an e-mail subject and body and then send the message using Zend_Mail. Obviously, if there are no log messages, then we don’t want to send an e-mail at all, which is why the code checks whether $this->_events is empty before creating the e-mail. Listing 14-3 shows the code for shutdown(). You may want to use different text for the sub- ject and body than what I’ve listed here. I have simply included the number of messages in the subject and then joined the messages to make up the body. ■Note The default formatter puts a line feed after each message, so by joining on an empty string, each message will appear on a new line. Listing 14-3. Sending the Log Message E-mail in shutdown() (EmailLogger.php) public function shutdown() { if (count($this->_events) == 0) return; $subject = sprintf('Web site log messages (%d)', count($this->_events)); $mail = new Zend_Mail(); $mail->addTo($this->_email) ->setSubject($subject) ->setBodyText(join('', $this->_events)) ->send(); } } ?> CHAPTER 14 ■ DEPLOYMENT AND MAINTENANCE 521 9063Ch14CMP2 11/13/07 8:20 PM Page 521 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Specifying the E-mail Recipient Next we must define who receives the log e-mail. To do this we add a new setting to the config- uration file (./settings.ini). Listing 14-4 shows this new value—remember to insert your own e-mail address accordingly. Listing 14-4. Specifying the Log Recipient (settings.ini) logging.file = /var/www/phpweb20/data/logs/debug.log logging.email = admin@example.com ■Note You may have noticed there is a catch-22 developing. If the application cannot read the settings file (for example, if the file is missing or doesn’t have read permissions), then the log file path and the recipient e-mail address cannot be determined for Zend_Log to record the error. We will resolve this in the “Site Error Handling” section by using the Apache server configuration. Adding the EmailLogger Writer to Zend_Log The next step is to instantiate the EmailLogger class and notify the logger about it. Before we can do this, there is one important step we have not covered yet. That is to filter messages by their priority. We want to send e-mails only for critical errors (while still writing all other mes- sages to the filesystem log). This means we will filter out messages that don’t have the priority levels Zend_Log::CRIT, Zend_Log::ALERT, and Zend_Log::EMERG. To do this we use the Zend_Log_Filter_Priority class. This filter accepts the priority level as the first argument to the constructor. The default priority comparison operator is <=, mean- ing all messages matching that argument and lower will be matched. In our case, we will specify Zend_Log::CRIT as the priority level (this evaluates to a priority level of 2), which will therefore include Zend_Log::ALERT (priority level 1) and Zend_Log::EMERG (priority level 0). Once the filter has been created, we add it to the writer using the addFilter() method. Listing 14-5 shows the code we add to the application bootstrap file, located in ./htdocs/index.php. Note that in the EmailLogger class we implemented earlier, an exception is thrown if the provided e-mail address is invalid. Thus, we must catch this exception accordingly. In this code, we continue if an invalid e-mail address is used; however, in the code we add later this chapter, we will handle this using a global exception handler. Listing 14-5. Adding the E-mail Logger to the Application Bootstrap (index.php) <?php // other code // create the application logger $logger = new Zend_Log(new Zend_Log_Writer_Stream($config->logging->file)); try { $writer = new EmailLogger($config->logging->email); CHAPTER 14 ■ DEPLOYMENT AND MAINTENANCE522 9063Ch14CMP2 11/13/07 8:20 PM Page 522 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com $writer->addFilter(new Zend_Log_Filter_Priority(Zend_Log::CRIT)); $logger->addWriter($writer); } catch (Exception $ex) { // invalid e-mail address } Zend_Registry::set('logger', $logger); // other code ?> To test that this works, you can simply add a fake message such as the following in your code. Just remember to remove it afterward; otherwise, you will receive many e-mails. $logger->crit('Test message'); Now whenever a critical message is recorded in your application logger, you will automat- ically be e-mailed! If you have an e-mail address that sends you an SMS message whenever you receive an e-mail, you can be instantly notified when something critical occurs. ■Caution With a system such as this, you must be very careful how the e-mail reporting works, since you will be e-mailed for every single HTTP request on your site that generates an error. If you run a high-traffic site, it’s likely you will bog down your own server and perhaps even be blacklisted from your mail server for sending so much e-mail. Because of this, you should strongly consider adding extra mechanisms so that duplicate messages aren’t sent within a short timeframe. For example, you could hash the generated mes- sages and write this hash to the filesystem (using either the application temporary directory or the system temporary directory).Then the next time you go to send an e-mail, look for the hash of the new messages; if it exists and its age is less than n minutes (such as 15 minutes), you can safely skip sending the e-mail. Using Application Logs It’s difficult to say exactly how you should use your log files because everybody’s mileage will differ. Certainly the e-mail capabilities improve the system in that you will be instantly noti- fied if something goes wrong, but it’s also good to audit application data. As an example, we’re tracking user login attempts. We are recording both successful and unsuccessful attempts. We recorded successful attempts with the “notice” priority, while we recorded unsuccessful attempts with “warning” priority. If you wanted to find all unsuccessful login attempts, command-line tools such as grep will aid you with this: # cd /var/www/phpweb20/data/logs # grep -i "failed login" debug.log 2007-09-03T16:06:55+09:00 WARN (4): Failed login attempt from 192.168.0.75 user test CHAPTER 14 ■ DEPLOYMENT AND MAINTENANCE 523 9063Ch14CMP2 11/13/07 8:20 PM Page 523 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com ■Note The -i option for grep means the search is case-insensitive. It can also be useful to watch the log files in real time when developing new functionality. This is especially so when you can’t easily output debugging information directly to your browser (such as in an Ajax subrequest or when using a third-party service). You can use tail -f to achieve this, which means tail monitors the file for any changes and displays any new data in the file as it is written. tail -f /var/www/phpweb20/data/logs/debug.log ■Tip You can press Ctrl+C to exit from tail when using the -f option. Also note that when you run this command, the last ten lines will be shown before monitoring for new content, just like when you run tail normally (assuming you don’t specify the number of lines to be shown). Another consideration you may need to make is managing the log files, since they can potentially grow quite large on a high-traffic site that records lots of debugging information. You may want to consider using a tool such as logrotate. In any case, a solid foundation is now in place for your application logging and auditing needs. You can now easily add logging capabilities to any new classes you develop. Site Error Handling We are now going to change our code to handle any errors that may occur when users access the site. Several kinds of errors can occur in the day-to-day running of your web site: • Database errors. This is any error relating to accessing the database server or its data. For example, the following are possible errors that may occur: •Connection errors, caused because the server or network may be down, the user- name or password are incorrect, or the database name is incorrect. •Query errors, caused when the SQL being used is invalid. If your application has been correctly developed and tested, then these should never occur. •Data errors, caused by violating a constraint in the database. This may occur if you enter a duplicate value in a unique field or if you delete data from a table that is referenced elsewhere via a foreign key. Once again, this should not occur if the application has been developed and tested correctly. CHAPTER 14 ■ DEPLOYMENT AND MAINTENANCE524 9063Ch14CMP2 11/13/07 8:20 PM Page 524 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com • Application runtime errors. This is a fairly broad title for basically any error (including uncaught exceptions) that occurs in code. Examples of application errors that may occur are as follows: •Filesystem and permission errors, such as if the application tries to read a file that doesn’t exist or that it is not allowed to read. Similarly, if the application tries to write a file but isn’t allowed to, then this will also cause an error. A file that your application reads that is in the incorrect format may also cause an error. •If your application accesses web services on remote servers, then how well your application runs is partly dependent on these servers. For example, if you process credit card payments using a third-party gateway, then that gateway must be oper- ational for you to make sales on your site. • HTTP errors. These are errors that occur based on the user’s request. The most com- mon HTTP error (and the one that we are going to cover in this section) is a 404 File Not Found error. Although many different errors can occur, other common errors are 401 Unauthorized (if a user tries to access a resource that they must be logged in for) and 403 Forbidden (if the user simply isn’t allowed to access the resource). ■Note First, because of the Apache rewrite rules we set up for our application in Chapter 2, we won’t be handling 404 errors in the “traditional way” that people do with Apache (using ErrorDocument 404). Rather, we will generate 404 errors when a user tries to access a controller or action that does not exist. Second, the 401 and 403 errors don’t apply to our application, even though we have a permissions system. This is because we’ve implemented our own user login and permissions system, so the traditional HTTP codes don’t apply. Although we could have included the aforementioned error handling when setting up the application (specifically, for handling database and 404 errors), I have chosen to group it all together into this single section so you can see how the system reacts to errors as a whole. Note that in some cases we have already handled various application errors that occur. An example of this is catching exceptions that have been thrown in various circumstances, such as in Chapter 12 when implementing blog post indexing capabilities. In the error handling we are now going to implement, we will add handling capabilities to two areas of the application: •Before the request is dispatched. This is to handle any errors that occur prior to dis- patching the result with Zend_Controller_Front. In other words, it’ll deal with any errors that occur with code inside the index.php bootstrap. • While the request is being dispatched. This is to handle any errors that occur within the application, such as in a controller action or in one of the many classes we have written (errors that we haven’t yet handled, that is). This also includes HTTP errors such as when the file isn’t found (404). CHAPTER 14 ■ DEPLOYMENT AND MAINTENANCE 525 9063Ch14CMP2 11/13/07 8:20 PM Page 525 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Objectives of Error Handling Before we implement any error handling, we must determine what we’re actually trying to achieve by handling the error. An error handling system should do the following: • Notify the user that an error occurred. Whether it is a system error or user error that has occurred, the user should still know that something went wrong and their request could not be completed correctly. • Record the error. This may involve either writing the error to a log file or notifying the system administrator, or both. Typically a user error (such as a 404 error) is not some- thing you’d need to notify the administrator about (although logging 404 errors can be useful in statistics analysis). • Roll back the current request. If a server error occurs halfway through a client request, then any performed actions should be rolled back. Let’s use the example of having a system that saves a user-submitted form to a local database and submits it to a third- party server. If the third-party server is down (resulting in an error), then the form shouldn’t be saved locally and the user should be notified so. Note that our application isn’t required to handle errors in this manner. ■Note This example is somewhat crude. In actual fact, if you had such a system, you would typically have an external process (such as a cron job/scheduled task) that was responsible for communicating with the third-party server rather than performing the action in real time while completing the user request. The local database record would then have a status column to indicate whether the form has been successfully sub- mitted remotely. The example should demonstrate the point of rolling back the request. Handling Predispatch Errors First we are going to handle any errors that may arise prior to dispatching the user request. In our application, before we dispatch the request, we load the configuration file, initialize the application logger, and connect to the database. Additionally, we are going to catch any errors that were not caught elsewhere (that is, in the dispatch loop). Essentially what we are going to do is to wrap all of the code in the application bootstrap (./htdocs/index.php) in a single try … catch statement, meaning if any error occurs (that hasn’t otherwise been handled) in any part of handling the user request, we can deal with it in a single spot. Notifying the User of Errors When we detect that an error has occurred, we are going to redirect the user’s browser to a static HTML page that has no reliance on the database or even on PHP for that matter. This page will simply tell them that something went wrong and their request could not be completed. CHAPTER 14 ■ DEPLOYMENT AND MAINTENANCE526 9063Ch14CMP2 11/13/07 8:20 PM Page 526 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Listing 14-6 shows the code for the error.html file, which we store in the ./htdocs direc- tory. When using this in a production site, you will probably prefer to customize this page further (by adding your logo and CSS styles). Listing 14-6. Notifying the User the Site Is Undergoing Maintenance (error.html) <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <head> <title>This site is undergoing maintenance</title> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> </head> <body> <div> <h1>Site Under Maintenance</h1> <p> This site is currently under maintenance. Please check back shortly. </p> </div> </body> </html> Catching Errors Now that we have an error template to display when something goes wrong, we are going to make some changes to the bootstrap file. Instead of having several try … catch constructs in this file, we are going to have only one. This will encompass nearly all of the code in index.php. The only problem with this, however, is that we won’t be able to write any of these errors to the log file, since the logger will be created inside the try block and therefore will not be available in the catch block. To deal with this problem, we are going to create the logger first, and instead of using the value from the configuration, we will use the Apache SERVER_ADMIN variable. If the web server has been configured correctly, then this should contain a valid e-mail address with which to contact the administrator. We will use the SERVER_ADMIN value initially in the code and then use the logging.email value in the configuration file once settings.ini has been successfully loaded. If this value isn’t set correctly in the Apache configuration, then this will be caught by the exception han- dler. Since Zend_Log requires at least one writer, I have used Zend_Log_Writer_Null to ensure there will always be a writer. If this is not done, then an error will occur in the exception han- dler, since we write the exception message to $logger. CHAPTER 14 ■ DEPLOYMENT AND MAINTENANCE 527 9063Ch14CMP2 11/13/07 8:20 PM Page 527 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com ■Note Zend_Log_Writer_Null is a special writer that just discards all messages without actually writing or sending them anywhere. Listing 14-7 shows the beginning of the bootstrap file, stored in ./htdocs/index.php. This entire file will be shown in the coming listings. Listing 14-7. Using the Apache Configuration to Determine the Log E-mail Address (index.php) <?php require_once('Zend/Loader.php'); Zend_Loader::registerAutoload(); // setup the application logger $logger = new Zend_Log(new Zend_Log_Writer_Null()); try { $writer = new EmailLogger($_SERVER['SERVER_ADMIN']); $writer->addFilter(new Zend_Log_Filter_Priority(Zend_Log::CRIT)); $logger->addWriter($writer); Note that we use the $writer variable to hold the EmailLogger object so we can change the target e-mail address shortly. Next is Listing 14-8, in which we load the application configuration. Next we modify the $logger object so it will write to the filesystem, as well as send critical log messages to the e-mail address in settings.ini rather than the SERVER_ADMIN value. Listing 14-8. Altering the Logger to Use the Configuration Values (index.php) // load the application configuration $config = new Zend_Config_Ini(' /settings.ini', 'development'); Zend_Registry::set('config', $config); // alter the application logger $logger->addWriter(new Zend_Log_Writer_Stream($config->logging->file)); $writer->setEmail($config->logging->email); Zend_Registry::set('logger', $logger); Next we have the database connection code. As mentioned when we first created the database connection code in Chapter 2, the actual connection is not made until the first query is performed. As such, the first thing we must do is force the database connection to be made so we can trap any potential connection errors upon start-up—not halfway through handling a user request. CHAPTER 14 ■ DEPLOYMENT AND MAINTENANCE528 9063Ch14CMP2 11/13/07 8:20 PM Page 528 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... ServerName phpweb20 DocumentRoot /var/www/phpweb20/htdocs AllowOverride All Options All php_ value include_path :/var/www/phpweb20/include:/usr/local/lib/pear 9063Ch14CMP2 11/13/07 8:20 PM Page 541 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com CHAPTER 14 ■ DEPLOYMENT AND MAINTENANCE php_ value magic_quotes_gpc off php_ value register_globals... database phpweb20; Next you import the file from the command line You can do this by decompressing the file and then piping the results to the mysql program Note that we need to pass the stdout argument to gunzip for this to work: # gunzip stdout phpweb20.sql.gz | mysql –u phpweb20 -p phpweb20 If the database dump is already decompressed, you can use the following command: # mysql –u phpweb20 –p phpweb20... dbname | gzip > filename.sql.gz Because we connect to our database using the phpweb20 username as well as a password, we must specify the –u and –p parameters so we can use the required credentials Additionally, we can substitute the database name (phpweb20) into the earlier example: # mysqldump -u phpweb20 -p phpweb20 | gzip > phpweb20.sql.gz Once you have executed this command, you will have a compressed... recipient, 522 administrators users, 45 afterFinish callback, 410 Ajax combining with Prototype, Scriptaculous, and PHP 154–168 , index .php, 156–157 items .php, 159–161 processor .php, 161–163 schema.sql, 158–159 scripts.js, 163–168 styles.css, 157–158 deleting image files using, 406– 410 BlogImageManager JavaScript class, 407– 410 modifying PHP deletion code, 406–407 operations, in Prototype, 134–145 Ajax.Request... 226–228 BlogmanagerController .php class, 379–380 BlogmanagerController .php file, 232, 249, 274, 405–406, 413, 479 BlogmanagerController .php template, 284 BlogMonthlySummary class, 285–287 installing, 287 updating messages container, 289–291 BlogMonthlySummary.class.js file, 289 BlogPostImage .php file, 374, 380, 382, 396, 400, 404 BlogPostLocation .php, 475, 487–489 BlogPost .php file, 221, 233–234, 241,... suggestions, 457–459 tags on blogs, 344–346 tags on each post, 351 div element, 41, 343 tag, 277 Document Object Model See DOM document.createElement( ) function, 153 documentElement property, 141 document.getElementById( ) method, 124 document.getElementsByClassName( ) function, 125, 129 DOM (Document Object Model), 124 element builder, 153 Prototype selecting objects, 124–129 $( ) function, 124–125... application bootstrap file to use these values Listing 14-21 shows the changes we make to the /htdocs/index .php file so the configuration filename and section are no longer hard-coded Listing 14-21 Using the Apache Environment Variables to Determine the Configuration (index .php) < ?php require_once('Zend/Loader .php' ); Zend_Loader::registerAutoload(); // setup the application logger $logger=new Zend Log(new... and Split Unregistered Version - http://www.simpopdf.com CHAPTER 14 ■ DEPLOYMENT AND MAINTENANCE ■ Many web sites take advantage of 404 errors by attempting to do something useful with the Tip requested URI, such as performing a search For example, if you try to access the PHP web site at http:/ /php. net but specify an invalid URL, the site will automatically perform a search based on the terms in the... http://phpweb20/admin, you will be denied access unless your user type is administrator Application Deployment We’ll now look at the process of deploying our web application to a live server So far, we have presumed that all development has taken place on a dedicated development server and that the users of the application don’t have access to this server A typical setup for how versions of web applications. .. 4 account management, 4, 116–121 allowing users to update details, 120–121 home page, 116–118 updating web site navigation, 118–119 account resource, 60 account template, 109 AccountController class, 81–82, 116, 178, 210 211, 214 AccountController controller, 57 AccountController .php file, 57, 81, 102 , 112, 114, 211 /account/login directory, 174 ACL (access control list), 54 action element, 162 action . grep will aid you with this: # cd /var/www/phpweb 20/ data/logs # grep -i "failed login" debug.log 20 07 -09 -03 T16 :06 :55 +09 :00 WARN (4): Failed login attempt from 1 92. 168 .0. 75 user test CHAPTER. MAINTENANCE 5 20 906 3Ch14CMP2 11/13 /07 8 : 20 PM Page 5 20 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com Next we must create the _write() method, shown in Listing 14 -2. This. EmailLogger($config->logging->email); CHAPTER 14 ■ DEPLOYMENT AND MAINTENANCE 522 906 3Ch14CMP2 11/13 /07 8 : 20 PM Page 522 Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com $writer->addFilter(new

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

TỪ KHÓA LIÊN QUAN