Files
wordpress-theme-aksrt/tools/psr4-fix-multi.php
T
kate522 aaf458af1c refactor(autoload): migrate theme classes to Composer PSR-4
Move 288 types from global AKSRT_* + inc/classes manual requires into
namespace AKSRT\{Domain} under src/AKSRT/, wire composer.json psr-4,
and slim bootstrap to helpers/modules/hooks only. Split multi-class
widget/gateway files, update admin contract audit paths, and document
the layout in ADR/README.
2026-07-17 13:29:22 +08:00

216 lines
6.3 KiB
PHP

<?php
/**
* Robust multi-file splitter for PSR-4.
*
* @package AKSRT
*/
declare(strict_types=1);
$root = dirname(__DIR__);
$map = json_decode((string) file_get_contents($root . '/tools/psr4-map/map.json'), true, 512, JSON_THROW_ON_ERROR);
$by_old = [];
foreach ($map['entries'] as $e) {
$by_old[$e['old']] = $e;
}
/**
* Find all top-level type declarations with start offsets.
*
* @return list<array{name:string,kind:string,start:int,decl:int}>
*/
function list_types(string $text): array {
$out = [];
if (!preg_match_all(
'/^(?:abstract\s+|final\s+)?(class|trait|interface)\s+(\w+)/m',
$text,
$m,
PREG_SET_ORDER | PREG_OFFSET_CAPTURE
)) {
return [];
}
foreach ($m as $match) {
$decl = (int) $match[0][1];
$kind = $match[1][0];
$name = $match[2][0];
// Include preceding docblock.
$before = substr($text, 0, $decl);
$start = $decl;
if (preg_match('/(?:\n|^)(\s*\/\*\*[\s\S]*?\*\/\s*)\z/', $before, $dm, PREG_OFFSET_CAPTURE)) {
$start = (int) $dm[1][1];
}
$out[] = ['name' => $name, 'kind' => $kind, 'start' => $start, 'decl' => $decl];
}
return $out;
}
function extract_by_index(string $text, array $types, int $idx): string {
$start = $types[$idx]['start'];
// End is start of next type or EOF (but need end of braces of this type).
$brace = strpos($text, '{', $types[$idx]['decl']);
$len = strlen($text);
$depth = 0;
$in_str = false;
$str_q = '';
$in_line = false;
$in_block = false;
$escape = false;
for ($i = $brace; $i < $len; $i++) {
$ch = $text[$i];
$next = $i + 1 < $len ? $text[$i + 1] : '';
if ($in_line) {
if ($ch === "\n") {
$in_line = false;
}
continue;
}
if ($in_block) {
if ($ch === '*' && $next === '/') {
$in_block = false;
$i++;
}
continue;
}
if ($in_str) {
if ($escape) {
$escape = false;
continue;
}
if ($ch === '\\') {
$escape = true;
continue;
}
if ($ch === $str_q) {
$in_str = false;
}
continue;
}
if ($ch === '/' && $next === '/') {
$in_line = true;
$i++;
continue;
}
if ($ch === '/' && $next === '*') {
$in_block = true;
$i++;
continue;
}
if ($ch === '"' || $ch === "'") {
$in_str = true;
$str_q = $ch;
continue;
}
if ($ch === '{') {
$depth++;
} elseif ($ch === '}') {
$depth--;
if ($depth === 0) {
return substr($text, $start, $i - $start + 1);
}
}
}
throw new RuntimeException('brace match failed for ' . $types[$idx]['name']);
}
$rep = [];
foreach ($map['entries'] as $e) {
$rep[$e['old']] = '\\' . $e['new_fqcn'];
}
$rep['AKSRT_Creator_Revenue'] = '\\AKSRT\\Payment\\CreatorRevenue';
uksort($rep, static fn($a, $b) => strlen($b) <=> strlen($a));
function rewrite_refs(string $code, array $rep): string {
$preserve = [
'AKSRT_DIR', 'AKSRT_URI', 'AKSRT_VERSION', 'AKSRT_NO_EMAIL_GATE',
'AKSRT_PAY_TYPE_NO', 'AKSRT_PAY_TYPE_READING', 'AKSRT_PAY_TYPE_DOWNLOAD',
'AKSRT_PAY_TYPE_GALLERY', 'AKSRT_PAY_TYPE_VIDEO', 'AKSRT_PAY_TYPE_VIP',
];
foreach ($preserve as $i => $tok) {
$code = str_replace($tok, "\0P{$i}\0", $code);
}
$pattern = '/\b(' . implode('|', array_map(static fn($k) => preg_quote($k, '/'), array_keys($rep))) . ')\b/';
$code = preg_replace_callback($pattern, static fn($m) => $rep[$m[1]] ?? $m[1], $code) ?? $code;
foreach ($preserve as $i => $tok) {
$code = str_replace("\0P{$i}\0", $tok, $code);
}
return $code;
}
$files = [
'inc/classes/payment/class-aksrt-payment-gateway.php',
'inc/classes/widgets/class-aksrt-forum-widgets.php',
'inc/classes/widgets/class-aksrt-home-widgets.php',
'inc/classes/widgets/class-aksrt-shop-widgets.php',
'inc/classes/widgets/class-aksrt-widgets-batch1.php',
'inc/classes/widgets/class-aksrt-widgets-new.php',
'inc/classes/widgets/class-aksrt-widgets-original.php',
];
foreach ($files as $git_path) {
$text = shell_exec('git -C ' . escapeshellarg($root) . ' show HEAD:' . escapeshellarg($git_path));
if (!$text) {
fwrite(STDERR, "git show fail $git_path\n");
continue;
}
$types = list_types($text);
echo $git_path, ' has ', count($types), " types: ", implode(', ', array_column($types, 'name')), "\n";
foreach ($types as $idx => $info) {
$old = $info['name'];
if (!isset($by_old[$old])) {
fwrite(STDERR, " unmapped $old\n");
continue;
}
$entry = $by_old[$old];
$chunk = extract_by_index($text, $types, $idx);
// Rename: only the declaration line's name.
$chunk = preg_replace(
'/^((?:abstract\s+|final\s+)?' . preg_quote($info['kind'], '/') . '\s+)' . preg_quote($old, '/') . '\b/m',
'$1' . $entry['short'],
$chunk,
1
);
$chunk = rewrite_refs($chunk, $rep);
$ns = $entry['namespace'];
// Shorten same-namespace refs BUT not if it would re-break declaration - declaration already short.
$chunk = preg_replace('/\\\\' . preg_quote($ns, '/') . '\\\\(\w+)/', '$1', $chunk) ?? $chunk;
// Global WP types.
foreach (['WP_Widget', 'WP_REST_Request', 'WP_REST_Response', 'WP_Error', 'WP_Post', 'WP_User', 'WP_Query'] as $g) {
$chunk = preg_replace('/(?<!\\\\)\b' . $g . '\b/', '\\' . $g, $chunk) ?? $chunk;
}
$chunk = str_replace('\\\\', '\\', $chunk);
$out = "<?php\n/**\n * @package AKSRT\n */\n\nnamespace {$ns};\n\ndefined('ABSPATH') || exit;\n\n" . rtrim($chunk) . "\n";
// Verify
if (!preg_match('/\b' . preg_quote($info['kind'], '/') . '\s+' . preg_quote($entry['short'], '/') . '\b/', $out)) {
fwrite(STDERR, " VERIFY FAIL $old -> {$entry['short']}\n");
fwrite(STDERR, " head: " . substr($out, 0, 250) . "\n");
continue;
}
$path = $root . '/' . $entry['new_path'];
if (!is_dir(dirname($path))) {
mkdir(dirname($path), 0777, true);
}
file_put_contents($path, $out);
echo " OK {$old} => {$entry['new_path']} (", strlen($out), " bytes)\n";
}
}
// Globalize WP_* in all src files once more carefully
$rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($root . '/src/AKSRT'));
foreach ($rii as $f) {
if (!$f->isFile() || !str_ends_with($f->getFilename(), '.php')) {
continue;
}
$p = $f->getPathname();
$t = (string) file_get_contents($p);
$orig = $t;
foreach (['WP_REST_Request', 'WP_REST_Response', 'WP_Error', 'WP_Post', 'WP_User', 'WP_Query', 'WP_Widget', 'WP_Admin_Bar', 'WP_HTTP_Response', 'WP_Term'] as $g) {
// only when used as type/extends/new/catch - simple: bare WP_ not already escaped
$t = preg_replace('/(?<!\\\\|[\w\\\\])' . preg_quote($g, '/') . '\b/', '\\' . $g, $t) ?? $t;
}
if ($t !== $orig) {
file_put_contents($p, $t);
}
}
echo "done\n";