你如何在 php 脚本中嵌入你的 sql 查询(编码风格)?
Posted
技术标签:
【中文标题】你如何在 php 脚本中嵌入你的 sql 查询(编码风格)?【英文标题】:How do you embedded your sql queries in php scripts (coding-style)? 【发布时间】:2009-01-14 12:41:51 【问题描述】:你如何在 php 中嵌入你的 sql 脚本?你只是把它们写在一个字符串或一个heredoc中,还是你把它们外包给一个sql文件?外包时是否有最佳实践?有没有一种优雅的方式来组织这个?
【问题讨论】:
我要问完全相同的问题。似乎在这个线程中没有人回答甚至提到问题的这个特定部分:是否有任何最佳实践何时外包? - 所以,我在这里添加我的评论,并希望听到有关该特定方面的 cmets / 建议 / 意见。特别是,如果有人知道一些用于拆分 SQL 查询和 PHP 代码的优秀开源工具/脚本,我将不胜感激。 【参考方案1】:使用带有 ORM(对象关系映射)层的框架。这样您就不必将直接的 SQL 放在任何地方。嵌入式 SQL 在可读性、维护和所有方面都很糟糕。
【讨论】:
"这样您就不必在任何地方直接放置 SQL。"很好的建议,但理论上很好,在实践中很难。 ORM 永远不会拥有 SQL 所具备的原始能力,有时运行复杂的报告需要这种原始能力。 一个框架通常有助于拥有一个有助于避免复杂查询的数据库模式。这意味着数据库没有被完全使用(主要作为对象的持久存储),但它有助于维护。 @Alan:ORM 层确实允许您在需要时直接编写 SQL。选择权留给程序员。 @Swanand True,但我们又回到了 OP 的问题。即使使用 ORM,它仍然必须回答。【参考方案2】:始终记得转义输入。不要手动执行,使用准备好的语句。这是我的报告类中的一个示例方法。
public function getTasksReport($rmId, $stage, $mmcName)
$rmCondition = $rmId ? 'mud.manager_id = :rmId' : 'TRUE';
$stageCondition = $stage ? 't.stage_id = :stageId' : 'TRUE';
$mmcCondition = $mmcName ? 'mmcs.username = :mmcName' : 'TRUE';
$sql = "
SELECT
mmcs.id AS mmc_id,
mmcs.username AS mmcname,
mud.band_name AS mmc_name,
t.id AS task_id,
t.name AS task,
t.stage_id AS stage,
t.role_id,
tl.id AS task_log_id,
mr.role,
u.id AS user_id,
u.username AS username,
COALESCE(cud.full_name, bud.band_name) AS user_name,
DATE_FORMAT(tl.completed_on, '%d-%m-%Y %T') AS completed_on,
tl.url AS url,
mud.manager_id AS rm_id
FROM users AS mmcs
INNER JOIN banduserdetails AS mud ON mud.user_id = mmcs.id
LEFT JOIN tasks AS t ON 1
LEFT JOIN task_log AS tl ON tl.task_id = t.id AND tl.mmc_id = mmcs.id
LEFT JOIN mmc_roles AS mr ON mr.id = t.role_id
LEFT JOIN users AS u ON u.id = tl.user_id
LEFT JOIN communityuserdetails AS cud ON cud.user_id = u.id
LEFT JOIN banduserdetails AS bud ON bud.user_id = u.id
WHERE mmcs.user_type = 'mmc'
AND $rmCondition
AND $stageCondition
AND $mmcCondition
ORDER BY mmcs.id, t.stage_id, t.role_id, t.task_order
";
$pdo = new PDO(.....);
$stmt = $pdo->prepare($sql);
$rmId and $stmt->bindValue('rmId', $rmId); // (1)
$stage and $stmt->bindValue('stageId', $stage); // (2)
$mmcName and $stmt->bindValue('mmcName', $mmcName); // (3)
$stmt->execute();
return $stmt->fetchAll();
在标记为 (1)、(2) 和 (3) 的行中,您将看到条件绑定的一种方式。
对于简单的查询,我使用 ORM 框架来减少手动构建 SQL 的需要。
【讨论】:
呵呵,我以为我是唯一一个在代码中真正格式化 SQL 的人,好看!【参考方案3】:这取决于查询的大小和难度。
我个人喜欢 heredocs。但我不将它用于简单的查询。 那不重要。最主要的是“永远不要忘记转义值”
【讨论】:
【参考方案4】:你应该总是真的总是使用带有占位符的prepare语句来代表你的变量。
它的代码稍微多一些,但它在大多数数据库上运行效率更高,并且可以保护您免受 SQL 注入攻击。
【讨论】:
【参考方案5】:我更喜欢这样:
$sql = "SELECT tbl1.col1, tbl1.col2, tbl2.col1, tbl2.col2"
. " FROM Table1 tbl1"
. " INNER JOIN Table2 tbl2 ON tbl1.id = tbl2.other_id"
. " WHERE tbl2.id = ?"
. " ORDER BY tbl2.col1, tbl2.col2"
. " LIMIT 10, 0";
PHP 可能需要稍长一点的时间来连接所有字符串,但我认为它看起来更好并且更易于编辑。
当然,对于极长且专门的查询,读取 .sql 文件或使用存储过程是有意义的。根据您的框架,这可能很简单:
$sql = (string) View::factory('sql/myfile');
(如有必要,您可以选择在视图/模板中分配变量)。如果没有模板引擎或框架的帮助,您将使用:
$sql = file_get_contents("myfile.sql");
希望这会有所帮助。
【讨论】:
【参考方案6】:我通常把它们写成函数参数:
db_exec ("SELECT ...");
除了 sql 会很大的情况,我将它作为变量传递:
$SQL = "SELECT ...";
$result = db_exec ($SQL);
(我使用包装函数或对象进行数据库操作)
【讨论】:
【参考方案7】:$sql = sprintf("SELECT * FROM users WHERE id = %d", mysql_real_escape_string($_GET["id"]));
MySQL 注入安全
【讨论】:
【参考方案8】:您可以使用 ORM 或 sql 字符串构建器,但一些复杂的查询需要编写 sql。在编写 sql 时,如 Michał Słaby 所示,使用查询绑定。查询绑定可防止 sql 注入并保持可读性。至于将查询放在哪里:使用模型类。
【讨论】:
【参考方案9】:一旦达到一定水平,您就会意识到您编写的 99% 的 SQL 都可以自动化。如果你写了这么多查询,以至于你想到了一个属性文件,那么你可能正在做一些更简单的事情:
我们程序员做的大部分事情都是 CRUD:创建读取更新删除
作为我自己的工具,我构建了 Pork.dbObject。 2个简单类中的对象关系映射器+活动记录(数据库抽象+dbObject类)
我网站上的几个例子:
创建一个博客:
$weblog = new Weblog(); // create an empty object to work with.
$weblog->Author = 'SchizoDuckie'; // mapped internally to strAuthor.
$weblog->Title = 'A test weblog';
$weblog->Story = 'This is a test weblog!';
$weblog->Posted = date("Y-m-d H:i:s");
$weblog->Save(); // Checks for any changed values and inserts or updates into DB.
echo ($weblog->ID) // outputs: 1
还有一个回复:
$reply = new Reply();
$reply->Author = 'Some random guy';
$reply->Reply = 'w000t';
$reply->Posted = date("Y-m-d H:i:s");
$reply->IP = '127.0.0.1';
$reply->Connect($weblog); // auto-saves $reply and connects it to $weblog->ID
然后,获取并显示博客 + 所有回复:
$weblog = new Weblog(1); //Fetches the row with primary key 1 from table weblogs and hooks it's values into $weblog;
echo("<h1>$weblog->Title</h1>
<h3>Posted by $weblog->Author @ $weblog->Posted</h3>
<div class='weblogpost'>$weblog->Story</div>");
// now fetch the connected posts. this is the real magic:
$replies = $weblog->Find("Reply"); // fetches a pre-filled array of Reply objects.
if ($replies != false)
foreach($replies as $reply)
echo("<div class='weblogreply'><h4>By $reply->Author @ $reply->Posted</h4> $reply->Reply</div>");
weblog 对象如下所示:
class Weblog extends dbObject
function __construct($ID=false)
$this->__setupDatabase('blogs', // database table
array('ID_Blog' => 'ID', // database field => mapped object property
'strPost' => 'Story', // as you can see, database field strPost is mapped to $this->Story
'datPosted' => 'Posted',
'strPoster' => 'Author',
'strTitle' => 'Title',
'ipAddress' => 'IpAddress',
'ID_Blog', // primary table key
$ID); // value of primary key to init with (can be false for new empty object / row)
$this->addRelation('Reaction'); // define a 1:many relation to Reaction
看,无需手动编写 SQL :) 链接+更多示例:Pork.dbObject
哦,是的,我还为我的脚手架工具创建了一个基本的 GUI:Pork.Generator
【讨论】:
【参考方案10】:我喜欢这种格式。之前的评论中提到过,但我觉得对齐方式不对。
$query = "SELECT "
. " foo, "
. " bar "
. "FROM "
. " mytable "
. "WHERE "
. " id = $userid";
足够容易阅读和理解。点与等号对齐,使所有内容保持清晰。
我也喜欢将您的 SQL 保存在一个单独的文件中的想法,尽管我不确定在上面的示例中这将如何处理像 $userid 这样的变量。
【讨论】:
复制之前的评论有什么用?改为投票。此外,使用参数化查询并避免所有这些双引号。以上是关于你如何在 php 脚本中嵌入你的 sql 查询(编码风格)?的主要内容,如果未能解决你的问题,请参考以下文章
如何在执行php脚本后将mysql查询结果返回到html页面?