许多 PHP 程序员学会了如何使用 MySQL 或 MySQLi 扩展来访问数据库。从 PHP 5.1 开始,有一个更好的方法。 PHP 数据对象 (PDO) 为准备好的语句和使用对象提供了方法,这将使您的工作效率更高!
PDO简介
PDO(PHP 数据对象)是一个数据库访问层,提供了访问多个数据库的统一方法。
它不考虑特定于数据库的语法,但可以让切换数据库和平台的过程相当轻松,只需在许多情况下切换连接字符串即可。
本教程并非旨在提供有关 SQL 的完整方法。它主要是为当前使用 mysql
or mysqli
扩展的人编写的,以帮助他们跳转到更便携和更强大的 PDO。
当涉及到 PHP 中的数据库操作时,PDO 提供了许多优于原始语法的优势。让我们快速列出一些:
- 抽象层
- 面向对象语法
- 支持准备好的陈述
- 更好的异常处理
- 安全且可重用的 API
- 支持所有流行的数据库
数据库支持
该扩展可以支持已为其编写 PDO 驱动程序的任何数据库。在撰写本文时,可以使用以下数据库驱动程序:
PDO_DBLIB
(FreeTDS/Microsoft SQL Server/Sybase)PDO_FIREBIRD
(火鸟/Interbase 6)PDO_IBM
(IBM DB2)PDO_INFORMIX
(IBM Informix 动态服务器)PDO_MYSQL
(MySQL 3.x/4.x/5.x)PDO_OCI
(Oracle 调用接口)PDO_ODBC
(ODBC v3(IBM DB2、unixODBC 和 win32 ODBC))PDO_PGSQL
(PostgreSQL)PDO_SQLITE
(SQLite 3 和 SQLite 2)PDO_4D
(四)
所有这些驱动程序不一定在您的系统上可用;这是找出您拥有哪些驱动程序的快速方法:
1
|
print_r(PDO::getAvailableDrivers());
|
连接
不同的数据库可能有略微不同的连接方法。下面,您可以看到连接到一些最流行的数据库的方法。你会注意到前三个是相同的,除了数据库类型——然后 SQLite 有自己的语法。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
try {
# MS SQL Server and Sybase with PDO_DBLIB
$DBH = new PDO( "mssql:host=$host;dbname=$dbname" , $user , $pass );
$DBH = new PDO( "sybase:host=$host;dbname=$dbname" , $user , $pass );
# MySQL with PDO_MYSQL
$DBH = new PDO( "mysql:host=$host;dbname=$dbname" , $user , $pass );
# SQLite Database
$DBH = new PDO( "sqlite:my/database/path/database.db" );
}
catch (PDOException $e ) {
echo $e ->getMessage();
}
|
请注意 try/catch 块。您应该始终将您的 PDO 操作包装在 try/catch 中并使用异常机制——稍后会详细介绍。通常,您只需要建立一个连接——列出了几个来向您展示语法。 $DBH
代表“数据库句柄”,将在本教程中使用。
您可以通过将句柄设置为 null 来关闭任何连接。
1
2
|
# close the connection
$DBH = null;
|
您可以从PHP.net获取有关其他数据库的特定于数据库的选项和/或连接字符串的更多信息 。
例外和 PDO
PDO 可以使用异常来处理错误,这意味着您对 PDO 所做的任何事情都应该包装在 try/catch 块中。您可以通过在新创建的数据库句柄上设置错误模式属性来强制 PDO 进入三种错误模式之一。这是语法:
1
2
3
|
$DBH ->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$DBH ->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$DBH ->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
|
无论您设置什么错误模式,错误连接总是会产生异常,并且创建连接应该始终包含在 try/catch 块中。
PDO::ERRMODE_SILENT
这是默认的错误模式。如果您将其保留在此模式下,您将不得不以您可能习惯的方式检查错误(如果您使用了 mysql
或 mysqli
扩展名)。其他两种方法更适合 DRY 编程。
PDO::ERRMODE_WARNING
此模式将发出标准 PHP 警告并允许程序继续执行。它对调试很有用。
PDO::ERRMODE_EXCEPTION
这是您在大多数情况下想要的模式。它会触发一个异常,允许您优雅地处理错误并隐藏可能帮助他人利用您的系统的数据。下面是一个利用异常的例子:
01
02
03
04
05
06
07
08
09
10
11
12
|
# connect to the database
try {
$DBH = new PDO( "mysql:host=$host;dbname=$dbname" , $user , $pass );
$DBH ->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
# UH-OH! Typed DELECT instead of SELECT!
$DBH ->prepare( 'DELECT name FROM people' );
}
catch (PDOException $e ) {
echo "I'm sorry, Dave. I'm afraid I can't do that." ;
file_put_contents ( 'PDOErrors.txt' , $e ->getMessage(), FILE_APPEND);
}
|
select语句中存在故意错误;这将导致异常。异常将错误的详细信息发送到日志文件,并向用户显示友好(或不那么友好)的消息。
插入和更新
插入新数据(或更新现有数据)是更常见的数据库操作之一。使用 PHP PDO,这通常是一个两步过程。本节涵盖的所有内容同样适用于UPDATE
和 INSERT
操作。
这是最基本的插入类型的示例:
1
2
3
|
# STH means "Statement Handle"
$STH = $DBH ->prepare( "INSERT INTO folks ( first_name ) values ( 'Cathy' )" );
$STH ->execute();
|
您也可以使用该 exec()
方法完成相同的操作,只需少调用一次。在大多数情况下,您将使用更长的方法,以便您可以利用准备好的语句。即使您只打算使用一次,使用准备好的语句也有助于保护您免受 SQL 注入攻击。
准备好的报表
使用准备好的语句将有助于保护您免受 SQL 注入。
准备好的语句是预编译的 SQL 语句,可以通过仅将数据发送到服务器来执行多次。它具有自动使占位符中使用的数据免受 SQL 注入攻击的额外优势。
您可以通过在 SQL 中包含占位符来使用准备好的语句。下面是三个示例:一个没有占位符,一个有未命名的占位符,一个有命名的占位符。
1
2
3
4
5
6
7
8
|
# no placeholders - ripe for SQL Injection!
$STH = $DBH ->prepare( "INSERT INTO folks (name, addr, city) values ($name, $addr, $city)" );
# unnamed placeholders
$STH = $DBH ->prepare( "INSERT INTO folks (name, addr, city) values (?, ?, ?)" );
# named placeholders
$STH = $DBH ->prepare( "INSERT INTO folks (name, addr, city) values (:name, :addr, :city)" );
|
你想避免第一种方法;它在这里进行比较。使用命名或未命名占位符的选择将影响您为这些语句设置数据的方式。
未命名的占位符
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
# assign variables to each place holder, indexed 1-3
$STH ->bindParam(1, $name );
$STH ->bindParam(2, $addr );
$STH ->bindParam(3, $city );
# insert one row
$name = "Daniel"
$addr = "1 Wicked Way" ;
$city = "Arlington Heights" ;
$STH ->execute();
# insert another row with different values
$name = "Steve"
$addr = "5 Circle Drive" ;
$city = "Schaumburg" ;
$STH ->execute();
|
这里有两个步骤。首先,我们将变量分配给各种占位符(第 2-4 行)。然后,我们为这些占位符赋值并执行语句。要发送另一组数据,只需更改这些变量的值并再次执行该语句。
对于有很多参数的语句,这似乎有点笨拙吗?这是。但是,如果您的数据存储在数组中,则有一个简单的快捷方式:
1
2
3
4
5
|
# the data we want to insert
$data = array ( 'Cathy' , '9 Dark and Twisty Road' , 'Cardiff' );
$STH = $DBH ->prepare( "INSERT INTO folks (name, addr, city) values (?, ?, ?)" );
$STH ->execute( $data );
|
这很容易!
数组中的数据按顺序应用于占位符。 $data[0]
进入第一个占位符, $data[1]
第二个等。但是,如果您的数组索引不按顺序,这将无法正常工作,您需要重新索引数组。
命名占位符
您可能会猜到语法,但这里有一个示例:
1
2
3
|
# the first argument is the named placeholder name - notice named
# placeholders always start with a colon.
$STH ->bindParam( ':name' , $name );
|
您也可以在此处使用快捷方式,但它适用于关联数组。这是一个例子:
1
2
3
4
5
6
|
# the data we want to insert
$data = array ( 'name' => 'Cathy' , 'addr' => '9 Dark and Twisty' , 'city' => 'Cardiff' );
# the shortcut!
$STH = $DBH ->prepare( "INSERT INTO folks (name, addr, city) value (:name, :addr, :city)" );
$STH ->execute( $data );
|
数组的键不需要以冒号开头,否则需要匹配命名的占位符。如果您有一个数组数组,您可以遍历它们并简单地调用execute
每个数据数组。
命名占位符的另一个不错的功能是能够将对象直接插入到数据库中,假设属性与命名字段匹配。这是一个示例对象,以及如何执行插入:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
# a simple object
class person {
public $name ;
public $addr ;
public $city ;
function __construct( $n , $a , $c ) {
$this ->name = $n ;
$this ->addr = $a ;
$this ->city = $c ;
}
# etc ...
}
$cathy = new person( 'Cathy' , '9 Dark and Twisty' , 'Cardiff' );
# here's the fun part:
$STH = $DBH ->prepare( "INSERT INTO folks (name, addr, city) value (:name, :addr, :city)" );
$STH ->execute(( array ) $cathy );
|
将对象转换为数组execute
意味着属性被视为数组键。
选择数据
数据是通过 ->fetch()
, 语句句柄的一种方法获得的。在调用 fetch 之前,最好告诉 PDO 您希望如何获取数据。您有以下选择:
PDO::FETCH_ASSOC
: 返回一个按列名索引的数组。PDO::FETCH_BOTH
(默认):返回由列名和编号索引的数组。PDO::FETCH_BOUND
:将列的值分配给使用该->bindColumn()
方法设置的变量。PDO::FETCH_CLASS
:将列的值分配给命名类的属性。如果不存在匹配的属性,它将创建属性。PDO::FETCH_INTO
:更新命名类的现有实例。PDO::FETCH_LAZY
: 组合PDO::FETCH_BOTH
/PDO::FETCH_OBJ
,在使用时创建对象变量名称。PDO::FETCH_NUM
: 返回一个按列号索引的数组。PDO::FETCH_OBJ
: 返回一个匿名对象,其属性名称对应于列名。
实际上,有三种可以涵盖大多数情况: FETCH_ASSOC
、 FETCH_CLASS
和 FETCH_OBJ
。为了设置 fetch 方法,使用以下语法:
1
|
$STH ->setFetchMode(PDO::FETCH_ASSOC);
|
您还可以直接在 ->fetch()
方法调用中设置提取类型。
FETCH_ASSOC
此提取类型创建一个关联数组,按列名索引。任何使用过 mysql/mysqli 扩展的人都应该对此非常熟悉。以下是使用此方法选择数据的示例:
01
02
03
04
05
06
07
08
09
10
11
12
|
# using the shortcut ->query() method here since there are no variable
# values in the select statement.
$STH = $DBH ->query( 'SELECT name, addr, city from folks' );
# setting the fetch mode
$STH ->setFetchMode(PDO::FETCH_ASSOC);
while ( $row = $STH ->fetch()) {
echo $row [ 'name' ] . "\n" ;
echo $row [ 'addr' ] . "\n" ;
echo $row [ 'city' ] . "\n" ;
}
|
while 循环将继续逐行遍历结果集,直到完成。
FETCH_OBJ
这种提取类型为每行提取的数据创建一个 std 类的对象。这是一个例子:
01
02
03
04
05
06
07
08
09
10
11
12
|
# creating the statement
$STH = $DBH ->query( 'SELECT name, addr, city from folks' );
# setting the fetch mode
$STH ->setFetchMode(PDO::FETCH_OBJ);
# showing the results
while ( $row = $STH ->fetch()) {
echo $row ->name . "\n" ;
echo $row ->addr . "\n" ;
echo $row ->city . "\n" ;
}
|
FETCH_CLASS
在调用构造函数之前设置对象的属性。这个很重要。
此 fetch 方法允许您将数据直接提取到您选择的类中。当你使用 FETCH_CLASS
时,你的对象的属性被设置 BEFORE
,构造函数被调用。再读一遍——这很重要。如果不存在与列名匹配的属性,则会为您创建(作为公共)这些属性。
这意味着,如果您的数据在从数据库中出来后需要任何转换,则可以在创建每个对象时由您的对象自动完成。
例如,假设每个记录的地址都需要被部分遮蔽。我们可以通过在构造函数中对该属性进行操作来做到这一点。这是一个例子:
01
02
03
04
05
06
07
08
09
10
11
|
class secret_person {
public $name ;
public $addr ;
public $city ;
public $other_data ;
function __construct( $other = '' ) {
$this ->address = preg_replace( '/[a-z]/' , 'x' , $this ->address);
$this ->other_data = $other ;
}
}
|
当数据被提取到这个类中时,地址的所有小写 az 字母都被字母 x替换。现在,使用该类并进行数据转换是完全透明的:
1
2
3
4
5
6
|
$STH = $DBH ->query( 'SELECT name, addr, city from folks' );
$STH ->setFetchMode(PDO::FETCH_CLASS, 'secret_person' );
while ( $obj = $STH ->fetch()) {
echo $obj ->addr;
}
|
如果地址是“5 Rosebud”,您会看到“5 Rxxxxxx”作为您的输出。当然,在某些情况下,您可能希望在分配数据之前调用构造函数。PDO 也为您提供了保障。
1
|
$STH ->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'secret_person' );
|
现在,当您使用此获取模式 ( PDO::FETCH_PROPS_LATE
) 重复上一个示例时,地址不会被遮挡,因为调用了构造函数并分配了属性。
最后,如果确实需要,可以在使用 PDO 将数据提取到对象中时将参数传递给构造函数:
1
|
$STH ->setFetchMode(PDO::FETCH_CLASS, 'secret_person' , array ( 'stuff' ));
|
如果需要为每个对象传递不同的数据给构造函数,可以在fetch
方法内部设置获取模式:
1
2
3
4
5
|
$i = 0;
while ( $rowObj = $STH ->fetch(PDO::FETCH_CLASS, 'secret_person' , array ( $i ))) {
// do stuff
$i ++
}
|
其他一些有用的方法
虽然这并不意味着涵盖 PDO 中的所有内容(它是一个巨大的扩展!),但为了使用 PDO 执行基本操作,您还需要了解更多方法。
1
|
$DBH ->lastInsertId();
|
该 ->lastInsertId()
方法始终在数据库句柄上调用,而不是语句句柄,并将返回该连接最后插入的行的自动递增 id。
1
2
|
$DBH -> exec ( 'DELETE FROM folks WHERE 1' );
$DBH -> exec ( "SET time_zone = '-8:00'" );
|
该 ->exec()
方法用于除了受影响的行之外不能返回数据的操作。以上是使用 exec 方法的两个示例。
1
|
$safe = $DBH ->quote( $unsafe );
|
该 ->quote()
方法引用字符串,以便在查询中使用它们是安全的。如果您不使用准备好的语句,这是您的后备。
1
|
$rows_affected = $STH ->rowCount();
|
该 ->rowCount()
方法返回一个整数,指示受操作影响的行数。在至少一个已知的 PDO 版本中, 该方法不适用于 select 语句。但是,它在 PHP 5.1.6 及更高版本中可以正常工作。
如果您遇到此问题并且无法升级 PHP,您可以通过以下方式获取行数:
01
02
03
04
05
06
07
08
09
10
11
|
$sql = "SELECT COUNT(*) FROM folks" ;
if ( $STH = $DBH ->query( $sql )) {
# check the row count
if ( $STH ->fetchColumn() > 0) {
# issue a real select here, because there's data!
}
else {
echo "No rows matched the query." ;
}
}
|
CodeCanyon 的 PHP CRUD 生成器
您可以通过从 CodeCanyon找到PHP CRUD 生成器并在您的项目中使用它来节省自己的时间。以下是您现在可以开始使用的五个最受欢迎的下载。
1. Laravel 多用途应用:Sximo 6
Sximo 6 构建器基于周围最流行的框架。它还收到了 2021 年的全新更新,使其尽可能易于使用且功能丰富。其中一些功能包括:
- 数据库表管理
- 前端和后端模板
- 模块 MySQL 编辑器
- 多张图片和文件上传支持
如果您希望使用 CRUD PHP 模板节省时间,请尝试一下。
2. PDO Crud:表单生成器和数据库管理
这是另一个强大的 CRUD PHP 生成器。这个 PHP PDO 代码模板可以很好地进行数据库管理。但这还不是全部。您还可以使用 PDO CRUD 直接从数据库表中构建有用的表单。这是一个有用的功能,很多其他选项都没有。
3. Cicool:页面、表单、Rest API 和 CRUD 生成器
Cicool 是另一个值得研究的多功能构建器。它不仅提供了 CRUD 构建器,而且还具有:
- 页面构建器
- 表单生成器
- 休息 API 构建器
除了这些功能之外,您还可以向 Cicool 添加扩展并轻松自定义其主题。
4. PHP CRUD 生成器
简单的管理面板构建器?查看。易于导航的界面?查看。深入的数据库分析?另一个检查。这个 PHP CRUD 生成器拥有构建出色仪表板和存储数据所需的一切。具有不同的用户身份验证和权限管理功能,这个 PDO PHP 模板值得一试。
结论
我希望这可以帮助您摆脱mysql
和 mysqli
扩展。你怎么看?有没有人可能会进行转换?