Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0c62951
Update packages
ahegyes Nov 6, 2026
275412b
Test the scoping cases a run misses, misreports or rejects
ahegyes Nov 6, 2026
c98affa
Count a requirement the root replaces or provides as met
ahegyes Nov 6, 2026
c337d5f
Keep the previous build when php-scoper fails while scoping
ahegyes Nov 6, 2026
7ab03c7
Check each scoped name against the kind of symbol it names
ahegyes Nov 6, 2026
7f7182a
Fail on a class the scoped code declares outside the prefix
ahegyes Nov 6, 2026
99044dc
Rewrite only text-domain literals, and keep a domain the project shares
ahegyes Nov 6, 2026
2c82f03
Keep host namespaces global in a scoped package's namespace strings
ahegyes Nov 6, 2026
92229c0
Document that a metapackage loses its files' include order
ahegyes Nov 6, 2026
dd5d6ef
Read manifests and the scoping prefix in one place
ahegyes Nov 6, 2026
41de97b
Inline the scoping pipeline's single-caller helpers
ahegyes Nov 6, 2026
90de036
Mark the scoping pipeline's internals and name them by role
ahegyes Nov 6, 2026
c9381b9
Write configs' PHP and rulesets the way the other repositories do
ahegyes Nov 6, 2026
d394b87
Align configs' test harness with the other repositories
ahegyes Nov 6, 2026
e9f5fc5
Pin the PHPCS profile rules consumers rely on
ahegyes Nov 6, 2026
e1eed13
Pin the release workflow's input, archive and provenance checks
ahegyes Nov 6, 2026
2902de1
Assert what each Node baseline sets, not only that it loads
ahegyes Nov 6, 2026
030f4e4
Mutate only the code PHPUnit's coverage source lists
ahegyes Nov 6, 2026
40bd9cf
Name the PHPUnit workflow's wp-env branch as untested here
ahegyes Nov 6, 2026
0e768c8
Prefix a namespace string whose parent namespace stays global
ahegyes Nov 6, 2026
199dbd4
Rewrite the text domain in the PHP code a nowdoc holds
ahegyes Nov 6, 2026
abb6403
Skip the nowdoc code php-scoper leaves unscoped
ahegyes Nov 6, 2026
031f9b5
Remove the partial build when php-scoper dies mid-run
ahegyes Nov 6, 2026
0b9e163
Delete the JavaScript tsc emits when the smoke catches it
ahegyes Nov 6, 2026
b70a4eb
Trim a phantom example, a second comment sentence and a blank line
ahegyes Nov 6, 2026
c6d78b7
Keep each base-ruleset comment beside the setting it explains
ahegyes Nov 6, 2026
1b33528
Name php-scoper's expose options as unsupported
ahegyes Nov 6, 2026
b784b19
Separate the scoping pipeline's code into the groups it runs in
ahegyes Nov 6, 2026
0b5d651
Name the stubs or dropped-package file that does not parse
ahegyes Nov 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ body:
attributes:
label: Affected area
options:
- Composer scripts (CollectScopingStubs, ScopePhpDependencies)
- Composer scripts (GlobalSymbols, ScopePhpDependencies)
- php-scoper base config (scoper-base)
- PHPCS ruleset (phpcs.dist.xml)
- PHPStan config (phpstan.dist.neon)
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ body:
attributes:
label: Target area
options:
- Composer scripts (CollectScopingStubs, ScopePhpDependencies)
- Composer scripts (GlobalSymbols, ScopePhpDependencies)
- php-scoper base config (scoper-base)
- PHPCS ruleset
- PHPStan config
Expand Down
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ wordpress-configs/
├── php/
│ ├── quality-assurance/ # the shared PHPCS profiles and base, the PHPStan profile and base, opt-in path discovery
│ ├── php-scoper/ # scoper-base.inc.php and per-library recipes/<vendor>/<package>.inc.php
│ ├── composer/ # ScopePhpDependencies, ScopedPackages, CollectScopingStubs and their AST visitors
│ ├── composer/ # ScopePhpDependencies, ScopedPackages, GlobalSymbols, Manifest and their AST visitors
│ ├── phpstan/ # GlobalTagVisitor, which the PHPStan profile registers
│ └── release/ # the manifest and lock of the WP-CLI commands reusable-release.yml installs, which Dependabot keeps current
├── node/ # tsconfig, ESLint, Stylelint, PostCSS and Playwright baselines
├── docs/ # workflows.md (input reference) and scripts-contract.md (scripts, files, floors)
├── tests/
│ ├── Unit/ # PHPUnit tests that run the real tools
│ ├── Support/ # RunsWorkflowSteps, which runs a workflow step's script
│ ├── Support/ # RunsWorkflowStepsTrait, which runs a workflow step's script, and RecordsInstalledPackagesTrait
│ └── fixtures/ # projects and captured tool output the tests use
├── phpcs.dist.xml # self-lint: the production profile minus WordPress-runtime sniffs
├── phpcs.tests.dist.xml # self-lint for tests/
Expand Down Expand Up @@ -57,3 +57,4 @@ zizmor --config .github/zizmor.yml .github/
- `reusable-playwright-e2e.yml` and a full `reusable-release.yml` run have no target here: CI checks them with actionlint and zizmor, and the unit tests run the release's assertion steps.
- `symfony/filesystem` and `symfony/finder` stay a major behind their latest release, because `humbug/php-scoper` caps them.
- The release's PHP-floor check reads a strict `>` lower bound as inclusive.
- `reusable-phpunit.yml`'s wp-env branch runs in no job here: every consumer's Integration lane runs it, and a pull request that moves a consumer's pin catches a break.
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,20 +170,25 @@ Create `scoper.inc.php` at the project root and name the packages to scope:
return ( require __DIR__ . '/vendor/ahegyes/wordpress-configs/php/php-scoper/scoper-base.inc.php' )( __DIR__, 'dompdf/dompdf', 'php-di/php-di' );
```

A name is a package name or an `fnmatch()` pattern, such as `'your-vendor/your-library-*'`. The runtime dependencies of each named package follow from Composer's `installed.json`, through `require` and never `require-dev`, so naming a library scopes what it needs. A pattern matches every installed package, development tools included, so keep it as narrow as the packages it means. A name that starts with `!` keeps the packages it matches unscoped, such as a polyfill whose global functions must stay reachable (`'!symfony/polyfill-php80'`), and the symbols they declare stay global too. Keep the scoped packages in `require-dev`, so a production install never loads their unprefixed copies, and require an unscoped package in `require`, so a production install still ships it.
A name is a package name or an `fnmatch()` pattern, such as `'your-vendor/your-library-*'`. The runtime dependencies of each named package follow from Composer's `installed.json`, through `require` and never `require-dev`, so naming a library scopes what it needs; a requirement that another package or the project itself replaces or provides, as Composer records in `installed.php`, counts as met. A pattern matches every installed package, development tools included, so keep it as narrow as the packages it means. A name that starts with `!` keeps the packages it matches unscoped, such as a polyfill whose global functions must stay reachable (`'!symfony/polyfill-php80'`), and the symbols they declare stay global too. Keep the scoped packages in `require-dev`, so a production install never loads their unprefixed copies, and require an unscoped package in `require`, so a production install still ships it.

A scope run:

- copies every file of each scoped package to `vendor-prefixed/<vendor>/<package>/`, after emptying `vendor-prefixed/` and refusing one that is a symbolic link;
- scopes every file of each scoped package into `vendor-prefixed.tmp/` and, once php-scoper succeeds, moves it to `vendor-prefixed/<vendor>/<package>/` in place of the previous build, refusing a `vendor-prefixed/` that is a symbolic link, so a run php-scoper fails keeps the previous build;
- leaves global the WordPress symbols of `sniccowp/php-scoper-wordpress-excludes`, Action Scheduler (`as_*` and `ActionScheduler*`), every symbol of the stubs files the project or a scoped package declares under `extra.scoping-stubs`, and their namespaces;
- prefixes the strings that start with a scoped package's own namespace, which libraries use to build class names;
- prefixes the strings that start with a scoped package's own namespace, which libraries use to build class names, unless they name a namespace the run leaves global or a symbol in one;
- applies the recipe of each scoped package that has one in `php/php-scoper/recipes/<vendor>/<package>.inc.php`;
- rewrites the text domain a scoped package declares under `extra.scoping-text-domain` to the project's `extra.text-domain`;
- fails, naming each one, when the scoped code references a prefixed name it does not declare: a host symbol no stubs file declares, a dependency outside the scoped packages, or a string php-scoper took for a class name;
- rewrites each string literal of the text domain a scoped package declares under `extra.scoping-text-domain` to the project's `extra.text-domain`, unless the two are the same;
- fails, naming each one, when the scoped code references a prefixed name it does not declare as the kind of symbol the reference needs: a host symbol no stubs file declares, a dependency outside the scoped packages, a string php-scoper took for a class name, or the class of a `Class::MEMBER` string;
- fails, naming its namespace, when the scoped code declares a class, interface, trait or enum outside the prefix, which a copy the host or another plugin loads can replace, other than the polyfill of a PHP class;
- passes a `function_exists()` check of the prefixed name of a function it leaves global, PHP's own included, which php-scoper adds to every guard of that function and which is false while nothing declares the name;
- writes `vendor-prefixed/scoper-autoload.php`, a class map over each scoped package's `psr-4`, `psr-0` and `classmap` paths that honors `exclude-from-classmap`, followed by its `files` entries.

The run also fails when `extra.scoping-prefix` is missing or empty, when a name matches no installed package, when a scoped package requires one that is not installed, when a declared stubs file is missing, when a scoped package declares a text domain and the project declares none, when a declared text domain survives outside a string literal of its own, and when a recipe's target is gone.
The run also fails when `extra.scoping-prefix` is missing or empty, when a name matches no installed package, when a scoped package requires one that is not installed, when a declared stubs file is missing, when a scoped file, a declared stubs file or a dropped package's file does not parse, when a scoped package declares a text domain and the project declares none, when a declared text domain survives outside a string literal of its own, and when a recipe's target is gone.

A package that reaches a dependency only through a metapackage includes its `files` entries before that dependency's, because the autoloader orders them by each package's direct `require`.

The run supports none of php-scoper's `expose-*` options, since each plugin keeps its own prefixed copy of what it scopes: the namespace-string patcher prefixes the global name in the alias php-scoper writes for an exposed class, so the class stays unexposed.

The project or a scoped package declares the stubs files of the host symbols the scoped code calls, each as `./path/to/file.php` relative to its `composer.json`, as `vendor/package` for that package's `<package>.php`, or as `vendor/package:path/to/file.php`:

Expand Down
Loading
Loading