@mytechtoday/augment-extensions 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/augment-extensions/domain-rules/wordpress/README.md +163 -0
- package/augment-extensions/domain-rules/wordpress/module.json +32 -0
- package/augment-extensions/domain-rules/wordpress/rules/coding-standards.md +617 -0
- package/augment-extensions/domain-rules/wordpress/rules/directory-structure.md +270 -0
- package/augment-extensions/domain-rules/wordpress/rules/file-patterns.md +423 -0
- package/augment-extensions/domain-rules/wordpress/rules/gutenberg-blocks.md +493 -0
- package/augment-extensions/domain-rules/wordpress/rules/performance.md +568 -0
- package/augment-extensions/domain-rules/wordpress/rules/plugin-development.md +510 -0
- package/augment-extensions/domain-rules/wordpress/rules/project-detection.md +251 -0
- package/augment-extensions/domain-rules/wordpress/rules/rest-api.md +501 -0
- package/augment-extensions/domain-rules/wordpress/rules/security.md +564 -0
- package/augment-extensions/domain-rules/wordpress/rules/theme-development.md +388 -0
- package/augment-extensions/domain-rules/wordpress/rules/woocommerce.md +441 -0
- package/augment-extensions/domain-rules/wordpress-plugin/README.md +139 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/ajax-plugin.md +1599 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/custom-post-type-plugin.md +1727 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block-plugin.md +428 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/gutenberg-block.md +422 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/mvc-plugin.md +1623 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/object-oriented-plugin.md +1343 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/rest-endpoint.md +734 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/settings-page-plugin.md +1350 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/simple-procedural-plugin.md +503 -0
- package/augment-extensions/domain-rules/wordpress-plugin/examples/singleton-plugin.md +971 -0
- package/augment-extensions/domain-rules/wordpress-plugin/module.json +53 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/activation-hooks.md +770 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/admin-interface.md +874 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/ajax-handlers.md +629 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/asset-management.md +559 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/context-providers.md +709 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/cron-jobs.md +736 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/database-management.md +1057 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/documentation-standards.md +463 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/frontend-functionality.md +478 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/gutenberg-blocks.md +818 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/internationalization.md +416 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/migration.md +667 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/performance-optimization.md +878 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-architecture.md +693 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/plugin-structure.md +352 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/rest-api.md +818 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/scaffolding-workflow.md +624 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/security-best-practices.md +866 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing-patterns.md +1165 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/testing.md +414 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/vscode-integration.md +751 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/woocommerce-integration.md +949 -0
- package/augment-extensions/domain-rules/wordpress-plugin/rules/wordpress-org-submission.md +458 -0
- package/augment-extensions/examples/gutenberg-block-plugin/README.md +101 -0
- package/augment-extensions/examples/gutenberg-block-plugin/examples/testimonial-block.md +428 -0
- package/augment-extensions/examples/gutenberg-block-plugin/module.json +40 -0
- package/augment-extensions/examples/rest-api-plugin/README.md +98 -0
- package/augment-extensions/examples/rest-api-plugin/examples/task-manager-api.md +1299 -0
- package/augment-extensions/examples/rest-api-plugin/module.json +40 -0
- package/augment-extensions/examples/woocommerce-extension/README.md +98 -0
- package/augment-extensions/examples/woocommerce-extension/examples/product-customizer.md +763 -0
- package/augment-extensions/examples/woocommerce-extension/module.json +40 -0
- package/augment-extensions/workflows/wordpress-plugin/README.md +232 -0
- package/augment-extensions/workflows/wordpress-plugin/ai-prompts.md +839 -0
- package/augment-extensions/workflows/wordpress-plugin/bead-decomposition-patterns.md +854 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/complete-plugin-example.md +540 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/custom-post-type-example.md +1083 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/feature-addition-workflow.md +669 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/plugin-creation-workflow.md +597 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/secure-form-handler-example.md +925 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/security-audit-workflow.md +752 -0
- package/augment-extensions/workflows/wordpress-plugin/examples/wordpress-org-submission-workflow.md +773 -0
- package/augment-extensions/workflows/wordpress-plugin/module.json +49 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/best-practices.md +942 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/development-workflow.md +702 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/submission-workflow.md +728 -0
- package/augment-extensions/workflows/wordpress-plugin/rules/testing-workflow.md +775 -0
- package/cli/dist/cli.js +5 -1
- package/cli/dist/cli.js.map +1 -1
- package/cli/dist/commands/show.d.ts.map +1 -1
- package/cli/dist/commands/show.js +41 -0
- package/cli/dist/commands/show.js.map +1 -1
- package/modules.md +52 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
# Database Management
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This guide covers WordPress database operations for plugins including custom table creation with dbDelta, $wpdb methods, prepared statements, and database class patterns for CRUD operations.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Custom Table Creation
|
|
10
|
+
|
|
11
|
+
### Basic Table Creation with dbDelta
|
|
12
|
+
|
|
13
|
+
```php
|
|
14
|
+
<?php
|
|
15
|
+
/**
|
|
16
|
+
* Create custom table on plugin activation
|
|
17
|
+
*/
|
|
18
|
+
function my_plugin_create_tables() {
|
|
19
|
+
global $wpdb;
|
|
20
|
+
|
|
21
|
+
$table_name = $wpdb->prefix . 'my_plugin_data';
|
|
22
|
+
$charset_collate = $wpdb->get_charset_collate();
|
|
23
|
+
|
|
24
|
+
$sql = "CREATE TABLE $table_name (
|
|
25
|
+
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
26
|
+
user_id bigint(20) unsigned NOT NULL,
|
|
27
|
+
title varchar(255) NOT NULL,
|
|
28
|
+
content longtext NOT NULL,
|
|
29
|
+
status varchar(20) DEFAULT 'draft',
|
|
30
|
+
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
|
31
|
+
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
32
|
+
PRIMARY KEY (id),
|
|
33
|
+
KEY user_id (user_id),
|
|
34
|
+
KEY status (status),
|
|
35
|
+
KEY created_at (created_at)
|
|
36
|
+
) $charset_collate;";
|
|
37
|
+
|
|
38
|
+
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
|
|
39
|
+
dbDelta( $sql );
|
|
40
|
+
|
|
41
|
+
// Store database version
|
|
42
|
+
add_option( 'my_plugin_db_version', '1.0' );
|
|
43
|
+
}
|
|
44
|
+
register_activation_hook( __FILE__, 'my_plugin_create_tables' );
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### dbDelta Requirements
|
|
48
|
+
|
|
49
|
+
**Important**: dbDelta has strict formatting requirements:
|
|
50
|
+
|
|
51
|
+
1. **Two spaces** between PRIMARY KEY and the definition
|
|
52
|
+
2. **Key definitions** must be on their own line
|
|
53
|
+
3. **No spaces** around default values in quotes
|
|
54
|
+
4. **Must use** uppercase for SQL keywords
|
|
55
|
+
5. **Must include** $charset_collate
|
|
56
|
+
|
|
57
|
+
```php
|
|
58
|
+
<?php
|
|
59
|
+
// ✅ CORRECT dbDelta syntax
|
|
60
|
+
$sql = "CREATE TABLE $table_name (
|
|
61
|
+
id bigint(20) NOT NULL AUTO_INCREMENT,
|
|
62
|
+
name varchar(100) NOT NULL,
|
|
63
|
+
PRIMARY KEY (id)
|
|
64
|
+
) $charset_collate;";
|
|
65
|
+
|
|
66
|
+
// ❌ WRONG - Will not work
|
|
67
|
+
$sql = "CREATE TABLE $table_name (
|
|
68
|
+
id bigint(20) NOT NULL AUTO_INCREMENT,
|
|
69
|
+
name varchar(100) NOT NULL,
|
|
70
|
+
PRIMARY KEY (id)
|
|
71
|
+
) $charset_collate;"; // Missing space after PRIMARY KEY
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Database Version Management
|
|
75
|
+
|
|
76
|
+
```php
|
|
77
|
+
<?php
|
|
78
|
+
/**
|
|
79
|
+
* Check and update database version
|
|
80
|
+
*/
|
|
81
|
+
function my_plugin_update_db_check() {
|
|
82
|
+
$current_version = get_option( 'my_plugin_db_version', '0' );
|
|
83
|
+
$new_version = '1.1';
|
|
84
|
+
|
|
85
|
+
if ( version_compare( $current_version, $new_version, '<' ) ) {
|
|
86
|
+
my_plugin_update_tables();
|
|
87
|
+
update_option( 'my_plugin_db_version', $new_version );
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
add_action( 'plugins_loaded', 'my_plugin_update_db_check' );
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Update tables for new version
|
|
94
|
+
*/
|
|
95
|
+
function my_plugin_update_tables() {
|
|
96
|
+
global $wpdb;
|
|
97
|
+
|
|
98
|
+
$table_name = $wpdb->prefix . 'my_plugin_data';
|
|
99
|
+
$charset_collate = $wpdb->get_charset_collate();
|
|
100
|
+
|
|
101
|
+
// Use dbDelta to add new columns
|
|
102
|
+
$sql = "CREATE TABLE $table_name (
|
|
103
|
+
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
|
104
|
+
user_id bigint(20) unsigned NOT NULL,
|
|
105
|
+
title varchar(255) NOT NULL,
|
|
106
|
+
content longtext NOT NULL,
|
|
107
|
+
status varchar(20) DEFAULT 'draft',
|
|
108
|
+
new_field varchar(100) DEFAULT NULL,
|
|
109
|
+
created_at datetime DEFAULT CURRENT_TIMESTAMP,
|
|
110
|
+
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
111
|
+
PRIMARY KEY (id),
|
|
112
|
+
KEY user_id (user_id),
|
|
113
|
+
KEY status (status)
|
|
114
|
+
) $charset_collate;";
|
|
115
|
+
|
|
116
|
+
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
|
|
117
|
+
dbDelta( $sql );
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## $wpdb Methods
|
|
124
|
+
|
|
125
|
+
### Insert Data
|
|
126
|
+
|
|
127
|
+
```php
|
|
128
|
+
<?php
|
|
129
|
+
global $wpdb;
|
|
130
|
+
|
|
131
|
+
$table_name = $wpdb->prefix . 'my_plugin_data';
|
|
132
|
+
|
|
133
|
+
// Insert with $wpdb->insert()
|
|
134
|
+
$result = $wpdb->insert(
|
|
135
|
+
$table_name,
|
|
136
|
+
array(
|
|
137
|
+
'user_id' => get_current_user_id(),
|
|
138
|
+
'title' => sanitize_text_field( $_POST['title'] ),
|
|
139
|
+
'content' => wp_kses_post( $_POST['content'] ),
|
|
140
|
+
'status' => 'draft',
|
|
141
|
+
),
|
|
142
|
+
array(
|
|
143
|
+
'%d', // user_id (integer)
|
|
144
|
+
'%s', // title (string)
|
|
145
|
+
'%s', // content (string)
|
|
146
|
+
'%s', // status (string)
|
|
147
|
+
)
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if ( $result === false ) {
|
|
151
|
+
// Insert failed
|
|
152
|
+
error_log( 'Database insert error: ' . $wpdb->last_error );
|
|
153
|
+
} else {
|
|
154
|
+
// Get inserted ID
|
|
155
|
+
$inserted_id = $wpdb->insert_id;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Update Data
|
|
160
|
+
|
|
161
|
+
```php
|
|
162
|
+
<?php
|
|
163
|
+
global $wpdb;
|
|
164
|
+
|
|
165
|
+
$table_name = $wpdb->prefix . 'my_plugin_data';
|
|
166
|
+
|
|
167
|
+
// Update with $wpdb->update()
|
|
168
|
+
$result = $wpdb->update(
|
|
169
|
+
$table_name,
|
|
170
|
+
array(
|
|
171
|
+
'title' => sanitize_text_field( $_POST['title'] ),
|
|
172
|
+
'content' => wp_kses_post( $_POST['content'] ),
|
|
173
|
+
'status' => 'published',
|
|
174
|
+
),
|
|
175
|
+
array(
|
|
176
|
+
'id' => absint( $_POST['id'] ),
|
|
177
|
+
),
|
|
178
|
+
array(
|
|
179
|
+
'%s', // title
|
|
180
|
+
'%s', // content
|
|
181
|
+
'%s', // status
|
|
182
|
+
),
|
|
183
|
+
array(
|
|
184
|
+
'%d', // id
|
|
185
|
+
)
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if ( $result === false ) {
|
|
189
|
+
// Update failed
|
|
190
|
+
error_log( 'Database update error: ' . $wpdb->last_error );
|
|
191
|
+
} else {
|
|
192
|
+
// $result contains number of rows updated
|
|
193
|
+
echo "Updated $result row(s)";
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Delete Data
|
|
198
|
+
|
|
199
|
+
```php
|
|
200
|
+
<?php
|
|
201
|
+
global $wpdb;
|
|
202
|
+
|
|
203
|
+
$table_name = $wpdb->prefix . 'my_plugin_data';
|
|
204
|
+
|
|
205
|
+
// Delete with $wpdb->delete()
|
|
206
|
+
$result = $wpdb->delete(
|
|
207
|
+
$table_name,
|
|
208
|
+
array(
|
|
209
|
+
'id' => absint( $_POST['id'] ),
|
|
210
|
+
),
|
|
211
|
+
array(
|
|
212
|
+
'%d', // id
|
|
213
|
+
)
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if ( $result === false ) {
|
|
217
|
+
// Delete failed
|
|
218
|
+
error_log( 'Database delete error: ' . $wpdb->last_error );
|
|
219
|
+
} else {
|
|
220
|
+
// $result contains number of rows deleted
|
|
221
|
+
echo "Deleted $result row(s)";
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Delete with multiple conditions
|
|
225
|
+
$result = $wpdb->delete(
|
|
226
|
+
$table_name,
|
|
227
|
+
array(
|
|
228
|
+
'user_id' => get_current_user_id(),
|
|
229
|
+
'status' => 'draft',
|
|
230
|
+
),
|
|
231
|
+
array(
|
|
232
|
+
'%d', // user_id
|
|
233
|
+
'%s', // status
|
|
234
|
+
)
|
|
235
|
+
);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Query Data
|
|
239
|
+
|
|
240
|
+
#### Get Multiple Rows
|
|
241
|
+
|
|
242
|
+
```php
|
|
243
|
+
<?php
|
|
244
|
+
global $wpdb;
|
|
245
|
+
|
|
246
|
+
$table_name = $wpdb->prefix . 'my_plugin_data';
|
|
247
|
+
|
|
248
|
+
// Get all rows
|
|
249
|
+
$results = $wpdb->get_results(
|
|
250
|
+
$wpdb->prepare(
|
|
251
|
+
"SELECT * FROM $table_name WHERE user_id = %d ORDER BY created_at DESC",
|
|
252
|
+
get_current_user_id()
|
|
253
|
+
)
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
foreach ( $results as $row ) {
|
|
257
|
+
echo $row->title;
|
|
258
|
+
echo $row->content;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Get results as associative array
|
|
262
|
+
$results = $wpdb->get_results(
|
|
263
|
+
$wpdb->prepare(
|
|
264
|
+
"SELECT * FROM $table_name WHERE status = %s",
|
|
265
|
+
'published'
|
|
266
|
+
),
|
|
267
|
+
ARRAY_A
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
// Get specific columns
|
|
271
|
+
$results = $wpdb->get_results(
|
|
272
|
+
$wpdb->prepare(
|
|
273
|
+
"SELECT id, title FROM $table_name WHERE user_id = %d",
|
|
274
|
+
get_current_user_id()
|
|
275
|
+
)
|
|
276
|
+
);
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Get Single Row
|
|
280
|
+
|
|
281
|
+
```php
|
|
282
|
+
<?php
|
|
283
|
+
global $wpdb;
|
|
284
|
+
|
|
285
|
+
$table_name = $wpdb->prefix . 'my_plugin_data';
|
|
286
|
+
|
|
287
|
+
// Get single row as object
|
|
288
|
+
$row = $wpdb->get_row(
|
|
289
|
+
$wpdb->prepare(
|
|
290
|
+
"SELECT * FROM $table_name WHERE id = %d",
|
|
291
|
+
$item_id
|
|
292
|
+
)
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
if ( $row ) {
|
|
296
|
+
echo $row->title;
|
|
297
|
+
echo $row->content;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Get single row as associative array
|
|
301
|
+
$row = $wpdb->get_row(
|
|
302
|
+
$wpdb->prepare(
|
|
303
|
+
"SELECT * FROM $table_name WHERE id = %d",
|
|
304
|
+
$item_id
|
|
305
|
+
),
|
|
306
|
+
ARRAY_A
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
if ( $row ) {
|
|
310
|
+
echo $row['title'];
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### Get Single Value
|
|
315
|
+
|
|
316
|
+
```php
|
|
317
|
+
<?php
|
|
318
|
+
global $wpdb;
|
|
319
|
+
|
|
320
|
+
$table_name = $wpdb->prefix . 'my_plugin_data';
|
|
321
|
+
|
|
322
|
+
// Get single value
|
|
323
|
+
$count = $wpdb->get_var(
|
|
324
|
+
$wpdb->prepare(
|
|
325
|
+
"SELECT COUNT(*) FROM $table_name WHERE user_id = %d",
|
|
326
|
+
get_current_user_id()
|
|
327
|
+
)
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
echo "Total items: $count";
|
|
331
|
+
|
|
332
|
+
// Get specific column value
|
|
333
|
+
$title = $wpdb->get_var(
|
|
334
|
+
$wpdb->prepare(
|
|
335
|
+
"SELECT title FROM $table_name WHERE id = %d",
|
|
336
|
+
$item_id
|
|
337
|
+
)
|
|
338
|
+
);
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
#### Get Single Column
|
|
342
|
+
|
|
343
|
+
```php
|
|
344
|
+
<?php
|
|
345
|
+
global $wpdb;
|
|
346
|
+
|
|
347
|
+
$table_name = $wpdb->prefix . 'my_plugin_data';
|
|
348
|
+
|
|
349
|
+
// Get array of values from single column
|
|
350
|
+
$titles = $wpdb->get_col(
|
|
351
|
+
$wpdb->prepare(
|
|
352
|
+
"SELECT title FROM $table_name WHERE user_id = %d",
|
|
353
|
+
get_current_user_id()
|
|
354
|
+
)
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
foreach ( $titles as $title ) {
|
|
358
|
+
echo $title;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Get IDs
|
|
362
|
+
$ids = $wpdb->get_col(
|
|
363
|
+
$wpdb->prepare(
|
|
364
|
+
"SELECT id FROM $table_name WHERE status = %s",
|
|
365
|
+
'published'
|
|
366
|
+
)
|
|
367
|
+
);
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## Prepared Statements
|
|
373
|
+
|
|
374
|
+
### Using $wpdb->prepare()
|
|
375
|
+
|
|
376
|
+
**Always use prepared statements to prevent SQL injection.**
|
|
377
|
+
|
|
378
|
+
```php
|
|
379
|
+
<?php
|
|
380
|
+
global $wpdb;
|
|
381
|
+
|
|
382
|
+
$table_name = $wpdb->prefix . 'my_plugin_data';
|
|
383
|
+
|
|
384
|
+
// Single placeholder
|
|
385
|
+
$results = $wpdb->get_results(
|
|
386
|
+
$wpdb->prepare(
|
|
387
|
+
"SELECT * FROM $table_name WHERE user_id = %d",
|
|
388
|
+
$user_id
|
|
389
|
+
)
|
|
390
|
+
);
|
|
391
|
+
|
|
392
|
+
// Multiple placeholders
|
|
393
|
+
$results = $wpdb->get_results(
|
|
394
|
+
$wpdb->prepare(
|
|
395
|
+
"SELECT * FROM $table_name WHERE user_id = %d AND status = %s",
|
|
396
|
+
$user_id,
|
|
397
|
+
$status
|
|
398
|
+
)
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
// Array of values (WordPress 5.3+)
|
|
402
|
+
$statuses = array( 'draft', 'published', 'pending' );
|
|
403
|
+
$placeholders = implode( ', ', array_fill( 0, count( $statuses ), '%s' ) );
|
|
404
|
+
|
|
405
|
+
$results = $wpdb->get_results(
|
|
406
|
+
$wpdb->prepare(
|
|
407
|
+
"SELECT * FROM $table_name WHERE status IN ($placeholders)",
|
|
408
|
+
...$statuses
|
|
409
|
+
)
|
|
410
|
+
);
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Placeholder Types
|
|
414
|
+
|
|
415
|
+
- `%s` - String
|
|
416
|
+
- `%d` - Integer (signed)
|
|
417
|
+
- `%f` - Float
|
|
418
|
+
|
|
419
|
+
```php
|
|
420
|
+
<?php
|
|
421
|
+
// String placeholder
|
|
422
|
+
$wpdb->prepare( "SELECT * FROM $table WHERE name = %s", $name );
|
|
423
|
+
|
|
424
|
+
// Integer placeholder
|
|
425
|
+
$wpdb->prepare( "SELECT * FROM $table WHERE id = %d", $id );
|
|
426
|
+
|
|
427
|
+
// Float placeholder
|
|
428
|
+
$wpdb->prepare( "SELECT * FROM $table WHERE price = %f", $price );
|
|
429
|
+
|
|
430
|
+
// Multiple types
|
|
431
|
+
$wpdb->prepare(
|
|
432
|
+
"INSERT INTO $table (name, age, score) VALUES (%s, %d, %f)",
|
|
433
|
+
$name,
|
|
434
|
+
$age,
|
|
435
|
+
$score
|
|
436
|
+
);
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### ❌ DON'T - SQL Injection Vulnerability
|
|
440
|
+
|
|
441
|
+
```php
|
|
442
|
+
<?php
|
|
443
|
+
// WRONG - Direct variable insertion (SQL injection risk)
|
|
444
|
+
$results = $wpdb->get_results(
|
|
445
|
+
"SELECT * FROM $table_name WHERE user_id = $user_id"
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
// WRONG - String concatenation
|
|
449
|
+
$results = $wpdb->get_results(
|
|
450
|
+
"SELECT * FROM $table_name WHERE name = '" . $name . "'"
|
|
451
|
+
);
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### ✅ DO - Use Prepared Statements
|
|
455
|
+
|
|
456
|
+
```php
|
|
457
|
+
<?php
|
|
458
|
+
// CORRECT - Use $wpdb->prepare()
|
|
459
|
+
$results = $wpdb->get_results(
|
|
460
|
+
$wpdb->prepare(
|
|
461
|
+
"SELECT * FROM $table_name WHERE user_id = %d",
|
|
462
|
+
$user_id
|
|
463
|
+
)
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
// CORRECT - Multiple parameters
|
|
467
|
+
$results = $wpdb->get_results(
|
|
468
|
+
$wpdb->prepare(
|
|
469
|
+
"SELECT * FROM $table_name WHERE name = %s AND age = %d",
|
|
470
|
+
$name,
|
|
471
|
+
$age
|
|
472
|
+
)
|
|
473
|
+
);
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## Database Class Pattern
|
|
479
|
+
|
|
480
|
+
### Complete CRUD Class
|
|
481
|
+
|
|
482
|
+
```php
|
|
483
|
+
<?php
|
|
484
|
+
/**
|
|
485
|
+
* Database handler for custom table
|
|
486
|
+
*/
|
|
487
|
+
class My_Plugin_Database {
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Table name
|
|
491
|
+
*
|
|
492
|
+
* @var string
|
|
493
|
+
*/
|
|
494
|
+
private $table_name;
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Constructor
|
|
498
|
+
*/
|
|
499
|
+
public function __construct() {
|
|
500
|
+
global $wpdb;
|
|
501
|
+
$this->table_name = $wpdb->prefix . 'my_plugin_data';
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Create item
|
|
506
|
+
*
|
|
507
|
+
* @param array $data Item data
|
|
508
|
+
* @return int|false Inserted ID or false on failure
|
|
509
|
+
*/
|
|
510
|
+
public function create( $data ) {
|
|
511
|
+
global $wpdb;
|
|
512
|
+
|
|
513
|
+
$defaults = array(
|
|
514
|
+
'user_id' => get_current_user_id(),
|
|
515
|
+
'title' => '',
|
|
516
|
+
'content' => '',
|
|
517
|
+
'status' => 'draft',
|
|
518
|
+
);
|
|
519
|
+
|
|
520
|
+
$data = wp_parse_args( $data, $defaults );
|
|
521
|
+
|
|
522
|
+
$result = $wpdb->insert(
|
|
523
|
+
$this->table_name,
|
|
524
|
+
array(
|
|
525
|
+
'user_id' => absint( $data['user_id'] ),
|
|
526
|
+
'title' => sanitize_text_field( $data['title'] ),
|
|
527
|
+
'content' => wp_kses_post( $data['content'] ),
|
|
528
|
+
'status' => sanitize_text_field( $data['status'] ),
|
|
529
|
+
),
|
|
530
|
+
array( '%d', '%s', '%s', '%s' )
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
if ( $result === false ) {
|
|
534
|
+
error_log( 'Database insert error: ' . $wpdb->last_error );
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return $wpdb->insert_id;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Get item by ID
|
|
543
|
+
*
|
|
544
|
+
* @param int $id Item ID
|
|
545
|
+
* @return object|null Item object or null if not found
|
|
546
|
+
*/
|
|
547
|
+
public function get( $id ) {
|
|
548
|
+
global $wpdb;
|
|
549
|
+
|
|
550
|
+
return $wpdb->get_row(
|
|
551
|
+
$wpdb->prepare(
|
|
552
|
+
"SELECT * FROM {$this->table_name} WHERE id = %d",
|
|
553
|
+
$id
|
|
554
|
+
)
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Get items with filters
|
|
560
|
+
*
|
|
561
|
+
* @param array $args Query arguments
|
|
562
|
+
* @return array Array of items
|
|
563
|
+
*/
|
|
564
|
+
public function get_items( $args = array() ) {
|
|
565
|
+
global $wpdb;
|
|
566
|
+
|
|
567
|
+
$defaults = array(
|
|
568
|
+
'user_id' => 0,
|
|
569
|
+
'status' => '',
|
|
570
|
+
'orderby' => 'created_at',
|
|
571
|
+
'order' => 'DESC',
|
|
572
|
+
'limit' => 20,
|
|
573
|
+
'offset' => 0,
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
$args = wp_parse_args( $args, $defaults );
|
|
577
|
+
|
|
578
|
+
$where = array( '1=1' );
|
|
579
|
+
$values = array();
|
|
580
|
+
|
|
581
|
+
if ( $args['user_id'] ) {
|
|
582
|
+
$where[] = 'user_id = %d';
|
|
583
|
+
$values[] = $args['user_id'];
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if ( $args['status'] ) {
|
|
587
|
+
$where[] = 'status = %s';
|
|
588
|
+
$values[] = $args['status'];
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
$where_clause = implode( ' AND ', $where );
|
|
592
|
+
|
|
593
|
+
$orderby = sanitize_sql_orderby( $args['orderby'] . ' ' . $args['order'] );
|
|
594
|
+
if ( ! $orderby ) {
|
|
595
|
+
$orderby = 'created_at DESC';
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
$sql = "SELECT * FROM {$this->table_name}
|
|
599
|
+
WHERE $where_clause
|
|
600
|
+
ORDER BY $orderby
|
|
601
|
+
LIMIT %d OFFSET %d";
|
|
602
|
+
|
|
603
|
+
$values[] = $args['limit'];
|
|
604
|
+
$values[] = $args['offset'];
|
|
605
|
+
|
|
606
|
+
if ( ! empty( $values ) ) {
|
|
607
|
+
$sql = $wpdb->prepare( $sql, $values );
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return $wpdb->get_results( $sql );
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Update item
|
|
615
|
+
*
|
|
616
|
+
* @param int $id Item ID
|
|
617
|
+
* @param array $data Item data
|
|
618
|
+
* @return bool True on success, false on failure
|
|
619
|
+
*/
|
|
620
|
+
public function update( $id, $data ) {
|
|
621
|
+
global $wpdb;
|
|
622
|
+
|
|
623
|
+
$update_data = array();
|
|
624
|
+
$format = array();
|
|
625
|
+
|
|
626
|
+
if ( isset( $data['title'] ) ) {
|
|
627
|
+
$update_data['title'] = sanitize_text_field( $data['title'] );
|
|
628
|
+
$format[] = '%s';
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if ( isset( $data['content'] ) ) {
|
|
632
|
+
$update_data['content'] = wp_kses_post( $data['content'] );
|
|
633
|
+
$format[] = '%s';
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if ( isset( $data['status'] ) ) {
|
|
637
|
+
$update_data['status'] = sanitize_text_field( $data['status'] );
|
|
638
|
+
$format[] = '%s';
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if ( empty( $update_data ) ) {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
$result = $wpdb->update(
|
|
646
|
+
$this->table_name,
|
|
647
|
+
$update_data,
|
|
648
|
+
array( 'id' => $id ),
|
|
649
|
+
$format,
|
|
650
|
+
array( '%d' )
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
return $result !== false;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Delete item
|
|
658
|
+
*
|
|
659
|
+
* @param int $id Item ID
|
|
660
|
+
* @return bool True on success, false on failure
|
|
661
|
+
*/
|
|
662
|
+
public function delete( $id ) {
|
|
663
|
+
global $wpdb;
|
|
664
|
+
|
|
665
|
+
$result = $wpdb->delete(
|
|
666
|
+
$this->table_name,
|
|
667
|
+
array( 'id' => $id ),
|
|
668
|
+
array( '%d' )
|
|
669
|
+
);
|
|
670
|
+
|
|
671
|
+
return $result !== false;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Get count
|
|
676
|
+
*
|
|
677
|
+
* @param array $args Query arguments
|
|
678
|
+
* @return int Item count
|
|
679
|
+
*/
|
|
680
|
+
public function count( $args = array() ) {
|
|
681
|
+
global $wpdb;
|
|
682
|
+
|
|
683
|
+
$where = array( '1=1' );
|
|
684
|
+
$values = array();
|
|
685
|
+
|
|
686
|
+
if ( ! empty( $args['user_id'] ) ) {
|
|
687
|
+
$where[] = 'user_id = %d';
|
|
688
|
+
$values[] = $args['user_id'];
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
if ( ! empty( $args['status'] ) ) {
|
|
692
|
+
$where[] = 'status = %s';
|
|
693
|
+
$values[] = $args['status'];
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
$where_clause = implode( ' AND ', $where );
|
|
697
|
+
|
|
698
|
+
$sql = "SELECT COUNT(*) FROM {$this->table_name} WHERE $where_clause";
|
|
699
|
+
|
|
700
|
+
if ( ! empty( $values ) ) {
|
|
701
|
+
$sql = $wpdb->prepare( $sql, $values );
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
return (int) $wpdb->get_var( $sql );
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
### Using the Database Class
|
|
710
|
+
|
|
711
|
+
```php
|
|
712
|
+
<?php
|
|
713
|
+
// Initialize
|
|
714
|
+
$db = new My_Plugin_Database();
|
|
715
|
+
|
|
716
|
+
// Create item
|
|
717
|
+
$item_id = $db->create( array(
|
|
718
|
+
'title' => 'My Title',
|
|
719
|
+
'content' => 'My content',
|
|
720
|
+
'status' => 'published',
|
|
721
|
+
) );
|
|
722
|
+
|
|
723
|
+
// Get item
|
|
724
|
+
$item = $db->get( $item_id );
|
|
725
|
+
|
|
726
|
+
// Get items with filters
|
|
727
|
+
$items = $db->get_items( array(
|
|
728
|
+
'user_id' => get_current_user_id(),
|
|
729
|
+
'status' => 'published',
|
|
730
|
+
'limit' => 10,
|
|
731
|
+
) );
|
|
732
|
+
|
|
733
|
+
// Update item
|
|
734
|
+
$db->update( $item_id, array(
|
|
735
|
+
'title' => 'Updated Title',
|
|
736
|
+
) );
|
|
737
|
+
|
|
738
|
+
// Delete item
|
|
739
|
+
$db->delete( $item_id );
|
|
740
|
+
|
|
741
|
+
// Get count
|
|
742
|
+
$count = $db->count( array(
|
|
743
|
+
'status' => 'published',
|
|
744
|
+
) );
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
## Transactions
|
|
750
|
+
|
|
751
|
+
WordPress doesn't have built-in transaction support, but you can use raw SQL:
|
|
752
|
+
|
|
753
|
+
```php
|
|
754
|
+
<?php
|
|
755
|
+
global $wpdb;
|
|
756
|
+
|
|
757
|
+
// Start transaction
|
|
758
|
+
$wpdb->query( 'START TRANSACTION' );
|
|
759
|
+
|
|
760
|
+
try {
|
|
761
|
+
// Multiple operations
|
|
762
|
+
$wpdb->insert(
|
|
763
|
+
$wpdb->prefix . 'my_plugin_data',
|
|
764
|
+
array( 'title' => 'Item 1' ),
|
|
765
|
+
array( '%s' )
|
|
766
|
+
);
|
|
767
|
+
|
|
768
|
+
$wpdb->insert(
|
|
769
|
+
$wpdb->prefix . 'my_plugin_data',
|
|
770
|
+
array( 'title' => 'Item 2' ),
|
|
771
|
+
array( '%s' )
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
// Check for errors
|
|
775
|
+
if ( $wpdb->last_error ) {
|
|
776
|
+
throw new Exception( $wpdb->last_error );
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Commit transaction
|
|
780
|
+
$wpdb->query( 'COMMIT' );
|
|
781
|
+
|
|
782
|
+
} catch ( Exception $e ) {
|
|
783
|
+
// Rollback on error
|
|
784
|
+
$wpdb->query( 'ROLLBACK' );
|
|
785
|
+
error_log( 'Transaction failed: ' . $e->getMessage() );
|
|
786
|
+
}
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
---
|
|
790
|
+
|
|
791
|
+
## Best Practices
|
|
792
|
+
|
|
793
|
+
### Security
|
|
794
|
+
|
|
795
|
+
1. **Always use prepared statements**: Never concatenate user input into SQL queries
|
|
796
|
+
2. **Sanitize input**: Use appropriate sanitization functions before database operations
|
|
797
|
+
3. **Validate data**: Check that data is valid before inserting/updating
|
|
798
|
+
4. **Check capabilities**: Verify user has permission to perform database operations
|
|
799
|
+
5. **Escape output**: Use `esc_html()`, `esc_attr()` when displaying database values
|
|
800
|
+
|
|
801
|
+
```php
|
|
802
|
+
<?php
|
|
803
|
+
// Good security practices
|
|
804
|
+
function my_plugin_save_item() {
|
|
805
|
+
// Check capability
|
|
806
|
+
if ( ! current_user_can( 'edit_posts' ) ) {
|
|
807
|
+
return new WP_Error( 'forbidden', 'Insufficient permissions' );
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Validate input
|
|
811
|
+
$title = isset( $_POST['title'] ) ? sanitize_text_field( $_POST['title'] ) : '';
|
|
812
|
+
if ( empty( $title ) ) {
|
|
813
|
+
return new WP_Error( 'invalid', 'Title is required' );
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Use prepared statement
|
|
817
|
+
global $wpdb;
|
|
818
|
+
$wpdb->insert(
|
|
819
|
+
$wpdb->prefix . 'my_plugin_data',
|
|
820
|
+
array( 'title' => $title ),
|
|
821
|
+
array( '%s' )
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
### Performance
|
|
827
|
+
|
|
828
|
+
1. **Use indexes**: Add indexes to frequently queried columns
|
|
829
|
+
2. **Limit results**: Always use LIMIT in queries
|
|
830
|
+
3. **Cache results**: Use transients for expensive queries
|
|
831
|
+
4. **Avoid N+1 queries**: Fetch related data in single query when possible
|
|
832
|
+
5. **Use appropriate data types**: Choose correct column types for data
|
|
833
|
+
|
|
834
|
+
```php
|
|
835
|
+
<?php
|
|
836
|
+
// Cache expensive query
|
|
837
|
+
function my_plugin_get_popular_items() {
|
|
838
|
+
$cache_key = 'my_plugin_popular_items';
|
|
839
|
+
$items = get_transient( $cache_key );
|
|
840
|
+
|
|
841
|
+
if ( false === $items ) {
|
|
842
|
+
global $wpdb;
|
|
843
|
+
$table_name = $wpdb->prefix . 'my_plugin_data';
|
|
844
|
+
|
|
845
|
+
$items = $wpdb->get_results(
|
|
846
|
+
"SELECT * FROM $table_name
|
|
847
|
+
WHERE status = 'published'
|
|
848
|
+
ORDER BY views DESC
|
|
849
|
+
LIMIT 10"
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
set_transient( $cache_key, $items, HOUR_IN_SECONDS );
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
return $items;
|
|
856
|
+
}
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
### Error Handling
|
|
860
|
+
|
|
861
|
+
1. **Check return values**: Always check if database operations succeeded
|
|
862
|
+
2. **Log errors**: Use `error_log()` to record database errors
|
|
863
|
+
3. **Use WP_Error**: Return WP_Error objects for better error handling
|
|
864
|
+
4. **Check $wpdb->last_error**: Inspect error messages for debugging
|
|
865
|
+
5. **Enable WP_DEBUG**: Use debug mode during development
|
|
866
|
+
|
|
867
|
+
```php
|
|
868
|
+
<?php
|
|
869
|
+
function my_plugin_create_item( $data ) {
|
|
870
|
+
global $wpdb;
|
|
871
|
+
|
|
872
|
+
$result = $wpdb->insert(
|
|
873
|
+
$wpdb->prefix . 'my_plugin_data',
|
|
874
|
+
$data,
|
|
875
|
+
array( '%s', '%s' )
|
|
876
|
+
);
|
|
877
|
+
|
|
878
|
+
if ( $result === false ) {
|
|
879
|
+
error_log( 'Database error: ' . $wpdb->last_error );
|
|
880
|
+
return new WP_Error(
|
|
881
|
+
'db_error',
|
|
882
|
+
__( 'Failed to create item.', 'my-plugin' )
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
return $wpdb->insert_id;
|
|
887
|
+
}
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
### Code Organization
|
|
891
|
+
|
|
892
|
+
1. **Use classes**: Organize database operations in dedicated classes
|
|
893
|
+
2. **Separate concerns**: Keep database logic separate from business logic
|
|
894
|
+
3. **Use constants**: Define table names as constants
|
|
895
|
+
4. **Document methods**: Add PHPDoc blocks to all methods
|
|
896
|
+
5. **Follow WordPress coding standards**: Use WordPress naming conventions
|
|
897
|
+
|
|
898
|
+
```php
|
|
899
|
+
<?php
|
|
900
|
+
/**
|
|
901
|
+
* Database constants
|
|
902
|
+
*/
|
|
903
|
+
define( 'MY_PLUGIN_TABLE_DATA', 'my_plugin_data' );
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Database handler class
|
|
907
|
+
*/
|
|
908
|
+
class My_Plugin_DB {
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Get table name with prefix
|
|
912
|
+
*
|
|
913
|
+
* @return string
|
|
914
|
+
*/
|
|
915
|
+
private function get_table_name() {
|
|
916
|
+
global $wpdb;
|
|
917
|
+
return $wpdb->prefix . MY_PLUGIN_TABLE_DATA;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
---
|
|
923
|
+
|
|
924
|
+
## Common Pitfalls
|
|
925
|
+
|
|
926
|
+
### ❌ DON'T
|
|
927
|
+
|
|
928
|
+
```php
|
|
929
|
+
<?php
|
|
930
|
+
// Don't forget two spaces after PRIMARY KEY
|
|
931
|
+
$sql = "CREATE TABLE $table_name (
|
|
932
|
+
id bigint(20) NOT NULL AUTO_INCREMENT,
|
|
933
|
+
PRIMARY KEY (id)
|
|
934
|
+
) $charset_collate;"; // WRONG - Missing space
|
|
935
|
+
|
|
936
|
+
// Don't skip $wpdb->prepare()
|
|
937
|
+
$results = $wpdb->get_results(
|
|
938
|
+
"SELECT * FROM $table_name WHERE user_id = $user_id"
|
|
939
|
+
); // WRONG - SQL injection risk
|
|
940
|
+
|
|
941
|
+
// Don't ignore return values
|
|
942
|
+
$wpdb->insert( $table_name, $data ); // WRONG - Not checking result
|
|
943
|
+
|
|
944
|
+
// Don't use wrong placeholder types
|
|
945
|
+
$wpdb->prepare(
|
|
946
|
+
"SELECT * FROM $table WHERE id = %s",
|
|
947
|
+
$id
|
|
948
|
+
); // WRONG - Should use %d for integers
|
|
949
|
+
|
|
950
|
+
// Don't forget charset_collate
|
|
951
|
+
$sql = "CREATE TABLE $table_name (
|
|
952
|
+
id bigint(20) NOT NULL AUTO_INCREMENT,
|
|
953
|
+
PRIMARY KEY (id)
|
|
954
|
+
)"; // WRONG - Missing $charset_collate
|
|
955
|
+
|
|
956
|
+
// Don't skip sanitization
|
|
957
|
+
$wpdb->insert(
|
|
958
|
+
$table_name,
|
|
959
|
+
array( 'title' => $_POST['title'] ) // WRONG - Not sanitized
|
|
960
|
+
);
|
|
961
|
+
|
|
962
|
+
// Don't use SELECT *
|
|
963
|
+
$results = $wpdb->get_results(
|
|
964
|
+
"SELECT * FROM $table_name"
|
|
965
|
+
); // WRONG - No LIMIT, fetches all columns
|
|
966
|
+
|
|
967
|
+
// Don't hardcode table prefix
|
|
968
|
+
$results = $wpdb->get_results(
|
|
969
|
+
"SELECT * FROM wp_my_plugin_data"
|
|
970
|
+
); // WRONG - Should use $wpdb->prefix
|
|
971
|
+
```
|
|
972
|
+
|
|
973
|
+
### ✅ DO
|
|
974
|
+
|
|
975
|
+
```php
|
|
976
|
+
<?php
|
|
977
|
+
// Use two spaces after PRIMARY KEY
|
|
978
|
+
$sql = "CREATE TABLE $table_name (
|
|
979
|
+
id bigint(20) NOT NULL AUTO_INCREMENT,
|
|
980
|
+
PRIMARY KEY (id)
|
|
981
|
+
) $charset_collate;"; // CORRECT
|
|
982
|
+
|
|
983
|
+
// Always use $wpdb->prepare()
|
|
984
|
+
$results = $wpdb->get_results(
|
|
985
|
+
$wpdb->prepare(
|
|
986
|
+
"SELECT * FROM $table_name WHERE user_id = %d",
|
|
987
|
+
$user_id
|
|
988
|
+
)
|
|
989
|
+
); // CORRECT
|
|
990
|
+
|
|
991
|
+
// Check return values
|
|
992
|
+
$result = $wpdb->insert( $table_name, $data );
|
|
993
|
+
if ( $result === false ) {
|
|
994
|
+
error_log( 'Insert failed: ' . $wpdb->last_error );
|
|
995
|
+
} // CORRECT
|
|
996
|
+
|
|
997
|
+
// Use correct placeholder types
|
|
998
|
+
$wpdb->prepare(
|
|
999
|
+
"SELECT * FROM $table WHERE id = %d",
|
|
1000
|
+
$id
|
|
1001
|
+
); // CORRECT - %d for integers
|
|
1002
|
+
|
|
1003
|
+
// Include charset_collate
|
|
1004
|
+
$sql = "CREATE TABLE $table_name (
|
|
1005
|
+
id bigint(20) NOT NULL AUTO_INCREMENT,
|
|
1006
|
+
PRIMARY KEY (id)
|
|
1007
|
+
) $charset_collate;"; // CORRECT
|
|
1008
|
+
|
|
1009
|
+
// Sanitize all input
|
|
1010
|
+
$wpdb->insert(
|
|
1011
|
+
$table_name,
|
|
1012
|
+
array( 'title' => sanitize_text_field( $_POST['title'] ) )
|
|
1013
|
+
); // CORRECT
|
|
1014
|
+
|
|
1015
|
+
// Select specific columns with LIMIT
|
|
1016
|
+
$results = $wpdb->get_results(
|
|
1017
|
+
"SELECT id, title FROM $table_name LIMIT 10"
|
|
1018
|
+
); // CORRECT
|
|
1019
|
+
|
|
1020
|
+
// Use $wpdb->prefix
|
|
1021
|
+
$table_name = $wpdb->prefix . 'my_plugin_data';
|
|
1022
|
+
$results = $wpdb->get_results(
|
|
1023
|
+
"SELECT * FROM $table_name"
|
|
1024
|
+
); // CORRECT
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
---
|
|
1028
|
+
|
|
1029
|
+
## Summary
|
|
1030
|
+
|
|
1031
|
+
**Key Takeaways:**
|
|
1032
|
+
|
|
1033
|
+
1. **dbDelta**: Use for table creation with strict formatting (two spaces after PRIMARY KEY)
|
|
1034
|
+
2. **$wpdb methods**: Use `insert()`, `update()`, `delete()` for simple operations
|
|
1035
|
+
3. **Prepared statements**: Always use `$wpdb->prepare()` to prevent SQL injection
|
|
1036
|
+
4. **Query methods**: Use `get_results()`, `get_row()`, `get_var()`, `get_col()` for retrieving data
|
|
1037
|
+
5. **Database classes**: Organize CRUD operations in dedicated classes
|
|
1038
|
+
6. **Security**: Sanitize input, validate data, check capabilities
|
|
1039
|
+
7. **Performance**: Use indexes, cache results, limit queries
|
|
1040
|
+
8. **Error handling**: Check return values, log errors, use WP_Error
|
|
1041
|
+
|
|
1042
|
+
**Common Mistakes to Avoid:**
|
|
1043
|
+
|
|
1044
|
+
- Forgetting two spaces after PRIMARY KEY in dbDelta
|
|
1045
|
+
- Not using prepared statements
|
|
1046
|
+
- Skipping input sanitization
|
|
1047
|
+
- Ignoring return values
|
|
1048
|
+
- Using wrong placeholder types
|
|
1049
|
+
- Hardcoding table prefixes
|
|
1050
|
+
- Not using LIMIT in queries
|
|
1051
|
+
|
|
1052
|
+
**Resources:**
|
|
1053
|
+
|
|
1054
|
+
- [WordPress Database Class Reference](https://developer.wordpress.org/reference/classes/wpdb/)
|
|
1055
|
+
- [Creating Tables with Plugins](https://codex.wordpress.org/Creating_Tables_with_Plugins)
|
|
1056
|
+
- [Data Validation](https://developer.wordpress.org/plugins/security/data-validation/)
|
|
1057
|
+
|