Skip to content

Commit

Permalink
Breaking: PHP: Remove NodePHP and WebPHP classes in favor of a single…
Browse files Browse the repository at this point in the history
… "PHP" class (#1457)

> [!WARNING]  
> This is breaking change! Review the description below to assess the
impact on your app.

Consolidates the `BasePHP`, `NodePHP`, and `WebPHP` classes into a
single `PHP` class. This refactor reduces redundancy and clarifies the
PHP handling process across different environments.

## Examples

### NodePHP

**Before**

```ts
import { NodePHP } from '@php-wasm/node';
const php = await NodePHP.load('8.0');
```

**After**

```ts
import { loadNodeRuntime } from '@php-wasm/node';
import { PHP } from '@php-wasm/universal';
const php = new PHP(await loadNodeRuntime('8.0'));
```

### WebPHP

**Before**

```ts
import { WebPHP } from '@php-wasm/web';
const php = await WebPHP.loadRuntime('8.0');
```

**After**

```ts
import { loadWebRuntime } from '@php-wasm/web';
import { PHP } from '@php-wasm/universal';
const php = new PHP(await loadWebRuntime('8.0'));
```

### Mounting

**Before**

```ts
php.useHostFilesystem();
// or 
php.mount( '/home/users/adam/my-dir', '/my-dir-in-vfs');
```

**After**

```ts
import { NodeFSMount, useHostFilesystem } from '@php-wasm/node';
useHostFilesystem( php );
php.mount( new NodeFSMount( '/home/users/adam/my-dir' ), '/my-dir-in-vfs' );
```

Closes #1399

## Motivation

First, the public API surface of all the PHP classes and interfaces is
[overly
complex](#514).
This PR is a step towards simplifying it.

Second, in the [Boot
Protocol](#1398),
PR the `bootWordPress()` function requires separate `createPHP` and
`createPHPRuntime` callbacks just because Playground uses different
classes in node.js and in the browser. With this PR, `createPHP`
callback will no longer be needed anymore as `bootWordPress()` will just
always create a `new PHP()` instance.

## Public API Changes

- Removes `BasePHP`, `NodePHP`, and `WebPHP` classes in favor of a
single PHP class exported from `@php-wasm/universal`
- `php.useHostFilesystem()` was removed in favor of a decoupled function
`useHostFilesystem( php )` available in `@php-wasm/node`.
- `PHP.mount()` signature changed from `mount( hostPath: string,
vfsPath: string )` to `mount( mountable: Mountable, vfsPath: string )`
- Moves WebPHPEndpoint from `@php-wasm/web` to `@php-wasm/universal` and
renames it to `PHPWorker`. That class isn't specific to the web and
could be easily used by Node workers.
- Removes the `IsomorphicLocalPHP` interface. The PHP class is now the
source of truth for all derived worker, client, etc. implementations.
- Removes the `createPhpInstance()` option from `bootWordPress( options
)` in favor of always using the PHP class.
- Updates to Documentation: Adjusted examples and references in the
documentation to reflect the new `PHP` class.
- Refactoring of Test Suites: Updated tests to work with the new class
structure.

## Testing instructions

Confirm all the CI checks pass – this PR includes an extensive refactor
of all the tests.

Heads up @wojtekn @fluiddot @sejas
  • Loading branch information
adamziel authored May 25, 2024
1 parent 5df1896 commit 86da06b
Show file tree
Hide file tree
Showing 70 changed files with 1,353 additions and 981 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,6 @@
"@testing-library/react": "14.0.0",
"@tsconfig/docusaurus": "^1.0.5",
"@types/ajv": "1.0.0",
"@types/emscripten": "1.39.12",
"@types/file-saver": "^2.0.5",
"@types/jest": "^29.4.0",
"@types/node": "18.14.2",
Expand Down
63 changes: 0 additions & 63 deletions packages/docs/site/docs/09-blueprints-api/06-isomorphic.md

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { BlueprintSteps } from '@site/src/components/BlueprintsAPI/model';
</>
))}</span>

You can use Blueprints [both with the web and the node.js](./06-isomorphic.md) version of WordPress Playground.
You can use Blueprints both with the web and the node.js versions of WordPress Playground.

## Differences between JSON and Function APIs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ export const dependencyFilename = 'php.wasm';
export default function (jsEnv, emscriptenModuleArgs) {}
```

The generated JavaScript module is not meant for direct use. Instead, it can be consumed through a `NodePHP` class in Node.js and a `WebPHP` class in the browser:
The generated JavaScript module is not meant for direct use. Instead, it can be consumed through the `PHP` class:

```js
// In Node.js:
const php = NodePHP.load('7.4');
const php = new PHP(await loadNodeRuntime('8.0'));

// On the web:
const php = await WebPHP.load('8.0');
const php = new PHP(await loadWebRuntime('8.0'));
```

Both of these classes extend the `BasePHP` class exposed by the `@php-wasm/universal` package and implement the `UniversalPHP` interface that standardizes the API across all PHP environments.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,3 @@ const obj = {

Comlink.expose(obj);
```

In our case, the exposed object is the [WebPHPEndpoint](/api/web/class/WebPHPEndpoint) instance.
23 changes: 13 additions & 10 deletions packages/php-wasm/cli/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import {
SupportedPHPVersionsList,
} from '@php-wasm/universal';

import { NodePHP } from '@php-wasm/node';
import { PHP } from '@php-wasm/universal';
import { spawn } from 'child_process';
import { loadNodeRuntime, useHostFilesystem } from '@php-wasm/node';

let args = process.argv.slice(2);
if (!args.length) {
Expand All @@ -33,7 +34,7 @@ async function run() {
if (!SupportedPHPVersionsList.includes(phpVersion)) {
throw new Error(`Unsupported PHP version ${phpVersion}`);
}

// npm scripts set the TMPDIR env variable
// PHP accepts a TMPDIR env variable and expects it to
// be a writable directory within the PHP filesystem.
Expand All @@ -43,16 +44,18 @@ async function run() {
// @see https://github.com/npm/npm/issues/4531
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { TMPDIR, ...envVariables } = process.env;
const php = await NodePHP.load(phpVersion, {
emscriptenOptions: {
ENV: {
...envVariables,
TERM: 'xterm',
const php = new PHP(
await loadNodeRuntime(phpVersion, {
emscriptenOptions: {
ENV: {
...envVariables,
TERM: 'xterm',
},
},
},
});
})
);

php.useHostFilesystem();
useHostFilesystem(php);
php.setSpawnHandler((command: string) => {
const phpWasmCommand = `${process.argv[0]} ${process.execArgv.join(
' '
Expand Down
13 changes: 4 additions & 9 deletions packages/php-wasm/fs-journal/src/lib/fs-journal.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
BasePHP,
UniversalPHP,
__private__dont__use,
} from '@php-wasm/universal';
import type { IsomorphicLocalPHP } from '@php-wasm/universal';
import { PHP, UniversalPHP, __private__dont__use } from '@php-wasm/universal';
import { Semaphore, basename, joinPaths } from '@php-wasm/util';
import { logger } from '@php-wasm/logger';

Expand Down Expand Up @@ -113,7 +108,7 @@ export type FilesystemOperation =
| RenameOperation;

export function journalFSEvents(
php: BasePHP,
php: PHP,
fsRoot: string,
onEntry: (entry: FilesystemOperation) => void = () => {}
) {
Expand Down Expand Up @@ -276,7 +271,7 @@ const createFSHooks = (
* @param php
* @param entries
*/
export function replayFSJournal(php: BasePHP, entries: FilesystemOperation[]) {
export function replayFSJournal(php: PHP, entries: FilesystemOperation[]) {
// We need to restore the original functions to the FS object
// before proceeding, or each replayed FS operation will be journaled.
//
Expand Down Expand Up @@ -310,7 +305,7 @@ export function replayFSJournal(php: BasePHP, entries: FilesystemOperation[]) {
}

export function* recordExistingPath(
php: IsomorphicLocalPHP,
php: PHP,
fromPath: string,
toPath: string
): Generator<FilesystemOperation> {
Expand Down
7 changes: 4 additions & 3 deletions packages/php-wasm/fs-journal/src/test/fs-journal.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { NodePHP } from '@php-wasm/node';
import { PHP } from '@php-wasm/universal';
import {
FilesystemOperation,
journalFSEvents,
normalizeFilesystemOperations,
recordExistingPath,
} from '../lib/fs-journal';
import { LatestSupportedPHPVersion } from '@php-wasm/universal';
import { loadNodeRuntime } from '@php-wasm/node';

describe('Journal MemFS', () => {
let php: NodePHP;
let php: PHP;
beforeEach(async () => {
php = await NodePHP.load(LatestSupportedPHPVersion);
php = new PHP(await loadNodeRuntime(LatestSupportedPHPVersion));
});
it('Can recreate an existing directory structure', async () => {
php.mkdir('/test-ref');
Expand Down
4 changes: 3 additions & 1 deletion packages/php-wasm/node/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './get-php-loader-module';
export * from './networking/with-networking';
export * from './node-php';
export * from './load-runtime';
export * from './use-host-filesystem';
export * from './node-fs-mount';
40 changes: 40 additions & 0 deletions packages/php-wasm/node/src/lib/load-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
SupportedPHPVersion,
loadPHPRuntime,
EmscriptenOptions,
} from '@php-wasm/universal';

import { getPHPLoaderModule } from '.';
import { withNetworking } from './networking/with-networking.js';

export interface PHPLoaderOptions {
emscriptenOptions?: EmscriptenOptions;
}

/**
* Does what load() does, but synchronously returns
* an object with the PHP instance and a promise that
* resolves when the PHP instance is ready.
*
* @see load
*/
export async function loadNodeRuntime(
phpVersion: SupportedPHPVersion,
options: PHPLoaderOptions = {}
) {
const emscriptenOptions: EmscriptenOptions = {
/**
* Emscripten default behavior is to kill the process when
* the WASM program calls `exit()`. We want to throw an
* exception instead.
*/
quit: function (code, error) {
throw error;
},
...(options.emscriptenOptions || {}),
};
return await loadPHPRuntime(
await getPHPLoaderModule(phpVersion),
await withNetworking(emscriptenOptions)
);
}
13 changes: 13 additions & 0 deletions packages/php-wasm/node/src/lib/node-fs-mount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Emscripten, Mountable } from '@php-wasm/universal';

export class NodeFSMount implements Mountable {
constructor(private readonly localPath: string) {}

mount(FS: Emscripten.RootFS, vfsMountPoint: string): void | Promise<void> {
FS.mount(
FS.filesystems['NODEFS'],
{ root: this.localPath },
vfsMountPoint
);
}
}
Loading

0 comments on commit 86da06b

Please sign in to comment.