Files
rspade_system/app/RSpade/Core/Manifest/CLAUDE.md
root bd5809fdbd Reorganize RSpade directory structure for clarity
Improve Jqhtml_Integration.js documentation with hydration system explanation
Add jqhtml-laravel integration packages for traditional Laravel projects

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-24 09:41:48 +00:00

12 KiB
Executable File

Manifest System - Developer Documentation

This documentation is for developers working on Manifest.php itself. For usage documentation, see the man pages via php artisan rsx:man manifest_api.

CRITICAL: Testing Manifest.php Changes

When modifying Manifest.php, you MUST run php artisan rsx:manifest:build --clean to test your changes.

The --clean flag performs both rsx:clean and a full manifest rebuild, ensuring your modifications are properly tested. Without this, the manifest may use cached or partially-built data that doesn't reflect your changes.

Architecture Overview

The Manifest is a compiled cache of all file metadata in the RSX application, stored at storage/rsx-build/manifest_data.php. It replaces Laravel's scattered discovery mechanisms with a unified system that enables path-agnostic class loading.

Core Data Structure

The manifest cache contains:

[
    'generated' => '2025-09-28 05:02:15',
    'hash' => '42b9d0efb5c547eec0fb2ca19bf922e0',
    'data' => [
        'files' => [...],           // All indexed files with metadata
        'js_classes' => [...],       // JavaScript class map
        'php_classes' => [...],      // PHP class map
        'autoloader_class_map' => [...], // Simple name to FQCN mapping
        'models' => [...],           // Database model metadata
        'jqhtml' => [...]           // jqhtml component registry
    ]
]

Schema Discovery Tool

Use php artisan rsx:manifest:schema_dump to inspect the manifest structure:

# Pretty-printed JSON for human reading
php artisan rsx:manifest:schema_dump

# Compact JSON for LLM parsing (saves tokens)
php artisan rsx:manifest:schema_dump --no-pretty-print

This tool deduplicates array structures, showing unique patterns with example values. Essential for understanding the actual data structure when writing code that directly accesses manifest data.

6-Phase Build Process

The build process in Manifest.php follows these phases:

Phase 1: File Discovery (__discover_files)

  • Scans directories from config('rsx.manifest.scan_directories')
  • Default: ['rsx', 'app/RSpade/Core/Js', 'app/RSpade/Core/Manifest/Modules']
  • Excludes: vendor/, node_modules/, .git/, storage/, public/
  • Returns array of file paths with basic stats (mtime, size)

Phase 2: Token Parsing (__extract_basic_metadata)

  • Uses token_get_all() for fast PHP parsing without loading
  • Extracts: namespace, class name, extends, implements
  • Normalizes all class references to simple names (strips namespace qualifiers)
  • Builds dependency graph for loading order

Phase 3: Dependency Loading (__load_changed_php_files)

  • Loads PHP files in dependency order (parents before children)
  • Uses _load_class_hierarchy() to ensure parent classes exist
  • Critical for reflection to work properly

Phase 4: Reflection & Module Processing (__process_with_modules)

  • Runs registered ManifestModule processors
  • Core processors:
    • Php_ManifestModule: Extracts attributes, methods via reflection
    • Blade_ManifestModule: Parses Blade directives
    • Javascript_ManifestModule: Parses JS classes/methods
    • Model_ManifestSupport: Adds database schema to models
  • Stores ALL attributes without validation (agnostic extraction)

Phase 5: Stub Generation (__generate_js_stubs, __generate_model_stubs)

  • Creates JavaScript stub classes for:
    • Controllers with Ajax_Endpoint methods → storage/rsx-build/js-stubs/
    • Models with fetch() methods → storage/rsx-build/js-model-stubs/
  • Enables clean JavaScript API calls without manual Ajax wiring

Phase 6: Cache Writing (__write_manifest_cache)

  • Serializes to PHP array format
  • Writes to storage/rsx-build/manifest_data.php
  • Uses var_export() for fast include() loading

Key Implementation Details

Class Name Normalization Philosophy

CRITICAL: RSX enforces unique simple class names across the entire codebase. This architectural constraint allows the manifest to normalize ALL class references to simple names, eliminating an entire class of bugs related to namespace format variations.

The Problem: PHP allows multiple ways to reference classes:

  • \Rsx\Lib\DataGrid (leading backslash)
  • Rsx\Lib\DataGrid (no leading backslash)
  • DataGrid (simple name)

The Solution: Manifest::_normalize_class_name() strips namespace qualifiers:

public static function _normalize_class_name(string $class_name): string
{
    // Strip leading backslash
    $class_name = ltrim($class_name, '\\');

    // Extract just the class name (last part after final backslash)
    $parts = explode('\\', $class_name);
    return end($parts);
}

Applied At:

  1. Token parsing - Php_Parser::__extract_class_info() normalizes extends at extraction
  2. Parent lookups - Manifest::_load_class_hierarchy() normalizes before comparison
  3. All class name operations - Any code comparing class names uses normalization

Why This Works: RSX's unique simple class name enforcement means we only need FQCNs at actual include_once time. Throughout the manifest, simple names are sufficient and eliminate format inconsistencies.

Bug This Fixes: Previously, extends \Rsx\Lib\DataGrid (with leading \ from token parser) failed to match stored FQCN Rsx\Lib\DataGrid, causing parent class resolution failures.

Static State Management

The Manifest uses static properties for singleton behavior:

private static $_manifest = null;        // Cached manifest data
private static $_is_initialized = false; // Initialization flag
private static $_manifest_loaded_at = null; // Load timestamp

Public API Methods

Initialization & Loading

  • init() - Ensures manifest is loaded (auto-rebuilds in dev mode)
  • get_full_manifest() - Returns complete manifest structure with metadata
  • clear() - Clears the in-memory cache

File Lookups

  • get_all() - Returns all files array
  • get_file($path) - Get specific file metadata (throws if not found)
  • get_files_by_dir($dir) - Get all files in directory

PHP Class Resolution

  • php_find_class($name) - Find by simple class name
  • find_php_fqcn($fqcn) - Find by fully qualified name
  • php_get_metadata_by_class($name) - Get metadata by class name
  • php_get_metadata_by_fqcn($fqcn) - Get metadata by FQCN
  • php_get_extending($parent) - Find all concrete classes extending parent (filters out abstract)
  • php_is_subclass_of($sub, $super) - Check inheritance

JavaScript Class Resolution

  • js_find_class($name) - Find JavaScript class file
  • js_get_extending($parent) - Find extending JS classes
  • js_is_subclass_of($sub, $super) - Check JS inheritance

View Resolution

  • find_view($id) - Find by dot notation (e.g., 'frontend.index')
  • find_view_by_rsx_id($id) - Find by @rsx_id value

Attribute & Route Discovery

  • get_with_attribute($attr) - Find all files with specific attribute
  • get_routes() - Extract all route definitions from attributes

Utility Methods

  • get_build_key() - Get manifest hash for cache validation
  • get_stats() - Get statistics (file counts, build time)
  • get_autoloader_class_map() - Get class-to-file mappings
  • is_built() - Check if manifest exists
  • _normalize_class_name($class_name) - Strip namespace qualifiers to get simple name

Attribute Extraction Philosophy

The manifest practices agnostic attribute extraction:

  1. No Validation - Stores all attributes without checking validity
  2. No Instantiation - Never creates attribute objects
  3. No Class Loading - Attributes don't need backing classes
  4. Raw Storage - Stores exactly what reflection provides

Example from _extract_php_metadata:

foreach ($method->getAttributes() as $attribute) {
    $attributes[] = [
        'name' => $attribute->getName(),
        'arguments' => $attribute->getArguments()
    ];
}

Change Detection

Files are tracked by:

  • mtime - Modification time (primary)
  • size - File size in bytes (secondary)
  • hash - SHA1, only computed when mtime/size change

The __refresh_manifest() method compares these to detect changes.

Error Handling

All lookup methods throw RuntimeException when items not found:

if (!isset(self::$_manifest['data']['php_classes'][$class_name])) {
    throw new RuntimeException("PHP class not found in manifest: {$class_name}");
}

This ensures fail-fast behavior - no silent failures.

Module System

Built-in Modules

Located in /app/RSpade/Core/Manifest/Modules/:

  • Php_ManifestModule - PHP reflection and attribute extraction
  • Blade_ManifestModule - Blade directive parsing
  • Javascript_ManifestModule - JS class/method extraction
  • Scss_ManifestModule - SCSS metadata
  • Model_ManifestSupport - Database schema extraction
  • Jqhtml_ManifestSupport - jqhtml component registration

Creating Custom Modules

Implement the ManifestModule interface:

interface ManifestModule {
    function get_name(): string;
    function get_extensions(): array;  // File extensions to handle
    function process(string $file, array $metadata): array;
    function post_process(array $manifest): void;
}

Register in config/rsx.php:

'manifest' => [
    'modules' => [
        App\MyModule::class
    ]
]

Performance Considerations

Caching Strategy

  • Development: Auto-rebuilds via __refresh_manifest() on file changes
  • Production: Manual rebuild required, loads from cache file
  • Memory: Full manifest kept in static variable after first load

Optimization Points

  • Token parsing avoids loading PHP files unnecessarily
  • Dependency ordering minimizes class loading failures
  • SHA1 hashing only when size/mtime indicate changes
  • var_export() format allows fast PHP include()

Common Issues & Solutions

Issue: "Class not found" during reflection

Cause: Parent class not loaded before child Solution: Check _load_class_hierarchy() is working correctly

Issue: Parent class not found (extends mismatch)

Cause: Namespace format variations in extends declarations (e.g., \Rsx\Lib\DataGrid vs Rsx\Lib\DataGrid) Solution: Use Manifest::_normalize_class_name() to strip namespace qualifiers before comparison Fixed In: Version with class name normalization (2025-10-10)

Issue: Attributes not appearing in manifest

Cause: Method not public static Solution: Only public static methods are indexed

Issue: Manifest not updating

Cause: In production mode or file timestamps unchanged Solution: Run php artisan rsx:manifest:build --force

Issue: JavaScript stubs not generating

Cause: Missing Ajax_Endpoint attribute or method not public static Solution: Check method has attribute and correct visibility

Code Quality Integration

The manifest integrates with code quality checks via metadata storage. Rules that run during manifest scan store violations in code_quality_metadata field for each file.

Debugging

Enable verbose output:

Manifest::$_debug_options['verbose'] = true;

Or via command:

php artisan rsx:manifest:build --verbose

Testing Considerations

When testing manifest functionality:

  1. Use Manifest::clear() between tests to reset state
  2. Mock file system for predictable test data
  3. Test both scan() and rebuild() paths
  4. Verify exception throwing for not-found cases
  5. Check stub generation for API methods

Important Constants & Paths

  • Cache file: storage/rsx-build/manifest_data.php
  • JS stubs: storage/rsx-build/js-stubs/
  • Model stubs: storage/rsx-build/js-model-stubs/
  • Default scan dirs: ['rsx', 'app/RSpade/Core/Js', 'app/RSpade/Core/Manifest/Modules']

Direct Data Access

When bypassing helper methods to access manifest data directly:

$manifest = Manifest::get_full_manifest();

// Access files
$file_data = $manifest['data']['files']['path/to/file.php'];

// Access class maps
$class_file = $manifest['data']['php_classes']['ClassName'];
$js_class = $manifest['data']['js_classes']['JsClass'];

// Access models with schema
$model = $manifest['data']['models']['User_Model'];
$columns = $model['columns'];

Use rsx:manifest:schema_dump to understand the exact structure before writing direct access code.