WordPress数据库系列:如何与WordPress数据库交互

1,887 阅读5分钟

这是我参与更文挑战的第9天,活动详情查看: 更文挑战

本篇基本参考翻译自How To Interact With The WordPress Database,很不错介绍$wpdb使用的文章

wpdb——WordPress database access abstraction class

wpdb是wordpress数据库访问抽象类,通过它可以直接和MySQL数据进行交互,实现查询任何表以及处理返回的数据,实现更加灵活的数据操作。

它是WordPress内置的功能,不需要打开一个单独的数据库连接,也不需要在查询后修改数据集。

$wpdb构建于ezSQL类的基础上

在函数function中使用$wpdb时,需要通过global声明它。在主题/文件中可直接使用

开始使用

wpdb的基本使用可以直接通过示例实现更好的理解。如下,下面查询数据库中评论数最多4个posts的ID和title,评论数降序排序。

<?php
   $posts = $wpdb->get_results("SELECT ID, post_title FROM $wpdb->posts WHERE post_status = 'publish'
   AND post_type='post' ORDER BY comment_count DESC LIMIT 0,4")
?>

这是一个基本的SQL查询,通过PHP包裹。$wpdb类包含一个名为get_results()的方法(方法method是类内部函数functions特定的名称),它不仅获取结果,还会将其放入一个合适的对象。

注意,此处使用$wpdb->posts表名代替wp_posts。它是一个访问你的核心WordPress表的帮助器(helper)。

$results对象会按如下格式包含你的数据

Array
(
   [0] => stdClass Object
      (
         [ID] => 6
         [post_title] => The Male Angler Fish Gets Completely Screwed
      )

   [1] => stdClass Object
      (
         [ID] => 25
         [post_title] => 10 Truly Amazing Icon Sets From Germany
      )

   [2] => stdClass Object
      (
         [ID] => 37
         [post_title] => Elderberry Is Awesome
      )

   [3] => stdClass Object
      (
         [ID] => 60
         [post_title] => Gathering Resources and Inspiration With Evernote
      )

)

从数据库检索结果

如果想从数据库中检索一些信息,可以使用下面四个辅助函数(helper functions——帮助器函数)结构化数据、

GET_RESULTS()

GET_RESULTS()最好的用法是获取二维数据(多个行和列),它转换数据到每行对应一个独立的对象组成的数组中,

<?php
   $posts = $wpdb->get_results("SELECT ID, post_title FROM wp_posts WHERE post_status = 'future'
   AND post_type='post' ORDER BY post_date ASC LIMIT 0,4")

   // Echo the title of the first scheduled post
   echo $posts[0]->post_title;
?>

get_results用于SELECT一个通用结果。

GET_ROW()——查询一行

当需要在数据库中查找一个单独的行时(如查询评论最多的post),可以使用get_row()。它填充数据到一个一维对象中

<?php
   $posts = $wpdb->get_row("SELECT ID, post_title FROM wp_posts WHERE post_status = 'publish'
   AND post_type='post' ORDER BY comment_count DESC LIMIT 0,1")

   // Echo the title of the most commented post
   echo $posts->post_title;
?>

GET_COL()

get_col()get_row()类似,不过它获取的是单个列。如果你想仅仅获取评论最多的前10的posts的ID,get_row()非常合适。同样,存储数据到一维对象

<?php
   $posts = $wpdb->get_col("SELECT ID FROM wp_posts WHERE post_status = 'publish'
   AND post_type='post' ORDER BY comment_count DESC LIMIT 0,10")

   // Echo the ID of the 4th most commented post
   echo $posts[3]->ID;
?>

GET_VAR()

许多情况下,你仅仅需要从数据库获取一个值。例如,某个用户的email地址,此时,可以使用get_var检索单个值。该值的数据类型将和它在数据库中的类型一致(即整型是整型,字符串是字符串)

<?php
   $email = $wpdb->get_var("SELECT user_email FROM wp_users WHERE user_login = 'danielpataki' ")

   // Echo the user's email address
   echo $email;
?>

INSERT-插入单行

使用insert可以执行一个插入

$wpdb->insert( $table, $data, $format);

insert方法有3个参数,第一个为表名,要插入数据的表;第二个是包含列和对应值(键-值对)组成的数组;第三个参数是与给定值的顺序对应的数据类型。

失败返回false;成功返回受影响的行数

<?php
   $wpdb->insert($wpdb->usermeta, array("user_id" => 1, "meta_key" => "awesome_factor", "meta_value" => 10), array("%d", "%s", "%d"));

   // Equivalent to:
   // INSERT INTO wp_usermeta (user_id, meta_key, meta_value) VALUES (1, "awesome_factor", 10);
?>

如果用来执行插入,在开始是可能看起来比较笨拙;但是实际上insert()给了很大的灵活性,因为它使用数组作为输入。

指定格式是可选的:默认所有值都作为字符串对待。在方法中包含该参数是一个好的实践。三个值你可以使用%s对应string%d对应integer整型(数字decimal number)、%f对应浮点数(float)

自增列生成的ID,在使用insert方法后,可使用$wpdb->insert_id获取

UPDATE-更新数据

更新数据的辅助方法是update(),它类似上面的用法。但是为了处理更新的where子句,它需要两个额外的参数。

$wpdb->update( $table, $data, $where, $format = null, $where_format = null );

$table$data$format参数和insert一样。$where参数执行更新的条件,它是一个column-value组成的数组;指定多个参数,它们将通过AND逻辑连接。$where_format$format相似:指定$where参数值的格式。

失败返回false;成功返回受影响的行数。

$wpdb->update( $wpdb->posts, array("post_title" => "Modified Post Title"), array("ID" => 5), array("%s"), array("%d") );

DELETE-删除数据

delete()用来删除表中的行。成功返回影响的行数;失败(发生错误)返回false

delete( $table, $where, $where_format = null );

参数和update类似

// Default usage.
$wpdb->delete( 'table', array( 'ID' => 1 ) );
 
// Using where formatting.
$wpdb->delete( 'table', array( 'ID' => 1 ), array( '%d' ) );

其它查询(General Queries通用查询)

上面的辅助器很强大,但是有时需要执行不同的或更复杂的查询。比如,执行一个包含多个AND/OR逻辑的复杂where子句的更新,则无法使用update()方法。

此时可以使用“general”query()方法,它可以执行任何形式的查询。

query('query');

对于SELECT, INSERT, DELETE, UPDATE等查询,query()返回整数值表示受影响的行数;对于CREATE, ALTER, TRUNCATE和DROP等影响这个表的SQL语句,query()成功时返回TRUE。发生错误返回FALSE。

比如删除多个条件的行

$wpdb->query($wpdb->prepare("DELETE FROM wp_usermeta WHERE meta_key = 'first_login' OR meta_key = 'security_key' "));
$wpdb->query( 
    $wpdb->prepare( 
        "DELETE FROM $wpdb->postmeta
            WHERE post_id = %d
            AND meta_key = %s
        ",
         13, 'gargle'
        )
);

保护和验证

你需要首先明确数据安全和数据库不被篡改的重要性。关于数据验证可以参考下WordPress Codex的Data Validation

为了额外的验证,你需要转义所有的查询(escape all queries)

写原始SQL的所有方法中,比如query()get_var()get_results()等非辅助方法,需要使用prepare()方法手动转义。

$sql = $wpdb->prepare( 'query' [, value_parameter, value_parameter ... ] );

按照基本格式给出如下例子,可以更好的理解

$sql = $wpdb->prepare( "INSERT INTO $wpdb->postmeta (post_id, meta_key, meta_value ) VALUES ( %d, %s, %d )", 3342, 'post_views', 2290 )
$wpdb->query($sql);

通过输入数据类型,然后再添加实际数据作为后续参数,而不是像通常那样直接添加实际值。

类变量和其他方法

排除那些优秀的方法,还有几个其他函数和变量可以使你的操作更轻松。全部的函数变量列表可以参考官方文档。

INSERT_ID

每当插入某些内容到表中时,很可能会在其中有一个自增的ID,要查找执行insert的最新的值,可以使用$wpdb-> insert_id

$sql = $wpdb->prepare( "INSERT INTO $wpdb->postmeta (post_id, meta_key, meta_value ) VALUES ( %d, %s, %d )", 3342, 'post_views', 2290 )
$wpdb->query($sql);

$meta_id = $wpdb->insert_id;

NUM_ROWS

num_rows最近的查询返回的行的数量。即SELECT查询返回的总行数

表名

所有核心表的表名保存在与其对应的核心表完全相同的名称的变量中。posts表的表名(可能为wp_posts)存储在$posts变量中,因此可以使用$wpdb->posts输出它。

使用它是因为WordPress表允许选择一个前缀。大多数人们使用默认的wp,一些人会修改它。为了灵活性,前缀不能硬编码。因此,如果你写一个插件,在查询中使用wp_postmeta而不是$wpdb->postmeta,则你的代码在一些站点可能将无法使用。

如果想从WordPress非核心表中获取数据,则可以像通常那样直接使用表名

错误处理

通过调用show_errors()hide_errors()方法,可以打开(on)或关闭(off)错误报告,默认是off,进而获取相关的更多信息。

无论哪种方式,都可以使用print_error()方法来打印最新查询的错误

$wpdb->show_errors();
$wpdb->query("DELETE FROM wp_posts WHERE post_id = 554 ");

// When run, because show_errors() is set, the error message will tell you that the "post_id" field is an unknown
// field in this table (since the correct field is ID)

show_errors()只是用来报告错误。最有效的还是判断结果$result===false,然后print_error()打印查看

清除缓存

flush用于清除SQL结果缓存。

<?php 
// 清除$wpdb->last_result, $wpdb->last_query 和 $wpdb->col_info
$wpdb->flush();
?>

获取列信息

get_col_info('type', offset)可以获取最近查询结果的列信息。当函数返回一个不知道属性的对象时很有用

使用$Wpdb新知识构建一些基本跟踪

如果你是初次接触这些,可能理解了这些的内容,但是发现实现起来很困难。因此,让我们以作者(原文作者Daniel Pataki)为网站制作的简单WordPress跟踪插件为例。

简单起见,下面不会描述插件的细节,仅展示数据库的结构和一些查询。

表结构(table's structure)

为了跟踪广告的点击次数和展示次数,作者创建了一个表tracking。该表实时记录用户操作。每次展示和点击都按如下结构记录在自己的行中:

  • ID 自增ID.
  • time 动作发生的日期和时间.
  • deal_id 与动作(即广告被点击或被查看)相关的deal ID .
  • action 动作类型 (即点击或展示).
  • action_url 开启动作的页面.
  • user_id If 登陆用户的ID.
  • user_ip 用户的IP,用于清除任何顽皮的行为。

该表很快就会变得很大,因此将其汇总到每日统计信息中并定期进行刷新。但是,现在让我们先使用这张表。

插入数据到表中

当用户点击广告时,就会检测到,并将我们需要的信息以form的$_POST数组发送到脚本,其中包含以下数据:

Array
(
   [deal_id] => 643
   [action] => click
   [action_url] => http://thisiswhereitwasclicked.com/about/
   [user_id] => 223
   [user_ip] = 123.234.223.12
)

我们可以使用我们的辅助方法将此数据插入数据库中,如下所示:

$wpdb->insert('tracking', array("deal_id" => 643, "action" => "click", "action_url" => "http://thisiswhereitwasclicked.com/about/",
"user_id" => 223, "user_ip" => "123.234.223.12"), array("%d", %s", "%s", "%d", "%s"));

离题的一个冒险,我将解决您可能对这个特定示例所遇到的一些问题。您可能在想,数据验证又如何呢?该单击可能来自网站管理员,或者用户可能错误地单击了两次,或者可能发生了许多其他事情。

我们决定,因为我们不需要实时统计信息(每日统计信息就足够了),所以没有必要在每次插入时检查数据。每天大约在午夜(即低流量时间)将数据聚合到一个新表中。在汇总数据之前,我们会仔细清理数据,删除重复数据等。当然,由于我们使用的是辅助函数(helper function),数据在插入表之前会进行转义,因此,此处很安全。

只需一次批量删除管理员所做的全部操作,比每次插入时都检查更容易。这为服务器的负担减轻了大量处理。

从一个黑名单ip中删除动作(ACTION)

如果我们发现IP地址168.211.23.43非常非常淘气,我们可以拉黑它。此时,当我们聚合每日数据,我们需要删除该IP的所有条目

$sql = $wpdb->prepare("DELETE FROM tracking WHERE user_ip = %s ", '168.211.23.43');
$wpdb->query($sql);

你可能已经注意到,即使IP是从安全来源收到的,我仍在转义数据。我建议无论怎样都转义数据。首先,适当的黑客擅长于他们的工作,因为他们是优秀的程序员,并且可以以你不会想到的方式胜过你。另外,与黑客相比,我个人对于伤害自己的网站做得更多,因此,采取这些措施作为对自己的安全预防。

更新总数据

我们将广告存储为自定义post类型;为了简化统计报告,我们还分别存储了广告获得的点击总数。我们可以将tracking数据库中给定deal的所有点击总和相加,所以让我们先来看一下。

$total = $wpdb->get_var("SELECT COUNT(ID) WHERE deal_id = 125 ");

因为获取单个变量比总是让自己负担一个更复杂的查询要容易得多,所以每当我们汇总数据时,我们都将单独存储当前总数。我们的广告以自定义post类型存储为posts,因此在postmeta表中存储此总数是一个合乎逻辑的位置。让我们使用total_clicks键来存储此数据。

$wpdb->update( $wpdb->postmeta, array("meta_value" => $total), array("ID" => 125), array("%d"), array("%d") );

// note that this should be done with update_post_meta(), I just did it the way I did for example's sake

最后的想法和提示

希望您对WordPress的$wpdb类有更好的了解,并能够使用它来使项目变得更好。总结一下,这里是一些有效使用此类的最终提示和技巧:

  • 尽量保持谨慎:强大的力量伴随巨大的责任。确保转义数据并进行验证,因为不当使用此类(class)可能是导致网站被黑客入侵的主要原因!
  • 仅查找你需要的数据。如果只显示文章标题,则无需从每一行中检索所有数据。在这种情况下,只需询问标题和ID:
SELECT title, ID FROM wp_posts ORDER BY post_date DESC LIMIT 0,5;
  • 虽然可以对任何查询使用query()方法,但如果可能的话,最好使用辅助方法(helper methods:insertupdateget_row等)。它们具有更高的模块化和安全性,因为它们会自动转义您的数据。
  • 从WordPress(或任何其他)数据库中删除记录时请多加注意。当WordPress删除comment时,还会执行其他一系列操作:wp_posts表中的评论计数需要减少一个,comment_meta表中的所有数据也需要删除,等等。确保在自己之后进行正确的清理,尤其是在删除时。
  • 查看官方文档中的所有类变量和其他信息。这些将帮助您充分利用该类(class)的潜力。我还建议在非WordPress项目中查看ezSQL类用于常规用途;我几乎将其用于所有事情中。