All recent major Linux distributions (SUSE, Fedora, Mandriva, and Debian among them) come with support for PHP 5. If your distribution doesn’t support version 5, the easiest solution is to locate and install an updated Red Hat Package Manager (RPM). Otherwise, you will need to download the PHP source code and configure and install PHP your- self. (If you want to install PHP as a static module, you will also have to down- load the source code for Apache.) Instructions for compiling PHP are readily available at http://php.net, but taking this route requires familiarity with working from the command line in Linux.
PHP 5 also runs under Windows using Internet Information Server (IIS) or Apache Web Server. Although Windows does not come with built-in support for PHP, installing PHP is a relatively easy task. The Windows PHP installer will get you up and running in minutes, but it is not meant for a production server—it’s better to perform a manual installation. Comprehensive instruc- tions for doing this are provided at http://php.net/install, but here’s a brief overview of the process.
Download the Windows binary from the PHP website, and install it to a directory on your hard drive. If you are using IIS, find the web server config- uration window and map the .php file extension to the location of the php program.
For Apache Web Server 2, you will need to make the following changes to the httpd.conf file:
LoadModule php5_module "c:/php-5.1/php5apache2.dll"
If you are running version 1.3 of Apache, use the php5apache.dll file.
You will also have to add an application type to your configuration file. The example below will process files with the extensions .php or .inc as PHP files.
AddType application/x-httpd-php .php .inc
Comprehensive instructions for installing and configuring Apache under Windows can be found at http://httpd.apache.org/docs/2.0/platform/ windows.html, but, again, the process is fairly straightforward.
The code contained in this book should run equally well regardless of which combination of operating system and web server you choose.
php.ini Settings
The php.ini file controls configuration settings for PHP and is typically found in the c:\windows directory on Windows systems and in the /etc directory on Linux systems. When installing PHP 5 it is best to use the example php.ini file with the default settings. This section deals with changes that affect OOP. (For an overview of all the changes, see http://php.net/install.)
There is only one new configuration setting that relates directly to changes made to the object model in PHP 5. Specifically, showing the default setting, this is:
zend.ze1_compatibility_mode = Off
If you change this setting to On, objects will be copied by value in the manner of PHP 4. (See the section “__clone” on page 116 for more details about how objects are copied.) This option is provided in order to facilitate migration from PHP 4 to PHP 5. It should be used for this purpose only, as it is unlikely to be supported in any upcoming versions of PHP.
Another setting that has some bearing on changes made to the object model in PHP 5 is
allow_call_time_pass_reference = Off
This setting controls whether a warning is issued when a variable is passed by reference when making a function call. With this setting off, calling a function in the following way will issue a warning and will not pass
$some_variable by reference:
some_function(&$some_variable);
The recommended way of passing a variable by reference is by declaring the parameter as a reference in the function definition, like so:
function some_function ( &$some_variable ) {
...
}
If you do this, then there is no need to use an ampersand when passing a variable to some_function. (If you are upgrading PHP 4 code that passes objects at call time by reference, you can remove ampersands entirely. You will recall that in PHP 5 objects are automatically passed by reference, so there is no need for an ampersand at call time or in the function definition.) It is a good idea to upgrade your code to pass by reference in the recommended manner because call-time pass by reference is unlikely to be supported in future versions of PHP.
E_STRICT
A new error level, E_STRICT, has been introduced and is especially useful in the context of OOP. If you set error reporting to this value, a warning will be issued when deprecated functions or coding styles are used. Error level E_ALL does not encompass E_STRICT, so include this error level explicitly in the php.ini file in the following way:
error_reporting = E_ALL|E_STRICT
To see how this setting can be useful, suppose, in the style of PHP 4, that you do the following:
$obj1 =& new Person();
With error reporting set to E_STRICT and display_errors set to on, you’ll receive the message:
Strict Standards: Assigning the return value of new by reference is deprecated...
Other actions also raise a warning when error reporting is set to E_STRICT:
Use of is_a instead of instanceof.
Invoking a non-static function statically (this error is soon to be E_FATAL).
However, calling a static method against an instance variable does not raise a warning.
Use of var instead of public, private, or protected (prior to version 5.1.3).
Changing the number of parameters or the type hint when overriding a method in a derived class.
Making sure that your code follows strict standards can help ensure that it is forward compatible especially with respect to calling dynamic methods statically.
Don’t Escape Twice
There’s a final setting that has some bearing on OOP.
It’s worthwhile noting that the default setting for magic quotes is
magic_quotes_gpc = Off
As you have seen, methods such as the prepare method of the PDO class automatically escape database queries. So, if magic quotes are turned on, you can easily corrupt data by escaping it twice. Use care if you change this setting.
Wednesday, August 12, 2009
SETTING UP PHP5
Posted by Mr Procces at 6:43 AM 1 comments
Labels: PHP, PHP Programming, Web Applications, Web Development
USING PDO
Databases are important to any dynamic website. That’s why we’ve had a lot to say about them in this book (too much, some
of you may be thinking). However, PHP Data Objects (PDO) can’t be ignored because they are packaged with PHP version 5.1 and higher, and they are “something many of the PHP dev team would like to see as the recommended API for database work.”1
PDO is a data-access abstraction layer that aims for uniform access to any database. That’s a pretty good reason for looking at PDO, but what interests us in particular is that the PDO interface is entirely object-oriented (OO). It makes extensive use of the OO improvements to PHP 5. In fact, it cannot be run on lower versions of PHP.
Drivers are available for all the major databases supported by PHP— Oracle, Microsoft SQL Server, PostgreSQL, ODBC, SQLite, and all versions of MySQL up to version 5. So, if you use a variety of different databases, PDO
is especially worth investigating. However, even if you use only one database, PDO can be helpful for switching between versions. Be warned, though, that it is still early days for PDO, and some of the drivers may lack some functionality.
Pros and Cons
The promise of database abstraction is the ability to access any database using identical methods. This gives developers the flexibility to change the back-end database with minimal impact on code. Another advantage of an API such as PDO is a reduced learning curve. Instead of having to learn the specifics of each different database, you can learn one interface and use it with any database. Lastly, with an API you may be able to use features not available to the native database—prepared statements, for example—but more about that later.
On the negative side, a data-access abstraction layer may adversely affect performance and may deprive you of the ability to use non-standard features natively supported by specific databases. It may also introduce an unwanted degree of complexity into your code.
The best way to make a decision about the suitability of PDO is to try it. Converting the SQLite application created in Chapter 15 is a good way to do this. Our SQLite application makes use of a limited number of the features of PDO, so we’ll also look at some of PDO’s additional capabilities. We won’t look at every detail, but this chapter will show you enough to allow you to make an informed decision.
NOTE If you are running PHP 5.0.x you can install PDO using PEAR. See the PHP site for instructions. If you install the latest version of PDO you will be able to use SQLite version 3.
Converting the SQLite Application
The very first thing to realize is that we cannot use our derived class SQLiteDatabasePlus with PDO because the PDO driver for SQLite doesn’t know anything about our derived class. We could, of course, extend the PDO class to incorporate the methods we added to our SQLiteDatabasePlus class, but doing so is contrary to the whole purpose of a database abstraction layer. Taking that route would be an implicit admission of defeat right from the start—there wouldn’t be one interface for all databases, but instead any number of derived interfaces.
Code Changes
As usual, a complete version of the code discussed in this application is avail- able from the book’s website. The directory structure for the files accompa- nying this chapter is the same as those for Chapter 15, so you shouldn’t have trouble finding your way around. Also, as usual, I won’t reproduce all the code in this chapter, only relevant sections. We’ll start by looking at the constructor.
NOTE The application we developed in Chapter 15 uses version 2 of SQLite. Take this oppor- tunity to upgrade to SQLite version 3, since PHP 5.1 supports this version. It’s appre- ciably faster than version 2 and handles concurrency better. However, the database format has changed; versions 2 and 3 are incompatible. This is only a minor inconvenience. If you want to get off to a quick start, install the database by running the db_install
_script.php file found in the dbdir director y. This will create an SQLite version 3 database for you. Otherwise, you may download the command-line version of SQLite 3 from the SQLite website, and then see the section “Getting Started” on page 141 for details about using a database dump to install a database. The PDO driver for SQLite version 2 doesn’t support the sqliteCreateFunction method, so upgrading is required if you want to use this method. Matching the version of the command-line tool with the version of SQLite supported by PHP is equally important in this chapter, because of version incompatibilities. For example, a database created at the command line using SQLite version 3.5.5 will not work properly with the current SQLite PDO driver.
Constructing a PDO Object
When constructing a PDO database or connection object, a Data Source Name (DSN) is passed as a parameter. A DSN is made up of a driver name, followed by a colon, followed by database-specific syntax. Here’s how to create a connection to an SQLite database:
$pdo = new PDO('sqlite:resources.sqlite');
A PDO object is constructed in the same way as an SQLiteDatabase object except that the driver name must precede the path to the database and be separated from it by a colon.
The createFunction Method
You may recall that one of the deficiencies of SQLite was the lack of built-in functions, especially with respect to date manipulation. We were able to over- come this deficiency by adding functions to SQLite using the createFunction method. Fortunately for us, the developers of PDO have seen fit to incorporate this capability by including the SQLite-specific method sqliteCreateFunction. This saves us some work but also reduces the “abstractness” of the PDO layer— but more about this later.
Exceptions
In Chapter 15 we extended the SQLiteDatabase class in order to throw an exception if a query failed. For that reason we overrode the five existing query methods. The same effect can be achieved with PDO by using only one line of code:
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
Calling setAttribute against a PDO database connection in this way causes any failed attempt at querying the database to throw a PDOException. This is a very succinct way of dealing with one of the shortcomings of the
SQLite classes. We quickly reap all the benefits of throwing exceptions without having to extend the SQLite database class.
Methods of SQLiteDatabasePlus
Because a PDO connection can be configured to throw exceptions, we don’t need the query methods of our derived class SQLiteDatabasePlus. However, the utility methods we added are another matter. The only way to use these methods is to rewrite them as functions. They convert fairly readily, and the metadata queries they employ work as before. The only real difference is that as functions they are not quite as convenient to use. I won’t reproduce them here, but they can be found in the dbfunctions.inc file included in this chap- ter’s downloads.
Escaping Input
The cleanData utility method of our derived class incorporated the sqlite_escape_string function to escape input before insertion into the database. The equivalent PDO method is quote. Be aware that this method not only escapes single quotation marks, but also encloses the item quoted within quotation marks. If you need to manipulate data prior to inserting it, quote should only be called immediately prior to insertion. For portability reasons, the PHP site recommends using prepare rather than quote. We’ll investigate this method more fully when we discuss statements, in the section “Additional Capabilities of PDO” on page 161.
Query Methods
One of the attractions of SQLite is its variety of query methods. If you need a quick refresher, see the section “Query Methods” on page 148. In this section we’re going to compare SQLite’s query methods to what’s available using PDO.
The difference between the object models of SQLite and PDO makes a direct comparison a bit awkward. Principally, querying a PDO connection returns a statement object, and this statement effectively becomes a result set once it is executed. As already mentioned, we’ll cover statements in the section “Additional Capabilities of PDO” on page 161, but for the moment we can treat them as identical to result sets.
The five query methods of SQLite are singleQuery, execQuery, arrayQuery,
query, and unbufferedQuery. We’ll discuss each SQLite query method in turn.
The singleQuery method returns a single column as an array, bypassing the need to create a result set. To mimic it we would use a PDO query to return a statement and then call fetchColumn against this statement. The fetchColumn method can return any column in the current row.
The execQuery method of an SQLite result set can execute multiple, semicolon-separated queries. PDO can easily mimic this behavior—in fact this is something that statements excel at.
As you might expect, returning an array in the fashion of arrayQuery is also easily handled by PDO by calling the fetchAll method against a statement.
The two remaining query methods of SQLite, query and unbufferedQuery, return SQLiteResult and SQLiteUnbuffered objects, respectively. PDO statements are comparable to unbuffered result sets rather than buffered ones, so the
unbuffered behavior is easily reproduced. Our SQLite application uses buffered result sets in cases where we need to know that there are records returned or where the specific number of records is required. To buffer records using PDO you can use the fetchAll method of a statement to return an array. A record count can be obtained by using the count function on the returned array. Alternately, calling the function empty on the statement returned by a query will determine whether there is at least one record.
In general, when querying the database, it looks like some efficiencies have been lost. What is a single process using SQLite becomes a two-step process with PDO. Using two methods instead of one can make code more complicated. However, as we’ll soon see, there are some important advantages to the PDO methods, and in some cases this two-step process can be simplified.
Additional Capabilities of PDO
Converting one application certainly doesn’t tell the whole story about PDO, so let’s have a look at some of the other capabilities of PDO. There are three PDO classes: PDO; the database or connection class, PDOStatement; and PDORow. By far the most interesting and unfamiliar class is the statement class, and this is where we’ll concentrate our attention. We’ll briefly discuss PDORow when we come to the fetchObject method of the statement class.
The PDO Class
So far in this book we’ve created our own connection class and used the SQLiteDatabase class—classes that have many similarities to PDO. With this experience, I needn’t say a lot about the PDO class.
I’ve already mentioned the quote, setAttribute, and query methods of the PDO class. For databases such as SQLite that support transactions, this class also has methods to begin, commit, or roll back transactions.
The most important method, however, is prepare. This method is similar to the query method in that it also returns a PDOStatement. The major differ- ence is that query is typically used for SQL statements that are issued once and prepare for queries that will be issued a number of times.
PDOStatement
In the conversion of our application from SQLite to PDO, in some cases the difference between a result set and a statement isn’t apparent at all. For example, the snippet of SQLite code to display all the resources in our data- base (from the file getresources.php) is shown in Listing 16-1.
$result = $db->query($strsql);
if(!empty($result)){
$previous = "";
foreach ($result as $row){
foreach ($row as $key => $value){
The equivalent PDO code is identical. In one case, the variable $db represents an SQLiteDatabasePlus, and in the other it represents a PDO. Like- wise the $result variable is an SQLiteResult or a PDOStatement. Because result sets and statements are both iterable, they can be used in the same way within foreach loops. In this case, using PDO takes no more steps than using SQLite directly.
This similarity between a result set and a statement makes it easy to start using statements, but it also masks important differences. These differences are more apparent when the prepare method is used.
prepare
Instead of using the query method to create a PDOStatement object, the code
$result = $db->query($strsql); in Listing 16-1 can be changed to the following:
$result = $db->prepare($strsql);
$result->execute();
I have already hinted at one of the advantages of using prepare instead of query. Any variables used in the parameter to the prepare method will auto- matically be quoted. This is an easier and more portable way of escaping quotes than using the quote method. If used exclusively, you needn’t worry about forgetting to quote an SQL statement. This is a security advantage that will protect against SQL injection attacks.
This is one way in which a statement is superior to a result set, but it is not the most important difference. Statements are more commonly used to insert multiple records into a database, and they do this more efficiently than a series of individual SQL statements. This is what is referred to as a prepared statement.
Prepared Statements
There are a number of ways that statements can be used with both input and output parameters. We’ll content ourselves with one example of a prepared statement used to make multiple inserts. The SQLite application in Chapter 15 has no need for multiple inserts, so we’ll create a simple new example.
Suppose you have an ecommerce application. The inventory numbers for various purchased items are stored in an array. Here’s how we can update our database using a prepared statement:
//$pdo is an instance of a PDO connection
$orderid = "200";
$array_skus = array(1345, 2899, 6502);
$strsql = "INSERT INTO tblorderitems (orderid, inventorynumber) ". " Values ($orderid, ? ) ";
$stmt = $pdo->prepare($strsql);
$stmt-> bindParam(1, $number);
foreach ($array_skus as $number){
$stmt-> execute();
}
This is a fairly simple example of a prepared statement, but it will give you an understanding of how statements work. A replaceable parameter ( ) is indicated by a question mark, this parameter is bound to the variable
$number, and each iteration of the foreach loop executes the query, inserting a different value.
Using statements is much more efficient than separately querying the database. The performance improvements are due to the fact that after a parameterized query is first executed, for each subsequent query, only the bound data needs to be passed.
Remember, there’s no such thing as a prepared statement in SQLite. The developers of PDO thought it important to support this feature for all databases regardless of native support. Using PDO is a good way to familiar- ize yourself with statements and makes it easy to switch to a database that supports this capability.
Fetching Objects
For an OO programmer, the ability to retrieve rows as objects is important. PDO has a number of ways of doing this. An easy way of doing this is to create an instance of the PDORow class in the following way:
$stmt = $pdo->query( "SELECT * FROM tblresources", PDO::FETCH_LAZY );
$pdorow = $stmt->fetch();
There is also a fetchObject method that can be used to create an instance of a specific class. Supposing we have defined a class called RowInfo, creating an instance of that class is done in this way:
$row = $stmt->fetchObject('RowInfo');
This method is perhaps the simplest way to create an object. You can use it with an existing class or, if you don’t specify a class, it will create an instance of stdClass, the generic object class.
What these various ways of creating objects have in common is that they instantiate an object, creating data members from the columns of the current row.
PDOStatement also has a method, getColumnMeta, to dynamically retrieve metadata about the current query. By using this method in conjunction with one of the create object methods and adding a magic get method to the class you’re instantiating, it is easy to retrieve the data members of any object cre-
ated from any query without knowing the structure of that query beforehand.2
Perhaps our criticisms of magic set and get methods in Chapter 13 were a little harsh.
NOTE SQLite has a procedural version of fetchObject that returns a stdClass object. It is documented as a result set method but not yet implemented.
2 You could, of course, query the sqlite_master table for this information, but the PDO method provides a database-independent way of doing this.
Assessment
We’ve touched on a number of the capabilities of PDO. We’ve used some of them in our application, but not all of them. This is by no means a definitive overview of PDO, but we certainly have enough information to make a judgment about the utility of this data-access abstraction layer.
Our application behaves exactly as it did without PDO. We haven’t had to sacrifice any functionality and some things were much easier to implement— catching exceptions, for example. All our queries, triggers, and views work in exactly the same way. One minor inconvenience was converting the utility methods of our derived class, but we were able to implement them proce- durally without loss of functionality. The object model of PDO is perhaps a little more difficult, but along with this we’ve gained the ability to use pre- pared statements should we need them. No question—PDO works well with SQLite.
But what if we decided to use a MySQL back-end instead? How many changes would we have to make? Beyond changing the driver, the most obvious change would be removal of the SQLite-specific function sqliteCreateFunction. As noted in Chapter 15, this could be replaced by the MySQL function SUBDATE. Likewise, any other operators or functions not used by MySQL would have to be changed.
Another option would be to use standard SQL wherever possible. The date manipulation functions could be ignored, and this task performed from within PHP. That’s a choice each developer will have to make for themselves, but I expect most won’t quickly give up on hard-won knowledge about specific SQL dialects.
Is It the Holy Grail?
One very legitimate concern might be voiced over the inclusion of the SQLite-specific method sqliteCreateFunction, and this is certainly not the only database-specific capability provided by PDO.3 Doesn’t providing database- specific functionality do exactly what we refrained from doing at the start— namely, extending the PDO class?
The short answer is, unquestionably, yes. But the whole notion of a perfect database abstraction layer is a Holy Grail—glimpsed here and there but never grasped. Providing some database-specific functionality is a sensible compromise and an impetus to use PDO. As always with PHP, utility and not purity of concept is paramount. The important thing to note is that each developer can make their own decision about an acceptable level of database abstraction by incorporating database-specific methods and database-specific SQL or not as the case may be. However, in one respect there’s no choice at all: If you choose to use PDO, you must take an OO approach.
Posted by Mr Procces at 6:40 AM 0 comments
Labels: PHP, PHP Programming, Web Applications, Web Development
EXTENDING SQLITE
SQLite comes packaged with PHP 5. It has advanced capabilities and a built-in object-
oriented (OO) interface. Examining the classes and methods of SQLite is the ostensible
reason for including this chapter—but that’s not the only reason. SQLite is a great addition to PHP, but because MySQL is so entrenched, programmers tend to ignore SQLite.
Don’t let the “Lite” in SQLite lead you to underestimate the capabilities of this database. Because it is bundled with PHP, there is no external server to worry about—think of it as “Lite” in the sense of “no extra baggage.” In some situations it is the ideal database to use. Its advanced features can help simplify your code and create an application that outperforms other solutions.
In this chapter, we will develop a link management application using a class derived from the SQLite database class. A minimum of PHP version
5.0.5 is a requirement. (Prior to this version the SQLite database class is declared as final, so it cannot be extended.)
Brief Overview
Relevant sections of code will be reproduced here, but, as usual, the entire application is available for download on the companion website. The front end for this application will display alphabetically ordered website links, as shown in Figure 15-1.
An alphabetic navigation bar of hyperlinks will make any specific link easily accessible. Recently added links will be highlighted, making the list even more useful to regular visitors.
A submission form will allow visitors to suggest additional links. These links will not appear on the site until they have been reviewed. There will be a back end to review and maintain links.
Directory Structure
Because of the number of files in the download for this chapter, it’s helpful to make a few comments about the way the files are organized. Download and decompress the files to follow along.
The front-end capabilities of this application are accessible from the links in the index.php file in the top level directory and the back end is found using the index.php file in the linkmanagement directory. On a production server the linkmanagement directory would be password protected but for ease of use that hasn’t been done here.
For reasons of version compatibility, the database file itself is not included with the downloads. It should be installed in the dbdir directory. Version 2.8.17 of SQLite was used to test this application (but if you are already up and run- ning with another version of SQLite you shouldn’t run into any problems). Install the database from the db_install_script.php file (also included in the dbdir directory). Instructions on how to do this will follow shortly.
How It’s Done
In this application we take advantage of some of SQLite’s advanced capa- bilities. Both triggers and views will be used. A trigger, code that executes in response to an add, edit, or delete event, will be used to mimic a datestamp field—records will be automatically stamped whenever they are added or changed.
Views are a convenient way of storing queries and can replace tables in the FROM clause of a SELECT statement. They can also be used with triggers so that “updating” a view updates the associated table.
No database used in conjunction with PHP can escape comparison to MySQL. Where appropriate, I will point out differences in SQL syntax between SQLite and MySQL. Likewise, SQLite has a variety of different query methods. These will also be contrasted with MySQL functions.
As you have seen, throwing exceptions rather than trapping errors makes for cleaner code. SQLite has a built-in OO interface, and there is an SQLiteException class. However, only the SQLite database constructor throws exceptions. By extending the SQLite database class, we can override the query methods so that a failed query also throws an exception. This derived class will also include data verification methods that make use of metadata extracted from the database. This will be done by querying the sqlite_master table and through the use of pragmas. A pragma modifies the way the SQLite library works but can also be used to query the database structure. We’re only inter- ested in the second use.
A limited number of functions are available for use with SQLite’s dialect of SQL. You’ll see how this shortcoming can be overcome by creating user- defined functions (UDFs).
Getting Started
SQLite comes bundled with PHP 5, so all you have to do to install the database is run the db_install_script.php file.
However, if you do things this way you’ll have to write code just to view your data or to examine the structure of your database. You might want to download the command-line version of SQLite instead. PHP 5, depending upon the subversion number, comes with SQLite versions 2.8.11 through
2.8.17. To find out which version is running on your system, display the
results of the PHP function phpinfo in your browser and search for SQLite. For convenience, you might want to install the binary of sqlite in the same directory as your database.
Creating a database is as simple as typing the name of the SQLite executable file at the command line followed by the database name—for example, sqlite resources.sqlite. Doing so will run sqlite and create or open an existing database of the specified name. You can now create a table using SQL from the command line. However, let me make one more suggestion. At some point you will want to dump your database, and if you have created it from the command line the output won’t be very readable.
If you use a text editor to format your CREATE TABLE statement and then redirect this file to the database, the results will be much more acceptable. Do this whenever you create tables, views, or triggers.
NOTE Precompiled binaries for most operating systems are available at the SQLite download page (http://sqlite.org/download.html). For compatibility reasons it is important to get the command-line version number that matches the version incorporated into PHP. At the SQLite site you may have difficulty finding older versions. If there is no link to the version you require, enter the URL http://sqlite.org, followed by the version number
you require, into the address bar of your browser—for example, http://www.sqlite.org/ sqlite-2_8_16.zip. You might get away with using a slightly higher or lower version number, but version 3 databases are an entirely different format from version 2, so do not attempt to use the version 3 command-line tool with a version 2 database.
The database used in this application is called resources.sqlite and is stored in a subdirectory named dbdir. If you haven’t already created it using the db_install_script.php file, you can do so by redirecting the dump.sql file from the command line in the following way:
sqlite resources.sqlite < dump.sql
A database dump is formatted as a transaction, so, if this command worked properly, you’ve already used one of SQLite’s advanced features.
You can test that the database has been installed by executing a SELECT statement. Typing SELECT * FROM tblresources; should display all the records in the resources table.
Creating a Table
The SQL used to create the tblresources table in our database is shown in
Listing 15-1.
CREATE TABLE tblresources(
id INTEGER PRIMARY KEY,
url VARCHAR(255) NOT NULL UNIQUE default '', email VARCHAR(70) NOT NULL default '', precedingcopy VARCHAR(100) NOT NULL default '', linktext VARCHAR(255) NOT NULL default '', followingcopy VARCHAR(255) NOT NULL default '', target VARCHAR(35) default '_blank',
category VARCHAR(100) NOT NULL default '',
theirlinkpage VARCHAR(100) default NULL, whenaltered TIMESTAMP default '0000-00-00', reviewed BOOLEAN default 0,
whenadded DATE default '2006-05-05');
Listing 15-1: Creating a table
Let’s have a look at the details.
To create a table with an autonumber field named id, the data type INTEGER is used in conjunction with PRIMARY KEY. This is equivalent to identifying a field as INTEGER auto_increment PRIMARY KEY in MySQL. In SQLite this field definition is the one exception to the rule that SQLite fields are typeless—otherwise all fields are strings. Creating fields as types other than string helps document the data types you are expecting but will not restrict the value entered. You can put a string into a float type field and a float into a Boolean. Further, specify- ing the length of a VARCHAR type field will not truncate data that exceeds the defined length. Any length of string can be entered into any field. Otherwise, the syntax for creating a table functions exactly as you might expect.
The field names used in creating this table are self-documenting, but a few comments are in order. A resource won’t be displayed until the reviewed field is set to true. The field with the data type TIMESTAMP whenaltered will be maintained using a trigger as will the whenadded field.
Views
Views are stored SELECT queries. If you repeatedly use the same query, it is worthwhile creating it as a view.
To make resource links easily accessible, let’s order them alphabetically and create hyperlinks to each letter of the alphabet. With this in mind, take a look at the alphabet view shown in Listing 15-2.
CREATE VIEW alphabet AS
SELECT DISTINCT UPPER(SUBSTR(linktext,1,1)) AS letter
FROM tblresources
WHERE reviewed = 1
ORDER BY letter;
CREATE VIEW specific_link AS SELECT id, url,
(precedingcopy || ' ' || linktext || ' ' || followingcopy) AS copy
FROM tblresources;
Listing 15-2: Views
The alphabet view creates a row of links as pictured at the top of
Figure 15-1.
Rather than repeat the SQL statement that makes up the alphabet view, we can instead simply SELECT * FROM alphabet using the name of the view in the FROM clause.
The second view, specific_link, also shown in Listing 15-2, demonstrates how a view can be “updated” when used in conjunction with a trigger. We will return to this view in the following discussion about triggers, but do note the use of || as the string concatenation operator.
As you can see, SQLite defines its own string manipulation functions. For a complete list of functions and operators, see www.sqlite.org/lang_expr.html.
Triggers
For those programmers who pride themselves on their laziness, triggers are a wonderful thing. By creating a trigger you can get maximum effect with mini- mum effort.
Triggers are activated by an INSERT, DELETE, or UPDATE SQL statement. They are often used to maintain referential integrity and avoid orphaned records— for example, deleting an invoice might well trigger deletion of all related invoice items. We’re going to create three triggers for our application: one to mimic a timestamp field, another to show the advantages of laziness, and finally a trigger to demonstrate how a view can be “updated.”
The timestamp triggers are shown in Listing 15-3. They are activated whenever a record in the tblresources table is added or updated.
CREATE TRIGGER insert_resources AFTER INSERT ON tblresources
BEGIN
UPDATE tblresources SET whenaltered = DATETIME('NOW','LOCALTIME') WHERE id = new.id;
END;
CREATE TRIGGER update_resources AFTER UPDATE ON tblresources
BEGIN
UPDATE tblresources SET whenaltered = DATETIME('NOW','LOCALTIME') WHERE id = new.id;
END;
CREATE TRIGGER add_date AFTER INSERT ON tblresources
BEGIN
UPDATE tblresources SET whenadded = DATE('NOW','LOCALTIME') WHERE id = new.id;
END;
CREATE TRIGGER delete_link INSTEAD OF DELETE ON specific_link
FOR EACH ROW BEGIN
DELETE FROM tblresources
WHERE id = old.id; END;
Listing 15-3: Triggers
There is no need to remember to update the whenaltered field each time a change is made to a record—the insert_resources and update_resources triggers will do this for you. The current date and time will be added in the background. Effectively, this field will now function like a MYSQL TIMESTAMP field.
Likewise with the add_date trigger, also shown in Listing 15-3. We want to highlight new links. This trigger makes it possible to capture the date a link is added. By using a trigger we don’t have to worry about forgetting to main- tain this field, and we don’t have to write additional code each time a record is added.
Creating a trigger on a view is a convenient way of performing an “update” against a view. By themselves, views are not updatable. If you attempt to delete from a view that has no associated trigger, you’ll get a warning like the following:
Warning: SQLiteDatabase::query() [function.query]: cannot modify specific_link because it is a view...
We solved this problem in the trigger we created on the view specific_link shown in Listing 15-3. Because we used an INSTEAD OF clause, any attempt to delete from this view instead removes the appropriate record from the table, tblresources.
In this trigger we have specified FOR EACH ROW. Doing so is optional. A FOR EACH STATEMENT clause also exists but is not yet supported.
The WHERE clause of a trigger is somewhat intuitive but may cause some confusion. Using new.id to specify a newly inserted record and old.id for a deleted record clearly makes sense. Either old or new may be used when a record is updated.
Using triggers is very convenient, although the same effect could be achieved programmatically. But because triggers are embedded in the data- base, they are activated even when you make changes from the command line. Triggers help maintain the integrity of your database when it is modified outside of your application. Laziness has its rewards.
PHP Implementation of SQLite
For the most part, the OO methods of SQLite are exactly the same as the procedural functions. The only difference is that the leading sqlite is dropped and the studly caps naming convention is used in place of under- scores (although some methods added in version 5.1 don’t quite follow this rule). Method parameters are the same as those used with the procedural functions, except that there is no need to pass a resource handle since the object itself is the handle. A few functions are only available in a procedural form; these will be mentioned where appropriate.
There are three built-in, ready-to-use SQLite objects: an SQLite database, a buffered result set, and an unbuffered result set. All three classes will be used in this chapter, but the focus will be on the database class.
Extending SQLiteDatabase
One of the nice things about object-oriented programming (OOP) is excep- tion handling. Procedural error trapping is not only tedious, it clutters up your code and can make it unreadable. Taking an OO approach and using exception handling sounds like the ideal solution—until you realize that the database constructor is the only method of all the SQLite classes that throws
an exception. If you want to check for errors when creating result sets, you are stuck using procedural code. It looks like we’re right back where we started.
We’ll next discuss how this can be fixed.
Override the Query Methods
The simple solution to this problem is inheritance. On the surface, this would seem fairly straightforward: Create a class that extends SQLiteDatabase and override all the query methods. If errors arise within those overridden methods, simply throw an exception. In this way, the messy details of error trapping can be buried inside the class file and a single catch block can handle all errors. The first five methods in the class definition file shown in Listing 15-4 do exactly this.
Error Messages
The comment immediately inside each method definition shows the method prototype as defined on the PHP site. This is especially useful because it shows the type of object returned. Some of the base class methods take an optional string reference argument (&$error_msg).
NOTE In versions of PHP prior to 5.1, passing in this string reference results in this warning:
SQLiteDatabase::query() expects at most 2 parameters, 3 given.
The reason a third parameter is necessary is explained as follows (from http://php.net/sqlite_query):
... [$error_msg] will be filled if an error occurs.
This is especially important because SQL syntax errors can’t be fetched using the [sqlite_last_error()] function.
Quite true. The sqlite_last_error function returns an uninformative message: SQL logic error or missing database. Our code doesn’t make use of this error message but this isn’t an insurmountable problem. A more
specific error message would certainly help in the debugging process, how- ever. Fortunately, if you have warnings turned on while you are developing,
you will get something more meaningful. Forcing a warning by referencing a nonexistent table results in the following, more specific, output:
Warning: SQLiteDatabase::query()[function.query]: no such table: tblnonexistent...
Query Methods
Look again at Listing 15-4. It includes the five methods for creating result sets. The buffered and unbuffered methods are fairly self-explanatory—you are probably quite familiar with the equivalent MySQL functions. However, MySQL (prior to the MySQL improved extension) has nothing to match the singleQuery, queryExec, or arrayQuery methods. Let’s look at these methods in more detail.
The singleQuery method is a recent addition, and the PHP site warns that it is not currently documented. Let’s carry on regardless because this method looks especially useful for those situations where a query returns only one row—when using the COUNT function to return the number of records in a table, for example. Here’s one view of how this method ought to behave: This method returns only one record, and no result set is created. If the second argument is false, the value returned is an array of the first row. If the sec- ond argument is true, then only the first column of the first row is returned, and it is returned as a scalar value.
This speculation may make the best sense of how this method ought to work, but it doesn’t describe what actually happens. In fact, this method only ever returns the first column and any number of rows. If the second argument is false, then an array is returned; if the second argument is true and only one row is returned, a scalar is returned. On the PHP site, this second argument is identified as bool first_row_only and the return type is identified as an array. It looks like the return type should be mixed. In any case, this method doesn’t yet work the way it ought to. We were warned.
There is no requirement that you use the singleQuery method instead
of query. As with MySQL, you can always create a result set and then use the appropriate fetch function to retrieve the value of the first row or a specific field. But why return an object or an array when all that’s needed is the value of one column? You may use the singleQuery method for any kind of query— data manipulation or otherwise—but it was designed specifically for situa- tions where a single value or single column is returned, and is presumably optimized for this situation.
As you can see, there is also an arrayQuery method. Like the singleQuery method, this method allows us to directly copy results into an array, bypassing the intermediate step of creating a result set. This method is best used when a limited number of records are returned.
MySQL versions prior to 4.1 have no equivalent to the queryExec method of SQLite because queryExec is specifically designed for use with multiple queries. Multiple, semicolon-separated queries may be passed as a single query string to queryExec. (The install script uses this method to create the tables, triggers, and views and to insert records into the tblresources table.) This method gives significant performance improvements over repeated querying and performs
the same job as the MySQL-improved (the mysqli functions added to PHP 5 to support MySQL 4.1) method, mysqli_multi_query. If you like, you can of course use this method to execute a single query.
query
Use of this method to create an SQLiteResult object is shown in Listing 15-5.
$db = new SQLiteDatabasePlus('resources.sqlite');
//alphabet view
$strsql = "SELECT * FROM alphabet";
//use buffered result set to get number of rows
$result = $db->query($strsql);
//create alphabet here if($result->numRows() > 0){
echo get_alphabet($result);
}
Listing 15-5: query method returns a buffered result set
Remember, an SQLiteResult is buffered so you can use the numRows method with this result set. It is also iterable, so this result set may be used in a foreach loop. In this, SQLite differs from MySQL. Because SQLiteResult implements Iterator, all the iterator methods are present—rewind, next, valid, and current. These methods can be used directly, but their real purpose is to allow an SQLite result set to be used in a foreach loop in exactly the same way that you might use an array. (As you might expect, the rewind method can’t be applied to an unbuffered result set.) Only this method and the unbuffered query method return a result set object.
unbufferedQuery
There is no need to buffer the result set returned in Listing 15-6.
try{
$db = new SQLiteDatabasePlus('../dbdir/resources.sqlite');
$type="Edit";
//retrieve from db
$strsql = "SELECT * FROM tblresources ". "WHERE id = '$id'";
//get recordset as row
$result = $db->unbufferedQuery($strsql);
$row = $result->fetch();
//can't use below because returns first column only
//$row = $db->singleQuery($strsql, false);
// assume vars same as fields while(list($var, $val)=each($row)) {
$$var=$val;
}
}catch(SQLiteException $e){
//debug msg
echo $e->getMessage();
}
}
Listing 15-6: The unbufferedQuery method
This listing shows an unbuffered query. In this case, a functional singleQuery method would be preferable because we know that only one record will be returned. However, given the problems with singleQuery, we use the unbufferedQuery method of an SQLiteDatabase object to create a result set object and then use the fetch method to copy the first row into an array.
arrayQuery
The PHP site warns against using the arrayQuery method with queries that return more than 45 records (a somewhat arbitrary number perhaps, but this method stores results in memory so returning a large number of records can exhaust memory). We’ve used this method in Listing 15-7.
...
$db = new SQLiteDatabasePlus('../dbdir/resources.sqlite');
$db->createFunction('class_id','set_class_id',0);
$sql = "SELECT id, url, email, ".
"(precedingcopy || ' ' || linktext || ' ' || followingcopy) ". "AS copy, linktext, reviewed, class_id() AS classid ".
"FROM tblresources ". "ORDER BY id DESC ".
"LIMIT $recordoffset,". PERPAGE;
//use arrayQuery
$resultarray = $db->arrayQuery($sql);
Listing 15-7: Using arrayQuery
As you can see, we know exactly how many records are returned because our SQL has a LIMIT clause. Again, this method allows us to bypass creation of a result set.
singleQuery
The code below uses the singleQuery method and does exactly what we need—it returns a single scalar value rather than a result set or an array.
$totalrecords = $db->singleQuery('Select COUNT(*) FROM
tblresources', true);
queryExec
This method is commonly used to process a transaction. Use the command- line command .dump to dump your database or view the file dump.sql. You’ll see that it is formatted as a transaction. You can recreate an entire database by passing this listing as a string to the queryExec method, as we have done with the install script, db_install_script.php.
The ability to perform multiple queries using one string does raise security issues. When using this query method, it is especially important to filter data in order to avoid a possible SQL injection attack.
Utility Methods
By overriding all the query methods of the SQLiteDatabase class we ensure that any failed query throws an exception. This done, we needn’t worry about error trapping whenever we perform a query. The remaining methods of our derived class are utility methods aimed at helping verify data posted from a form. These methods give us an opportunity to explore some of the ways to retrieve metadata from an SQLite database. Find those methods in Listing 15-8.
/**
Get all table names in database
*/
public function getTableNames(){
if (!isset($this->tablenames)){
$this->setTablenames();
}
return $this->tablenames;
}
/////////////////////////////////////////////////////////////
/**
Retrieve field names/types for specified table
*/
public function getFields($tablename){
if (!isset($this->tablenames)){
$this->setTablenames();
}
if (!in_array($tablename, $this->tablenames)){
throw new SQLiteException("Table $tablename not in database.");
}
$fieldnames = array();
$sql = "PRAGMA table_info('$tablename')";
$result = $this->unbufferedQuery($sql);
//no error - bad pragma ignored
//get name and data type as defined upon creation foreach ($result as $row){
$fieldnames[$row['name']] = $row['type'];
}
return $fieldnames;
}
//////////////////////////////////////////////////////////////
//private methods
/**
private method - initializes table names array
*/
private function setTableNames(){
$sql = "SELECT name ". "FROM sqlite_master ". "WHERE type = 'table' ". "OR type = 'view'";
$result = $this->unbufferedQuery($sql);
foreach ($result as $row){
$this->tablenames[] = $row['name'];
}
}
Listing 15-8: Metadata methods
The two methods that make use of metadata are setTableNames and getFieldNames. Let’s examine the method setTableNames in Listing 15-8. This method makes use of the table sqlite_master—a table that defines the schema for the database. By querying sqlite_master, we can retrieve the names of all the tables and views in the database. The type field defines the kind of resource, in our case a table or view. This method retrieves the names of all the tables and views and stores them in an array.
Ideally, this method would be called once from the constructor, but the constructor for an SQLite database is declared final, so it may not be overridden.
Pragmas perform a variety of functions in SQLite. One of those func- tions is to provide information about the database schema—about indices, foreign keys, and tables.
Running the pragma table_info returns a result set that contains the column name and data type. The data type returned is the data type used when the table was created. This may seem pointless—since, excepting one case, all fields are strings—but this information could be used to assist data validation. For example, with access to a data type description, we could programmatically enforce which values are allowed for which fields. Notice that the pragma table_info can also be used with views. However, when used with views, all field types default to numeric.
A word of warning about pragmas: They fail quietly, issuing no warning or error, and there is no guarantee of forward compatibility with newer versions of SQLite.
Getting Metadata
Metadata methods allow us to discover field names at runtime. This is useful when we want to match posted values to the appropriate field in a table. Fig- ure 15-2 shows the form that we will use to post data to the database.
As you can see in Listing 15-9, the cleanData method verifies that the keys of the posted array match table field names by calling the matchNames method. It throws an exception if they don’t. However, it also removes slashes if magic quotes are on. If you regularly use MySQL with magic quotes on, escaping data may be something you never give much thought to. However, unlike MySQL, SQLite does not escape characters by using a backslash; you must use the sqlite_escape_string function instead. There is no OO method for doing this.
There is no requirement to call the cleanData method, and there may be situations where its use is not appropriate—perhaps where security is a prime concern, so naming form controls with table field names is not advisable. How- ever, it is a convenient way of confirming that the right value is assigned to the right field.
User-Defined Functions
One of the requirements of our application is to highlight recently added links. We are going to achieve this effect by using a different CSS class for links that have been added within the last two weeks. Subtracting the value stored in the whenadded field from the current date will determine the links that satisfy this criterion. Were we to attempt this task using MySQL, we could add the following to a SELECT statement:
IF(whenadded > SUBDATE(CURDATE(),INTERVAL '14' DAY), 'new',
'old') AS cssclass
This would create a field aliased as cssclass that has a value of either new or old. This field identifies the class of the anchor tag in order to change its appearance using CSS. It’s much tidier to perform this operation using SQL rather than by manipulating the whenadded field from PHP each time we retrieve a row.
But SQLite has no date subtraction function. In fact, the SQLite site doesn’t document any date functions whatsoever. Does this mean that we are stuck retrieving the whenadded field from the database and then performing the date operations using PHP? Well, yes and no. SQLite allows for user-defined functions (UDFs). Let’s take a look at how this works.
The first thing to do is create a function in PHP to subtract dates—not a terribly difficult task. See the function check_when_added in Listing 15-10 for the implementation.
function check_when_added($whenadded){
//less than 2 weeks old
$type = 'old';
// use date_default_timezone_set if available
$diff = floor(abs(strtotime('now') - strtotime($whenadded))/86400);
if($diff < 15){
$type = 'new';
}
}
...
...
return $type;
//register function
$db-> createFunction('cssclass','check_when_added',1);
$strsql ="SELECT url, precedingcopy, linktext, followingcopy, ". "UPPER(SUBSTR(linktext,1,1)) AS letter, ". "cssclass(whenadded) AS type, target ".
"FROM tblresources ". "WHERE reviewed = 1 ". "ORDER BY letter ";
$result = $db->query($strsql);
Listing 15-10: A user-defined function
Also shown in Listing 15-10 is the createFunction method of an SQLite- Database, which is used to make check_when_added available from SQLite. Calling this function is as simple as adding the expression cssclass(whenadded) AS type to our SELECT statement. Doing this means that the result set will
contain a field called type with either a value of new or no value at all. We can use this value as the class identifier for each resource anchor tag. The new anchors can be highlighted by assigning them different CSS display
characteristics.
The back end of our application also makes use of a UDF; improved readability is the motivation behind its creation.
The set_class_id function in Listing 15-11 ( ) shows how the mod operator can be used in a UDF to return alternate values. When this value is used as the id attribute for a tr tag, text can be alternately shaded and unshaded by setting the style characteristics for table rows with the id set to shaded. Again, it is much tidier to return a value in our result set rather than to perform this operation from PHP. Once you are familiar with UDFs you’ll see more and more opportunities for using them. Be careful. Using them can become addictive.
//add function to SQLite function set_class_id(){
static $x = 0;
$class = 'unshaded';
if(($x % 2) == 0){
$class = "shaded";
}
...
}
$x++;
return $class;
$db = new SQLiteDatabasePlus('../dbdir/resources.sqlite');
$db->createFunction('class_id','set_class_id',0);
Listing 15-11: UDF shades alternate rows
You can’t permanently add a UDF to a database, but the ability to create them certainly compensates for the limited number of functions in SQLite, especially those related to date manipulation. In fact, in my view, this way of subtracting dates is much easier to implement because it doesn’t involve looking up or remembering the quirky syntax of functions such as the MySQL SUBDATE function referenced earlier. However, UDFs lack the performance benefits of built-in functions.
Uses and Limitations of SQLite
No database can be all things to all people. SQLite supports any number of simultaneous readers, but a write operation locks the entire database. Version 3 offers some improvement in this respect, but SQLite is still best used in appli- cations with infrequent database updates, like the one described here.
Of course, if access control is important, then SQLite is not the appropri- ate tool. Because GRANT and REVOKE are not implemented, there can be no
database-enforced user restrictions.
However, even a relatively modest application can make use of the advanced capabilities of SQLite. With the application discussed in this chapter, we haven’t had to sacrifice anything by using SQLite rather than MySQL. Unavailability of a timestamp field is remedied by use of a trigger. A UDF makes up for SQLite’s lack of date manipulation functions. In fact,
overall, we achieve better performance because there is no overhead incurred by a database server, and maintenance is reduced through the use of triggers.
Not only has using SQLite simplified our code through the use of views, triggers, and UDFs, as well as by extending the OO interface, but it also makes for cleaner code through its more varied ways of querying a database. In these or similar circumstances, SQLite is definitely a superior choice.
Posted by Mr Procces at 6:16 AM 0 comments
Labels: PHP, PHP Programming, Web Applications, Web Development
CREATING DOCUMENTATION USING THE REFLECTION CLASSES
In Chapter 4, I introduced a simple class called DirectoryItems. You may remember
what it does, but you probably can’t remem- ber the specific methods. With a user-defined class,
looking up forgotten methods usually means rooting through the class definition file. This can take a long time, especially for large classes. For an internal class you can go to http://php.net to look up the information you need. But there are more than 100 internal classes and interfaces, and their documentation is scattered throughout the PHP site. Wouldn’t it be useful to have a central repository of documentation for all classes?
Finding documentation is one problem, but the quality of documentation is another, equally important, problem. Most developers know the value of accurately commented code, but when you are in the middle of coding, the meaning of your code always seems crystal clear, so comments appear super- fluous. Besides, there’s always the ultimate excuse for the absence of internal documentation—you want to keep file size small to reduce download time.
This is often the situation with internal documentation, but external documentation fares no better. It doesn’t make sense to write it as you go because things always change, but, by the time you’ve finished coding, docu- mentation is the furthest thing from your mind. You’re ready to move on to something else.
This chapter offers a solution to the two problems of ready availability and quality of documentation. We’re going to create a documentation class derived from a new set of classes introduced in PHP 5, the reflection group. You’ll learn how to generate documentation dynamically that will fully
describe methods and data members and that will incorporate properly formatted internal comments for user-defined classes.
What Are the Reflection Classes?
In Chapter 10, before implementing the Iterator interface, you had to under- stand its methods. To create a class that will do your documenting for you, you need to become familiar with the reflection classes. This group of classes was created for the express purpose of introspecting other classes. These classes make it possible to examine the properties of other classes by retrieving meta- data about classes; you can even use them to examine the reflection classes themselves.
Reflection provides information about the modifiers of a class or interface—whether that class is final or static, for example. It can also reveal all the methods and data members of a class and all the modifiers applied to them. Parameters passed to methods can also be introspected and the names of variables exposed. Through reflection it is possible to automate the docu- mentation of built-in classes or user-defined classes. It turns out that the central repository of information about classes was right in front of us all
the time. PHP can tell us all about itself through the mirror of the reflection classes.
The Reflection Group of Classes
The reflection group of classes or Application Programming Interface (API)
is made up of a number of different classes and one interface, shown here:
class Reflection interface Reflector
class ReflectionException extends Exception class ReflectionFunction implements Reflector class ReflectionParameter implements Reflector class ReflectionMethod extends ReflectionFunction class ReflectionClass implements Reflector
class ReflectionObject extends ReflectionClass class ReflectionProperty implements Reflector class ReflectionExtension implements Reflector
We won’t be concerned with every class in the reflection API, but a gen- eral overview will help put things in perspective. Looking at this list, you may suppose that the Reflection class is the parent class of all the reflection classes, but there is actually no class ancestor common to all reflection classes. On the other hand, the Reflector interface is shared by all classes except Reflection and ReflectionException. As far as class hierarchies are concerned, ReflectionMethod extends ReflectionFunction, ReflectionObject extends ReflectionClass, and ReflectionException extends Exception.
Our concern is with objects, so we won’t spend any time on the method ReflectionFunction. ReflectionObject shares all the methods of ReflectionClass; the only difference between these classes is that ReflectionObject takes a class instance rather than a class name as a parameter—using an instance, you can introspect a class without knowing anything about it, even its name. The class ReflectionException is derived from Exception, a class we’ve already examined.
We’re principally interested in Reflection, ReflectionClass, ReflectionMethod,
ReflectionParameter, and ReflectionProperty.
The Reflection Class
The Reflection class has two static methods: export and getModifierNames. We’ll discuss getModifierNames later in this chapter, but let’s take a look at the export method—a quick way to introspect a class—to get a taste of what Reflection can tell us. Reflection requires a ReflectionClass object as the parameter to the export method. Let’s use the SOAPFault class as an example, since we recently encountered it in Chapter 12. The export method is static. As you’ll recall from Chapter 11, static methods are invoked by using the class name and the scope resolution operator. Here’s the code to export this class:
Reflection::export(new ReflectionClass('SOAPFault'));
In this example, a class name is passed to the ReflectionClass constructor, and the resultant object is the argument to the export method of the Reflection class. The output of the export method is shown in Listing 14-1.
Class [ <internal:soap> class SoapFault extends Exception ] {
- Constants [0] {
}
- Static properties [0] {
}
- Static methods [0] {
}
- Properties [4] {
Property [ <default> protected $message ] Property [ <default> protected $code ] Property [ <default> protected $file ] Property [ <default> protected $line ]
}
- Methods [9] {
Method [ <internal> <ctor> public method __construct ] {
}
Method [ <internal> public method __toString ] {
}
Method [ <internal> final private method __clone ] {
}
Method [ <internal> final public method getMessage ] {
}
Method [ <internal> final public method getCode ] {
}
Method [ <internal> final public method getFile ] {
}
Method [ <internal> final public method getLine ] {
}
Method [ <internal> final public method getTrace ] {
}
Method [ <internal> final public method getTraceAsString ] {
}
}
}
Listing 14-1: Exporting SOAPFault
The export method gives a quick overview of a class. As you can see, SOAPFault extends the Exception class and possesses all the properties of Exception. Its methods are Exception class methods. This is exactly the sort of thing we want the reflection classes to do for us.
The ReflectionClass Class
The export method is quick and easy; but what if you want more information in a user-friendly format? The place to begin is with the ReflectionClass class, which you’ll extend to create a Documenter class.
NOTE There are nearly 40 methods of ReflectionClass. Often, the methods’ names clearly indi- cate their purpose. For instance, isInterface determines whether you are introspecting a class or an interface. We will only examine those methods that are of particular interest.
Methods of ReflectionClass
The getMethods and getProperties methods play an important role in class documentation. Invoking getMethods returns an array of ReflectionMethod objects. Invoking getProperties returns an array of ReflectionProperty objects. These methods and the objects returned make it possible to fully describe a class’s methods and data members.
You will recall that I promised we’d use internal comments when documenting a class. If internal comments are properly formatted, the getDocComment method of ReflectionClass can be used to incorporate them directly into your documentation.
Fortunately, ReflectionMethod and ReflectionProperty also have
getDocComment methods, so method-level and data member–level comments can also be included.
NOTE Those of you familiar with PEAR (PHP Extension and Application Repository) and phpDocumentor or the Java utility Javadoc will already know the proper format for internal comments.
ReflectionMethod and ReflectionParameter
ReflectionMethod objects contain all the information you need to fully describe a method. By using this object you can document the modifiers of a method; you can use its getParameters method to return an array of ReflectionParameter objects, which is essential for describing a method’s parameters.
A ReflectionParameter object will give you the number of parameters, their names, and any default values. You can even determine whether a parameter is a specific type of object if it is type hinted—yet another good reason to use type hinting.
There is one respect in which you might find the ReflectionMethod class wanting, however. Sometimes it’s important to know what a method returns; for example, when using the getMethods method, it is essential to know that an array of ReflectionMethod objects is returned. Since you can type hint parameters and retrieve this information it would be nice to do the same with returned values. However, because PHP is a weakly-typed language, it’s not surprising that this capability is not supported, so be sure to document return types in your comments where appropriate.
NOTE Type hinting return values is planned for PHP 6, so perhaps we can expect support for this capability in future versions of the reflection classes.
ReflectionProperty
The getProperties method of ReflectionClass is similar to the getMethods method. It returns an array of ReflectionProperty objects that can be queried in much the same way as ReflectionMethod objects. (Determining whether default values exist for data members poses some challenges; more about this shortly.)
Built-in Functions
We’ve looked at the principal classes and methods of the reflection classes, but there are some built-in PHP functions that can also be helpful when documenting classes. Most of the functions in the Class/Object group have been effectively, if not explicitly, deprecated in PHP 5 precisely because there are now reflection classes that do a superior job. However, a number of functions, such as get_declared_classes and is_object, continue to be
useful.
What Format Do You Want?
One of the major reasons for documenting classes is to make them easier for a client programmer to use. Because the client programmer is primarily interested in the public methods of a class (he wants to know how to use the class, not how it works), you should sort methods and data members by visibility, giving priority to those with public visibility.
If you have ever used a plain text editor to write code you know that syntax highlighting greatly improves readability. For this reason, the ability to change the appearance of keywords is also a desirable characteristic to incorporate into your class.
You’ve been acquainted with the capabilities of various reflection classes, and now have a fair idea of what kind of off-the-shelf functionality is available as well as what you will have to customize. You’re in a good position to begin extending ReflectionClass.
The Documenter Class
We won’t be looking at each and every line of code in this class, but to help put the following comments in context you might want to download the code now. The export method of Reflection gave us a rough idea of the kind of information we would like to see (refer to Listing 14-1). Now let’s discuss the Documenter class in terms of how class information will be displayed.
Describing the Documenter Class
At the very minimum you need basic information about a class. The getFullDescription method combines existing ReflectionClass methods to create a string that matches the actual class declaration.
public function getFullDescription(){
$description = "";
if($this->isFinal()){
$description = "final ";
}
if($this->isAbstract()){
$description = "abstract ";
}
if($this->isInterface()){
$description .= "interface ";
}else{
$description .= "class ";
}
$description .= $this->name . " ";
if($this->getParentClass()){
$name = $this->getParentClass()->getName();
$description .= "extends $name ";
}
$interfaces = $this-> getInterfaces();
$number = count($interfaces);
if($number > 0){
$counter = 0;
$description .= "implements ";
foreach($interfaces as $i){
$description .= $i->getName();
$counter ++;
if($counter != $number){
$description .= ", ";
}
}
}
return $description;
}
This code calls a number of self-explanatory, inherited methods to build a class description. The only slight complication is that, because a class can implement more than one interface, the getInterfaces method returns an array, and so requires a foreach loop. When applied to the SoapFault class, the following string is returned by the getFullDescription method:
class SoapFault extends Exception
SoapFault is correctly identified as a class rather than an interface, it is neither final nor abstract, and its derivation from Exception is documented. This is exactly the same description that you saw in Listing 14-1 when you exported this class.
Describing Methods and Data Members
Since methods are more important than data members, let’s next deal with how to adapt the reflection classes to document methods. Calling the getMethods method of the ReflectionClass class creates an array of ReflectionMethod objects. The visibility of each method can then be determined by the isPublic, isProtected, or isPrivate methods of the ReflectionMethod class.
However, you want to display methods sorted by visibility—basically, you want a getPublicMethods method and an identical method for displaying private and protected methods. In order to be able to retrieve an array of ReflectionMethod objects sorted by visibility, you are going to loop through all the methods in a class and create separate arrays of each type. Let’s see how this is done.
private function createMethodArrays(){
$methods = $this->getMethods();
//ReflectionMethod array returned foreach($methods as $m){
$name = $m->getName();
if($m->isPublic()){
$this->publicmethods[$name] = $m;
}
if($m->isProtected()){
$this->protectedmethods[$name] = $m;
}
if($m->isPrivate()){
$this->privatemethods[$name] = $m;
}
}
}
Again, the code is quite simple. An array of all methods of a class is retrieved using the inherited ReflectionClass method getMethods, and each ReflectionMethod object is stored in the appropriate associative array, using the method name as the array key.
Each array is a private variable with a public accessor method—the prescribed way for retrieving data members. For example, to examine the public methods of a class, you simply call getPublicMethods, which will return the array populated by createMethodArrays.
Data member arrays are created in exactly the same fashion. Your class has a createDataMemberArrays that uses the getProperties method inherited from the ReflectionClass to create an array of ReflectionProperty objects. You then query each ReflectionProperty object to create arrays of public, private, and protected data members. These arrays can, in turn, be retrieved using accessor methods.
The Constructor
The createDataMemberArrays method and the companion method for creating an array of methods are both private and called from within the constructor of the Documenter class.
public function __construct($name){
parent::__construct($name);
$this->createDataMemberArrays();
$this->createMethodArrays();
}
Placement of the call to the parent constructor is noteworthy. Because createDataMemberArrays and createMethodArrays both invoke methods of the parent class, it is essential that the call to the parent constructor occur first. Doing otherwise results in calling methods on a not-yet-existent object.
Method and Data Member Modifiers
It is essential to know the access modifiers for methods and data members of a class. Both the ReflectionMethod and the ReflectionParameter classes have a getModifiers method that returns an integer with bit fields set to flag the different access modifiers. Your Documenter class has its own getModifiers method that converts these flags to their string equivalents using the static getModifierNames method of the Reflection class.
public function getModifiers($r){
if($r instanceof ReflectionMethod ||
$r instanceof ReflectionProperty){
$arr = Reflection::getModifierNames($r->getModifiers());
$description = implode(" ", $arr );
}else{
$msg = "Must be ReflectionMethod or ReflectionProperty";
throw new ReflectionException( $msg );
}
return $description;
}
You want to ensure that only ReflectionMethod objects or ReflectionProperty objects are passed into this method so you use the operator, instanceof. This operator was introduced with PHP 5 and replaces the now-deprecated func- tion is_a. This operator allows you to restrict use of your method to classes that support the getModifiers method and to throw a ReflectionException if the wrong type of object is passed in.
When you pass the return value of getModifiers to the static method of the Reflection class, getModifierNames, a string array of all the modifiers is returned. A series of calls to isPublic, isStatic, and like methods would achieve the same result, but using getModifierNames is by far the most succinct way of getting the string values of method and data member modifiers.
NO C O MMO N A N C E ST O R
You might think that ReflectionMethod and ReflectionProperty objects each have a getModifiers method because they share a common interface, Reflector, and, con- sequently, you could type hint the parameter to this method to check for an instance of this particular interface only. However, you would be mistaken. There are only two methods of the Reflector interface: export and __toString. As far as a common class heritage is concerned, ReflectionMethod derives from ReflectionFunction and ReflectionProperty has no parent class. So there is no common parentage. That said, the fact remains that checking for an instance of the Reflector class would achieve essentially the same result as checking for ReflectionFunction and ReflectionProperty— but for the wrong reasons. It is only fortuitous that both classes have a getModifiers method. Another way to screen for the correct class would be to introspect the variable
$r to determine whether it has a getModifiers method.
As an interesting aside, when introspecting the methods of a built-in inter- face, the modifiers are always public and abstract. In Chapter 11 you saw that PHP prohibits the use of the modifier abstract when defining the methods of a user-defined interface, despite the fact that the methods of an interface must in fact be abstract.
Using the Documenter Class
That completes the description of the Documenter class. We will now use it in
a web page to display information about all internal and user-defined classes. We’ll create a sidebar of links to all existing classes and interfaces, and display detailed information in the main portion of the page. Again, we won’t discuss every line of code, only those lines that are of special interest.
Creating a Sidebar of Classes and Interfaces
Let’s create a sidebar that will display the names of all PHP classes as hyperlinks—fulfilling the promise of a central repository of information about all classes. Clicking a hyperlink will display documentation for this class in the main portion of your web page. The code to do this follows:
include 'MySQLResultSet.php'; include 'MySQLConnect.php'; include 'Documenter.php'; include 'PageNavigator.php';
$arr = get_declared_classes();
natcasesort($arr);
$classname = @$_GET["class"];
if(!isset($classname)){
$classname = current($arr);
}
echo "<h4 style=\"background-color:#fff;\">Classes</h4>";
foreach($arr as $key => $value){
echo "<a href=\"getclass.php ?class=$value\">". "$value</a><br />";
}
In addition to built-in classes, any user-defined classes that have been loaded using an include or require statement will be retrieved when you call
the get_declared_classes function. If no variable named class has been passed to this page, then $classname will default to the name of the first class in the array of declared classes. This $classname variable contains the class name that will be passed to the constructor of the Documenter class. Information about the specified class will be displayed in the center of the page. A foreach loop creates the list of hyperlinks to all available classes by creating a query string that includes the class name.
Your sidebar also displays links to all the declared interfaces. The code to do this is identical to the code to retrieve classes except that it calls the func- tion get_declared_interfaces instead of get_declared_classes. Therefore this code will not be reproduced here.
Formatting Detailed Documentation
The MySQLException class is a derived class that has a variety of methods (see Fig- ure 14-1), so use it as an example of how we would like the class documentation to look.
Let’s proceed by relating the code to this output. The web page that displays your documentation first creates an instance of the Documenter class:
try{
$class = new Documenter($classname);
echo "<h2>Name: ". $class-> getName() . "</h2>\n";
$today = date("M-d-Y");
echo "<p> Date: $today<br />";
echo "PHP version: ". phpversion() . "<br />";
echo "Type: ". $class-> getClassType() . "<br /><br />\n";
echo "<span class=\"fulldescription\">". $class-> getFullDescription(). "</span><br /><br />\n";
echo $class-> getDocComment() . "</p>\n";
...
}
Because creating an instance may throw a ReflectionException, you enclose your call to the constructor within a try block. You need to know which class we are documenting, so you display the class name by calling the inherited method getName. Knowing when documentation was created is
important, so you display the date using the date function. Likewise with the PHP version number. Since you are mixing built-in and user-defined classes, specifying the class type will reduce confusion.
As you saw earlier in this chapter, the full class description identifies whether you are dealing with a class or an interface, and also details the class parentage. Because internal comments within the class file have been properly formatted, you can extract them using the getDocComment method. When this method is called against an instance of a class, it retrieves the comment that immediately precedes the class definition. Let’s see how that’s done.
Formatting Comments for the Documenter
The getDocComment method is fussy about what it will retrieve, so let’s look at the format of the comments within an existing user-defined class. We’ll con- tinue using the MySQLException class as an example.
/** For use with MySQLConnection and MySQLResultSet classes */
class MySQLException extends Exception
{ ... }
A class-related, internal comment must meet the following conditions for the getDocComment method to work:
It must immediately precede the class definition statement.
It may run to any number of lines but must begin with a forward slash, followed by two asterisks, followed by white space, and be terminated by an asterisk and forward slash—in other words, exactly the format required by Javadoc.
The ReflectionFunction, ReflectionMethod, and ReflectionObject classes also support a getDocComment method. (As of PHP 5.1, the ReflectionProperty class also supports this method.) Exactly the same formatting rules apply. Again, internal comments must immediately precede what they document.
As you can see in Figure 14-1, the internal comments documenting the constructor are displayed immediately after the class description—as prom- ised, the Documenter class incorporates internal comments. Unfortunately, getDocComment only applies to user-defined classes and user-defined methods or data members; comments cannot be extracted for internal classes.
Documenting Methods
As shown in Figure 14-1, method documentation is displayed immediately after the class description and comments. With a view to the client programmer, public methods are displayed immediately after the class name and descrip- tion, followed by protected methods, and finally private methods. Because the MySQLException class has no protected methods, none are shown.
Methods of all levels of visibility are passed to the show_methods function to handle the details of displaying method descriptions. Here is the prototype for this function:
function show_methods(Documenter $d, $type, $arr)
One of the parameters of this function is an object. In PHP 4 you would want to ensure that this object was passed by reference by preceding the variable with & (an ampersand). As discussed in Chapter 13 in the section “__clone” on page 116, in PHP 5 all objects are automatically passed by reference, so there is no need to do this. This parameter is also type hinted, disallowing anything other than a Documenter object.
To summarize, this function displays the variable names of method parameters, type hints, and default values where applicable. Syntax high- lighting has been used for the keywords describing each method—you can quickly see in Figure 14-1 that the getMessage method of the MySQLException class is both final and public. User-defined methods are flagged as such, and any internal comments are displayed.
NOTE If you are running PHP 5.1 or higher, you can type hint the array passed to show_methods by changing the function prototype to read function show_methods(Documenter $d, $type, array $arr).
Data Members
Data members are handled in much the same way as methods. Those with the least restrictive visibility are presented first. Again, keywords are high- lighted. Even default values assigned to data members can be retrieved. Somewhat surprisingly, this is done using the getDefaultProperties method of ReflectionClass rather than by using a ReflectionProperty class method. As with methods, all modifiers are shown. The value of constants is retrieved using the ReflectionClass method getConstants.
Reflecting
The reflection classes make it easy to generate documentation for both internal and user-defined classes. Documentation can be created directly from the class files themselves, so any changes to the class are immediately reflected in the documentation—much easier than separately maintaining both code and documentation. Descriptions of methods and hints about class usage are invaluable not only for the client programmer but also for the class originator, especially when a few months have lapsed between creation of a class and its subsequent use. Class documentation can effortlessly incorporate internal comments as long as you simply pay a little attention to their format during coding.
Posted by Mr Procces at 6:12 AM 0 comments
Labels: PHP, PHP Programming, Web Applications, Web Development
MORE MAGIC METHODS
So far we have come across the magic meth- ods __construct, __destruct, and __toString,
and have discussed them in detail. The remaining magic methods are __autoload,
__call, __clone, __get, __set, __sleep, __wakeup, __unset,
and __isset.1 As you might expect, they only make sense in the context of object-oriented programming (OOP).
The syntactic element common to all magic methods is that they begin with a double underscore. They are all also usually invoked indirectly rather than directly. As we have seen, the __construct method of a class is invoked when we use the new operator and a class name. If we have a class called MyClass that defines a constructor, the statement $m = new MyClass(); indirectly calls the __construct method of this class.
However, the fact that all magic methods are called indirectly masks important differences between them. Having a uniform constructor for every class yields benefits when a parent constructor needs to be called, but there is no intrinsic need for this method to be magic. For example, in Java, construc- tors bear the name of the class with no serious negative consequences. On the other hand, destructors are a necessity and would seem to have to be magic. They are not invoked by any action of the developer, but automatically when an object goes out of scope. Then there’s the __toString method, which is called implicitly whenever an object is displayed using print or echo—a convenience method more than anything else. In any case, the point is that the reasons for providing magic methods are various and in each case worth examining.
In this chapter we will look at those magic methods that we haven’t yet discussed. Related and complementary methods will be discussed together.
To set the context for this discussion, remember that we spent some time discussing accessor, or set and get methods, in Chapter 6. There I argued that instance variables should be made private and only retrieved or changed through accessor methods. Doing otherwise violates the object-oriented (OO) principle of data hiding (or encapsulation if you prefer) and leaves instance variables exposed to inadvertent changes.
PHP 5 introduces magic set and get methods for undefined instance vari- ables. Let’s see what this means by looking at an example. Suppose you have a class, Person, devoid of any data members or methods, defined as follows:
class Person{
}
PHP allows you to do the following:
$p = new Person();
$p->name = "Fred";
$p->street = "36 Springdale Blvd";
Even though name and street data members have not been declared within the Person class, you can assign them values and, once assigned, you can retrieve those values. This is what is meant by undefined instance variables. You can cre- ate magic set and get methods to handle any undefined instance variables by making the following changes to the Person class, as shown in Listing 13-1.
class Person{
protected $datamembers = array();
public function __set($variable, $value){
//perhaps check value passed in
$this->datamembers[$variable] = $value;
}
public function __get($variable){
return $this->datamembers[$variable];
}
}
$p = new Person();
$p-> name = "Fred";
You add an array to your class and use it to capture any undeclared instance variables. With these revisions, assigning a value to an undeclared data member called name invokes the __set method in the background, and an array element with the key name will be assigned a value of “Fred.” In a similar fashion the __get method will retrieve name.
Is It Worth It?
Magic set and get methods are introduced as a convenience, but it is certainly questionable whether they are worth the effort. Encouraging the use of undefined data members can easily lead to difficulties when debugging. For instance, if you want to change the value of the name data member of your Person class instance, but misspell it, PHP will quietly create another instance variable. Setting a nonexistent data member produces no error or warning, so your spelling error will be difficult to catch. On the other hand, attempting to use an undefined method produces a fatal error. For this reason, declaring data members to be private (or protected), and ensuring that they are only accessible through declared accessor methods, eliminates the danger of accidentally creating a new unwanted data member. Using declared data members means fewer debugging problems.
Undeclared data members also seem contrary to the principles of OOP. Although you might argue that encapsulation has been preserved because undeclared data members are only accessed indirectly through the magic methods, the real point of accessor methods is to control how instance variables are changed or retrieved. The comment inside the __set method (//perhaps check value passed in) in Listing 13-1 suggests that such controls could be implemented, but in order to do so you would need to know the vari- able names beforehand—an impossibility given that they are undeclared. Why not just set up properly declared data members?
Allowing undeclared data members also undermines another basic con- cept of OOP, namely inheritance. It’s hard to see how a derived class might inherit undeclared instance variables.
One might argue, though, that these magic methods make PHP easier to use and this convenience offsets any of the disadvantages. After all, the original and continuing impetus behind PHP is to simplify web development. Allowing undeclared data members in PHP 5 is perhaps a necessary evil because doing so keeps backward compatibility with PHP 4. While it’s easy to criticize magic set and get methods, in Chapter 16, when discussing the PDORow class, you’ll see that these methods can come in very handy.
isset and unset
PHP 5.1.0 introduces the magic methods __isset and __unset. These methods are called indirectly by the built-in PHP functions isset and unset. The need for these magic methods results directly from the existence of magic set and get methods for undeclared data members. The magic method __isset will be called whenever isset is used with an undeclared data member.
Suppose you want to determine whether the name variable of your Person instance in Listing 13-1 has been set. If you execute the code isset($t->name);, the return value will be false. To properly check whether an undeclared data member has been set, you need to define an __isset method. Redo the code for the Person class to incorporate a magic isset method (see Listing 13-2).
class Person{
protected $datamembers = array();
private $declaredvar = 1;
public function __set($variable, $value){
//perhaps check value passed in
$this->datamembers[$variable] = $value;
}
public function __get($variable){
return $this->datamembers[$variable];
}
function __isset($name){
return isset($this->datamembers[$name]);
}
function getDeclaredVariable(){
return $this->declaredvar;
}
}
$p = new Person();
$p->name = 'Fred';
echo '$name: '. isset($p-> name). '<br />';//returns true
$temp = $p->getDeclaredVariable();
echo '$declaredvar: '. isset( $temp). '<br />';//returns true
true true
Listing 13-2: The Person class with a magic __isset method
Calling isset against the undeclared data member name will return true because an implicit call is made to the __isset method. Testing whether
a declared data member is set will also return true, but no call, implicit or otherwise, is made to __isset. We haven’t provided an __unset method, but by looking at the __isset method you can easily see how an undeclared variable might be unset.
You have __isset and __unset methods only because there are magic set and get methods. All in all, in most situations, it seems simpler to forget about using undeclared data members, and thereby do away with the need for magic set and get methods and their companion __isset and __unset methods.
The magic method __call is to undeclared methods what get and __set are
to undeclared data members. This is another magic method provided as a con- venience. At first, it is a little difficult to imagine what an undeclared method might be and what use it might have. Well, here’s one way that this method
can prove useful. Suppose you wanted to add to the functionality of the MySQLResultSet class defined in Chapters 9 and 10, so as to retrieve the current system status in this fashion:
//assume $rs is an instance of MySQLResultSet
$rs->stat();
You could just create a wrapper method for the existing MySQL function, mysql_stat, as you did when creating other methods of this class. For example, the existing getInsertId method simply encloses a call to mysql_insert_id. You could do exactly the same thing with mysql_stat. However, the more versatile option is to add a __call method similar to the following code:
public function __call($name, $args){
$name = "mysql_". $name(;
if(function_exists($name)){
return call_user_func_array($name, $args);
}
}
When you call the stat method against a MySQLResultSet object, the method name, stat, is passed to the __call method where mysql_ is prepended. The mysql_stat method is then invoked by the call_user_func_array function. Not only can you call the mysql_stat function, but once __call is defined you can call any MySQL function against a MySQLResultSet class instance by simply using the function name, minus the leading mysql_, and supplying any required arguments. This magic method does away with the need for writing wrapper methods for existing MySQL function, and allows them to be “inherited.” If you’re already familiar with the MySQL function names it also makes for easy use of the class.
However, this magic method is not quite as convenient as it might seem at first glance. Functions such as mysql_fetch_array that require that a result set resource be passed even though the class is itself a result set resource make nonsense of the whole notion of an object—why should an object need to pass a copy of itself in order to make a method call? On the other hand, this is an easy and natural way to incorporate functions such as mysql_stat
and mysql_errno that don’t require any arguments, or functions such as mysql_escape_string that require primitive data types as arguments. If properly used, this convenience method seems much more defensible than the __set and __get methods.
The __autoload function is a convenience that allows you to use classes without having to explicitly write code to include them. It’s a bit different from other magic methods because it is not incorporated into a class definition. It is simply included in your code like any other procedural function.
Normally, to use classes you would include them in the following way:
require 'MySQLResultSet.php'; require 'MySQLConnect.php'; require 'PageNavigator.php'; require 'DirectoryItems.php'; require 'Documenter.php';
These five lines of code can be replaced with the following:
function __autoload($class) {
require $class '.php';
}
The __autoload function will be invoked whenever there is an attempt to use a class that has not been explicitly included. The class name will be passed to this magic function, and the class can then be included by creating the filename that holds the class definition. Of course, to use __autoload as coded above, the class definition file will have to be in the current directory or in the include path.
Using autoload is especially convenient when your code includes numer- ous class files. There is no performance penalty to pay—in fact, there may be performance improvements if not all classes are used all the time. Use of the
autoload function also has the beneficial side effect of requiring strict naming conventions for files that hold class definitions. You can see from the previous code listing that the naming conventions used in this book (i.e., combining the class name and the extension .php to form the filename) will work fine with __autoload.
sleep and wakeup
These magic methods have been available since PHP 4 and are invoked by the variable handling functions serialize and unserialize. They control how an object is represented so that it can be stored and recreated. The way that you store or communicate an integer is fairly trivial, but objects are more com- plex than primitive data types. Just as the __toString method controls how an object is displayed to the screen, sleep controls how an object will be stored. This magic method is invoked indirectly whenever a call to the serialize function is made. Cleanup operations such as closing a database connection can be performed within the __sleep method before an object is serialized.
Conversely, __wakeup is invoked by unserialize and restores the object.
clone
Like the constructor, __clone is invoked by a PHP operator, in this case clone. This is a new operator introduced with PHP 5. To see why it is necessary, we need to take a look at how objects are copied in PHP 4.
In PHP 4 objects are copied in exactly the same way that regular variables are copied. To illustrate, let’s reuse the Person class shown in Listing 13-1 (see Listing 13-3).
$x = 3;
$y = $x;
$y = 4;
echo $x. '<br />';
echo $y. '<br />';
$obj1 = new Person();
$obj1->name = 'Waldo';
$obj2 = $obj1;
$obj2->name = 'Tom';
echo $obj1->name. '<br />';
echo $obj2->name;
Listing 13-3: Using the assignment operator under PHP 4
If the code in Listing 13-3 is run under PHP 4, the output will be as follows:
3
4
Waldo
Tom
The assignment of $obj1 to $obj2 ( ) creates a separate copy of a Person just as the assignment of $x to $y creates a separate integer container. Chang- ing the name attribute of $obj2 does not affect $obj1 in any way, just as changing the value of $y doesn’t affect $x.
In PHP 5, the assignment operator behaves differently when it is used with objects. When run under PHP 5, the output of the code in Listing 13-3 is the following:
3
4
Tom
Tom
For both objects the name attribute is now Tom.
Where’s Waldo?
In PHP 5, the assignment of one object to another creates a reference rather than a copy. This means that $obj2 is not an independent object but another means of referring to $obj1. Any changes to $obj2 will also change $obj1. Using the assignment operator with objects under PHP 5 is equivalent to assigning by reference under PHP 4. (You may recall our use of the assignment by ref- erence operator in Chapter 4.)
In other words, in PHP 5
//PHP 5
$obj2 = $obj1;
achieves the same result as
//PHP 4
$obj2 =& $obj1;
The same logic applies when an object is passed to a function. This is not surprising, because there is an implicit assignment when passing a variable to a function. Under PHP 4, when objects are passed to functions, the default is to pass them by value, creating a copy in exactly the same way as with any primi- tive variable. This behavior was changed in PHP 5 because of the inefficiencies associated with passing by value. Why pass by value and use up memory when, in most cases, all that’s wanted is a reference? To summarize, in PHP 5, when an object is passed to a function or when one object is assigned to another, it is assigned by reference. However, there are some situations where you do want to create a copy of an object and not just another reference to the same object. Hence the need to introduce the clone operator.
NOTE If you are porting PHP 4 code to a server running PHP 5, you can remove all those ungainly ampersands associated with passing an object by reference or assigning it by reference.
clone
To understand the clone operator, let’s use the Person class again, adding a few more lines of code to Listing 13-3 to create the code in Listing 13-4.
if ($obj1 === $obj2) {
echo '$obj2 equals $obj1.<br />';
}
$obj3 = clone $obj1; echo 'After cloning '; if ($obj1 === $obj3){
//this code will execute
echo '$obj3 equals $obj1.<br />';
}else{
echo '$obj3 does not equal $obj1.<br />';
}
$obj3->name = 'Waldo';
echo 'Here\'s '. $obj1->name. '.<br />';
echo 'Here\'s '. $obj3->name. '.<br />';
$obj2 equals $obj1
After cloning $obj3 does not equal $obj1.
Here's Tom. Here's Waldo.
Listing 13-4: Finding Waldo
Remember that in Listing 13-3 $obj1 was assigned to $obj2, so the identity test conducted here shows that they are equal. This is because $obj2 is a reference to $obj1. After $obj1 is cloned to create $obj3 in Listing 13-4, the test for identity produces a negative result.
The name attribute of your newly cloned object is changed, and the output shows that this change does not affect the original object. In PHP 5, cloning an object makes a copy of an object just as the assignment operator does in PHP 4.
You may have supposed that in our search for Waldo we lost sight of our ultimate goal. Not true. Now that you understand the clone operator, you can make sense of the __clone method. It is invoked in the background when an object is cloned. It allows you to fine-tune what happens when an object is copied. This is best demonstrated using an aggregate class as an example.
Aggregate Classes
An aggregate class is any class that includes a data member that is itself an object. Let’s quickly create a Team class as an example. This class has as a data member, an array of objects called players. The class definitions for the Player class and the Team class are shown in Listing 13-5.
class Player{ private $name; private $position;
public function __construct($name){
$this->name = $name;
}
public function getName(){
return $this->name;
}
public function setPosition($position){
$this->position = $position;
}
}
class Team{
private $players = array();
private $name;
public function __construct($name){
$this->name = $name;
}
public function addPlayer(Player $p){
$this->players[] = $p;
}
public function getPlayers(){
return $this->players;
}
public function getName(){
return $this->name;
}
public function setName($name){
$this->name = $name;
}
}
Listing 13-5: The Team aggregate class
Let’s create a player, add him to a team, and see what happens when you clone that object (see Listing 13-6).
$rovers = new Team('Rovers');
$roy = new Player('Roy');
$roy->setPosition('striker');
$rovers->addPlayer($roy);
$reserves = clone $rovers;
$reserves->setName('Reserves');
//changes both with __clone undefined
$roy->setPosition('midfielder'); echo $rovers->getName(). ' '; print_r($rovers->getPlayers()); echo '<br /><br />';
echo $reserves->getName(). ' ';
print_r($reserves->getPlayers());
Listing 13-6: Cloning an aggregate object
Setting a player’s position after the clone operation changes the value of position for the player in both objects. Outputting the players array proves this—Roy’s position is the same for both objects (see Listing 13-7).
Rovers Array ( [0] => Player Object ( [name:private] => Roy [position:private]
=> midfielder ) )
Reserves Array ( [0] => Player Object ( [name:private] => Roy
[position:private] => midfielder ) )
Listing 13-7: Undesired result of cloning
Because player is an object, the default behavior when making a copy is to create a reference rather than an independent object. For this reason, any change to an existing player affects the players array for both Team instances. This is known as a shallow copy and in most cases doesn’t yield the desired result. The magic clone method was introduced in order to deal with situa- tions such as this. Let’s add a __clone method to the Team class so that each team has a separate array of players. The code to do this is as follows:
public function __clone(){
$newarray = array();
foreach ($this->players as $p){
$newarray[] = clone $p;
}
$this->players = $newarray;
}
While looping through the array of players each individual player
is cloned and added to a new array. Doing this creates a separate array for the cloned team. After making these changes, running the code in Listing 13-6 now yields this output:
Rovers Array ( [0] => Player Object ( [name:private] => Roy [position:private]
=> midfielder ) )
Reserves Array ( [0] => Player Object ( [name:private] => Roy
[position:private] => striker ) )
Changing a player originally added to the $rovers has no effect on the player array in the cloned $reserves object. This is the result you want. The magic clone method allows you to define what happens when an object is cloned. Using the terminology of other OO languages, the __clone method is
a copy constructor. It’s up to the developer to decide what’s appropriate for any particular class, but as a general rule, a __clone method should always be defined for aggregate classes for the exact reasons shown in the sample code—normally the same variable isn’t shared among different instances of a class. (That’s what static data members are for.)
A Get Method for Object Data Members of an Aggregate Class
When I first introduced accessor methods in Chapter 5, I noted that one of the advantages of a get method over direct access to a public data member was that accessor methods return a copy rather than an original. This is not true when the data members are themselves objects—by default objects are returned by reference. In the interests of data protection, it is usually better to return a copy using the clone operator. With this in mind, let’s rewrite the getPlayers method originally shown in Listing 13-5, shown here in Listing 13-8.
public function getPlayers(){
$arraycopy = array();
foreach ($this->players as $p){
$arraycopy[] = clone $p;
}
return $arraycopy;
}
Listing 13-8: Returning a copy of an object
The array returned by this version of the getPlayers method is only a copy so changes made to it will have no effect on the data member, $players. If you need to make changes to the players array a set method will have to be written. Doing so is a fairly straightforward matter so I’ll leave that up to you.
The fact that objects are passed by reference also has implications for how objects are added to an aggregate class. For instance, consider the player Roy who is added to the team Rovers in Listing 13-6. Any changes made to the variable $roy will change the first element of the $players array in the $rovers object. This may or may not be what’s wanted. If not, then players should be cloned before being added to the players array.
The addPlayer method of the Team class could be rewritten as:
public function addPlayer(Player $p){
$newplayer = clone $p;
$this->players[] = $newplayer;
}
The Team class now has complete control over any player added. This will doubtless be the implementation preferred by any Team manager.
NOTE The fact that PHP 5 returns a reference to an object rather than a copy may have serious implications for aggregate objects written under PHP 4 and running under PHP 5. Objects formerly returned by value will now be returned by reference, breaking
encapsulation.
No Clones Allowed
As you saw when we discussed the singleton class in Chapter 11, in some cases it may make sense to disallow copies altogether. This can be done by imple- menting code such as the following:
public final function __clone(){
throw new Exception('No clones allowed!');
}
Making this method final ensures that this behavior can’t be overridden in derived classes, and making it public displays a clear error message when there is an attempt at cloning. Making the __clone method private would also disallow cloning but displays a less explicit message: Access level must be public.
A Note About Overloading
On the PHP website the __set, __get, and __call methods are referred to as overloaded methods. Within the context of OOP, an overloaded method is one that has the same name as another method of the same class but differs in the number or type of arguments—methods with the same name but a differ- ent “signature.” Because PHP is a typeless language and doesn’t really care how many arguments are passed, this kind of overloading is an impossibility. In PHP, overloading usually refers to methods that perform a variety of differ- ent tasks.
Languages such as C++ also support something called operator overloads. For example, a programmer can define what the “greater than” operator means when two objects of the same class are compared. Support for such features has been described as “syntactic sugar” because, most of the time, operator overloads are not strictly necessary—the same effect could be
achieved by writing a method rather than overloading an operator. How- ever, operator overloads can be convenient and intuitive. It makes more sense to write:
if($obj1 > $obj2)
than
if($obj1->isGreaterThan($obj2))
The __toString method, while it is not an operator overload, offers a con- venience similar to that of an operator overload. It is certainly nice to be able to control what is displayed to the screen when an object is echoed.
PHP supports operator overloading for clone and, as you have seen, this is not syntactic sugar but a matter of necessity. The same can be said of the
__sleep and __wakeup methods because, as with destructors, the circumstances under which these methods are invoked aren’t always under the direct control of the developer.
All other magic methods are there for convenience so would perhaps qualify as “syntactic sugar.” I won’t repeat the opinions expressed earlier about __set and __get or press the point. After all, PHP places a high premium on user convenience, aiming to get the job done quickly and easily. Undoubt- edly, this is the reason for its success. If PHP’s aim was language purity, OO or otherwise, it probably wouldn’t still be with us.
Posted by Mr Procces at 6:08 AM 0 comments
Labels: PHP, PHP Programming, Web Applications, Web Development