Skip to content

Export interactive PHP DocBlock snippets - #258

Merged
adamziel merged 14 commits into
WordPress:masterfrom
adamziel:adamziel/phpdoc-fences-json
Aug 13, 2026
Merged

Export interactive PHP DocBlock snippets#258
adamziel merged 14 commits into
WordPress:masterfrom
adamziel:adamziel/phpdoc-fences-json

Conversation

@adamziel

@adamzieladamziel commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

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-outputHello```

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

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.

@sirrealsirreal 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 threadlib/runner.php
Comment threadlib/runner.php Outdated
@sirreal

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
ContributorAuthor

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
ContributorAuthor

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' );
```

@adamzieladamziel changed the title Export method code snippetsExport interactive PHP DocBlock snippetsJul 17, 2026
Comment threadlib/runner.php
@adamziel
adamzielforce-pushed the adamziel/phpdoc-fences-json branch from f96f9fe to ca7c852CompareJuly 20, 2026 12:24
@sirreal

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
adamzielforce-pushed the adamziel/phpdoc-fences-json branch from 0db1f61 to 48099d1CompareJuly 20, 2026 15:04
@adamziel
adamziel marked this pull request as ready for review July 20, 2026 15:37
@adamziel
adamzielforce-pushed the adamziel/phpdoc-fences-json branch from d316a67 to e809487CompareAugust 13, 2026 10:48
adamziel added a commit to WordPress/wporg-developer that referenced this pull request Aug 13, 2026
Renders PHP examples parsed from DocBlocks as runnable WordPress
Playground snippets in their original positions in code reference
descriptions.
WordPress/phpdoc-parser#258 removes `php interactive` fenced examples
from `long_description`, stores the structured snippets and setup
Blueprints as post metadata, and leaves exact `<!--
wp-parser-code-snippet:N -->` placeholders behind. Without a theme-side
renderer, those examples disappear after import. This PR replaces the
placeholders with `<php-snippet>` elements, emits only the setup
Blueprints they reference, preserves Blueprint JSON object shapes
through post meta, and loads the web component only when a snippet is
rendered. A snippet whose declared Blueprint is missing or malformed
remains visible without a Run action. Metadata imported without a
placeholder is appended rather than dropped.
<img
src="https://github.com/adamziel/wporg-developer/releases/download/pr-screenshot-php-snippets-2026-06-08/actual-wporg-code-path-snippets-fixed.png"
alt="A code reference page showing three runnable PHP examples with
expected output" width="900">
## Testing
Import parser data from WordPress/phpdoc-parser#258, then open a code
reference page containing standalone, inline-Blueprint, and
shared-Blueprint examples. Confirm each snippet appears where its fence
was, valid snippets run and show their expected output, empty and
nested-empty Blueprint objects remain JSON objects, and a snippet with
an unusable Blueprint has no Run action.
@adamziel
adamziel merged commit 66f26b0 into WordPress:masterAug 13, 2026
2 checks passed
@adamziel
adamziel deleted the adamziel/phpdoc-fences-json branch August 13, 2026 11:22
Comment on lines +165 to +166
* <?php
* require '/wordpress/wp-load.php';

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.

It would be nice if every WP PHP code example didn't need to include these two lines. Note how GitHub doesn't require <?php to parse code as PHP:

echodocs_shared_greeting( 'second' );

The fact that it should be loading WordPress seems like it should be implied, no? Like the code could be presumed to be running in a WP-CLI wp eval context.

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.

I agree. Let's figure out how to do that. Maybe we just inject those lines to every snippet.

@adamziel any thoughts?

Sign up for freeto 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.

3 participants

@adamziel@sirreal@westonruter