使用PHP数据库对象
第一步
上一章节里,我们简单了解了PDO是什么,怎么通过PDO连接到你喜爱的数据库,怎么执行简单查询和处理错误,现在你可以认为PDO是个很好的工具,在想怎么才能灵活地运用它。我们将以运用所有的它提供的特点进行开发。
在这一章,我们将看到更多关于使用PDO连接数据库,连接字符串(数据源),PDOstatement类,遍历结果集。我们将要那立一个小型图书库管理应用程序,它充许我们管理你的家庭图书库所收藏的图书。这个应用程序也能列出图片和作者,并能添加和修改它。
我们先从连接字符串开始,因为没有它我们装不能连接到任数据库。我们将创建一个示例数据库,以它为基础,创建所有的书的例子。
我们将舍弃十分简单虚构的汽车数据库,创建一个真正运行几个表的数据库。我们之所以选择这个例子,是因为它存在普遍性。关系模型也相对简单,如果你在别的地方见过这样的数据库,你可以很轻松地理解例子。
连接字符串
在PDO文档里被引用连接字符串或数据源名(简称DSN)是一个包含数据库管理系统名称,数据库本身的PHP字符串,和其它连接参数.
与使用传统方式创建数据连接相比,其优点是如果改变数据库管理系统不必去修改码。连接字符串可以被定义在配置文件里,这个文件被程序处理,如果你的数据库要改变,你只要修改配置文件,其它的代码保持不动。
连接字符串因不同的数据库管理系统而不同,但他们常常有一个通用前缀,它代表基础数据库驱动程序。记住第一章MySQL, SQLite, 和PostgreSQL的例子,三个连接字符串像下面这样:
mysql:host=localhost;dbname=cars
sqlite:/path/to/cars.db
pgsql:host=localhost dbname=cars
正如我们看到的,前缀(每一个分号前的了句)通常保存PDO驱动程序的名称。PDO使用相同的程序去创建连接,前缀告诉我们那个内部驱动程序要被使用。字符串的其余部分由该驱动程序进行分析,进一步启动连接。这种情况我们为MySQL and PostgreSQL提供了数据库名称,我们也提供服务器的主机名。(因为SQLite是一个本地数据库引擎,所以这样的参数没有意义)
如果你想指定额外的参数,你要去查阅你的数据库手册((www.php.net/pdo能常是一个错的开始之处),例如,MySQL PDO驱动程序能接受下面的参数
host-数据库的主机名(在我们例子中的localhost)
port-数据库服务器监听的端口(默认:3306)
dbname-数据库名称(例子中的cars)
unix_socket-mysql uxix 套接字(替代主机名,端口)
noted:SQLite:前缀表示到SQLite3的连接,要连接到SQLite2,你必须使用SQLite2:前缀。可到http://www.php.net/manual/en/ref.pdo-sqlite.connection.php查看更多细节。
你可能已经明白,不同的驱动程序使用不同的字符定界参数,比如mysql用分号,PostgreSQL用空格。
创建一个简单的数据库
可能你有一个不错的图书库,你想用你的电脑帮你管理它,你决定用PHP建立一个基于网络的数据库,当然,是通过PDO。从现在起,例子将是关于mysql和SQLite数据库。
数据库模型
我们的数据库是非常简单,我们只有两个实体,author和books,我们也将用这两个名字建立两个表,现在让我们考虑一下这两个实体要有什么属性。
作者要有他们的姓和名,简介。这个表也要有一个id作为主键。我们利用它关键别一个名为books的表。
书被作者所写。(有时它们是多人一起写的,在这里我们只考虑为一个所写)。所以我们需要一个作者ID的字段,还有书的标题,ISBN号码,出版社名称,出版日期,也将有一个关书此书的简介。
我们需要为作者创建一个单独的表,国为一个作者可能会写了不止一本书。我们的例子是非常的简单,因此我们选择了两个表的结构,如果我们认为书可以被多人所写,那我们将需要三个表,那我们会例子将非常复杂。
创建Mysql数据库
当你启动你的Mysql命令行客户端,你将看到mysql>提示,你可以执行命令创建数据库和表。
mysql> create database pdo;
Query OK, 1 row affected (0.05 sec)
mysql> use pdo;
Database changed
mysql> create table books(
-> id int primary key not null auto_increment,
-> author int not null,
-> title varchar(70) not null,
-> isbn varchar(20),
-> publisher varchar(30) not null,
-> year int(4) not null,
-> summary text(2048));
Query OK, 0 rows affected (0.17 sec)
mysql> create
mysql> create table authors(
-> id int primary key not null auto_increment,
-> firstName varchar(30) not null,
-> lastName varchar(40) not null,
-> bio text(2048));
Query OK, 0 rows affected (0.00 sec)
正如你看到的,正如我们计划的,我们已经创那了名为pdo的数据库,也创那建了books,authors两个表.让我们看看如何在SQLite里怎么做。因为我们不能在SQLite命令行客户端里创建数据库,我们像这样开始:
> sqlite3 pdo.db
sqlite> create table books(
…> id integer primary key,
…> author integer(11) not null,
…> title varchar(70) not null,
…> isbn varchar(20),
…> publisher varchar(30) not null,
…> year integer(4) not null,
…> summary text(2048));
sqlite> create table authors(
…> id integer(11) primary key,
…> firstName varchar(30) not null,
…> lastName varchar(40) not null,
…> bio text(2048));
正如你所看到的,SQL与SQLite还是有所不同:主键没有声明NOT NULL 和aut0_increment选项。在SQLite,列声明为INTEGER PRIMARY KEY是自动增加的,现在让我们插话一些数据到我们的数据库里,两者的语法都是一样的,所以这里我们注只列举mysql的命令行客户端。我们从作者表开始,因为我们需要的主键的值,它要被插入到books表中。
mysql> insert into authors(firstName, lastName, bio) values(
-> ‘Marc’, ‘Delisle’, ‘Marc Delisle is a member of the MySQL
Developers Guide’);
Query OK, 1 row affected (0.14 sec)
mysql> insert into authors(firstName, lastName, bio) values(
-> ‘Sohail’, ‘Salehi’, ‘In recent years, Sohail has contributed
to over 20 books, mainly in programming and computer graphics’);
Query OK, 1 row affected (0.00 sec)
mysql> insert into authors(firstName, lastName, bio) values(
-> ‘Cameron’, ‘Cooper’, ‘J. Cameron Cooper has been playing
around on the web since there was not much of a web with which to
play around’);
Query OK, 1 row affected (0.00 sec)
现在我们已经添加了三位作者,让我们添加一些书。在做之前,我们要知道作者对应的id.一个简单的select查询就可以办到:
mysql> select id, firstName, lastName from authors;
+—-+———–+———-+
| id | firstName | lastName |
+—-+———–+———-+
| 1 | Marc | Delisle |
| 2 | Sohail | Salehi |
| 3 | Cameron | Cooper |
+—-+———–+———-+
3 rows in set (0.03 sec)
现在我们终于可以使用这些信息添加三本书,每一本被一个作者所写。
mysql> insert into books(author, title, isbn, publisher, year, summary) values(
-> 1, ‘Creating your MySQL Database: Practical Design Tips and
Techniques’, ‘1904811302′, ‘Packt Publishing Ltd’, ‘2006′,
-> ‘A short guide for everyone on how to structure your data and
set-up your MySQL database tables efficiently and easily.’);
Query OK, 1 row affected (0.00 sec)
mysql> insert into books(author, title, isbn, publisher, year, summary) values(
-> 2, ‘ImageMagick Tricks’, ‘1904811868′, ‘Packt Publishing
Ltd’, ‘2006′,
-> ‘Unleash the power of ImageMagick with this fast, friendly
tutorial, and tips guide’);
Query OK, 1 row affected (0.02 sec)
mysql> insert into books(author, title, isbn, publisher, year,
summary) values(
-> 3, ‘Building Websites with Plone’, ‘1904811027′, ‘Packt
Publishing Ltd’, ‘2004′,
-> ‘An in-depth and comprehensive guide to the Plone content
management system’);
Query OK, 1 row affected (0.00 sec)
现在我们已经填充了authors和books表,我们也将开始创建第一个小型图书库管理WEB应用程序页面。
notes:
这些数据是Packt Publishing Ltd已经出版的图书真实数据。(你所读的书的出版商正是他们。)你可以到他们的网站上找到更多信息:http://www.packpub.com
设计你的代码
除了好的数据库模型,好的程序体系结构是另外一个应用程序的关键因素。因为我们这章要开发的应用程序是很简单,相对比较小,所以任务不是很复杂。首先,我们创建两个页面去罗列books和aauthors.我们先考虑一下这些页面的外观,使我们的页面简单,紧凑。我们将在所有的页面显示一个头部,它包含
链接到books列表和作者列表的锚。稍后我们添加别外两个添加author,book的页面。
当然,我们将创建一个公用的包含文件,它定义了显示头部与底部的公用程序和连接到数据库。我们的例子真的很小,所以我们将不用任何模板系统或面向对象的语法。(这些话题也超出了本书的范围),总结一下:
所有的公用程序(包括创建PDO连接对象的代码)都包含在一个引用文件(名为common.inc.php).
每个页面将保留一个独立的文件,它引用 common.inc.php文件
每个页面将访问数据,并显示它,(所以我们没有把处理数据和显示数据分开,正如应用程序设计所期望的model-view-controller模式思想)
现在我们计划先从commn.icn.php文件开始,正如我们讨论的,它包含了显示的头部和底部的程序,它也能创建连接对象。让我们把PDO对象赋于给全局变量 $conn,和头部程序showHeader()底部程序showFooter().我们也把数据库连接字符串,用户名,密码放在这个引用文件里:
<?php
/**
* This is a common include file
* PDO Library Management example application
* @author Dennis Popel
*/
// DB connection string and username/password
$connStr = ‘mysql:host=localhost;dbname=pdo’;
$user = ‘root’;
$pass = ‘root’;
/**
* This function will render the header on every page,
* including the opening html tag,
* the head section and the opening body tag.
* It should be called before any output of the
* page itself.
* @param string $title the page title
*/
function showHeader($title)
{
?>
<html>
<head><title><?=htmlspecialchars($title)?></title></head>
<body>
<h1><?=htmlspecialchars($title)?></h1>
<a href=”books.php”>Books</a>
<a href=”authors.php”>Authors</a>
<hr>
<?php
}
/**
* This function will ‘close’ the body and html
* tags opened by the showHeader() function
*/
function showFooter()
{
?>
</body>
</html>
<?php
}
// Create the connection object
$conn = new PDO($connStr, $user, $pass);
正如你看到的,这个文件是很简单,你需要改动变量$user$pass值与你的设定匹配。关于SQLite,你要改动第8行适当的连接字符串,例如:
$connStr = ’sqlite:/www/hosts/localhost/pdo.db’;
当然,你要把它成指向创建SQLite数据库的路径。函数showHeader()返回hmtl代码,并把传递过来的$title通过函数htmlspecialchars()把不合法的字符合理地转义。
把文件保存到根目录,这个依赖你的服务器的设置。例如它可能是C:\Apache\htdocs or /var/www/html.
让我们创建books列表页面,我们要查询并迭代查询后的结果集,以每行一本书的形式显式,之后我们也要创建显示所有作者的页面,完成这些后,遍历结果集.
让我们创建并访问books.php
<?php
/**
* This page lists all the books we have
* PDO Library Management example application
* @author Dennis Popel
*/
// Don’t forget the include
include(’common.inc.php’);
// Issue the query
$q = $conn->query(”SELECT * FROM books ORDER BY title”);
// Display the header
showHeader(’Books’);
// now create the table
?>
<table width=”100%” border=”1″ cellpadding=”3″>
<tr style=”font-weight: bold”>
<td>Title</td>
<td>ISBN</td>
<td>Publisher</td>
<td>Year</td>
<td>Summary</td>
</tr>
<?php
// Now iterate over every row and display it
while($r = $q->fetch(PDO::FETCH_ASSOC))
{
?>
<tr>
<td><?=htmlspecialchars($r['title'])?></td>
<td><?=htmlspecialchars($r['isbn'])?></td>
<td><?=htmlspecialchars($r['publisher'])?></td>
<td><?=htmlspecialchars($r['year'])?></td>
<td><?=htmlspecialchars($r['summary'])?></td>
</tr>
<?php
}
?>
</table>
<?php
// Display footer
showFooter();
这个文件将被保存到与common.icn.php相册目录。正如你看到的,里面有很多注释和html,但一点也不复杂,我们之前所做的决定,文件引用common.inc.php文件,然后返回页面的header,在10行处执行查询,返回表格的header,从结果集迭代所有的行,显示出每本书的详细信息。
与第一章一样,我们使用函数PD)Statement的fetch()在一行内遍历结果集。我们指示该方法返回的行是数组索引,其值为表中列的名称。(指定PDO::FETCH_ASSOC为参数)
在热循环中,我们在每一行都返回了html.插入表的列。退出循环,我们关闭了表格并显示了footer.
现在是测试我们第一个PDO驱动的程序的时间了,启动浏览器并浏览http://localhost/books.php,如果一切正常(你的web服务器和数据库服务器被正确配置),你将看到与下面的截屏类似(可能你的页面看起来更宽点,我们在截图前调整了窗口尺寸,以适应打印页面)
一旦我们的应用程序与mysql能正确运行 ,让我们看看它与sqlite运行的。我们要the common.inc.php文件的第8行处添加sqlite dsn:
$connStr = ’sqlite:/www/hosts/localhost/pdo.db’;
如果一切正常,刷新你的浏览器,你将看到同样的结果。正如我们早前讨论的-如果你要使用其它的数据库,只有一处的配置选项要改动。
现在我们在上次保存两个文件的文件里创建名为author.php的author列表的页面。代码内容基本与books列表页面相同:
<?php
/**
* This page lists all the authors we have
* PDO Library Management example application
* @author Dennis Popel
*/
// Don’t forget the include
include(’common.inc.php’);
// Issue the query
$q = $conn->query(”SELECT * FROM authors ORDER BY lastName,
firstName”);
// Display the header
showHeader(’Authors’);
// now create the table
?>
<table width=”100%” border=”1″ cellpadding=”3″>
<tr style=”font-weight: bold”>
<td>First Name</td>
<td>Last Name</td>
<td>Bio</td>
</tr>
<?php
// Now iterate over every row and display it
while($r = $q->fetch(PDO::FETCH_ASSOC))
{
?>
<tr>
<td><?=htmlspecialchars($r['firstName'])?></td>
<td><?=htmlspecialchars($r['lastName'])?></td>
<td><?=htmlspecialchars($r['bio'])?></td>
</tr>
<?php
}
?>
</table>
<?php
// Display footer
showFooter();
这个文件执行一样的逻辑:引用common.inc.php文件,并执行查询,遍历结果集。如果一切运行正常,在books页面点击authors链接,浏览器将会显示下面的内容:
正如你看到的,页面正确显示我们在第一章添加的三个作者,如果你想测试一下sqlite的,修改10行Sqlite连接字符串,刷新浏览器,你将看到一样的结果,但它是连接到SQlite数据库上的。
现在我们已经创建了两个页面,了解到使用PDO不是那么复杂,让我们在扩展这个程序之前了解一些理论知识。
pdo语句和结果设置
我们的例子使用了PHP数据库对象两个类:pdo类和PDOstatement类。pdo类被用来创建连接,执行查询。PDOStatement类被用来遍历结果集。在下一章节我们将研究pdo类。这里我们将验证PDOStatement类遍历结果的其它方式。
我们已经知道,PDOStatement类是通过调用PDO::query方法后返回的。这个类的最大用途是提供结果集的接口。事实上,我们已经使用最重要的方法迭代结果集,我们只看到一种获取方法(返回行的模式),但PDO提供几种方式。这个类支持给结果集符加信息,比如行数和列,获取以二维数组方式的整个结果集。
让我们考虑一下不同的获取方式。我们已经知道PDO::FETCH_ASSOC模式,它返回以列名为索引的数组。PDOStatement类默认操作是返回索引是整数和列名的数组。它是PDO::FETCH_BOTH模式,我们也可以使用PDO::FETCH_NUM模式得到整数索引数组。PDO也支持通过使用PDO::FETCH_OBJ模式将提取行作为对象来处理。在这种情况,通过调用PDO::fetch方法将返回stdClass内部类的实例,它的属性是行的值。代码如下:
$q = $conn->query(’SELECT * FROM authors ORDER BY lastName,
firstName’);
$r = $q->fetch(PDO::FETCH_OBJ);
var_dump($r);
//would print:
object(stdClass)#4 (4)
{
["id"]=>
string(1) “3″
["firstName"]=>
string(7) “Cameron”
["lastName"]=>
string(6) “Cooper”
["bio"]=>
string(112) “J. Cameron Cooper has been playing around on the web
since there was not much of a web with which to play around”
}
PDOStatement类也允许你一次设置后来调用的fetch()方法,它通过调用PDOStatement::setFetchMode()方法。此方法接受PDO::FETCH_ASSOC,PDO_FETCH_BOTH,PDO_FETCH_NUM,PDO::FETCH_OBJ这些常量。考虑这一点,我们修改authors.php23-24行为
// Now iterate over every row and display it
$q->setFetchMode(PDO::FETCH_ASSOC);
while($r = $q->fetch())
{
你可以试着打开authors.php文件并刷新浏览器,看看显示结果。
你可能已经注册到SQLite,MySQL,pgSQL PHP扩展名提供相似的功能。的确,我们可以使用一些mysql_fetch_row(),mysql_fetch_assoc(),mysql_fetch_array();mysql_fetch_object()方法完成一样的结果。这也是为什么PDO进一步允许我们使用那三个符加获取模式。这三个模式可以被通过PDOStatement::setFetchMode()设置,它们的介绍:
PDO::FETCH_COLUMN 允许你指示PDOStatement对象每行返回指定的列,基于这种情况,PDO::fetch()将返回标量值。列数从0开始。代码如下:
$q = $conn->query(’SELECT * FROM authors ORDER BY lastName,
firstName’);
$q->setFetchMode(PDO::FETCH_COLUMN, 1);
while($r = $q->fetch())
{
var_dump($r);
}
//would print:
string(7) “Cameron”
string(4) “Marc”
string(6) “Sohail”
这表明调用$q->fetch()确实返回标量值(不是数组),如果你简单看一下作者页面,会注意到索引1的列是作者姓,不是名。我们的像select * from authors查询,它也会返回作者id,它被保存在首列,你可能会意识到,因为这你可能会发几个小时来查找逻辑错误的资源。
PDO::FETCH_INTO能被用来修改对象的引用。让我们重写下面的例子:
$q = $conn->query(’SELECT * FROM authors ORDER BY lastName,
firstName’);
$r = new stdClass();
$q->setFetchMode(PDO::FETCH_INTO, $r);
while($q->fetch())
{
var_dump($r);
}
object(stdClass)#3 (4)
{
["id"]=>
string(1) “3″
["firstName"]=>
string(7) “Cameron”
["lastName"]=>
string(6) “Cooper”
["bio"]=>
string(112) “J. Cameron Cooper has been playing around on the
web since there was not much of a web with which to play around”
}
object(stdClass)#3 (4)
{
["id"]=>
string(1) “1″
["firstName"]=>
string(4) “Marc”
["lastName"]=>
string(7) “Delisle”
["bio"]=>
string(54) “Marc Delisle is a member of the MySQL Developer
Guide”
}
object(stdClass)#3 (4)
{
["id"]=>
string(1) “2″
["firstName"]=>
string(6) “Sohail”
["lastName"]=>
string(6) “Salehi”
["bio"]=>
string(101) “In recent years, Sohail has contributed to over 20
books, mainly in programming and computer graphics”
}
notes:
在整个热循环里我们没有定义变量$r,这个变量是方法$q->fetch()返回的值,在循环之前通过调用$q->setFetchmode($r已创经被绑定在这个方法.
PDO::FETCH_CLASS能返回指定类的对象。每一行,这个类的实例被创建。命名属性并把结果集赋值于它,注意从PHP允许运行时创建对象属性,那个类并不是必须要有声明的属性,例如:
$q = $conn->query(’SELECT * FROM authors ORDER BY lastName,firstName’);
$q->setFetchMode(PDO::FETCH_CLASS, stdClass);
while($r = $q->fetch())
{
var_dump($r);
}
这将与上一个例显示相似的内容。这个获取模式允许你去创建实例,通过传递一个数组作为原素到其构造:
$q->setFetchMode(PDO::FETCH_CLASS, SomeClass, array(1, 2, 3));
(只有当SomeClass类被定义了,才会正常运行)
我们推建使用PDOStatement::setFetchMode(),因为它更容易修护(当然,有更多的特点)
描述所有的获取方法似乎过多,但它们在某些情况下很有用,的确,你可能注意到books列表少些什么,它没有包含作者的名字,我们将添加这个缺少的列,使我们例子复杂一些,让作者姓名可点击,并链接到作者单独页面(我们将创建),这个单独的页面需要作者的ID,我们可以放id放在URL里面,页面将显示关于这个作者的所有信息,并列出他所有的书,让我们开始写独立的作者页面:
<?php
/**
* This page shows an author’s profile
* PDO Library Management example application
* @author Dennis Popel
*/
// Don’t forget the include
include(’common.inc.php’);
// Get the author
$id = (int)$_REQUEST['id'];
$q = $conn->query(”SELECT * FROM authors WHERE id=$id”);
$author = $q->fetch(PDO::FETCH_ASSOC);
$q->closeCursor();
// Now see if the author is valid – if it’s not,
// we have an invalid ID
if(!$author) {
showHeader(’Error’);
echo “Invalid Author ID supplied”;
showFooter();
exit;
}
// Display the header – we have no error
showHeader(”Author: $author[firstName] $author[lastName]“);
// Now fetch all his books
$q = $conn->query(”SELECT * FROM books WHERE author=$id ORDER BY title”);
$q->setFetchMode(PDO::FETCH_ASSOC);
// now display everything
?>
<h2>Author</h2>
<table width=”60%” border=”1″ cellpadding=”3″>
<tr>
<td><b>First Name</b></td>
<td><?=htmlspecialchars($author['firstName'])?></td>
</tr>
<tr>
<td><b>Last Name</b></td>
<td><?=htmlspecialchars($author['lastName'])?></td>
</tr>
<tr>
<td><b>Bio</b></td>
<td><?=htmlspecialchars($author['bio'])?></td>
</tr>
</table>
<h2>Books</h2>
<table width=”100%” border=”1″ cellpadding=”3″>
<tr style=”font-weight: bold”>
<td>Title</td>
<td>ISBN</td>
<td>Publisher</td>
<td>Year</td>
<td>Summary</td>
</tr>
<?php
// Now iterate over every book and display it
while($r = $q->fetch())
{
?>
<tr>
<td><?=htmlspecialchars($r['title'])?></td>
<td><?=htmlspecialchars($r['isbn'])?></td>
<td><?=htmlspecialchars($r['publisher'])?></td>
<td><?=htmlspecialchars($r['year'])?></td>
<td><?=htmlspecialchars($r['summary'])?></td>
</tr>
<?php
}
?>
</table>
<?php
// Display footer
showFooter();
文件名为author.php,保存在其它被引用文件的位置。
因为防止存在安全漏洞的,我们把作者ID强制转换为数字。稍后会把变量$id下传递到查询语句中.因为它是一个数字,所以我们就不用加引号。
我们将在下章讨论13行处引用$q->closeCursor();$q=null。现在我们只需要注意到,在连接对象相同的情况下执行查询与之后设置它为null之间,引用这些方法是一个好主意。少了它我们的例子也不会正常执行。在最后查询之后我们会意识到这些也不是必须的。
我们也做了简单的错误处理,检查作者ID是否有效,如果无效,我们将显示一个错误信息,并退出程序运行.(查看22到27行)
在25-27行我们使用作者ID创建了查询语句,并设置获取模式为:PDO:FETCH_ASSOC.我们开始显示数据:首先是作者的详细信息,然后是他的所有书.
现在用浏览器打开地址:http://localhost/author.php?id=1.将显示:
如你所见,这个页面一切正常:作者的详细信息,我们首先归档的(id=1),只有这个作者的书.现在我们看一下程序是怎么应对无效的ID,我们只有三个作者,除123之外的数字都是无效的,如果没有值将被认为是0,也是无效的,如果我们把地址改成http://localhost/author.php?id=zzz.我们将看到下面的结果:
你也可以在common.inc.php里把数据库改成SQLite,也会看到这个页会正常运行.
现在让我们修改一下books.php,添加作者列链接到单独的页面.我们要一起查询两个表,books的author字段与author`s id字段相同,authors ID ,first name,last name,查询语句会像这样:
SELECT authors.id, authors.firstName, authors.lastName, books.* FROM authors, books WHERE author=authors.id ORDER BY title;
开如之前我们先在命令行里执行查询语句,我们也将修改一下查询语句,因为命令行窗口不适合整行显示.
mysql> SELECT authors.id, firstName, lastName, books.id, title FROM
authors, books WHERE books.author=authors.id;
+—-+———–+———-+—-+——————————+
| id | firstName | lastName | id | title |
+—-+———–+———-+—-+——————————+
| 1 | Marc | Delisle | 1 | Creating your MySQL… |
| 2 | Sohail | Salehi | 2 | ImageMagick Tricks | | 3 | Cameron | Cooper | 3 | Building Websites with Plone |
+—-+———–+———-+—-+——————————+
3 rows in set (0.00 sec)
正如你看到的,执行结果是有两列叫id,这意味着我们将不能使用PDO::FETCH_ASSOC模式.因为数据只能有一个id索引,这里我们有两个选择:使用PDO::FETCH_NUM模式,使用id字段的别名
让我们看看在代码中如何使用PDO::FETCH_NUM
<?php
/**
* This page lists all the books we have
* PDO Library Management example application
* @author Dennis Popel
*/
// Don’t forget the include
include(’common.inc.php’);
// Issue the query
$q = $conn->query(”SELECT authors.id, firstName, lastName, books.*
FROM authors, books WHERE author=authors.id ORDER
BY title”);
$q->setFetchMode(PDO::FETCH_NUM);
// Display the header
showHeader(’Books’);
// now create the table
?>
<table width=”100%” border=”1″ cellpadding=”3″>
<tr style=”font-weight: bold”>
<td>Author</td>
<td>Title</td>
<td>ISBN</td>
<td>Publisher</td>
<td>Year</td>
<td>Summary</td>
</tr>
<?php
// Now iterate over every row and display it
while($r = $q->fetch())
{
?>
<tr>
<td><a href=”author.php?id=<?=$r[0]?>”>
<?=htmlspecialchars(”$r[1] $r[2]“)?></a></td>
<td><?=htmlspecialchars($r[5])?></td>
<td><?=htmlspecialchars($r[6])?></td>
<td><?=htmlspecialchars($r[7])?></td>
<td><?=htmlspecialchars($r[8])?></td>
<td><?=htmlspecialchars($r[9])?></td>
</tr>
<?php
}
?>
</table>
<?php
// Display footer
showFooter();
注意高亮行-它们的不同之外,文件其它的地方是相同的.正如你所见,我们引用了方法$q->setFetchMode(),改变热循环使用了数据索引.
打开http://localhost/books.php页面,我们将看到与下面截屏相似:
我们点击作者后会转到他们独立的页面,当然换到SQLite数据库也会一样的效果.
别外一个方法是我们在sql里使用字段的别名(也是更好的方法).这样我们就不会关心每次在表中添加或删除一列后数字索引的改变.我们只需要把SQL改成:
SELECT authors.id AS authorId, firstName, lastName, books.* FROM
authors, books WHERE author=authors.id ORDER BY title;
books.php的代码最终会这样:
<?php
/**
* This page lists all the books we have
* PDO Library Management example application
* @author Dennis Popel
*/
// Don’t forget the include
include(’common.inc.php’);
// Issue the query
$q = $conn->query(”SELECT authors.id AS authorId, firstName,
lastName, books.* FROM authors, books WHERE
author=authors.id
ORDER BY title”);
$q->setFetchMode(PDO::FETCH_ASSOC);
// Display the header
showHeader(’Books’);
// now create the table
?>
<table width=”100%” border=”1″ cellpadding=”3″>
<tr style=”font-weight: bold”>
<td>Author</td>
<td>Title</td>
<td>ISBN</td>
<td>Publisher</td>
<td>Year</td>
<td>Summary</td>
</tr>
<?php
// Now iterate over every row and display it
while($r = $q->fetch()
{
?>
<tr>
<td><a href=”author.php?id=<?=$r['authorId']?>”>
<?=htmlspecialchars(”$r[firstName] $r[lastName]“)?></a></td>
<td><?=htmlspecialchars($r['title'])?></td>
<td><?=htmlspecialchars($r['isbn'])?></td>
<td><?=htmlspecialchars($r['publisher'])?></td>
<td><?=htmlspecialchars($r['year'])?></td>
<td><?=htmlspecialchars($r['summary'])?></td>
</tr>
<?php
}
?>
</table>
<?php
// Display footer
showFooter();
注意到我们把获取模式改回到PDO::FETCH_ASSOC,在34行我们通过$['authorId']访问author’s ID.因为我们在查询语句里把它重命名为authorId.
PDO也充许我们以数组的形式得到返回的结果.我们需要进一步处理,传递给一些程序.这要做一些很小结果处理.强烈建议不要像我们这样的做,因为简单显示作者和书.整个结果集变成数组会占用很多内存空间,我们是一行一行显示结果,所以只需要一行占用的空间.
这个方法是引用PDOStatement::fetchAll(),返回的数组是一个二维数组or对象列表.这依赖获取模式.与PDOStatement::fetch()相似,接受PDO::FETCH_xxxx 内容.例如 我们要以下面的方式改写books.php,以达到相同的效果.boods.php相关在部分在9-46行:
// Issue the query
$q = $conn->query(”SELECT authors.id AS authorId, firstName,
lastName, books.* FROM authors, books WHERE
author=authors.id ORDER BY title”);
$books = $q->fetchAll(PDO::FETCH_ASSOC);
// Display the header
showHeader(’Books’);
// now create the table
?>
<table width=”100%” border=”1″ cellpadding=”3″>
<tr style=”font-weight: bold”>
<td>Author</td>
<td>Title</td>
<td>ISBN</td>
<td>Publisher</td>
<td>Year</td>
<td>Summary</td>
</tr>
<?php
// Now iterate over every row and display it
foreach($books as $r)
{
?>
<tr>
<td><a href=”author.php?id=<?=$r['authorId']?>”>
<?=htmlspecialchars(”$r[firstName] $r[lastName]“)?></a></td>
<td><?=htmlspecialchars($r['title'])?></td>
<td><?=htmlspecialchars($r['isbn'])?></td>
<td><?=htmlspecialchars($r['publisher'])?></td>
<td><?=htmlspecialchars($r['year'])?></td>
<td><?=htmlspecialchars($r['summary'])?></td>
</tr>
<?php
}
?>
</table>
注意到高亮的-我们在第5行获取整个结果集到$books数组,在21行遍在结果集,打开修改的页面,你将会看到一样的结果.把数据库换成SQLite后也是一样的.
PDOStatement::fetchAll()方法也充许我们使用PDO::FETCH_COLUMN模式返回单独列.如果我们想获取所有书的标题,我们可以这样做(注意到number and ordering列)
$q = $conn->query(”SELECT authors.id AS authorId, firstName,
lastName, books.* FROM authors, books WHERE
author=authors.id ORDER BY title”);
$books = $q->fetchAll(PDO::FETCH_COLUMN, 5);
var_dump($books);
将显示
array(3)
{
[0]=>
string(28) “Building Websites with Plone”
[1]=>
string(66) “Creating your MySQL Database: Practical Design Tips and
Techniques”
[2]=>
string(18) “ImageMagick Tricks”
}
我们看到只有一行被表求,只返回一维数组.
检索结果集元数据
上一章了解到,PDOStatement类允许我们检索一些关于结果集数据内容的信息。这种信息叫元数据,你可能已经通过这种或那种方法使用过它。
最重要结果集的元数据是内容包含的行数。我们使用行数提交用户体验,例如分页,我们的应用程序是很小的,只有三本书,但是数据会增加的,我会需要为每个表统计行数的工具,为了方便浏览标出页数。
一般我们会使用mysql_num_rows(),sqlite_num_rows(),pg_um_rows()得到查询结果的行数。在PDO里,通过引用PDOStatement方法检索行数。你可以用下面的代码进行测试:
$q = $conn->query(”SELECT * FROM books ORDER BY title”);
$q->setFetchMode(PDO::FETCH_ASSOC);
var_dump($q->rowCount());
你将看到在mysql和sqlite里pdo返回0,这是因为pdo与普通的数据库扩展操作有所不同,文档里说:如果最后的sql语句是select语句,该语句被关联的PDOStatement类执行,一些数据库会返回该查询行数,但是这不能保证所有数据库有效并不能保证其可移植性,mysql和 sqlite都不支持这个功能。这也是该方法返回0的原因。我们将在第5章看到怎么通过PDO(它是一个可移植方法)得到返回的行数.
notes:
关系数据库不知道一个查询有多少行被返回,直到最后一行被检索。这样做是性能原因。大多数情况下,查询会有where子句,返回表中一部分行,数据库服务器尽最大努力确保以最快的速度查询。意味着找到与where子句相匹配结果时就返回-这要比检索到最后一行早很多。这是为什么它们预先不知道多少行被返回。mysql_num_rows(), sqlite_num_rows() function or the pg_num_rows()方法操控的结果集已经放入内存中,(缓冲查询),PDO默认操作是不使用缓冲查询,我们将在第6章讨论mysql缓冲查询。
另一个方法PDOSatement::columnCount()可能感兴趣,它返回结果集的列数。当我们执行任意查询,都很方便。(例如像phpmyadmin的数据库管理应用程序就很有用,因为它充许使用任意的SQL查询),我们能以下面的方式使用它:
$q = $conn->query(”SELECT authors.id AS authorId, firstName,
lastName, books.* FROM authors, books WHERE
author=authors.id ORDER BY title”);
var_dump($q->columnCount());
将显示结果集包含10列。
不幸的是,PDO现在还不允许检索表名或结果集的某列。这个功能是非常有用,当你的程序利用多表查询。在这种情况下,它可能获取指定的列,它的数字索引从0开始的表。列别名正确使用,无需使用这些功能,例如,当我们修改books列表页显示作者名字,我们对author的ID列使用别名来避免名字冲突。那个别名将被认为是从符于au
thors表的列。
小结
在这一章节里,我们通过PDO寻了一个动态的应用程序,它能运行在两个不同的数据库上。现在通过构建连接字符串的规则,能连接到任何被支持的数据库。能执行并遍历显示结果集。
下一章我们将处理非常重要的方法,所有动态程序的错误处理。我也将继续使用我们的例子,让它能添加修改books,authors,使它更有用。


