最适合您的行动方案将取决于您如何处理数据访问。您可以采取三种方法:
- 使用存储过程
- 将查询保留在代码中(但将所有查询放入函数并修复所有内容以使用 PDO 作为参数,如前所述)
- 使用 ORM 工具
如果您想将您自己的原始 SQL 传递给数据库引擎,那么如果您想要做的只是从 PHP 代码中获取原始 SQL,但保持它相对不变,那么存储过程将是您的最佳选择。存储过程与原始 SQL 的争论有点像一场圣战,但 K. Scott Allen 在一篇关于版本控制数据库的文章中提出了一个很好的观点——尽管是一次性的观点:
其次,存储过程在我眼中已经失宠。我来自 WinDNA 的灌输学校,认为应该一直使用存储过程。今天,我将存储过程视为数据库的 API 层。如果您需要数据库级别的 API 层,这很好,但我看到许多应用程序会产生创建和维护它们不需要的额外 API 层的开销。在这些应用程序中,存储过程更多的是负担而不是好处。
我倾向于不使用存储过程。我曾参与过数据库通过存储过程公开 API 的项目,但存储过程可能会施加一些自身的限制,并且这些项目都在不同程度上使用代码中动态生成的原始 SQL 来访问数据库。
在 DB 上拥有 API 层可以更好地划分 DB 团队和 Dev 团队之间的职责,但代价是如果查询保留在代码中,您将拥有一些灵活性,但是 PHP 项目不太可能有相当大的规模足够多的团队从这种划分中受益。
从概念上讲,您可能应该对数据库进行版本控制。然而,实际上,您更有可能只对代码进行版本控制,而不是对数据库进行版本控制。当您更改代码时,您可能会更改查询,但如果您更改存储在数据库中的存储过程中的查询,那么当您签入代码时您可能不会签入这些查询并且您会丢失对应用程序的重要领域进行版本控制的许多好处。
不管你是否选择不使用存储过程,你至少应该确保每个数据库操作都存储在一个独立的函数中,而不是嵌入到每个页面的脚本中——本质上是你的数据库的 API 层,它使用您的代码进行维护和版本控制。如果您使用的是存储过程,这实际上意味着您的 DB 有两个 API 层,一个带有代码,一个带有 DB,如果您的项目没有单独的团队,您可能会觉得这会使事情变得不必要地复杂化。我当然愿意。
如果问题是代码整洁,有一些方法可以使代码中的 SQL 卡在其中更美观,下面显示的 UserManager 类是一个很好的开始方式 - 该类只包含与“用户”表相关的查询,每个查询在类中都有自己的方法,查询缩进到准备语句中,并像在存储过程中格式化它们一样格式化。
// UserManager.php:
class UserManager
{
function getUsers()
{
$pdo = new PDO(...);
$stmt = $pdo->prepare('
SELECT u.userId as id,
u.userName,
g.groupId,
g.groupName
FROM user u
INNER JOIN group g
ON u.groupId = g.groupId
ORDER BY u.userName, g.groupName
');
// iterate over result and prepare return value
}
function getUser($id) {
// db code here
}
}
// index.php:
require_once("UserManager.php");
$um = new UserManager;
$users = $um->getUsers();
foreach ($users as $user) echo $user['name'];
但是,如果您的查询非常相似,但您的查询条件中有大量排列,例如复杂的分页、排序、过滤等,那么对象/关系映射器工具可能是您的最佳选择,尽管对现有代码进行大修的过程使用该工具可能非常复杂。
如果你决定研究 ORM 工具,你应该看看Propel,Yii的 ActiveRecord 组件,或者是王爷 PHP ORM,Doctrine。每一个都使您能够以编程方式使用各种复杂的逻辑构建对数据库的查询。Doctrine 是功能最齐全的,它允许您使用诸如开箱即用的嵌套集树模式之类的东西来模板化您的数据库。
在性能方面,存储过程是最快的,但通常不会超过原始 sql。ORM 工具可以通过多种方式对性能产生重大影响——低效或冗余查询、在每个请求上加载 ORM 库时的巨大文件 IO、每个查询的动态 SQL 生成……所有这些都会产生影响,但是与使用手动查询创建自己的 DB 层相比,使用 ORM 工具可以用更少的代码显着增加可用的功能。
不过, Gary Richardson是绝对正确的,如果您要继续在代码中使用 SQL,那么无论您使用的是查询还是存储过程,都应该始终使用 PDO 的准备好的语句来处理参数。PDO 会为您执行输入的清理工作。
// optional
$attrs = array(PDO::ATTR_PERSISTENT => true);
// create the PDO object
$pdo = new PDO("mysql:host=localhost;dbname=test", "user", "pass", $attrs);
// also optional, but it makes PDO raise exceptions instead of
// PHP errors which are far more useful for debugging
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare('INSERT INTO venue(venueName, regionId) VALUES(:venueName, :regionId)');
$stmt->bindValue(":venueName", "test");
$stmt->bindValue(":regionId", 1);
$stmt->execute();
$lastInsertId = $pdo->lastInsertId();
var_dump($lastInsertId);
警告:假设 ID 为 1,上述脚本将输出string(1) "1"
. PDO->lastInsertId()
无论实际列是否为整数,都将 ID 作为字符串返回。这对您来说可能永远不会成为问题,因为 PHP 会自动将字符串转换为整数。
以下将输出bool(true)
:
// regular equality test
var_dump($lastInsertId == 1);
但是如果您的代码期望值是整数,例如is_int或 PHP 的“真的,真的,100% 等于”运算符:
var_dump(is_int($lastInsertId));
var_dump($lastInsertId === 1);
你可能会遇到一些问题。
编辑:关于存储过程的一些很好的讨论here