Skip to content

Refactor Examples - #36

Merged
jakejackson1 merged 1 commit into
mainfrom
refactor-examples
Aug 21, 2026
Merged

Refactor Examples#36
jakejackson1 merged 1 commit into
mainfrom
refactor-examples

Conversation

@jakejackson1

@jakejackson1 jakejackson1 commented Dec 11, 2022

Copy link
Copy Markdown
Member

Modernise and refactor the examples in the /examples/ directory, repair the ones that no longer ran, and add CI so they cannot rot again unnoticed.

Pull Request type

Please check the type of change your PR introduces:

  • Bugfix
  • Feature
  • Code style update (formatting, renaming)
  • Refactoring (no functional changes, no API changes)
  • Build-related changes
  • Documentation content changes
  • Other (please describe):

What is the current behavior?

Most of the examples were written between 2009 and 2012 and had not kept up. They sat loose in /examples/ with no organisation, and a good number of them simply did not run:

  • rss.php required ../src/QueryPath/QueryPath.php — removed years ago.
  • sparql.php referenced an unqualified QueryPath class, fatal since the move to namespaces.
  • techniques.php demonstrated eachLambda(), which calls create_function() and cannot run on PHP 8.
  • odt.php read from a tmp/ directory that was never in the repository, and its .odt fixture had at some point been truncated to zero bytes.
  • The remote examples had been outrun by the sites they call: Wikipedia, Urban Dictionary, and api.jquery.com now reject requests that send no User-Agent, and MusicBrainz answers back-to-back requests with a 503.

Nothing checked any of this, so the breakage was only discoverable by running an example and watching it fail.

What is the new behavior?

Structure

Every example is now its own directory with an index.php and its own fixtures. The seven that were still loose have been converted to match:

Was Now
simple_example.php hello-world/
techniques.php iterating-over-matches/
svg.php create-svg-document/
rss.php generating-rss-feed/
odt.php basic-odt-parser/
parse_php.php parsing-php-source/
sparql.php sparql-endpoint-query/

All 17 examples run clean.

Repairs

Beyond the four dead scripts above, the examples that had already been converted needed work too:

  • User-Agent. The Wikipedia, Urban Dictionary, and jQuery API examples now identify themselves. Worth knowing for anyone hitting the same wall: html5qp() fetches URLs through masterminds/html5, which takes no stream context, so the context option is silently ignored there. That example fetches the page itself and parses the markup as a string.
  • jQuery's api.xml is currently served truncated (the etag confirms the file is short at the source, and it ends mid-tag). That example parses in recovery mode and uses the 331 entries that do arrive.
  • The docx parser resolved its fixture relative to the working directory, so it only ran from inside its own folder.
  • Off-by-one in the Urban Dictionary example. It picked a random index with random_int(0, 7) from a list of seven, so one run in eight selected nothing and printed an empty word. It now counts the matches and guards the empty case. Found by the new test suite, not by reading.
  • The cURL example has been rewritten against a different data source. It queried MusicBrainz, which throttles by IP address — so it returned a 503 from any shared address, a CI runner or an office alike, no matter how politely it asked. It now uses the PubMed E-utilities API, which keeps the two-request REST shape the example exists to demonstrate (esearch returns IDs, esummary returns the records) and adds a good use of attribute selectors, since PubMed names its fields with a Name attribute rather than a tag per field. It also retries when an API asks it to slow down.

Error paths ended with die($message), which exits 0 — so a caught QueryPath\Exception looked like success to anything checking exit codes. They now echo the message and exit(1), which reads the same in a browser and is correct on the command line.

Tests

QueryPathTests\ExamplesTest runs every offline example in a subprocess and asserts it exits cleanly, emits no PHP diagnostic, and produces a reasonable amount of output. It is part of the unit suite, so the examples are now exercised on every supported PHP version on every pull request without a separate workflow.

The examples that call third-party services are excluded from that suite, so it stays fast and deterministic. The new Examples workflow runs those weekly, on demand, and whenever an example changes. It is a blocking check — the examples retry when an API asks them to slow down, so a failure there means an example genuinely needs attention rather than a remote host having a slow minute.

The split is a single list, ExampleRunner::NETWORK_EXAMPLES. Anything not on it is treated as offline and run with allow_url_fopen disabled, so a new example that needs the network but is missing from the list fails immediately rather than quietly becoming flaky.

To run them by hand:

composer run test:examples           # all 17
composer run test:examples:network   # only the ones that need a remote service

Documentation

examples/quickstart-guide.md described the qp.php autoloader and claimed that qp() and htmlqp() do not work under Composer — neither has been true for some time, and it made no mention of html5qp() at all. It has been rewritten and now indexes every example with a note on what it covers. examples/README.md is a short landing page pointing at it, so the list is maintained in one place.

Housekeeping

  • Removed the unreferenced The_Beatles.rdf, testGrid.html, and out.svg fixtures.
  • Fixed the retrival typo in the curl example's directory name.
  • Added ext-dom and ext-simplexml to require, and ext-zip to require-dev (the docx and odt examples need it).
  • Corrected two docblock types in src/DOMQuery.php.

Does this introduce a breaking change?

  • Yes
  • No

No library code changed. The only edits under src/ are two docblock corrections.

Other information

Two library bugs surfaced while writing these examples. Both are filed separately rather than fixed here, since this PR is deliberately confined to examples/, tests, and CI:

On the earlier thought about moving the examples into their own repository: the new test suite ties them to the library's PHP version matrix, which is worth more while they are in-tree. Splitting them out later would mean giving that up or rebuilding it.

Verified: 319 tests pass, composer run lint and lint:min-php clean across all 77 files, all 17 examples run.

@codecov

codecov Bot commented Dec 11, 2022

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.44%. Comparing base (e36b7b7) to head (296d828).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##               main      #36   +/-   ##
=========================================
  Coverage     89.44%   89.44%           
  Complexity     1342     1342           
=========================================
  Files            26       26           
  Lines          3023     3023           
=========================================
  Hits           2704     2704           
  Misses          319      319           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@jakejackson1
jakejackson1 force-pushed the refactor-examples branch 2 times, most recently from 8cf04b0 to ee2e99f Compare December 12, 2022 21:58
@jakejackson1
jakejackson1 force-pushed the refactor-examples branch 2 times, most recently from d84beb9 to 61ad4a9 Compare August 21, 2026 04:16
Reorganise every example into its own subdirectory with an index.php, fix the
ones that no longer ran, and add CI so they cannot rot again unnoticed.

Structure
---------

Each example is now a directory with an index.php and its own fixtures, so the
whole thing can be browsed, and the seven that were still loose in examples/
have been converted to match:

  simple_example.php -> hello-world/
  techniques.php     -> iterating-over-matches/
  svg.php            -> create-svg-document/
  rss.php            -> generating-rss-feed/
  odt.php            -> basic-odt-parser/
  parse_php.php      -> parsing-php-source/
  sparql.php         -> sparql-endpoint-query/

Repairs
-------

Most of the remaining examples were dead. rss.php required the long-removed
qp.php; sparql.php referenced an unqualified QueryPath class; techniques.php
demonstrated eachLambda(), which calls create_function() and so cannot run on
PHP 8; odt.php read from a tmp/ directory that never existed, and its .odt
fixture had been truncated to zero bytes.

The examples that had already been converted had since broken too:

- Wikipedia, Urban Dictionary, and api.jquery.com now reject requests with no
  User-Agent. Those examples send one. html5qp() fetches URLs through
  masterminds/html5, which takes no stream context, so the Urban Dictionary
  example fetches the page itself and parses the markup as a string.
- jQuery's api.xml is currently served truncated, so that example parses in
  recovery mode and uses the entries that do arrive.
- The docx parser resolved its fixture relative to the working directory.
- The Urban Dictionary example picked a random index with random_int(0, 7)
  from a list of seven, so one run in eight selected nothing. It now counts
  the matches and guards the empty case.
- The cURL example queried MusicBrainz, which throttles by IP address and so
  answered with a 503 from any shared address - a CI runner, an office. It has
  been rewritten against the PubMed E-utilities API, which keeps the two-request
  REST shape the example is there to demonstrate and adds a nice use of
  attribute selectors, since PubMed names its fields with a Name attribute
  rather than a tag per field.

Error paths ended with die($message), which exits 0 - a caught QueryPath
exception looked like success. They now echo the message and exit(1), which
reads the same in a browser and is correct on the command line.

Tests
-----

QueryPathTests\ExamplesTest runs every offline example in a subprocess and
asserts it exits cleanly, emits no PHP diagnostic, and produces a reasonable
amount of output. It is part of the unit suite, so the examples are exercised
on every supported PHP version on every pull request.

The examples that call third-party services are excluded from that suite, so
it stays fast and deterministic. The new Examples workflow runs those weekly,
on demand, and whenever an example changes. It is a blocking check: the
examples retry when an API asks them to slow down, so a failure there means an
example genuinely needs attention. The split is one list in
ExampleRunner::NETWORK_EXAMPLES; anything not on it is treated as offline and
runs with allow_url_fopen disabled, so a new example that needs the network
but is missing from the list fails immediately rather than becoming flaky.

composer run test:examples runs the whole set locally.

Documentation
-------------

examples/quickstart-guide.md described the qp.php autoloader and claimed the
qp()/htmlqp() functions do not work under Composer, neither of which has been
true for some time. It has been rewritten and now indexes every example with a
note on what it covers. examples/README.md is a short landing page pointing at
it, so the list is maintained in one place.

Also drop the unreferenced The_Beatles.rdf, testGrid.html, and out.svg
fixtures, fix the "retrival" typo in the curl example's directory name, add
ext-dom and ext-simplexml to require (ext-zip to require-dev), and correct two
docblock types in src/DOMQuery.php.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jakejackson1
jakejackson1 marked this pull request as ready for review August 21, 2026 04:26
@jakejackson1
jakejackson1 merged commit e23fbb0 into main Aug 21, 2026
15 checks passed
@jakejackson1
jakejackson1 deleted the refactor-examples branch August 21, 2026 04:26
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.

1 participant