Mark PHP version compatibility fallback as legitimate in Php_Fixer
Add public directory asset support to bundle system Fix PHP Fixer to replace ALL Rsx\ FQCNs with simple class names 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,12 @@ class BundleCompiler
|
||||
*/
|
||||
protected array $cdn_assets = ['js' => [], 'css' => []];
|
||||
|
||||
/**
|
||||
* Public directory assets (served via AssetHandler with filemtime cache-busting)
|
||||
* Format: ['js' => [['url' => '/path/to/file.js', 'full_path' => '/full/filesystem/path']], ...]
|
||||
*/
|
||||
protected array $public_assets = ['js' => [], 'css' => []];
|
||||
|
||||
/**
|
||||
* Cache keys for vendor/app
|
||||
*/
|
||||
@@ -175,6 +181,14 @@ class BundleCompiler
|
||||
$result['cdn_css'] = $this->cdn_assets['css'];
|
||||
}
|
||||
|
||||
// Add public directory assets
|
||||
if (!empty($this->public_assets['js'])) {
|
||||
$result['public_js'] = $this->public_assets['js'];
|
||||
}
|
||||
if (!empty($this->public_assets['css'])) {
|
||||
$result['public_css'] = $this->public_assets['css'];
|
||||
}
|
||||
|
||||
// Add bundle file paths for development
|
||||
if (!$this->is_production) {
|
||||
if (isset($outputs['vendor_js'])) {
|
||||
@@ -501,6 +515,43 @@ class BundleCompiler
|
||||
}
|
||||
$this->resolved_includes[$include_key] = true;
|
||||
|
||||
// Check for /public/ prefix - static assets from public directories
|
||||
if (is_string($item) && str_starts_with($item, '/public/')) {
|
||||
$relative_path = substr($item, 8); // Strip '/public/' prefix
|
||||
|
||||
try {
|
||||
// Resolve via AssetHandler (with Redis caching)
|
||||
$full_path = \App\RSpade\Core\Dispatch\AssetHandler::find_public_asset($relative_path);
|
||||
|
||||
// Determine file type
|
||||
$extension = strtolower(pathinfo($relative_path, PATHINFO_EXTENSION));
|
||||
|
||||
if ($extension === 'js') {
|
||||
$this->public_assets['js'][] = [
|
||||
'url' => '/' . $relative_path,
|
||||
'full_path' => $full_path
|
||||
];
|
||||
} elseif ($extension === 'css') {
|
||||
$this->public_assets['css'][] = [
|
||||
'url' => '/' . $relative_path,
|
||||
'full_path' => $full_path
|
||||
];
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Public asset must be .js or .css file: {$item}\n" .
|
||||
"Only JavaScript and CSS files can be included via /public/ prefix."
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) {
|
||||
throw new RuntimeException(
|
||||
"Failed to resolve public asset: {$item}\n" .
|
||||
$e->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check bundle aliases and resolve to actual class
|
||||
$bundle_aliases = config('rsx.bundle_aliases', []);
|
||||
if (is_string($item) && isset($bundle_aliases[$item])) {
|
||||
|
||||
@@ -391,6 +391,12 @@ abstract class Rsx_Bundle_Abstract
|
||||
$html[] = $tag;
|
||||
}
|
||||
|
||||
// Add public directory CSS (with filemtime cache-busting)
|
||||
$public_css = $compiled['public_css'] ?? [];
|
||||
foreach ($public_css as $asset) {
|
||||
$html[] = '<link rel="stylesheet" href="' . htmlspecialchars($asset['url']) . '?v=<?php echo filemtime(\'' . addslashes($asset['full_path']) . '\'); ?>">';
|
||||
}
|
||||
|
||||
// Add JS: jQuery first, then others
|
||||
foreach (array_merge($jquery_js, $other_js) as $asset) {
|
||||
$tag = '<script src="' . htmlspecialchars($asset['url']) . '" defer';
|
||||
@@ -402,6 +408,12 @@ abstract class Rsx_Bundle_Abstract
|
||||
$html[] = $tag;
|
||||
}
|
||||
|
||||
// Add public directory JS (with filemtime cache-busting and defer)
|
||||
$public_js = $compiled['public_js'] ?? [];
|
||||
foreach ($public_js as $asset) {
|
||||
$html[] = '<script src="' . htmlspecialchars($asset['url']) . '?v=<?php echo filemtime(\'' . addslashes($asset['full_path']) . '\'); ?>" defer></script>';
|
||||
}
|
||||
|
||||
// Add CSS bundles
|
||||
// In development mode with split bundles, add vendor then app
|
||||
if (!empty($compiled['vendor_css_bundle_path']) || !empty($compiled['app_css_bundle_path'])) {
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace App\RSpade\Core\Dispatch;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
@@ -215,14 +216,88 @@ class AssetHandler
|
||||
// Set additional security headers
|
||||
static::__set_security_headers($response, $mime_type);
|
||||
|
||||
// Enable gzip if supported
|
||||
if (static::__should_compress($mime_type)) {
|
||||
$response->headers->set('Content-Encoding', 'gzip');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a public asset by relative path with Redis caching
|
||||
*
|
||||
* Resolves paths like "sneat/css/demo.css" to full filesystem paths like
|
||||
* "rsx/public/sneat/css/demo.css" by scanning all public/ directories.
|
||||
*
|
||||
* Results are cached in Redis indefinitely. Cached paths are validated
|
||||
* before use - if file no longer exists, cache is invalidated and re-scan occurs.
|
||||
*
|
||||
* @param string $relative_path Relative path like "sneat/css/demo.css"
|
||||
* @return string Full filesystem path
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException If not found or ambiguous
|
||||
*/
|
||||
public static function find_public_asset(string $relative_path): string
|
||||
{
|
||||
// Ensure directories are discovered
|
||||
static::__ensure_directories_discovered();
|
||||
|
||||
// Sanitize the path
|
||||
$relative_path = static::__sanitize_path($relative_path);
|
||||
|
||||
// Check Redis cache first
|
||||
$cache_key = 'rspade:public_asset:' . $relative_path;
|
||||
$cached_path = Redis::get($cache_key);
|
||||
|
||||
if ($cached_path) {
|
||||
// Verify cached file still exists
|
||||
if (File::exists($cached_path) && File::isFile($cached_path)) {
|
||||
return $cached_path;
|
||||
}
|
||||
|
||||
// Stale cache - invalidate and re-scan
|
||||
Redis::del($cache_key);
|
||||
}
|
||||
|
||||
// NEVER serve PHP files under any circumstances
|
||||
$extension = strtolower(pathinfo($relative_path, PATHINFO_EXTENSION));
|
||||
if ($extension === 'php') {
|
||||
throw new HttpException(403, 'PHP files cannot be served as static assets');
|
||||
}
|
||||
|
||||
// Scan all public directories for matches
|
||||
$matches = [];
|
||||
|
||||
foreach (static::$public_directories as $module => $directory) {
|
||||
$full_path = $directory . '/' . $relative_path;
|
||||
|
||||
if (File::exists($full_path) && File::isFile($full_path)) {
|
||||
// Check exclusion rules
|
||||
if (static::__is_file_excluded($full_path, $relative_path)) {
|
||||
throw new HttpException(403, 'Access to this file is forbidden');
|
||||
}
|
||||
$matches[] = $full_path;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for ambiguous matches
|
||||
if (count($matches) > 1) {
|
||||
// Show first two matches in error
|
||||
$first_two = array_slice($matches, 0, 2);
|
||||
throw new HttpException(
|
||||
500,
|
||||
"Ambiguous public asset request: '{$relative_path}' matches multiple files: '" .
|
||||
implode("', '", $first_two) . "'"
|
||||
);
|
||||
}
|
||||
|
||||
// Check for no matches
|
||||
if (count($matches) === 0) {
|
||||
throw new NotFoundHttpException("Public asset not found: {$relative_path}");
|
||||
}
|
||||
|
||||
// Single match - cache and return
|
||||
$resolved_path = $matches[0];
|
||||
Redis::set($cache_key, $resolved_path);
|
||||
|
||||
return $resolved_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure directories are discovered (lazy initialization)
|
||||
*/
|
||||
@@ -294,52 +369,22 @@ class AssetHandler
|
||||
/**
|
||||
* Find asset file in public directories
|
||||
*
|
||||
* Wrapper around find_public_asset() that returns null instead of throwing
|
||||
* NotFoundHttpException for backward compatibility with existing code.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string|null Full file path or null if not found
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException For PHP files, exclusions, or ambiguous matches
|
||||
*/
|
||||
protected static function __find_asset_file($path)
|
||||
{
|
||||
// NEVER serve PHP files under any circumstances
|
||||
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||||
if ($extension === 'php') {
|
||||
throw new HttpException(403, 'PHP files cannot be served as static assets');
|
||||
try {
|
||||
return static::find_public_asset($path);
|
||||
} catch (NotFoundHttpException $e) {
|
||||
// Not found - return null for backward compatibility
|
||||
return null;
|
||||
}
|
||||
|
||||
// Try each public directory
|
||||
foreach (static::$public_directories as $module => $directory) {
|
||||
$full_path = $directory . '/' . $path;
|
||||
|
||||
if (File::exists($full_path) && File::isFile($full_path)) {
|
||||
// Check exclusion rules before returning
|
||||
if (static::__is_file_excluded($full_path, $path)) {
|
||||
throw new HttpException(403, 'Access to this file is forbidden');
|
||||
}
|
||||
return $full_path;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if path includes module prefix (e.g., "admin/css/style.css")
|
||||
$parts = explode('/', $path, 2);
|
||||
|
||||
if (count($parts) === 2) {
|
||||
$module = $parts[0];
|
||||
$asset_path = $parts[1];
|
||||
|
||||
if (isset(static::$public_directories[$module])) {
|
||||
$full_path = static::$public_directories[$module] . '/' . $asset_path;
|
||||
|
||||
if (File::exists($full_path) && File::isFile($full_path)) {
|
||||
// Check exclusion rules before returning
|
||||
if (static::__is_file_excluded($full_path, $asset_path)) {
|
||||
throw new HttpException(403, 'Access to this file is forbidden');
|
||||
}
|
||||
return $full_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
// Let other exceptions (403, 500) bubble up
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -674,32 +719,6 @@ class AssetHandler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if content should be compressed
|
||||
*
|
||||
* @param string $mime_type
|
||||
* @return bool
|
||||
*/
|
||||
protected static function __should_compress($mime_type)
|
||||
{
|
||||
// Compress text-based content
|
||||
$compressible = [
|
||||
'text/',
|
||||
'application/javascript',
|
||||
'application/json',
|
||||
'application/xml',
|
||||
'image/svg+xml'
|
||||
];
|
||||
|
||||
foreach ($compressible as $type) {
|
||||
if (str_starts_with($mime_type, $type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get discovered public directories
|
||||
*
|
||||
|
||||
@@ -1383,7 +1383,34 @@ class Manifest
|
||||
// Validate class names are unique.
|
||||
static::__check_unique_base_class_names();
|
||||
|
||||
// Apply Php_Fixer to all PHP files in rsx/ and app/RSpade/ before parsing
|
||||
// ==================================================================================
|
||||
// PHP FIXER INTEGRATION POINT
|
||||
// ==================================================================================
|
||||
// This is where automatic code fixes are applied before Phase 2 parsing.
|
||||
//
|
||||
// WHAT PHP_FIXER DOES:
|
||||
// 1. Fixes namespaces to match file paths
|
||||
// 2. Removes/rebuilds use statements (strips Rsx\ and App\RSpade\ prefixes)
|
||||
// 3. Replaces FQCNs like \Rsx\Models\User_Model with simple names User_Model
|
||||
// 4. Adds #[Relationship] attributes to model ORM methods
|
||||
// 5. Removes leading backslashes from attributes: #[\Route] → #[Route]
|
||||
//
|
||||
// SMART REBUILDING:
|
||||
// - Tracks SHA1 hash of all class structures (ClassName:ParentClass)
|
||||
// - If structure changed: Fixes ALL files (cascading updates needed)
|
||||
// - If structure unchanged: Fixes ONLY $files_to_process (incremental)
|
||||
//
|
||||
// WHY BEFORE PHASE 2:
|
||||
// - Phase 2 parses metadata from file content
|
||||
// - If we fix AFTER parsing, manifest would have old/incorrect metadata
|
||||
// - By fixing BEFORE, we parse the corrected content
|
||||
//
|
||||
// RE-PARSING LOOP BELOW:
|
||||
// - If Php_Fixer modified files, we MUST re-parse them
|
||||
// - This updates manifest with corrected namespace/class/FQCN data
|
||||
// - Without this, manifest would reference old class locations
|
||||
// ==================================================================================
|
||||
|
||||
$php_fixer_modified_files = [];
|
||||
if (!app()->environment('production')) {
|
||||
$php_fixer_modified_files = static::__run_php_fixer($files_to_process);
|
||||
@@ -2282,6 +2309,29 @@ class Manifest
|
||||
* Run Php_Fixer on all PHP files in rsx/ and app/RSpade/
|
||||
* Called before Phase 2 parsing to ensure all files are fixed
|
||||
*
|
||||
* SMART REBUILD STRATEGY:
|
||||
* This method implements an intelligent rebuild strategy to avoid unnecessary file writes:
|
||||
*
|
||||
* 1. STRUCTURE HASH: Creates SHA1 hash of "ClassName:ParentClass" for ALL classes
|
||||
* - Detects when classes are added, removed, renamed, or inheritance changes
|
||||
*
|
||||
* 2. FULL REBUILD TRIGGERS:
|
||||
* - New class added (may need new use statements elsewhere)
|
||||
* - Class renamed (all references need updating)
|
||||
* - Inheritance changed (may affect use statement resolution)
|
||||
* → When triggered: Fix ALL PHP files in rsx/ and app/RSpade/
|
||||
*
|
||||
* 3. INCREMENTAL REBUILD:
|
||||
* - Structure hash unchanged (no new/renamed classes)
|
||||
* - Only fixes files that actually changed on disk
|
||||
* → More efficient, avoids touching unchanged files
|
||||
*
|
||||
* WHY THIS MATTERS:
|
||||
* - use statement management depends on knowing all available classes
|
||||
* - FQCN replacement needs to check class name uniqueness
|
||||
* - When class structure changes, files referencing those classes need updating
|
||||
* - When structure stable, only changed files need processing
|
||||
*
|
||||
* @param array $changed_files List of changed files from Phase 1
|
||||
* @return array List of files that were modified by Php_Fixer
|
||||
*/
|
||||
@@ -2289,7 +2339,14 @@ class Manifest
|
||||
{
|
||||
$modified_files = [];
|
||||
|
||||
// Build hash array of all PHP classes to detect structural changes
|
||||
// ==================================================================================
|
||||
// STEP 1: BUILD CLASS STRUCTURE HASH
|
||||
// ==================================================================================
|
||||
// Create a fingerprint of ALL classes in the codebase.
|
||||
// Format: "path/to/file.php" => "ClassName:ParentClass"
|
||||
// This lets us detect when the class structure itself changes (not just file contents)
|
||||
// ==================================================================================
|
||||
|
||||
$class_structure_hash_data = [];
|
||||
|
||||
foreach (static::$data['data']['files'] as $file_path => $metadata) {
|
||||
@@ -2311,12 +2368,24 @@ class Manifest
|
||||
// Calculate hash of class structure
|
||||
$new_class_structure_hash = sha1(json_encode($class_structure_hash_data));
|
||||
|
||||
// Check if class structure has changed
|
||||
// ==================================================================================
|
||||
// STEP 2: DECIDE REBUILD STRATEGY
|
||||
// ==================================================================================
|
||||
// Compare with previous hash to detect structural changes
|
||||
// ==================================================================================
|
||||
|
||||
$previous_hash = static::$data['data']['php_fixer_hash'] ?? null;
|
||||
$structure_changed = ($previous_hash !== $new_class_structure_hash);
|
||||
|
||||
if ($structure_changed) {
|
||||
// Class structure changed - fix ALL PHP files in rsx/ and app/RSpade/
|
||||
// ==================================================================================
|
||||
// FULL REBUILD: Class structure changed
|
||||
// ==================================================================================
|
||||
// When class structure changes, we MUST fix ALL files because:
|
||||
// - New classes may be referenced in existing files → need new use statements
|
||||
// - Renamed classes need all references updated
|
||||
// - Inheritance changes may affect use statement resolution
|
||||
// ==================================================================================
|
||||
$php_files_to_fix = [];
|
||||
|
||||
foreach (static::$data['data']['files'] as $file_path => $metadata) {
|
||||
@@ -2340,10 +2409,19 @@ class Manifest
|
||||
}
|
||||
}
|
||||
|
||||
// Store updated hash
|
||||
// Store updated hash for next rebuild comparison
|
||||
static::$data['data']['php_fixer_hash'] = $new_class_structure_hash;
|
||||
} else {
|
||||
// Class structure unchanged - only fix changed PHP files with classes
|
||||
// ==================================================================================
|
||||
// INCREMENTAL REBUILD: Class structure unchanged
|
||||
// ==================================================================================
|
||||
// Only fix files that actually changed on disk.
|
||||
// Safe because:
|
||||
// - No new classes = no new use statements needed elsewhere
|
||||
// - No renamed classes = no references to update
|
||||
// - No inheritance changes = use statement resolution unchanged
|
||||
// Result: Much faster, avoids touching 99% of files on typical edits
|
||||
// ==================================================================================
|
||||
$php_files_to_fix = [];
|
||||
|
||||
foreach ($changed_files as $file_path) {
|
||||
|
||||
@@ -15,9 +15,57 @@ use RuntimeException;
|
||||
* Performs automatic fixes and enhancements to PHP source files during development:
|
||||
* - Auto-adds #[Relationship] attributes to model files
|
||||
* - Auto-updates namespaces based on file location
|
||||
* - Other automatic code improvements
|
||||
* - Removes/rebuilds use statements for Rsx\ and App\RSpade\ classes
|
||||
* - Replaces FQCNs like \Rsx\Models\User_Model with simple names User_Model
|
||||
* - Removes leading backslashes from attributes: #[\Route] → #[Route]
|
||||
*
|
||||
* Only runs in non-production environments to avoid modifying deployed code.
|
||||
*
|
||||
* ======================================================================================
|
||||
* HOW TO ADD NEW RULES
|
||||
* ======================================================================================
|
||||
*
|
||||
* TO ADD A NEW FIX:
|
||||
* 1. Create a new private static method: __fix_your_feature($file_path, $content, $manifest)
|
||||
* 2. Add it to the fix() method's sequential application list (line ~123)
|
||||
* 3. Method signature: private static function __fix_*($file_path, $content, &$step_2_manifest_data): string
|
||||
* 4. Return the modified content (or original if no changes)
|
||||
*
|
||||
* EXAMPLE SKELETON:
|
||||
* ```php
|
||||
* private static function __fix_rsx_fqcn($file_path, string $content, array &$step_2_manifest_data): string
|
||||
* {
|
||||
* // Only process files in rsx/ or app/RSpade/
|
||||
* if (!str_starts_with($file_path, 'rsx/') && !str_starts_with($file_path, 'app/RSpade/')) {
|
||||
* return $content;
|
||||
* }
|
||||
*
|
||||
* // Parse tokens for accurate replacement
|
||||
* $tokens = token_get_all($content);
|
||||
*
|
||||
* // Find patterns to fix
|
||||
* // Build modifications array with positions
|
||||
*
|
||||
* // Apply modifications (usually in reverse order to preserve positions)
|
||||
*
|
||||
* return $modified_content;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* IMPORTANT PATTERNS:
|
||||
* - Always work with tokens for PHP syntax awareness
|
||||
* - Build modifications array, then apply in reverse order (preserves positions)
|
||||
* - Use step_2_manifest_data to look up class information
|
||||
* - Return original content if no changes needed
|
||||
* - Never modify files in production environment (checked by caller)
|
||||
*
|
||||
* MANIFEST DATA AVAILABLE:
|
||||
* - static::$data['data']['files'] - All indexed files with metadata
|
||||
* - Files processed earlier in Phase 2 have complete metadata
|
||||
* - Current file NOT in manifest yet (being processed now)
|
||||
* - See method docblock for fix() for complete manifest structure details
|
||||
*
|
||||
* ======================================================================================
|
||||
*/
|
||||
class Php_Fixer
|
||||
{
|
||||
@@ -335,6 +383,32 @@ class Php_Fixer
|
||||
/**
|
||||
* Fix use statements - remove unnecessary ones, add missing ones
|
||||
*
|
||||
* THREE-STEP PROCESS:
|
||||
*
|
||||
* STEP 1: Remove all Rsx\ and App\RSpade\ use statements
|
||||
* - We'll rebuild these based on actual usage
|
||||
* - Protects vendor/Laravel use statements (never removes)
|
||||
*
|
||||
* STEP 2: Replace ALL Rsx\ FQCNs with simple names
|
||||
* - Converts: \Rsx\Models\User_Model::class → User_Model::class
|
||||
* - Works for ALL classes in manifest (even non-unique names)
|
||||
* - Relies on Step 3 to add disambiguating use statements
|
||||
*
|
||||
* STEP 3: Re-add use statements based on actual usage
|
||||
* - Scans for simple class name references (from Step 2 replacements)
|
||||
* - Looks up FQCNs in manifest
|
||||
* - Adds: use Rsx\Models\User_Model;
|
||||
* - Disambiguates non-unique class names automatically
|
||||
*
|
||||
* EXAMPLE TRANSFORMATION:
|
||||
* Before:
|
||||
* return $this->belongsTo(\Rsx\Models\User_Model::class, 'team_lead_id');
|
||||
*
|
||||
* After:
|
||||
* use Rsx\Models\User_Model;
|
||||
* ...
|
||||
* return $this->belongsTo(User_Model::class, 'team_lead_id');
|
||||
*
|
||||
* @param string $file_path Relative path from base_path()
|
||||
* @param string $content Current file content
|
||||
* @param array $step_2_manifest_data Manifest state during Phase 2
|
||||
@@ -648,7 +722,10 @@ class Php_Fixer
|
||||
* Replace fully qualified Rsx class names with simple names
|
||||
*
|
||||
* This function finds \Rsx\Namespace\ClassName patterns and replaces them
|
||||
* with just ClassName if that class exists uniquely (only one file with that class name)
|
||||
* with just ClassName for ALL Rsx\ classes in manifest (even non-unique names).
|
||||
*
|
||||
* Step 3 of __fix_use_statements() will add the appropriate use statement,
|
||||
* which disambiguates non-unique class names.
|
||||
*
|
||||
* @param string $content File content
|
||||
* @param array $step_2_manifest_data Manifest data
|
||||
@@ -656,15 +733,15 @@ class Php_Fixer
|
||||
*/
|
||||
private static function __replace_rsx_fqcn_with_simple_names(string $content, array &$step_2_manifest_data): string
|
||||
{
|
||||
// First, build a map of simple class names to count occurrences
|
||||
$class_name_counts = [];
|
||||
// Build a set of all valid Rsx\ class names in manifest
|
||||
$valid_rsx_classes = [];
|
||||
foreach ($step_2_manifest_data['data']['files'] ?? [] as $manifest_file => $metadata) {
|
||||
if (isset($metadata['class']) && !empty($metadata['class'])) {
|
||||
$simple_name = $metadata['class'];
|
||||
if (!isset($class_name_counts[$simple_name])) {
|
||||
$class_name_counts[$simple_name] = 0;
|
||||
// Check if this file is an Rsx\ class (in rsx/ directory)
|
||||
if (str_starts_with($manifest_file, 'rsx/')) {
|
||||
$simple_name = $metadata['class'];
|
||||
$valid_rsx_classes[$simple_name] = true;
|
||||
}
|
||||
$class_name_counts[$simple_name]++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,8 +758,18 @@ class Php_Fixer
|
||||
$modifications = [];
|
||||
|
||||
for ($i = 0; $i < count($tokens); $i++) {
|
||||
// Look for namespace separator that starts a fully qualified name
|
||||
if ($tokens[$i] === '\\' || (is_array($tokens[$i]) && $tokens[$i][0] === T_NS_SEPARATOR)) {
|
||||
$fqcn = null;
|
||||
$fqcn_start = null;
|
||||
$fqcn_end = null;
|
||||
|
||||
// PHP 8+ uses T_NAME_FULLY_QUALIFIED for complete FQCN like \Rsx\Models\User_Model
|
||||
if (is_array($tokens[$i]) && defined('T_NAME_FULLY_QUALIFIED') && $tokens[$i][0] === T_NAME_FULLY_QUALIFIED) {
|
||||
$fqcn = $tokens[$i][1];
|
||||
$fqcn_start = $i;
|
||||
$fqcn_end = $i + 1;
|
||||
}
|
||||
// Fallback* for older PHP or partial namespaces
|
||||
elseif ($tokens[$i] === '\\' || (is_array($tokens[$i]) && $tokens[$i][0] === T_NS_SEPARATOR)) {
|
||||
// Check if this starts \Rsx\
|
||||
$fqcn_start = $i;
|
||||
$fqcn = '\\';
|
||||
@@ -707,40 +794,42 @@ class Php_Fixer
|
||||
break;
|
||||
}
|
||||
}
|
||||
$fqcn_end = $j;
|
||||
}
|
||||
|
||||
// Check if this is an Rsx FQCN (ONLY process \Rsx\ namespaced classes)
|
||||
if (str_starts_with($fqcn, '\\Rsx\\')) {
|
||||
// Extract simple class name (last part after final \)
|
||||
$parts = explode('\\', trim($fqcn, '\\'));
|
||||
$simple_name = end($parts);
|
||||
// Process FQCN if we found one
|
||||
if ($fqcn && str_starts_with($fqcn, '\\Rsx\\')) {
|
||||
// Extract simple class name (last part after final \)
|
||||
$parts = explode('\\', trim($fqcn, '\\'));
|
||||
$simple_name = end($parts);
|
||||
|
||||
// Only replace if this class name is UNIQUE (appears only once in manifest)
|
||||
if (isset($class_name_counts[$simple_name]) && $class_name_counts[$simple_name] === 1) {
|
||||
// Calculate the byte positions for replacement
|
||||
$start_pos = 0;
|
||||
for ($k = 0; $k < $fqcn_start; $k++) {
|
||||
if (is_array($tokens[$k])) {
|
||||
$start_pos += strlen($tokens[$k][1]);
|
||||
} else {
|
||||
$start_pos += strlen($tokens[$k]);
|
||||
}
|
||||
// Replace if this class exists in manifest (even if name is not unique)
|
||||
// Step 3 will add the correct use statement to disambiguate
|
||||
if (isset($valid_rsx_classes[$simple_name])) {
|
||||
// Calculate the byte positions for replacement
|
||||
$start_pos = 0;
|
||||
for ($k = 0; $k < $fqcn_start; $k++) {
|
||||
if (is_array($tokens[$k])) {
|
||||
$start_pos += strlen($tokens[$k][1]);
|
||||
} else {
|
||||
$start_pos += strlen($tokens[$k]);
|
||||
}
|
||||
|
||||
$length = 0;
|
||||
for ($k = $fqcn_start; $k < $j; $k++) {
|
||||
if (is_array($tokens[$k])) {
|
||||
$length += strlen($tokens[$k][1]);
|
||||
} else {
|
||||
$length += strlen($tokens[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
$modifications[] = [
|
||||
'start' => $start_pos,
|
||||
'length' => $length,
|
||||
'replacement' => $simple_name,
|
||||
];
|
||||
}
|
||||
|
||||
$length = 0;
|
||||
for ($k = $fqcn_start; $k < $fqcn_end; $k++) {
|
||||
if (is_array($tokens[$k])) {
|
||||
$length += strlen($tokens[$k][1]);
|
||||
} else {
|
||||
$length += strlen($tokens[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
$modifications[] = [
|
||||
'start' => $start_pos,
|
||||
'length' => $length,
|
||||
'replacement' => $simple_name,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user