Skip to content

Commit 0b05edf

Browse files
authored
Explore: Setup SQLite database integration without creating wp-content/db.php (#1382)
Related to #1379 Loads the SQLite database integration without creating a drop-in `wp-content/db.php` plugin. Instead, it leans on the `auto_prepend_file` PHP option to provide a fake global `$wpdb` variable. This prevents WordPress from trying to connect to MySQL. The first time that `$wpdb` is used for reading or a method call, it loads the actual SQLite database integration. ## Follow-up work Use the same SQLite setup method in the web version of Playground. This could happen either when a non-minified WordPress build is used or, for simplicity, always if we remove the SQLite integration plugin from the minified build. ## Testing instructions Run `bun packages/playground/cli/src/cli.ts server --login` and confirm the server starts without any database errors. Test coverage coming as a part of the larger boot protocol discussion, see #1379.
1 parent 7531696 commit 0b05edf

File tree

4 files changed

+121
-39
lines changed

4 files changed

+121
-39
lines changed

packages/playground/cli/src/setup-wp.ts

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import {
1616
readAsFile,
1717
} from './download';
1818
import { withPHPIniValues } from './setup-php';
19-
import { playgroundMuPlugin } from '@wp-playground/wordpress';
19+
import {
20+
playgroundMuPlugin,
21+
preloadSqliteIntegration,
22+
} from '@wp-playground/wordpress';
2023

2124
/**
2225
* Ensures a functional WordPress installation in php document root.
@@ -49,7 +52,9 @@ export async function setupWordPress(
4952
monitor
5053
),
5154
]);
52-
await prepareWordPress(php, wpZip, sqliteZip);
55+
56+
await prepareWordPress(php, wpZip);
57+
await preloadSqliteIntegration(php, sqliteZip);
5358

5459
const preinstalledWpContentPath = path.join(
5560
CACHE_FOLDER,
@@ -105,7 +110,7 @@ export async function setupWordPress(
105110
* accept the limitation, and switch to the PHP implementation as soon
106111
* as that's viable.
107112
*/
108-
async function prepareWordPress(php: NodePHP, wpZip: File, sqliteZip: File) {
113+
async function prepareWordPress(php: NodePHP, wpZip: File) {
109114
php.mkdir('/internal/shared/mu-plugins');
110115
php.writeFile(
111116
'/internal/shared/mu-plugins/0-playground.php',
@@ -128,33 +133,4 @@ async function prepareWordPress(php: NodePHP, wpZip: File, sqliteZip: File) {
128133
'/wordpress/wp-config.php',
129134
php.readFileAsText('/wordpress/wp-config-sample.php')
130135
);
131-
// }}}
132-
133-
// Setup the SQLite integration {{{
134-
php.mkdir('/tmp/sqlite-database-integration');
135-
await unzip(php, {
136-
zipFile: sqliteZip,
137-
extractToPath: '/tmp/sqlite-database-integration',
138-
});
139-
php.mv(
140-
'/tmp/sqlite-database-integration/sqlite-database-integration-main',
141-
'/internal/shared/mu-plugins/sqlite-database-integration'
142-
);
143-
144-
const dbPhp = php
145-
.readFileAsText(
146-
'/internal/shared/mu-plugins/sqlite-database-integration/db.copy'
147-
)
148-
.replace(
149-
"'{SQLITE_IMPLEMENTATION_FOLDER_PATH}'",
150-
"'/internal/shared/mu-plugins/sqlite-database-integration/'"
151-
)
152-
.replace(
153-
"'{SQLITE_PLUGIN}'",
154-
"'/internal/shared/mu-plugins/sqlite-database-integration/load.php'"
155-
);
156-
// @TODO do not create the db.php file. Either find a way to mount it, or
157-
// load the custom $wpdb object in a different way.
158-
php.writeFile('/wordpress/wp-content/db.php', dbPhp);
159-
// }}}
160136
}

packages/playground/wordpress/package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,5 @@
2626
"publishConfig": {
2727
"access": "public",
2828
"directory": "../../../dist/packages/playground/wordpress"
29-
},
30-
"dependencies": {
31-
"@php-wasm/universal": "^0.6.6"
3229
}
3330
}

packages/playground/wordpress/project.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,7 @@
4747
"executor": "@nx/linter:eslint",
4848
"outputs": ["{options.outputFile}"],
4949
"options": {
50-
"lintFilePatterns": [
51-
"packages/playground/wordpress/**/*.ts",
52-
"packages/playground/wordpress/package.json"
53-
]
50+
"lintFilePatterns": ["packages/playground/wordpress/**/*.ts"]
5451
}
5552
}
5653
},

packages/playground/wordpress/src/index.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import { UniversalPHP } from '@php-wasm/universal';
2+
import { joinPaths, phpVar } from '@php-wasm/util';
3+
import { unzip } from '@wp-playground/blueprints';
4+
15
export * from './rewrite-rules';
26

37
export const RecommendedPHPVersion = '8.0';
@@ -105,3 +109,111 @@ export const specificMuPlugins = {
105109
export const playgroundMuPlugin = Object.values(specificMuPlugins)
106110
.map((p) => p.trim())
107111
.join('\n');
112+
113+
export async function preloadSqliteIntegration(
114+
php: UniversalPHP,
115+
sqliteZip: File
116+
) {
117+
if (await php.isDir('/tmp/sqlite-database-integration')) {
118+
await php.rmdir('/tmp/sqlite-database-integration', {
119+
recursive: true,
120+
});
121+
}
122+
await php.mkdir('/tmp/sqlite-database-integration');
123+
await unzip(php, {
124+
zipFile: sqliteZip,
125+
extractToPath: '/tmp/sqlite-database-integration',
126+
});
127+
const SQLITE_PLUGIN_FOLDER = '/internal/shared/sqlite-database-integration';
128+
await php.mv(
129+
'/tmp/sqlite-database-integration/sqlite-database-integration-main',
130+
SQLITE_PLUGIN_FOLDER
131+
);
132+
const dbCopy = await php.readFileAsText(
133+
joinPaths(SQLITE_PLUGIN_FOLDER, 'db.copy')
134+
);
135+
const dbPhp = dbCopy
136+
.replace(
137+
"'{SQLITE_IMPLEMENTATION_FOLDER_PATH}'",
138+
phpVar(SQLITE_PLUGIN_FOLDER)
139+
)
140+
.replace(
141+
"'{SQLITE_PLUGIN}'",
142+
phpVar(joinPaths(SQLITE_PLUGIN_FOLDER, 'load.php'))
143+
);
144+
const SQLITE_MUPLUGIN_PATH =
145+
'/internal/shared/mu-plugins/sqlite-database-integration.php';
146+
await php.writeFile(SQLITE_MUPLUGIN_PATH, dbPhp);
147+
await php.writeFile(
148+
`/internal/shared/preload/0-sqlite.php`,
149+
`<?php
150+
/**
151+
* Loads the SQLite integration plugin before WordPress is loaded
152+
* and without creating a drop-in "db.php" file.
153+
*
154+
* Technically, it creates a global $wpdb object whose only two
155+
* purposes are to:
156+
*
157+
* * Exist – because the require_wp_db() WordPress function won't
158+
* connect to MySQL if $wpdb is already set.
159+
* * Load the SQLite integration plugin the first time it's used
160+
* and replace the global $wpdb reference with the SQLite one.
161+
*
162+
* This lets Playground keep the WordPress installation clean and
163+
* solves dillemas like:
164+
*
165+
* * Should we include db.php in Playground exports?
166+
* * Should we remove db.php from Playground imports?
167+
* * How should we treat stale db.php from long-lived OPFS sites?
168+
*
169+
* @see https://github.com/WordPress/wordpress-playground/discussions/1379 for
170+
* more context.
171+
*/
172+
class Playground_SQLite_Integration_Loader {
173+
public function __call($name, $arguments) {
174+
$this->load_sqlite_integration();
175+
if($GLOBALS['wpdb'] === $this) {
176+
throw new Exception('Infinite loop detected in $wpdb – SQLite integration plugin could not be loaded');
177+
}
178+
return call_user_func_array(
179+
array($GLOBALS['wpdb'], $name),
180+
$arguments
181+
);
182+
}
183+
public function __get($name) {
184+
$this->load_sqlite_integration();
185+
if($GLOBALS['wpdb'] === $this) {
186+
throw new Exception('Infinite loop detected in $wpdb – SQLite integration plugin could not be loaded');
187+
}
188+
return $GLOBALS['wpdb']->$name;
189+
}
190+
public function __set($name, $value) {
191+
$this->load_sqlite_integration();
192+
if($GLOBALS['wpdb'] === $this) {
193+
throw new Exception('Infinite loop detected in $wpdb – SQLite integration plugin could not be loaded');
194+
}
195+
$GLOBALS['wpdb']->$name = $value;
196+
}
197+
protected function load_sqlite_integration() {
198+
require_once ${phpVar(SQLITE_MUPLUGIN_PATH)};
199+
}
200+
}
201+
$wpdb = $GLOBALS['wpdb'] = new Playground_SQLite_Integration_Loader();
202+
`
203+
);
204+
/**
205+
* Ensure the SQLite integration is loaded and clearly communicate
206+
* if it isn't. This is useful because WordPress database errors
207+
* may be cryptic and won't mention the SQLite integration.
208+
*/
209+
await php.writeFile(
210+
`/internal/shared/mu-plugins/sqlite-test.php`,
211+
`<?php
212+
global $wpdb;
213+
if(!($wpdb instanceof WP_SQLite_DB)) {
214+
var_dump(isset($wpdb));
215+
die("SQLite integration not loaded " . get_class($wpdb));
216+
}
217+
`
218+
);
219+
}

0 commit comments

Comments
 (0)