Skip to content

Export interactive PHP DocBlock snippets#258

Open
adamziel wants to merge 13 commits into
WordPress:masterfrom
adamziel:adamziel/phpdoc-fences-json
Open

Export interactive PHP DocBlock snippets#258
adamziel wants to merge 13 commits into
WordPress:masterfrom
adamziel:adamziel/phpdoc-fences-json

Conversation

@adamziel

@adamziel adamziel commented Jun 7, 2026

Copy link
Copy Markdown

Exploration work in progress for WordPress/wporg-developer#567.

Exports only PHP DocBlock fences explicitly marked interactive as structured doc.code_snippets data:

```setup-blueprint shared-greeting
{"steps":[{"step":"writeFile","path":"/wordpress/wp-content/mu-plugins/greeting.php","data":"<?php ..."}]}
```

```php interactive setup-blueprint=shared-greeting
<?php
echo docs_shared_greeting();
```

```expected-output
Hello
```

The accepted forms are exactly php interactive, php interactive setup-blueprint=NAME, setup-blueprint, setup-blueprint NAME, and expected-output. Plain php fences remain ordinary documentation. Setup Blueprints must be JSON objects. The parser rejects malformed JSON, duplicate or shadowed definitions, ambiguous or unattached metadata, and unresolved references with source locations.

File-level definitions are inherited by functions, classes, methods, properties, and hooks. Class-level definitions are inherited by their properties and methods. Each descendant copies only the definitions it references.

Interactive fences become indexed <!-- wp-parser-code-snippet:N --> comments in long_description. The corresponding code_snippets entry contains type, code, and optional expected_output and blueprint; reusable definitions live in setup_blueprints. This lets the companion theme render snippets between the surrounding paragraphs instead of appending them afterward.

Screenshot

Actual rendering through the companion wporg-developer PR, including expected output and a shared setup Blueprint:

Developer.WordPress.org method reference page rendering interactive PHP examples with expected output and Blueprint-backed snippets

Backward compatibility

This can land before the theme change. Existing DocBlocks do not use php interactive, plain PHP fences remain in long_description, and unconsumed placement comments are invisible.

Testing

Generate parser JSON from DocBlocks containing plain and interactive PHP fences, expected output, inline and reusable setup Blueprints, nested Markdown indentation, and tag-looking PHP. Confirm the JSON preserves snippet order, object shapes, source text, and inherited definitions. Import it and open the corresponding reference page with the companion theme PR; confirm every snippet renders at its placement comment.

@sirreal

sirreal commented Jul 16, 2026

Copy link
Copy Markdown
Member

This makes all PHP-backtick fenced code examples runnable and editable? So the existing syntax highlighted code blocks are marked by extra indentation (I think they start with extra 4 spaces) and this form uses the php code fence.

I imagine folks will gradually migrate to code fences, especially since it allows for different languages to be indicated. Not all the snippets we have are PHP, and not all are even code.

What do you think of using a specific name for this like php-runnable as the code fence langauge name?

I looked at MDN and how they do different types of code examples. This doc is interesting.

What types of code example are on MDN?
There are four types of code examples available:

Static examples — Code blocks that display source code on a page.
Live samples — A macro takes code blocks from a page, combines them into an <iframe>, and embeds the iframe in the page to show the result. The published page displays the source code blocks and the results side-by-side.
Interactive examples — A macro renders source code onto the page and renders the results in a panel beside the source. Readers can edit the source code and re-run the example to see the effect of their changes.
GitHub embeds — A macro takes a document in a GitHub repo in the MDN organization, puts it in an <iframe> , and embeds it into the page to show the result.

@sirreal sirreal left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great start. My thoughts:

  • Use a special indicator for a runnable snippet, not just php.
  • Be more strict about what's allowed, e.g. only one form of info like setup-blueprint and not setup_blueprint, setupblueprint etc.

The expected-output is interesting, but it's just informative, right? It would be awesome to leverage that as part of the source test suite as a check on whether these snippets run as expected.

Comment thread lib/runner.php
Comment thread lib/runner.php Outdated
@sirreal

sirreal commented Jul 16, 2026

Copy link
Copy Markdown
Member

I think we'd also want either global blueprints or a default blueprint. It seems like practically everything want require '/wordpress/wp-load.php';.

@adamziel

Copy link
Copy Markdown
Author

The expected-output is interesting, but it's just informative, right? It would be awesome to leverage that as part of the source test suite as a check on whether these snippets run as expected.

It's supposed to be the default displayed output so that you can see the results without booting Playground. We could also use it in CI to keep all code examples working.

@adamziel

Copy link
Copy Markdown
Author

I looked at MDN and how they do different types of code examples. This doc is interesting.

I really like it, thank you for sharing! In particular, this syntax sounds like such a good idea:

```js interactive-example
const array1 = ["a", "b", "c"];
const array2 = ["d", "e", "f"];
const array3 = array1.concat(array2);

console.log(array3);
// Expected output: Array ["a", "b", "c", "d", "e", "f"]
```

If we adopted it, it could be:

```php interactive setup-blueprint=shared-greeting
<?php
require '/wordpress/wp-load.php';
echo docs_shared_greeting( 'first' );
```

I also like the metadata syntax 🤔

{{InteractiveExample("JavaScript Demo: Array.concat()", "shorter")}}

But it seems like too much. Maybe we could just do:

```php interactive setup-blueprint=shared-greeting title="Display greetings"
<?php
require '/wordpress/wp-load.php';
echo docs_shared_greeting( 'first' );
```

@adamziel adamziel changed the title Export method code snippets Export interactive PHP DocBlock snippets Jul 17, 2026
Comment thread lib/runner.php
@adamziel
adamziel force-pushed the adamziel/phpdoc-fences-json branch from f96f9fe to ca7c852 Compare July 20, 2026 12:24
@sirreal

sirreal commented Jul 20, 2026

Copy link
Copy Markdown
Member

I merged this (at 06ec2b1) with #250 and WordPress/wporg-developer#567. I made the changes below to the Core source to test the features and output.

It works great!

One of my examples fails because it doesn't load the WordPress environment. It seems like that should be default behavior for these examples. require '/wordpress/wp-load.php'; shouldn't be necessary in every snippet.

php -dmemory_limit=4G generate-json-manually.php -d /path/to/wordpress-develop/src -o /path/to/wporg/dev/env/wordpress-phpdoc.json
wp-env run cli wp -- parser import path/to/wordpress-phpdoc.json --user=1
Core diff
diff --git i/src/wp-includes/html-api/class-wp-html-processor.php w/src/wp-includes/html-api/class-wp-html-processor.php
index b2a597b938..f96068e80d 100644
--- i/src/wp-includes/html-api/class-wp-html-processor.php
+++ w/src/wp-includes/html-api/class-wp-html-processor.php
@@ -138,6 +138,10 @@
  * The parser does not implement the "maybe clone an option into selectedcontent" algorithm.
  * SELECTEDCONTENT elements may not reflect the actual selected content.
  *
+ * ```setup-blueprint require-wp
+ * {"steps":[{"step":"writeFile","path":"/wordpress/wp-content/mu-plugins/shared.php","data":"<?php require '/wordpress/wp-load.php';"}]}
+ * ```
+ *
  * @since 6.4.0
  *
  * @see WP_HTML_Tag_Processor
@@ -5431,14 +5435,28 @@ class WP_HTML_Processor extends WP_HTML_Tag_Processor {
 	 *
 	 * This generator function is designed to be used inside a "foreach" loop.
 	 *
-	 * Example:
+	 * ```php
+	 * <?php
+	 * echo 'not really a demo';
+	 * ```
 	 *
-	 *     $p = WP_HTML_Processor::create_fragment( "<div class='free &lt;egg&lt;\tlang-en'>" );
-	 *     $p->next_tag();
-	 *     foreach ( $p->class_list() as $class_name ) {
-	 *         echo "{$class_name} ";
-	 *     }
-	 *     // Outputs: "free <egg> lang-en "
+	 * ```php interactive
+	 * <?php
+	 * echo 'really a demo';
+	 * ```
+	 *
+	 * ```php interactive setup-blueprint=require-wp
+	 * <?php
+	 * echo 'doing things';
+	 * $p = WP_HTML_Processor::create_fragment( "<div class='free &lt;egg&lt;\tlang-en'>" );
+	 * $p->next_tag();
+	 * foreach ( $p->class_list() as $class_name ) {
+	 *   echo "{$class_name} ";
+	 * }
+	 * ```
+	 * ```expected-output
+	 * "free <egg> lang-en "
+	 * ```
 	 *
 	 * @since 6.6.0 Subclassed for the HTML Processor.
 	 */

@adamziel
adamziel force-pushed the adamziel/phpdoc-fences-json branch from 0db1f61 to 48099d1 Compare July 20, 2026 15:04
@adamziel
adamziel marked this pull request as ready for review July 20, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants