Guide

Testing PHP documentation examples

Lint every PHP block, run complete examples in a clean directory, check Composer instructions in a scratch project and assert the results the page promises.

Published
Updated
November 10, 2026
Reading time
14 min
Facts checked against
PHP manual (command line options, assert), Composer basic usage, PHPUnit supported versions and PHPUnit 12.4 manual (November 10, 2026)
Outlined documentation pages with a lime pulse line running out of the stack
On this pageThe short answerWhy PHP documentation examples breakDecide what each block representsLint before executionRun complete examples in a clean directoryTest Composer instructions in a scratch projectExtract and lint PHP fencesKeep examples in a normal test suitePut PHP documentation checks in CITriage failures before assigning blameWhere DocsAlive fitsChecklistThe sources we checkedFAQ

The short answer

Test PHP documentation examples in layers: classify each block, lint copyable PHP, run complete examples in a clean directory, and compare stable results with assertions enabled. Check Composer instructions in a scratch project using the package and PHP versions the page names. Keep fragments, configuration, and examples requiring services visible as separate coverage decisions.

A syntax check is fast and useful, but it does not prove an example can load its dependencies or produce the documented result. A complete check follows the reader's setup, including the opening PHP tag, autoloading, namespaces, configuration, and any files created by earlier steps.

DocsAlive recognizes PHP fences but does not lint or run them. It reports them as skipped with not executable (php). Use PHP and, where appropriate, PHPUnit in your own CI; the product can still check supported blocks elsewhere on the page. See documentation code testing and README testing for related workflows.

Why PHP documentation examples break

A PHP example can look plausible in a browser while depending on details that disappear when a reader copies it into a new file. A missing <?php opening tag changes how a file is interpreted. A missing require 'vendor/autoload.php' leaves Composer-installed classes unavailable. A namespace or use statement may have existed in the author's source file but not in the published snippet.

Version drift can affect both code and installation. A function may have changed or been removed between PHP releases. A Composer package name or constraint may no longer represent the package the page intends readers to install. A test against a maintainer's existing vendor directory can hide a mistake in the page's installation instructions.

Runtime configuration is another dependency. An example might rely on a particular php.ini setting or an enabled capability without saying so. A local run that happens to inherit the right setting does not establish that a new reader can reproduce the result. State configuration requirements and test them deliberately.

These problems affect different documentation surfaces. A README usually needs a short path from installation to a first result. SDK documentation may assume a Composer project and a particular version of the library. An API reference may intentionally show just a method call. Each is useful, but the test must know which setup and surrounding code the reader is expected to have.

Finally, a successful process exit is not the same as correct output. If the page promises a value, verify that value. If it shows a representative response with fields that can vary, identify and check the stable parts.

Decide what each block represents

Before extracting PHP fences, record whether each block is meant to stand alone. This prevents a valid fragment from being judged as a complete file and prevents a complete example from passing only because the test silently supplies missing context.

Block type What the page promises Useful check
Complete PHP file A reader can save and run it Lint, then execute in a clean directory
Fragment A reader inserts it into existing PHP code Lint and run it in the documented context
Composer configuration A project can install the stated dependency Run Composer in a scratch project with the stated constraint
Command A reader runs it in a shell Check the command and required tools separately
Output The program produces a result Compare stable output or assert the promised value
Illustrative or broken code The page explains an idea or failure Label it clearly and do not treat it as a successful program

For a fragment, document the enclosing function, class, namespace, or prior setup that makes it meaningful. Prepending an opening tag can make a fragment parse as PHP, but it does not prove that variables, imports, or dependencies are present. Similarly, a file can pass lint while containing code that fails only when executed.

When a page offers several alternatives, test each supported path independently. Do not combine a Composer setup from one tab with source from another unless the page tells the reader to do so. For a multi-step tutorial, preserve the files and state created by earlier steps within the documented path.

Lint before execution

The PHP CLI can check syntax without executing code. Save this complete example as hello.php:

<?php
declare(strict_types=1);

$message = 'Documentation examples should run';
echo $message, PHP_EOL;

Run:

php -l hello.php

A successful lint reports that no syntax errors were detected. A parse failure produces a nonzero status. The PHP CLI options manual describes -l as a syntax check that does not execute the file.

That distinction matters. Lint cannot establish that vendor/autoload.php exists, that an SDK class is installed, or that an API call returns the right response. It can also accept a file whose intended PHP text is outside PHP tags as plain output. The extraction script later in this guide adds an opening tag when one is absent so that a fence intended as PHP is actually parsed as PHP.

Use the PHP versions your page supports. A successful lint on a newer CLI does not establish that the same source parses on an older supported runtime. Record the version and any configuration needed to reproduce a failure.

Run complete examples in a clean directory

For a dependency-free example, start with an empty directory, copy the file there, and run it:

work_dir="$(mktemp -d)"
trap 'rm -rf "$work_dir"' EXIT
cp hello.php "$work_dir/hello.php"
cd "$work_dir"
php hello.php

The expected output is:

Documentation examples should run

A scratch directory exposes reliance on files that exist only in the author's working tree. It does not automatically isolate PHP configuration or installed runtime capabilities. Record the PHP version and relevant settings separately, and supply only the files the documentation tells the reader to create.

For examples that need a package, a clean directory must also include the documented installation step. Do not make a check pass by copying an unexplained vendor directory into place. If the page assumes a preexisting application, say so and test in a representative application; if it promises a new project, start with a new project.

Check output with assertions

Save this separate complete file as output_check.php:

<?php
declare(strict_types=1);

$actual = strtoupper('hello');
assert($actual === 'HELLO', 'Unexpected result');
echo $actual, PHP_EOL;

Run it with assertions explicitly enabled:

php -d zend.assertions=1 output_check.php

The expected output is HELLO. With assertions enabled, a false assertion raises an assertion error. PHP can be configured so assertions are not executed, so the command sets zend.assertions=1 for this check. The PHP assert() manual describes the behavior of that setting.

Choose assertions that correspond to promises in the page. For a deterministic calculation, exact equality is useful. For a response with changing identifiers or timestamps, assert the stable fields and explain the variable fields to readers. If a page shows exact output, decide whether that exactness is part of the promise before weakening a comparison.

Assertions do not replace a test of the documented command. Run the same entry point and setup a reader is told to use. If a test adds configuration that the example needs, either document that configuration or explain why the test's setting is only part of the verification.

Test Composer instructions in a scratch project

Composer instructions have their own failure modes: the package name, version constraint, PHP compatibility, lock file, repository configuration, and autoload step can each affect the result. The Composer basic usage guide describes require, composer install, the lock file, and vendor/autoload.php.

The following is a conceptual composer.json for the fictional Acme SDK. acme/acme-sdk is a fictional package name, so this is not a working installation command and should not be used to claim that a real package resolves.

{
  "require": {
    "acme/acme-sdk": "^1.0"
  }
}

For a real SDK, create a fresh scratch directory, write the actual documented composer.json, and run composer install there. If a lock file is supplied, composer install uses it; decide whether your documentation promises the locked dependency set or a fresh resolution from the stated constraints. Record the resulting versions and the PHP version.

A complete SDK example commonly needs the generated autoloader:

<?php
require 'vendor/autoload.php';

// Continue with the installed package's documented API.

This file requires a Composer installation that has created vendor/autoload.php; it is not a standalone standard-library example. The comment is intentionally a placeholder for the real package's code. For an actual page, include the appropriate namespace and use statements, then execute the complete example against the intended package version.

Test the installation and the code together. A source file may be correct against an SDK already installed on a developer's machine while the published package name or constraint is wrong. Conversely, a successful install does not prove the API calls in the next block are current.

Separate configuration from accidental local state

If an example depends on a PHP setting, identify the setting and its required value. Test with that value explicitly and, where useful, test what happens without it. The goal is to make the reader's prerequisite visible, not to assume a particular machine's configuration.

Likewise, distinguish a documented fixture from an accidental file. A tutorial may reasonably ask the reader to create an input file in an earlier step. A clean test should follow that step in order. If the page never explains how to obtain the file, the failure is a prompt to improve the instructions.

Extract and lint PHP fences

The following Python 3 standard-library script extracts simple triple-backtick php fences from one Markdown file. It prepends <?php when the block does not already start with that tag, writes each block to a temporary file, and runs php -l. It does not execute the PHP.

Save it as lint_php_fences.py:

import pathlib
import re
import subprocess
import sys
import tempfile

if len(sys.argv) != 2:
    raise SystemExit("Usage: python lint_php_fences.py PAGE.md")

page = pathlib.Path(sys.argv[1])
text = page.read_text(encoding="utf-8")
blocks = re.findall(
    r"(?m)^```php[ \t]*\r?\n(.*?)^```[ \t]*$",
    text,
    flags=re.DOTALL,
)
failed = False

with tempfile.TemporaryDirectory() as temporary:
    root = pathlib.Path(temporary)
    for number, source in enumerate(blocks, start=1):
        if not source.lstrip().startswith("<?php"):
            source = "<?php\n" + source
        source_file = root / f"block_{number}.php"
        source_file.write_text(source, encoding="utf-8")
        result = subprocess.run(
            ["php", "-l", str(source_file)],
            check=False,
        )
        if result.returncode:
            print(f"Block {number}: lint failed")
            failed = True
        else:
            print(f"Block {number}: lint passed")

if not blocks:
    print("No simple php fences found")
if failed:
    raise SystemExit(1)

Run it with Python and the PHP CLI available:

python3 lint_php_fences.py docs/page.md

This script deliberately has a narrow scope. It handles unindented fences labeled exactly php, not every Markdown or MDX fence form. It does not understand tab groups, setup steps, or intentional broken examples. A fragment that is valid only inside a class or function may fail when parsed as a file; review the documented context before changing the page.

Prepending the tag is useful for checking PHP syntax, but it can conceal a missing tag in a block that claims to be a complete file. Review complete examples for the tag separately. A passing lint also says nothing about whether the file can load Composer dependencies or run correctly.

For a larger documentation set, maintain a manifest that identifies complete files, fragments, intentional failures, fixtures, and expected outputs. Use the manifest to select the right test rather than making a regular expression infer every author's intent.

Keep examples in a normal test suite

Some documentation examples are easier to maintain as project tests, especially when they require an SDK, fixtures, and several assertions. PHPUnit provides a place to run such examples alongside the code they describe. Keep a clear connection between the tested source and the published snippet so one cannot silently change without the other.

Select a PHPUnit release compatible with the PHP runtime under test. According to the PHPUnit supported versions page, PHPUnit 12 requires PHP 8.3 or later, and PHPUnit 13 requires PHP 8.4 or later. The PHPUnit 12.4 manual describes writing tests and expressing requirements.

Runtime under test Example test-suite choice What to verify
PHP 8.3 A compatible PHPUnit 12 setup The example works on the stated runtime and dependencies
PHP 8.4 A compatible PHPUnit 12 or 13 setup The chosen suite and package constraints install together
Another supported PHP version A PHPUnit release compatible with that version The page's minimum and the test runner's requirements both hold

A test suite can verify behavior in depth, but it should not hide the reader's path. If the documentation tells readers to install a package and run a file, also test that path in a clean project. A unit test with elaborate fixtures can pass while the README omits an autoloader or gives a wrong installation command.

Put PHP documentation checks in CI

A CI job can lint each selected fence, execute complete dependency-free examples, and run project tests for examples that need dependencies. The following is a plain GitHub Actions outline. It assumes the named files exist in the repository.

name: PHP documentation examples

on:
  pull_request:
  push:
  workflow_dispatch:

jobs:
  php-docs:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # Add a step that installs PHP 8.3 or later on the runner.
      - name: Lint PHP fences
        run: python3 lint_php_fences.py docs/page.md
      - name: Run the output check
        run: php -d zend.assertions=1 output_check.php

Supply an actual PHP installation step for your runner before using the outline. Test the minimum runtime your project supports and add other supported versions as appropriate. The outline's lint script does not run Composer or PHPUnit; add those commands in the project that contains the real dependency and test configuration.

Run relevant checks when docs, public APIs, or dependency constraints change. Before release, test the artifact intended for readers where practical. A scheduled run can reveal dependency drift, while a post-release check can verify the published install path. See GitHub Actions documentation testing and installation documentation testing for related practices.

Triage failures before assigning blame

Keep the PHP version, command, settings, dependency versions, and relevant output with each result. Those details help distinguish a page defect from an environment or test-context problem.

Finding Possible cause Next action
Lint fails on a complete file Invalid syntax or unsupported PHP version Reproduce on the stated runtime and fix the source or version guidance
PHP text prints instead of running Opening tag is missing from a file Add the tag or clarify the intended insertion context
Class is unavailable Autoloader, namespace, import, or dependency is missing Follow the page's setup in a clean project
Composer cannot resolve a package Name, constraint, repository, platform, or network issue Check the documented package and capture the resolver's explanation
Function is unavailable Runtime version or required capability differs State and test the actual prerequisite
Fragment fails by itself The test omitted its enclosing context Test the context described on the page
Output differs Stale expected output or variable data Compare stable behavior and update the page or assertion
Live service is unavailable Credentials or an external dependency are absent Record the gap and use an authorized live check when needed

A fragment is not defective solely because it is not a complete file. It becomes a documentation problem when the reader is not told enough to use it in the intended context. Similarly, a package-resolution failure needs investigation before it is attributed to the package name: the runtime, constraints, and network can all matter.

Where DocsAlive fits

PHP fences are recognized but skipped by DocsAlive with the reason not executable (php); the supported stacks page lists PHP among the languages that are recognized but not executed yet. They are not linted or executed, so use php -l, php, and a suitable project test suite in your own CI for PHP coverage.

Other blocks on the same page are still handled according to their language. Shell blocks and curl requests can run; JSON and TOML blocks are syntax-checked. A shell block that invokes php in the default runner image would be reported as missing-tool, because the default image is not a PHP image. Review the skip and finding reasons alongside your own PHP results.

This division is useful when a page mixes installation commands, configuration, output, and PHP source. It gives reviewers a visible record of what the product checked and what the PHP-specific job must cover. See findings for the report categories. To see how a scan reports one of your pages, you can request a health check for a public repository you own or maintain. For the same pattern in another language, see testing Java documentation examples.

Checklist

Copy this list into a pull request or release checklist:

  • [ ] Classify each PHP block as a complete file, fragment, configuration, command, or output.
  • [ ] State the supported PHP version and test the minimum version.
  • [ ] Check opening tags in complete files and lint PHP fences.
  • [ ] Review namespace, use, and autoload requirements in the reader's context.
  • [ ] Run complete examples in a clean directory with only documented files and setup.
  • [ ] Enable assertions explicitly when using assert() to check output.
  • [ ] Install real Composer dependencies in a scratch project using the documented constraints.
  • [ ] Record relevant PHP settings and test them deliberately.
  • [ ] Keep PHPUnit examples connected to the published source.
  • [ ] Record fragments, fixtures, credentials, and other skipped coverage.

The sources we checked

These sources were read on November 10, 2026.

Source What it covers
PHP CLI options Syntax checking with php -l
PHP assert() Assertion behavior and zend.assertions
Composer basic usage Requirements, installation, lock files, and autoloading
PHPUnit supported versions PHPUnit and PHP version requirements
PHPUnit 12.4 manual Writing and organizing tests
Supported stacks Product language coverage and limitations

FAQ

Does php -l run an example?

No. It checks syntax without executing the file. Run complete examples separately to check dependencies, runtime behavior, and output.

Should every PHP fence include an opening tag?

A complete file intended to be saved and run should include the tag. A fragment may omit it if the page clearly explains where the fragment belongs.

Why set zend.assertions when checking output?

Assertions can be configured not to execute. Running with php -d zend.assertions=1 makes an assert-based documentation check exercise its assertions.

Does a successful Composer install prove an SDK example works?

No. It verifies dependency resolution for the tested project and environment. Run the documented source with its autoloader, imports, and configuration to check behavior.

Which PHP versions do PHPUnit 12 and 13 require?

PHPUnit 12 requires PHP 8.3 or later; PHPUnit 13 requires PHP 8.4 or later. Choose a test runner compatible with each runtime in your support matrix.

Can DocsAlive lint PHP fences?

No. PHP fences are reported as skipped with the reason not executable (php). Use your own PHP checks while reviewing the product's results for other supported blocks on the page.

Keep reading
Outlined documentation pages with a lime pulse line running out of the stackGuide

Docs Testing: How to Test the Code Samples in Your Docs

Why code examples in documentation break, the ways teams test them (doctest, docs-as-tests, CI runners) and how to run every sample on each release.

Two lenses held over the same documentation page, each bringing a different layer into focusComparison

Vale vs markdownlint: What Each Docs Linter Catches

Vale checks prose style; markdownlint checks Markdown structure. What each catches, sample configs, running both in CI and what neither one tests.

Blueprint of two documentation pages: one with a handwritten test script, one whose code blocks are framed and checkedComparison

Doc Detective vs DocsAlive: Testing the Code in Your Docs

Doc Detective is a free, open-source docs-as-tests framework; DocsAlive is a hosted docs CI for code examples. Features, price, setup and when to use each.

A machine blueprint above a ruled documentation page, with a caliper measuring between themComparison

Promptless Alternative: Promptless vs DocsAlive Compared

Promptless drafts docs updates from what you ship; DocsAlive runs the code examples you've published. Features, pricing, security and when to use each.

A long workbench of outlined testing instruments, each with a small lime light, beside a stack of documentation pagesGuide

Best Free Tools to Test Code Examples in Documentation

Free, open-source tools that run the code examples in your docs: rustdoc, Go examples, doctest, pytest plugins, Sphinx doctest, Doc Detective and byexample.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing SDK Docs Before Every Release: A Release-Gate Guide

Test SDK and client-library docs against the package you ship: build the wheel or npm tarball, install it clean, run every example, and gate the release in CI.

Outlined documentation pages with a lime pulse line running out of the stackGuide

How to Test Quickstarts and Tutorials in Your Docs

Test the onboarding path a new reader follows: run quickstarts in a clean environment, in page order, sort real bugs from credentials and fragments, and wire it into CI.

Outlined documentation pages with a lime pulse line running out of the stackGuide

How to Test CLI Documentation: Commands, Flags and Output

Test the commands a CLI page promises: install in a clean container, run examples in order, compare flags with --help, check exit codes and output, and run it in CI.

Outlined documentation pages with a lime pulse line running out of the stackGuide

How to Test API Reference Docs: Requests, Responses and SDKs

Test API reference pages against the contract they publish: methods, parameters, request and response examples, SDK snippets, status codes and OpenAPI samples.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Docs testing for API-first companies

Keep API quickstarts, SDK snippets and curl examples working across releases.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Docs testing for CLI and developer-tool vendors

Test developer-tool install paths, package managers and command examples across releases.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing README Code Examples: Install, Usage and CI

Keep README install commands and usage examples working with clean environments, output checks and CI.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Notebooks and Executable Docs: Cells, Kernels and Output

Test notebooks in fresh kernels, check meaningful output and manage dependencies, data and drift in CI.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Docs in GitHub Actions: Pull Requests and Scheduled Checks

Run documentation example checks in GitHub Actions with practical guidance on schedules, path filters, caching, secrets and reports.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Code Examples in MkDocs, Docusaurus and Sphinx Sites

Find and test documentation examples in Markdown, MDX and reStructuredText, alongside site builds and link checks.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Python Docs Testing: Doctest, Pytest and Markdown

Test Python documentation with doctest, pytest, Markdown example execution and isolated environments.

Outlined documentation pages with a lime pulse line running out of the stackGuide

JavaScript Docs Testing: Node, TypeScript and Modules

Test Node and TypeScript documentation examples across module formats, package versions and type checks.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Go documentation examples

Test Go example functions and Markdown snippets against the intended module versions in clean environments.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Sphinx Docs Testing: doctest, code-block and MyST

Test Sphinx documentation examples: what the doctest and linkcheck builders prove, code-block and literalinclude, MyST fences, and what a Markdown scan covers.

Outlined documentation pages with a lime pulse line running out of the stackGuide

ReadMe Docs Testing: CLI, OpenAPI and Code Examples

Test ReadMe documentation examples: docs-as-code and bi-directional sync, what lint and oas:validate prove, Tabs variants and runnable code.

Outlined documentation pages with a lime pulse line running out of the stackGuide

TypeScript Docs Testing: tsc --noEmit and Runtime

Test TypeScript documentation examples: type-check snippets with tsc --noEmit in a consumer project, then run them, and what each check proves.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Docusaurus Docs Testing: Code Blocks, Tabs and MDX

Test Docusaurus documentation examples: builds vs runnable code, code block metadata, Tabs install variants, MDX pages and clean-container runs.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Mintlify Docs Testing: CodeGroup, Tabs and the CLI

Test Mintlify documentation examples: docs.json, MDX pages, CodeGroup and Tabs variants, what mint broken-links and mint validate prove, and clean runs.

Outlined documentation pages with a lime pulse line running out of the stackGuide

MkDocs Docs Testing: Code Examples, Tabs and Snippets

Test MkDocs documentation examples: strict builds, Material tabs, Snippets includes, mkdocstrings pages and clean-container runs.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Installation Instructions: pip, npm and Containers

Test documented install steps in clean environments: pip and extras, venv, npm install vs npm ci, containers and the first import.

Outlined documentation pages with a lime pulse line running out of the stackGuide

AI SDK Docs Testing: LLM Examples, Keys and Drift

Test AI and LLM SDK documentation examples: requests, responses, streaming, tool calls, credentials, model retirements and CI.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing migration guides and changelog examples

Test before and after examples against the right releases and make migration documentation part of the release gate.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Docs Testing Checklist: Code Examples Before Release

A practical pre-release checklist for testing documentation examples, reviewing gaps and verifying published packages.

Outlined documentation pages with a lime pulse line running out of the stackGuide

The Cost of Broken Code Examples in Documentation

Understand how broken documentation examples create friction in developer adoption and support, and how to reduce it.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Rust documentation examples

Test Rust rustdoc examples and cargo documentation tests, and keep native Rust checks because compiled Rust fences are recognized but not executed.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing VitePress documentation examples

Test VitePress Markdown examples separately from the site build, including code groups, includes and what a plain Markdown scan covers.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing docs in GitLab CI

Run documentation example checks in GitLab CI jobs, keep site builds separate from execution, and review what a scan covers and skips.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing JSON, TOML and YAML examples in docs

Test JSON, TOML and YAML config examples in documentation: parse, schema and tool checks, honest fence labels, fragments, CI jobs and what a scan covers.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing Java documentation examples

Test Java examples in READMEs, SDK docs and Javadoc: the source launcher, javac, JShell scripts, assertions, @snippet files, CI and what a scan covers.

Outlined documentation pages with a lime pulse line running out of the stackGuide

Testing docs examples that need API keys

Test credential-gated docs examples: honest placeholders, offline stubs and fixtures, live sandbox jobs, CI secrets and fork rules, and how a scan classifies them.

A machine blueprint above a ruled documentation page, with a caliper measuring between themGuide

Documentation Drift: How to Detect It Before Users Do

What documentation drift is, why code examples drift first, and practical ways to detect it: ownership, release checks, link and execution tests.

See which of your code samples still run.

Send us a public repository. Our team runs DocsAlive on its docs and emails you the real report within 2 business days. Free, no signup.