When developing a custom WordPress plugin or feature, you might need to create custom database tables to handle specific functionality. This blog walks you through a dynamic approach for creating and managing custom database tables using PHP, leveraging the schema-based method.
What You’ll Learn:
- How to define a database table schema dynamically.
- How to create custom database tables.
- How to add indexes and foreign keys seamlessly.
The Problem
WordPress’s default database tables (like wp_posts
and wp_postmeta
) are powerful, but sometimes you need to create your own tables for more advanced use cases—for example, storing custom data that doesn’t fit into WordPress’s standard structure.
The Solution: WP_Create_Schema Class
We’ll create a PHP class that simplifies the process of creating custom tables by accepting a schema definition. Here’s how you can do it step by step.
Step 1: Define the Schema
The schema is a PHP array that defines the table’s columns, primary keys, indexes, and foreign keys. Here’s an example schema:
$schema = [
'columns' => [
'ID' => [
'type' => 'BIGINT(20) UNSIGNED',
'attributes' => 'NOT NULL AUTO_INCREMENT',
'primary' => true,
],
'movie_author' => [
'type' => 'BIGINT(20) UNSIGNED',
'attributes' => "NOT NULL DEFAULT '0'",
],
'movie_date' => [
'type' => 'DATETIME',
'attributes' => "NOT NULL DEFAULT '0000-00-00 00:00:00'",
],
'movie_title' => [
'type' => 'TEXT',
'attributes' => 'NOT NULL',
],
'movie_content' => [
'type' => 'LONGTEXT',
'attributes' => 'NOT NULL',
],
],
'primary_key' => ['ID'],
'indexes' => [
'post_author_index' => [
'columns' => ['movie_author'],
],
'post_date_index' => [
'columns' => ['movie_date'],
],
],
'foreign_keys' => [
'fk_key' => [
'columns' => ['post_author'],
'referenced_table' => 'wp_users',
'referenced_columns' => ['ID'],
'on_delete' => 'CASCADE',
],
],
];
new WP_Create_Schema('movie', $schema);
This schema defines a table with columns, indexes, and a foreign key relationship.
Step 2: The WP_Create_Schema Class
This class dynamically creates a table based on the provided schema and ensures indexes and foreign keys are added.
Key Features of the Class:
- Dynamic Column Creation: Generates SQL for columns from the schema array.
- Primary Key Handling: Adds primary keys easily.
- Index Creation: Supports multiple indexes, including custom column lengths.
- Foreign Key Support: Adds foreign key constraints with
ON DELETE
behaviors likeCASCADE
orRESTRICT
.
Here’s the class:
class WP_Create_Schema {
private $table_name;
private $schema;
public function __construct($table_name, $schema) {
global $wpdb;
$this->table_name = $wpdb->prefix . $table_name;
$this->schema = $schema;
$this->create_table();
$this->add_foreign_keys();
}
private function create_table() {
global $wpdb;
if ($wpdb->get_var("SHOW TABLES LIKE '{$this->table_name}'") === $this->table_name) {
error_log("Table `{$this->table_name}` already exists. Skipping creation.");
return;
}
$columns_sql = [];
foreach ($this->schema['columns'] as $column_name => $details) {
$column_sql = "`{$column_name}` {$details['type']} {$details['attributes']}";
$columns_sql[] = $column_sql;
}
if (!empty($this->schema['primary_key'])) {
$primary_keys = implode(', ', array_map(fn($col) => "`{$col}`", $this->schema['primary_key']));
$columns_sql[] = "PRIMARY KEY ({$primary_keys})";
}
if (!empty($this->schema['indexes'])) {
foreach ($this->schema['indexes'] as $index_name => $index_details) {
$index_columns = implode(', ', array_map(fn($col) => "`{$col}`", $index_details['columns']));
$columns_sql[] = "KEY `{$index_name}` ({$index_columns})";
}
}
$columns_sql_string = implode(",\n", $columns_sql);
$charset_collate = $wpdb->get_charset_collate();
$create_table_query = "CREATE TABLE `{$this->table_name}` (\n{$columns_sql_string}\n) {$charset_collate};";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($create_table_query);
if ($wpdb->get_var("SHOW TABLES LIKE '{$this->table_name}'") === $this->table_name) {
error_log("Table `{$this->table_name}` created successfully.");
} else {
error_log("Failed to create table `{$this->table_name}`.");
}
}
private function add_foreign_keys() {
global $wpdb;
if (!isset($this->schema['foreign_keys']) || empty($this->schema['foreign_keys'])) {
return;
}
foreach ($this->schema['foreign_keys'] as $key_name => $foreign_key) {
$check_query = $wpdb->prepare(
"SELECT CONSTRAINT_NAME \n FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE \n WHERE TABLE_NAME = %s \n AND CONSTRAINT_NAME = %s \n AND TABLE_SCHEMA = %s",
$this->table_name, $key_name, $wpdb->dbname
);
$existing_key = $wpdb->get_var($check_query);
if ($existing_key) {
error_log("Foreign key `{$key_name}` already exists. Skipping.");
continue;
}
$columns = implode(', ', array_map(fn($col) => "`{$col}`", $foreign_key['columns']));
$referenced_columns = implode(', ', array_map(fn($col) => "`{$col}`", $foreign_key['referenced_columns']));
$query = "ALTER TABLE `{$this->table_name}` \n ADD CONSTRAINT `{$key_name}` \n FOREIGN KEY ({$columns}) \n REFERENCES `{$wpdb->prefix}{$foreign_key['referenced_table']}` ({$referenced_columns}) \n ON DELETE {$foreign_key['on_delete']}";
$result = $wpdb->query($query);
if ($result === false) {
error_log("Failed to add foreign key `{$key_name}` to table `{$this->table_name}`.");
} else {
error_log("Foreign key `{$key_name}` added successfully to table `{$this->table_name}`.");
}
}
}
}
Step 3: Use the Class
To create a new table, instantiate the WP_Create_Schema
class and pass the table name and schema:
$schema = [
// Define your schema here
];
new WP_Create_Schema('custom_table_name', $schema);
Final Thoughts
By using this schema-driven approach, you can quickly define and manage custom database tables in WordPress. It’s a robust way to handle complex data structures without cluttering the default tables. Start building your custom tables now!
Leave a Reply