A PHP library that manages the lifecycle (installation, updates, deletion) of custom WordPress database tables via configuration, with PSR-3 logging. Schema management only (CREATE, ALTER, DROP) — no data manipulation.
A simple schema manager where all table configuration (including updates) is visible in a single file. The only dependencies are a PSR-3 logger and WordPress.
composer require dol-lab/custom-table-managerReturn an array of table definitions (e.g. from config/database-tables.php). Use the full, prefixed table name ($wpdb->prefix . '...'). The library replaces {name} in the create template and in update SQL; {columns_create} (generated from columns) only in the create template.
return array(
array(
'name' => $wpdb->prefix . 'my_logs',
'columns' => array(
'log_id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
'log_type' => 'varchar(50) NOT NULL',
'log_message' => 'text',
),
'create' => "CREATE TABLE {name} (
{columns_create},
PRIMARY KEY (log_id)
)",
'updates' => array(
'1.1.0' => 'create', // Special keyword: table was introduced in this version.
'1.2.0' => array( // One SQL string, or an array of them.
'ALTER TABLE {name} MODIFY COLUMN `log_message` LONGTEXT',
'ALTER TABLE {name} ADD INDEX `log_type_idx` (`log_type`)',
),
),
),
// ... more tables
);Keys: name (string, required) — full prefixed table name · columns (array, required) — ['column' => 'SQL definition'] · create (string|false, required) — CREATE TABLE template, false disables creation · updates (array, optional) — ['version' => 'create'|SQL|SQL[]].
Pass any PSR-3 logger as the third SchemaManager argument. Default: LoggerErrorLog (PHP error_log(), threshold ERROR); a directly constructed DatabaseTable defaults to NullLogger.
Injected loggers receive raw PSR-3 records: a message (which may contain {placeholder} tokens) plus a structured context array — interpolating is the logger's job. If your logger doesn't interpolate (or complains about unreferenced context keys), wrap it in the bundled decorator:
use DolLab\CustomTableManager\InterpolatingLogger;
$manager = new SchemaManager( $configs, $wpdb, new InterpolatingLogger( $logger ) );InterpolatingLogger interpolates {placeholders}, appends leftover context as " | key=value", and delegates a self-contained message string. Two kinds of keys are passed through instead of folded in: _-prefixed keys (reserved meta, e.g. _source) and the PSR-3 exception key holding a Throwable. All other structured context does not reach the wrapped logger — don't opt in if you store or filter on context fields.
All operations throw on failure (TableConfigurationException during construction, TableOperationException during operations) — wrap them in try...catch.
use DolLab\CustomTableManager\SchemaManager;
// Activation:
register_activation_hook( __FILE__, function () use ( $logger ) {
global $wpdb;
try {
$configs = require __DIR__ . '/config/database-tables.php';
( new SchemaManager( $configs, $wpdb, $logger ) )->install();
update_option( 'myplugin_db_version', MYPLUGIN_VERSION );
} catch ( \Exception $e ) {
$logger->critical( 'Activation failed: ' . $e->getMessage() );
}
} );
// Update check (e.g. on 'plugins_loaded'):
$installed = get_option( 'myplugin_db_version', '0.0.0' );
if ( version_compare( $installed, MYPLUGIN_VERSION, '<' ) ) {
try {
$manager = new SchemaManager( $configs, $wpdb, $logger );
$manager->update_table_version( $installed, MYPLUGIN_VERSION );
update_option( 'myplugin_db_version', MYPLUGIN_VERSION );
} catch ( \Exception $e ) {
$logger->critical( 'DB update failed: ' . $e->getMessage() );
}
}
// Uninstall hook: ( new SchemaManager( $configs, $wpdb, $logger ) )->uninstall();install(): void— creates missing tables.update_table_version(string $old, string $new): void— applies dueupdates.uninstall(): void— drops all configured tables.add_logger(LoggerInterface $logger): void— replaces the logger on the manager and all managed tables.get_logger(): LoggerInterface— the logger currently in use.get_table(string $name): DatabaseTable/get_all_tables(): DatabaseTable[]— access managed table objects.
- Uses direct
CREATE TABLE/ALTER TABLEqueries, not WordPress'sdbDelta— ensure your SQL fits your target MySQL/MariaDB version. - Store the installed schema version in
wp_options(see example) so updates apply exactly once.