WP DEVELOP

EP165. “$wpdb 查询:get_results 与 prepare 防注入”

首页 WordPress 开发课程 插件开发 CH3:数据库存储 · EP165
约 15 分钟· #EP165#插件开发 CH3:数据库存储
🔒 登录后可标记已读

/pet-adoption 页面模板真正查询 wp_pets 表、把结果渲染成表格。先在 Adminer 数据库管理界面里练习几句基础 SQL(WHERE/ORDER BY/LIMIT/只选部分列),再搬到 PHP 里用 $wpdb->get_results() 执行查询。这一讲的重点是安全意识:只要 SQL 语句里有任何一部分来自用户输入(比如 URL 参数),就必须改用 $wpdb->prepare() 生成语句——用 %s/%d 占位符代替直接拼接用户提供的值,从根本上避免 SQL 注入攻击。


涉及文件

  • wp-content/plugins/new-database-table/inc/template-pets.php (修改)

代码实现

Adminer 界面里练习的 SQL(不是写进代码,只是熟悉语法)

SELECT * FROM wp_pets WHERE species = 'cat' AND birthyear > 2017 ORDER BY birthyear DESC LIMIT 100
SELECT petname, birthyear FROM wp_pets

inc/template-pets.php:查询表数据并渲染成表格

<?php

get_header(); ?>

<div class="page-banner">
  <div class="page-banner__bg-image" style="background-image: url(<?php echo get_theme_file_uri('/images/ocean.jpg'); ?>);"></div>
  <div class="page-banner__content container container--narrow">
    <h1 class="page-banner__title">Pet Adoption</h1>
    <div class="page-banner__intro">
      <p>Providing forever homes one search at a time.</p>
    </div>
  </div>  
</div>

<div class="container container--narrow page-section">

  <p>This page took <strong><?php echo timer_stop();?></strong> seconds to prepare. Found <strong>x</strong> results (showing the first x).</p>
  
  <?php 
    global $wpdb;
    $tablename = $wpdb->prefix . 'pets';
    $ourQuery = $wpdb->prepare("SELECT * FROM $tablename LIMIT 100");
    $pets = $wpdb->get_results($ourQuery);
  ?>

  <table class="pet-adoption-table">
    <tr>
      <th>Name</th>
      <th>Species</th>
      <th>Weight</th>
      <th>Birth Year</th>
      <th>Hobby</th>
      <th>Favorite Color</th>
      <th>Favorite Food</th>
    </tr>
    <?php
      foreach($pets as $pet) { ?>
        <tr>
          <td><?php echo $pet->petname; ?></td>
          <td><?php echo $pet->species; ?></td>
          <td><?php echo $pet->petweight; ?></td>
          <td><?php echo $pet->birthyear; ?></td>
          <td><?php echo $pet->favhobby; ?></td>
          <td><?php echo $pet->favcolor; ?></td>
          <td><?php echo $pet->favfood; ?></td>
        </tr>
      <?php }
    ?>
  </table>
  
</div>

<?php get_footer(); ?>

关键改动点:

  • 先在数据库管理界面(Adminer/phpMyAdmin)里熟悉基础 SQL 语法WHERE 列名 = '值' 筛选、AND 叠加多个条件、ORDER BY 列名 ASC/DESC 排序、LIMIT 数字 限制返回条数、SELECT 列名1, 列名2 只选部分列而不是 SELECT * 全部列——这些练习不会体现在最终代码里,纯粹是先在图形界面里试出语法再搬进 PHP,比直接对着代码编辑器猜语法更直观
  • global $wpdb$wpdb 是 WordPress 提供的全局数据库操作对象,任何函数里想用它都要先用 global 关键字声明才能访问到
  • $wpdb->get_results($SQL语句):执行一段 SQL 查询语句,返回一个数组,数组的每一项默认是一个对象(不是关联数组),所以后面渲染表格时要用 $pet->petname 这种「对象属性访问」语法(箭头 ->),而不是 $pet['petname']
  • $wpdb->prepare($SQL模板, ...值) ——防 SQL 注入的关键工具
    • 只要 SQL 语句里任何一部分来自用户可控的输入(最常见的是 URL 查询参数,比如 ?species=dog),就绝对不能直接把这个值拼接进 SQL 字符串——恶意用户可能会在这个参数里塞入精心构造的 SQL 片段(即 SQL 注入攻击),篡改整条查询甚至破坏数据库
    • 正确做法:SQL 语句里该有动态值的地方,用占位符代替——%s 表示这里会替换成一个字符串,%d 表示会替换成一个数字(整数)
    • 第一个参数永远是「完全由你自己写死、绝对安全」的 SQL 模板字符串(带着 %s/%d 占位符);第二个及之后的参数依次对应每个占位符实际要代入的值——prepare() 会自动帮这些值做必要的转义处理,再把它们安全地嵌入最终的 SQL 字符串里
    • 举例:$wpdb->prepare("SELECT * FROM wp_pets WHERE species = %s AND birthyear > %d LIMIT 10", array('hamster', 2018))——hamster 替换第一个 %s2018 替换 %d
    • prepare() 本身不会真的去查询数据库,它只是返回一段处理好、可以安全使用的 SQL 字符串,真正执行查询还是要把这段字符串传给 get_results()
    • 这一讲最终的查询(SELECT * FROM $tablename LIMIT 100)其实完全不含任何动态/用户输入的值,理论上不需要用 prepare() 包一层也是安全的——但代码里依然保留了这个用法,是为了给下一讲「根据 URL 参数动态拼查询条件」提前搭好架子
  • $tablename = $wpdb->prefix . 'pets':表名前缀依然动态获取,不硬编码 wp_,跟 EP164 建表时的做法一致
  • 用双引号而不是单引号包住带变量的字符串:PHP 里想在字符串内部直接插值一个变量(比如 "SELECT * FROM $tablename LIMIT 100" 里的 $tablename),必须用双引号——单引号字符串不会做变量替换,会把 $tablename 原样当成字面文字输出
  • 渲染表格:保留表头 <tr>(写死的列名标题),把原本表示「一行数据」的那个 <tr> 模板行整体放进 foreach($pets as $pet) { ... } 循环里,每次循环用当前 $pet 对象的各个属性替换掉原本的占位横杠

Hook / Function 速查

名称类型用途
$wpdb->get_results($SQL)$wpdb 方法执行 SQL 查询,返回结果数组(每项默认是对象)
$wpdb->prepare($SQL模板, ...值)$wpdb 方法%s/%d 占位符安全拼接动态值,返回处理好的 SQL 字符串,防止 SQL 注入
%s / %dprepare 占位符)SQL 占位符语法分别表示这里会安全代入一个字符串 / 一个数字

常见坑

  • 把用户输入(尤其是 URL 参数)直接拼接进 SQL 字符串再执行——存在 SQL 注入风险,恶意用户可以借此篡改查询甚至破坏数据
  • 用单引号包住需要插值变量的 SQL 字符串——PHP 不会对单引号字符串里的 $变量 做替换,会把变量名原样当文字输出,查询会出错
  • get_results() 返回的每一项当成关联数组用 $pet['petname'] 访问——默认返回的是对象,要用 $pet->petname 这种箭头语法
  • 误以为 $wpdb->prepare() 会直接执行查询——它只是生成安全的 SQL 字符串,还需要传给 get_results()/query() 之类的方法才会真正执行

[截图:前台 /pet-adoption 页面渲染出的宠物数据表格,含 Name/Species/Weight/Birth Year 等列]


延伸 / 后续讲座会用到

下一讲要学怎么把 URL 参数(比如 ?favColor=green)动态拼进查询条件,这时候 prepare() 就真正派上用场了。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 27, EP165