syntaxai/tdd.md · main · content / blog / sama-v2-wordpress-plugin-audit.md

sama-v2-wordpress-plugin-audit.md 149 lines · 12776 bytes raw · source

Pointing SAMA v2 at a real WordPress plugin in the wild

Earlier today I published /sama/v2/example-wordpress — a hypothetical event-registration plugin laid out under the v2 discipline. Hypothetical examples are easy: you design the layers and then describe a codebase that fits them. The harder question is what v2 sees when you point it at code that wasn't designed under any layer discipline at all.

So I picked one. Open Graph and Twitter Card Tags by WPExperts (the plugin formerly known as Wonderm00n's, slug wonderm00ns-simple-facebook-open-graph-tags) — 200k+ active installs, ten-plus years of git history, the canonical "small useful plugin" shape that WordPress is full of. Downloaded the latest release (v3.4.0), unzipped, walked the source.

This isn't a takedown. The plugin works. It ships. It does exactly what its name says. The question this post answers is: if a PHP-aware SAMA v2 verifier existed, what would it report against this codebase?

What's actually in the box

Total: 6,445 lines of PHP across 17 source files (excluding the WordPress index.php security stubs and the vendored Post-SMTP-recommendation library).

The architecture, at a glance:

wonderm00n-open-graph.php                                 83 lines   # WP entry — header, includes, uninstall hook
fbimg.php                                                224 lines   # standalone helper (referenced from a URL)
includes/
  class-webdados-fb-open-graph.php                       674 lines   # CORE: hook registration, options loading
admin/
  class-webdados-fb-open-graph-admin.php                 784 lines   # ADMIN: settings save, FB debug call
  options-page-facebook.php                              556 lines   # admin UI tab (HTML + HTTP + JSON parsing)
  options-page-general.php                               486 lines   # admin UI tab
  options-page-3rdparty.php                              386 lines   # admin UI tab
  options-page-schema.php                                261 lines   # admin UI tab
  options-page.php                                       246 lines   # admin UI wrapper
  options-page-twitter.php                               188 lines   # admin UI tab
  options-page-seo.php                                   123 lines   # admin UI tab
  options-page-right.php                                  73 lines   # admin UI sidebar
  options-page-tools.php                                  51 lines   # admin UI tab
  options-page-recommend-post-smtp.php                    34 lines   # admin UI tab
public/
  class-webdados-fb-open-graph-public.php              1,554 lines   # PUBLIC: the actual og:* + twitter:* + schema rendering

Three god-classes (core, admin, public), each owning a vertical slice of feature work end-to-end. Around them, eleven options-page-*.php files that mix HTML, business logic, HTTP, and JSON parsing in the same callback. No layer separation, no test files anywhere in the source (zero *test*.php files in the entire archive).

This is normal WordPress. Most plugins in the directory look like this.

What the v2 verifier would report

Walking each of the seven §4 conformance checks against what's in the source:

#1 Sorted

Every file carries a profile-recognised prefix; lexicographic prefix order equals layer order.

The plugin has no prefix scheme at all. File names follow class-vendor-feature-area.php and options-page-tab.php patterns; their alphabetical sort has no relation to architectural layer. Would fail on every non-index source file.

#2 Architecture

Every file maps to exactly one canonical layer; no file is unprefixed or maps to two layers.

Without a profile mapping any prefix to a canonical layer, every file would be flagged unprefixed. Even if we tried to retrofit a profile — say class-*-public.php → Layer 1, class-*-admin.php → Layer 1 — the single file behind that pattern does enough Layer 2 work (HTTP, DB, JSON parsing) that the mapping wouldn't be honest.

#3 Modeled (tests)

Every Layer 1 and Layer 2 behavior file has a sibling test file.

Zero test files in the archive. The plugin has no unit tests at all — grep -r 'PHPUnit\|TestCase' /tmp/wonderm00ns-* returns nothing. Would fail the moment any source file is mapped to Layer 1 or 2.

#4 Modeled (boundary)

External input is parsed only in Layer 2.

This is where it gets interesting. Boundary patterns are scattered, not localised:

  • HTTP outboundwp_remote_get(...) lives in admin/class-webdados-fb-open-graph-admin.php:464 (calling Facebook's Graph debug API) AND in admin/options-page-facebook.php:307 (calling Facebook's translation XML) AND in public/class-webdados-fb-open-graph-public.php:1301 (fetching remote images). Three different files, three different layers conceptually.
  • JSON parsingjson_decode lives in admin/class-webdados-fb-open-graph-admin.php:475 AND admin/options-page-facebook.php:364. Mixed with HTML output in the second case.
  • Filesystem readsfile_get_contents() against a bundled XML file in admin/options-page-facebook.php:339. Same file mixes this with HTTP, JSON parsing, and direct HTML echoing.
  • Superglobals43 raw $_POST / $_GET / $_REQUEST accesses across the codebase. Most of them in admin classes, but the pattern is "wherever a hook needs to read the request, just touch the superglobal directly."

A boundary-ratio metric here would be near-zero: parsing happens almost anywhere except in a dedicated adapter layer (because there isn't one).

#5 Atomic

No file exceeds the line cap (default ~700; profile may lower, never raise). No barrel re-export files.

Three files over the 700-line cap:

  • public/class-webdados-fb-open-graph-public.php1,554 lines (220% of cap)
  • admin/class-webdados-fb-open-graph-admin.php784 lines (112%)
  • includes/class-webdados-fb-open-graph.php — 674 lines (just under, but close)

The 1,554-line public class is a textbook example of what §4.5 was written to prevent. Inside that one file: OG tag generation, Twitter Card generation, Schema.org JSON-LD generation, image URL resolution, image dimension caching, remote image fetching via wp_remote_get, WooCommerce product handling, multilingual locale logic, third-party SEO plugin compatibility (Yoast, AIOSEO), and the WP filter callbacks that wire it all up. Each of those is a sublayer or a separate file under v2.

#6 The Law (§1.2)

Imports always point to a strictly lower layer number — never upward, never sideways across a higher number, never cyclic.

PHP require_once does exist as an import edge, but without a profile mapping the verifier has nothing to check against. The architectural spirit of §1.2 — that lower concerns shouldn't reach into higher ones — is violated structurally: the entry file itself (wonderm00n-open-graph.php) contains direct $wpdb->query("DELETE FROM ...") calls in the uninstall hook. The Pure layer doesn't exist; the Adapter layer is everywhere.

#7 Consistency (§3)

No file's imports contradict its declared layer.

Vacuous — there are no declared layers to contradict.

Summary score: 0 of 7 checks would pass. Which is not surprising and not the point. The plugin wasn't built under v2; you can't grade an essay against rules its author never opted into.

Estimated §5 metrics

A PHP-aware metrics emitter doesn't exist yet, but the values can be estimated by hand against the operational definitions on /sama/v2 §5:

metric this plugin (estimated) tdd.md (measured)
graphDepth ~3 (entry → core → public/admin classes; very flat, almost no transitive depth) 7
boundaryRatio <10% (no dedicated adapter sublayer — parsing is spread across admin classes, UI pages, the entry file, and the public class) 100%
workingSetFit (50 ≤ LOC ≤ 500) ~47% (8 of 17 non-index source files in the sweet spot; three big god-classes drag it down) 80%
violationCounts (sum) 17+ Atomic, dozens of boundary, ~17 Architecture 0

The interesting comparison isn't ✓ vs ✗ — that's the compliance score §5 explicitly tells us not to lead with. The interesting comparison is the delta on the same metric axes. Compliance proves the rules were followed; the delta is the variable that proves whether following them was worth the cost.

What v2 makes visible that the existing code hides

Three architectural facts the v2 lens surfaces:

1. The 1,554-line public class is doing seven jobs at once. Under v2, the rendering of og:* tags, Twitter Cards, Schema.org JSON-LD, image-URL resolution, third-party SEO compatibility, and WooCommerce-specific behaviour would each live in their own Core (b*_) file with a sibling test. Today, changing one of them risks touching all of them, and there's no test fixture to catch a regression. A PR review on this file is essentially trust-based — the LOC budget is too big to read end-to-end.

2. The 43 superglobal accesses mean every change to a settings field is a hand-traced refactor. Without a c3_* controller sublayer that owns "how a settings POST becomes a typed value," the validation logic is duplicated across options-page-*.php files. An agent asked to add a new settings option has to find every place the related field is touched. v2's boundary rule reduces that to one file by construction.

3. $wpdb inside the entry file (uninstall hook). Under v2 this is c1_* work in an adapter file with a sibling test that asserts what gets deleted. Today it's an inline closure that runs once per uninstall, no test, and a future change to the schema requires editing a string literal inside a wp_* function. The Law check would catch this in v2 land; under stock WP idioms there's nothing to push back.

The honest interpretation

A v2-aware reviewer looking at this plugin doesn't conclude "this code is bad." They conclude:

  • The plugin is shaped exactly like WordPress's official patterns push you to shape it. WP teaches: register hooks at the top, do work in their callbacks, use $wpdb when you need to read or write data. There is no built-in pressure toward layer separation; in fact, the add_action / add_filter inversion-of-control pattern actively encourages mixing wiring with logic.
  • 0 of 7 checks passing is the expected baseline for code written under WP idioms with no external discipline. Most plugins in the repository score the same. That's not a SAMA failure — that's the data point that lets us measure the delta if we ever do build a plugin under v2 from scratch.
  • The PR that this post would generate — if I'd been the maintainer — wouldn't be "refactor everything." It would be one specific extraction: pull the 1,554-line public class apart into seven files of 200-300 lines each, each with a sibling unit test. The §5 metrics would shift measurably on that one refactor; the user-facing behaviour wouldn't change at all.

What this exercise actually buys us

Now there are two profile snapshots in the world: this site's own tdd-md profile (TypeScript, scoring 7/7 ✓ on §4 with graphDepth=7, boundaryRatio=100%, workingSetFit=80%), and this hand-estimated WordPress plugin (PHP, scoring 0/7 with graphDepth≈3, boundaryRatio<10%, workingSetFit≈47%).

That's still only n=2 and across different languages, so it's a long way from "v2 is empirically worth following." But it's the first data point where a real codebase that was not designed under v2 gets measured on the same axes. Future work — a v2-discipline plugin built from scratch, or a real refactor of one of the god-classes here — would let the delta speak for itself.

The chain extended one more link today: from "here is the spec and a hypothetical worked example" to "here is the spec, here is what a real plugin in the wild looks like through this lens, here is what the §5 metrics would say if a PHP-aware verifier existed." The PHP-aware verifier itself is the next piece of cable.


See for yourself: