Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
77 changes: 68 additions & 9 deletions bin/check-package-autoload.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
exit( 2 );
}

$declared = collect_declared_symbols( $pkg_dir );
$declared = collect_declared_symbols( $pkg_dir, load_classmap_excludes( $pkg_dir ) );
if ( empty( $declared ) ) {
echo "NOTE: {$pkg} declares no class/interface/trait symbols (file-only package)\n";
exit( 0 );
Expand All @@ -66,20 +66,46 @@
exit( 1 );

/**
* Walks every PHP file under $dir (skipping Tests, vendor, fixtures) and
* extracts the fully-qualified name of each top-level class, interface, and
* trait. Uses PHP's tokenizer so we never execute the package code.
* Walks every PHP file under $dir and extracts the fully-qualified name of
* each top-level class, interface, and trait. Uses PHP's tokenizer so we
* never execute the package code.
*
* Skips standard non-autoloaded directories (Tests, fixtures, vendor) and
* any path the package itself excluded from its classmap via the
* exclude-from-classmap directive in composer.json. We honour that
* directive because anything excluded there is, by definition, not part of
* the autoload surface — checking that those classes are reachable would
* be a false positive.
*/
function collect_declared_symbols( $dir ) {
$out = array();
$iterator = new RecursiveIteratorIterator(
function collect_declared_symbols( $dir, array $excluded_paths = array() ) {
$out = array();
$dir_norm = rtrim( str_replace( DIRECTORY_SEPARATOR, '/', $dir ), '/' );
$skipped_dir = array( 'Tests', 'tests', 'fixtures', 'vendor' );
$iterator = new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator(
new RecursiveDirectoryIterator( $dir, FilesystemIterator::SKIP_DOTS ),
function ( $current ) {
function ( $current ) use ( $dir_norm, $excluded_paths, $skipped_dir ) {
$name = $current->getFilename();
if ( $current->isDir() && in_array( $name, array( 'Tests', 'tests', 'fixtures', 'vendor' ), true ) ) {
if ( $current->isDir() && in_array( $name, $skipped_dir, true ) ) {
return false;
}
if ( $excluded_paths ) {
// Build a leading-slash, forward-slash path relative to
// the package root so substring matching against entries
// like "/Tests/" or "/vendor-patched/foo/bar/" behaves
// the way composer's classmap exclusion does.
$path = str_replace( DIRECTORY_SEPARATOR, '/', $current->getPathname() );
if ( 0 === strpos( $path, $dir_norm . '/' ) ) {
$path = substr( $path, strlen( $dir_norm ) );
}
$rel = '/' . ltrim( $path, '/' );
$haystack = $current->isDir() ? rtrim( $rel, '/' ) . '/' : $rel;
foreach ( $excluded_paths as $excluded ) {
if ( false !== strpos( $haystack, $excluded ) ) {
return false;
}
}
}
return true;
}
)
Expand All @@ -95,6 +121,39 @@ function ( $current ) {
return $out;
}

/**
* Reads the package's composer.json and returns its
* autoload.exclude-from-classmap entries, normalised to paths that start
* with a leading "/". Returns an empty array if the file is missing or
* unreadable.
*/
function load_classmap_excludes( $pkg_dir ) {
$composer_json = $pkg_dir . '/composer.json';
if ( ! is_file( $composer_json ) ) {
return array();
}
$decoded = json_decode( file_get_contents( $composer_json ), true );
if ( ! is_array( $decoded ) ) {
return array();
}
$raw = $decoded['autoload']['exclude-from-classmap'] ?? array();
if ( ! is_array( $raw ) ) {
return array();
}
$out = array();
foreach ( $raw as $entry ) {
if ( ! is_string( $entry ) || '' === $entry ) {
continue;
}
$entry = str_replace( '\\', '/', $entry );
if ( '/' !== $entry[0] ) {
$entry = '/' . $entry;
}
$out[] = $entry;
}
return $out;
}

function extract_symbols( $source ) {
$tokens = token_get_all( $source );
$namespace = '';
Expand Down
18 changes: 17 additions & 1 deletion components/Blueprints/bin/blueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,23 @@
* ✅ @TODO: Prevent remote resources from using local bundle paths
*/

require __DIR__ . '/../../../vendor/autoload.php';
if ( ! class_exists( 'Composer\\Autoload\\ClassLoader', false ) ) {
$_blueprint_autoload_candidates = array(
// Installed as a dependency: vendor/wp-php-toolkit/blueprints/bin/.
__DIR__ . '/../../../autoload.php',
// Standalone clone of the wp-php-toolkit/blueprints repo.
__DIR__ . '/../vendor/autoload.php',
// Inside the php-toolkit monorepo.
__DIR__ . '/../../../vendor/autoload.php',
);
foreach ( $_blueprint_autoload_candidates as $_blueprint_autoload ) {
if ( file_exists( $_blueprint_autoload ) ) {
require $_blueprint_autoload;
break;
}
}
unset( $_blueprint_autoload_candidates, $_blueprint_autoload );
}

use WordPress\CLI\CLI;
use WordPress\Blueprints\DataReference\AbsoluteLocalPath;
Expand Down
6 changes: 5 additions & 1 deletion components/Blueprints/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@
"./"
],
"exclude-from-classmap": [
"/Tests/"
"/Tests/",
"/bin/",
"/Steps/scripts/",
"/vendor-patched/symfony/event-dispatcher/DependencyInjection/",
"/vendor-patched/symfony/event-dispatcher/ContainerAwareEventDispatcher.php"
]
}
}
10 changes: 7 additions & 3 deletions components/CORSProxy/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@
"phpunit/phpunit": "^9.5"
},
"autoload": {
"psr-4": {
"WordPress\\CORSProxy\\": ""
},
"classmap": [
"./"
],
"files": [
"cors-proxy-functions.php"
],
"exclude-from-classmap": [
"/tests/",
"/Tests/"
]
}
Expand Down
2 changes: 1 addition & 1 deletion components/CORSProxy/cors-proxy-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ function rewrite_relative_redirect(
}

if ( ! parse_url( $redirect_location, PHP_URL_SCHEME ) ) {
$target_scheme = parse_url( $request_url, PHP_URL_SCHEME ) ? PHP_URL_SCHEME ) : 'https';
$target_scheme = parse_url( $request_url, PHP_URL_SCHEME ) ?: 'https';
$redirect_location = "$target_scheme://$redirect_location";
}

Expand Down
4 changes: 3 additions & 1 deletion components/DataLiberation/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"php": ">=7.2",
"wp-php-toolkit/bytestream": "^0.8",
"wp-php-toolkit/filesystem": "^0.8",
"wp-php-toolkit/html": "^0.8",
"wp-php-toolkit/http-client": "^0.8",
"wp-php-toolkit/xml": "^0.8"
},
Expand All @@ -39,7 +40,8 @@
"URL/functions.php"
],
"exclude-from-classmap": [
"/Tests/"
"/Tests/",
"/bin/"
]
}
}
7 changes: 6 additions & 1 deletion components/Markdown/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@
"vendor-patched"
],
"exclude-from-classmap": [
"/Tests/"
"/Tests/",
"/bin/",
"/vendor-patched/symfony/yaml/Command/",
"/vendor-patched/webuni/front-matter/src/Haml/",
"/vendor-patched/webuni/front-matter/src/Pug/",
"/vendor-patched/webuni/front-matter/src/Twig/"
]
}
}
7 changes: 3 additions & 4 deletions components/Merge/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@
"docs": "https://wordpress.github.io/php-toolkit/reference/merge.html"
},
"require": {
"php": ">=7.4",
"yetanotherape/diff-match-patch": "^1.0",
"wordpress/data-liberation": "dev-trunk",
"ext-mbstring": "*"
"php": ">=7.2",
"ext-mbstring": "*",
"wp-php-toolkit/data-liberation": "^0.8"
},
"require-dev": {
"phpunit/phpunit": "^9.5",
Expand Down
28 changes: 28 additions & 0 deletions components/Polyfill/class-wp-error.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php
/**
* Polyfill for the WordPress core WP_Error class.
*
* Loaded lazily through Composer's classmap so that simply requiring
* `vendor/autoload.php` does not eagerly declare `WP_Error`. Eager
* declaration would fatal as soon as WordPress core loads its own copy,
* which is the regression caught by bin/check-wp-coexistence.php.
*/

// phpcs:disable

if ( ! class_exists( 'WP_Error' ) ) {
class WP_Error {
public $code;
public $message;
public $data;

public function __construct( $code = '', $message = '', $data = '' ) {
if ( empty( $code ) ) {
return;
}
$this->code = $code;
$this->message = $message;
$this->data = $data;
}
}
}
16 changes: 16 additions & 0 deletions components/Polyfill/class-wp-exception.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php
/**
* `WP_Exception` is the exception thrown by `wp_trigger_error()` when it is
* called with `E_USER_ERROR`. It does not exist in WordPress core, so a
* downstream consumer that boots WordPress will not redeclare it — but we
* still load this class lazily through Composer's classmap to keep the
* autoload.files entrypoint free of class declarations and consistent with
* the WP_Error polyfill alongside it.
*/

// phpcs:disable

if ( ! class_exists( 'WP_Exception' ) ) {
class WP_Exception extends Exception {
}
}
6 changes: 5 additions & 1 deletion components/Polyfill/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@
"wp-php-toolkit/html": "^0.8"
},
"autoload": {
"classmap": [
"./"
],
"files": [
"wordpress.php",
"mbstring.php"
"mbstring.php",
"php-functions.php"
],
"exclude-from-classmap": [
"/Tests/"
Expand Down
27 changes: 6 additions & 21 deletions components/Polyfill/wordpress.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,12 @@

// phpcs:disable

if ( ! class_exists( 'WP_Error' ) ) {
class WP_Error {
public $code;
public $message;
public $data;

public function __construct( $code = '', $message = '', $data = '' ) {
if ( empty( $code ) ) {
return;
}
$this->code = $code;
$this->message = $message;
$this->data = $data;
}
}
}
// `WP_Error` and `WP_Exception` are deliberately NOT declared in this
// file: declaring WordPress-core class names at composer-bootstrap time
// fatals as soon as a downstream consumer loads WordPress, because WP
// core re-declares the same names without a guard. Those polyfills live
// in class-wp-error.php / class-wp-exception.php and are picked up by
// the autoloader's classmap on demand.

if ( ! function_exists( '_doing_it_wrong' ) ) {
$GLOBALS['_doing_it_wrong_messages'] = array();
Expand All @@ -29,11 +19,6 @@ function _doing_it_wrong( $method, $message, $version ) {
}
}

if ( ! class_exists( 'WP_Exception' ) ) {
class WP_Exception extends Exception {
}
}

if ( ! function_exists( 'wp_trigger_error' ) ) {
function wp_trigger_error( $function_name, $message, $error_level = E_USER_NOTICE ) {
if ( ! empty( $function_name ) ) {
Expand Down
7 changes: 5 additions & 2 deletions composer-ci-matrix-tests.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"components/Blueprints/",
"components/Blueprints/vendor-patched/",
"components/CLI/",
"components/CORSProxy/",
"components/DataLiberation/",
"components/DataLiberation/vendor-patched/",
"components/Filesystem/",
Expand All @@ -43,10 +44,12 @@
"components/Merge/",
"components/Merge/vendor-patched",
"components/ByteStream/",
"components/Polyfill/",
"components/XML/",
"components/Zip/"
],
"files": [
"components/CORSProxy/cors-proxy-functions.php",
"components/DataLiberation/URL/functions.php",
"components/Encoding/utf8.php",
"components/Encoding/compat-utf8.php",
Expand All @@ -55,12 +58,12 @@
"components/Zip/functions.php",
"components/Polyfill/wordpress.php",
"components/Polyfill/mbstring.php",
"components/Polyfill/php-functions.php",
"components/Git/functions.php"
],
"psr-4": {
"Rowbot\\": "components/DataLiberation/vendor-patched/",
"Brick\\": "components/DataLiberation/vendor-patched/",
"WordPress\\CORSProxy\\": "components/CORSProxy/"
"Brick\\": "components/DataLiberation/vendor-patched/"
}
},
"scripts": {
Expand Down
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"components/Blueprints/",
"components/Blueprints/vendor-patched/",
"components/CLI/",
"components/CORSProxy/",
"components/DataLiberation/",
"components/DataLiberation/vendor-patched/",
"components/Filesystem/",
Expand All @@ -60,11 +61,13 @@
"components/Merge/",
"components/Merge/vendor-patched",
"components/ByteStream/",
"components/Polyfill/",
"components/ToolkitCodingStandards/",
"components/XML/",
"components/Zip/"
],
"files": [
"components/CORSProxy/cors-proxy-functions.php",
"components/DataLiberation/URL/functions.php",
"components/Encoding/utf8.php",
"components/Encoding/compat-utf8.php",
Expand All @@ -78,8 +81,7 @@
],
"psr-4": {
"Rowbot\\": "components/DataLiberation/vendor-patched/",
"Brick\\": "components/DataLiberation/vendor-patched/",
"WordPress\\CORSProxy\\": "components/CORSProxy/"
"Brick\\": "components/DataLiberation/vendor-patched/"
}
},
"scripts": {
Expand Down
Loading