Fix code quality violations and rename select input components
Move small tasks from wishlist to todo, update npm packages Replace #[Auth] attributes with manual auth checks and code quality rule Remove on_jqhtml_ready lifecycle method from framework Complete ACL system with 100-based role indexing and /dev/acl tester WIP: ACL system implementation with debug instrumentation Convert rsx:check JS linting to RPC socket server Clean up docs and fix $id→$sid in man pages, remove SSR/FPC feature Reorganize wishlists: priority order, mark sublayouts complete, add email Update model_fetch docs: mark MVP complete, fix enum docs, reorganize Comprehensive documentation overhaul: clarity, compression, and critical rules Convert Contacts/Projects CRUD to Model.fetch() and add fetch_or_null() Add JS ORM relationship lazy-loading and fetch array handling Add JS ORM relationship fetching and CRUD documentation Fix ORM hydration and add IDE resolution for Base_* model stubs Rename Json_Tree_Component to JS_Tree_Debug_Component and move to framework Enhance JS ORM infrastructure and add Json_Tree class name badges 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,8 @@ use RecursiveCallbackFilterIterator;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use RuntimeException;
|
||||
use App\RSpade\Core\Bundle\Rsx_Asset_Bundle_Abstract;
|
||||
use App\RSpade\Core\Bundle\Rsx_Module_Bundle_Abstract;
|
||||
use App\RSpade\Core\Locks\RsxLocks;
|
||||
use App\RSpade\Core\Manifest\Manifest;
|
||||
|
||||
@@ -91,6 +93,12 @@ class BundleCompiler
|
||||
*/
|
||||
protected array $resolved_includes = [];
|
||||
|
||||
/**
|
||||
* The root module bundle class being compiled
|
||||
* Used for validation error messages
|
||||
*/
|
||||
protected string $root_bundle_class = '';
|
||||
|
||||
/**
|
||||
* Compiled jqhtml files (separated during ordering for special placement)
|
||||
*/
|
||||
@@ -129,6 +137,7 @@ class BundleCompiler
|
||||
|
||||
// Step 2: Mark the bundle we're compiling as already resolved
|
||||
$this->resolved_includes[$bundle_class] = true;
|
||||
$this->root_bundle_class = $bundle_class;
|
||||
|
||||
// Step 3: Process required bundles first
|
||||
$this->_process_required_bundles();
|
||||
@@ -467,18 +476,73 @@ class BundleCompiler
|
||||
$this->_process_include_item($bundle_aliases[$alias]);
|
||||
}
|
||||
}
|
||||
|
||||
// Include custom JS model base class if configured
|
||||
// This allows users to define application-wide model functionality
|
||||
$js_model_base_class = config('rsx.js_model_base_class');
|
||||
if ($js_model_base_class) {
|
||||
$this->_include_js_model_base_class($js_model_base_class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Include the custom JS model base class file in the bundle
|
||||
*
|
||||
* Finds the JS file by class name in the manifest and adds it to the bundle.
|
||||
* Validates that the class extends Rsx_Js_Model.
|
||||
*/
|
||||
protected function _include_js_model_base_class(string $class_name): void
|
||||
{
|
||||
// Find the JS file in the manifest by class name
|
||||
try {
|
||||
$file_path = Manifest::js_find_class($class_name);
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new \RuntimeException(
|
||||
"JavaScript model base class '{$class_name}' configured in rsx.js_model_base_class not found in manifest.\n" .
|
||||
"Ensure the class is defined in a .js file within your application (e.g., rsx/lib/{$class_name}.js)"
|
||||
);
|
||||
}
|
||||
|
||||
// Get metadata to verify it extends Rsx_Js_Model
|
||||
$metadata = Manifest::get_file($file_path);
|
||||
$extends = $metadata['extends'] ?? null;
|
||||
|
||||
if ($extends !== 'Rsx_Js_Model') {
|
||||
throw new \RuntimeException(
|
||||
"JavaScript model base class '{$class_name}' must extend Rsx_Js_Model.\n" .
|
||||
"Found: extends {$extends}\n" .
|
||||
"File: {$file_path}"
|
||||
);
|
||||
}
|
||||
|
||||
// Add the file to the bundle by processing it as a path
|
||||
$this->_process_include_item($file_path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve bundle and all its includes
|
||||
*
|
||||
* @param string $bundle_class The bundle class to resolve
|
||||
* @param bool $discovered_via_scan Whether this bundle was discovered via directory scan
|
||||
*/
|
||||
protected function _resolve_bundle(string $bundle_class): void
|
||||
protected function _resolve_bundle(string $bundle_class, bool $discovered_via_scan = false): void
|
||||
{
|
||||
// Get bundle definition
|
||||
if (!method_exists($bundle_class, 'define')) {
|
||||
throw new Exception("Bundle {$bundle_class} missing define() method");
|
||||
}
|
||||
|
||||
// Validate module bundle doesn't include another module bundle
|
||||
if (Manifest::php_is_subclass_of($bundle_class, 'Rsx_Module_Bundle_Abstract') &&
|
||||
$bundle_class !== $this->root_bundle_class) {
|
||||
Rsx_Module_Bundle_Abstract::validate_include($bundle_class, $this->root_bundle_class);
|
||||
}
|
||||
|
||||
// Validate asset bundles discovered via scan don't have directory paths
|
||||
if ($discovered_via_scan && Manifest::php_is_subclass_of($bundle_class, 'Rsx_Asset_Bundle_Abstract')) {
|
||||
Rsx_Asset_Bundle_Abstract::validate_no_directory_scanning($bundle_class, $this->root_bundle_class);
|
||||
}
|
||||
|
||||
$definition = $bundle_class::define();
|
||||
|
||||
// Process bundle includes
|
||||
@@ -732,6 +796,8 @@ class BundleCompiler
|
||||
|
||||
/**
|
||||
* Add all files from a directory
|
||||
*
|
||||
* Also auto-discovers Asset Bundles in the directory and processes them.
|
||||
*/
|
||||
protected function _add_directory(string $path): void
|
||||
{
|
||||
@@ -742,6 +808,9 @@ class BundleCompiler
|
||||
// Get excluded directories from config
|
||||
$excluded_dirs = config('rsx.manifest.excluded_dirs', ['vendor', 'node_modules', 'storage', '.git', 'public', 'resource']);
|
||||
|
||||
// Track discovered asset bundles to process after file collection
|
||||
$discovered_bundles = [];
|
||||
|
||||
// Create a recursive directory iterator with filtering
|
||||
$directory = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$filter = new RecursiveCallbackFilterIterator($directory, function ($current, $key, $iterator) use ($excluded_dirs) {
|
||||
@@ -763,7 +832,42 @@ class BundleCompiler
|
||||
|
||||
foreach ($iterator as $file) {
|
||||
if ($file->isFile()) {
|
||||
$this->_add_file($file->getPathname());
|
||||
$filepath = $file->getPathname();
|
||||
$extension = strtolower(pathinfo($filepath, PATHINFO_EXTENSION));
|
||||
|
||||
// For PHP files, check if it's an asset bundle via manifest
|
||||
if ($extension === 'php') {
|
||||
$relative_path = str_replace(base_path() . '/', '', $filepath);
|
||||
|
||||
// Get file metadata from manifest to check if it's an asset bundle
|
||||
try {
|
||||
$file_meta = Manifest::get_file($relative_path);
|
||||
$class_name = $file_meta['class'] ?? null;
|
||||
|
||||
// Use manifest to check if this PHP class is an asset bundle
|
||||
if ($class_name && Manifest::php_is_subclass_of($class_name, 'Rsx_Asset_Bundle_Abstract')) {
|
||||
$fqcn = $file_meta['fqcn'] ?? null;
|
||||
if ($fqcn && !isset($this->resolved_includes[$fqcn])) {
|
||||
$discovered_bundles[] = $fqcn;
|
||||
console_debug('BUNDLE', "Auto-discovered asset bundle: {$fqcn}");
|
||||
}
|
||||
// Don't add bundle file itself to file list - we'll process it as a bundle
|
||||
continue;
|
||||
}
|
||||
} catch (RuntimeException $e) {
|
||||
// File not in manifest, just add it normally
|
||||
}
|
||||
}
|
||||
|
||||
$this->_add_file($filepath);
|
||||
}
|
||||
}
|
||||
|
||||
// Process discovered asset bundles (marked as discovered via scan)
|
||||
foreach ($discovered_bundles as $bundle_fqcn) {
|
||||
if (!isset($this->resolved_includes[$bundle_fqcn])) {
|
||||
$this->resolved_includes[$bundle_fqcn] = true;
|
||||
$this->_resolve_bundle($bundle_fqcn, true); // true = discovered via scan
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1059,6 +1163,154 @@ class BundleCompiler
|
||||
return array_unique($stubs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate concrete model classes for PHP models in the bundle
|
||||
*
|
||||
* For each PHP model (subclass of Rsx_Model_Abstract) in the bundle:
|
||||
* 1. Check if a user-defined JS class with the same name exists
|
||||
* 2. If user-defined class exists:
|
||||
* - Validate it extends Base_{ModelName} directly
|
||||
* - If it exists in manifest but not in bundle, throw error
|
||||
* 3. If no user-defined class exists:
|
||||
* - Auto-generate: class ModelName extends Base_ModelName {}
|
||||
*
|
||||
* @param array $current_js_files JS files already in the bundle (to check for user classes)
|
||||
* @return string|null Path to temp file containing generated classes, or null if none needed
|
||||
*/
|
||||
protected function _generate_concrete_model_classes(array $current_js_files): ?string
|
||||
{
|
||||
$manifest = Manifest::get_full_manifest();
|
||||
$manifest_files = $manifest['data']['files'] ?? [];
|
||||
|
||||
// Get all files from all bundles to find PHP models
|
||||
$all_bundle_files = [];
|
||||
foreach ($this->bundle_files as $type => $files) {
|
||||
if (is_array($files)) {
|
||||
$all_bundle_files = array_merge($all_bundle_files, $files);
|
||||
}
|
||||
}
|
||||
|
||||
// Build a set of JS class names currently in the bundle for quick lookup
|
||||
$js_classes_in_bundle = [];
|
||||
foreach ($current_js_files as $js_file) {
|
||||
$relative = str_replace(base_path() . '/', '', $js_file);
|
||||
if (isset($manifest_files[$relative]['class'])) {
|
||||
$js_classes_in_bundle[$manifest_files[$relative]['class']] = $relative;
|
||||
}
|
||||
}
|
||||
|
||||
// Find all PHP models in the bundle
|
||||
$models_in_bundle = [];
|
||||
foreach ($all_bundle_files as $file) {
|
||||
$relative = str_replace(base_path() . '/', '', $file);
|
||||
|
||||
// Check if this is a PHP file with a class
|
||||
if (!isset($manifest_files[$relative]['class'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$class_name = $manifest_files[$relative]['class'];
|
||||
|
||||
// Check if this class is a subclass of Rsx_Model_Abstract (but not system models)
|
||||
if (!Manifest::php_is_subclass_of($class_name, 'Rsx_Model_Abstract')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip abstract model classes - only concrete models get JS stubs
|
||||
if (Manifest::php_is_abstract($class_name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$models_in_bundle[$class_name] = $relative;
|
||||
}
|
||||
|
||||
if (empty($models_in_bundle)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
console_debug('BUNDLE', 'Found ' . count($models_in_bundle) . ' PHP models in bundle: ' . implode(', ', array_keys($models_in_bundle)));
|
||||
|
||||
// Process each model
|
||||
$generated_classes = [];
|
||||
$base_class_name = config('rsx.js_model_base_class');
|
||||
|
||||
foreach ($models_in_bundle as $model_name => $model_path) {
|
||||
$expected_base_class = 'Base_' . $model_name;
|
||||
|
||||
// Check if user has defined a JS class with this model name
|
||||
$user_js_class_path = null;
|
||||
foreach ($manifest_files as $file_path => $meta) {
|
||||
if (isset($meta['class']) && $meta['class'] === $model_name && isset($meta['extension']) && $meta['extension'] === 'js') {
|
||||
// Make sure it's not a generated stub
|
||||
if (!isset($meta['is_model_stub']) && !isset($meta['is_stub'])) {
|
||||
$user_js_class_path = $file_path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($user_js_class_path) {
|
||||
// User has defined a JS class for this model - validate it
|
||||
console_debug('BUNDLE', "Found user-defined JS class for {$model_name} at {$user_js_class_path}");
|
||||
|
||||
// Check if it's in the bundle
|
||||
if (!isset($js_classes_in_bundle[$model_name])) {
|
||||
throw new RuntimeException(
|
||||
"PHP model '{$model_name}' is included in bundle (at {$model_path}) " .
|
||||
"but its custom JavaScript implementation exists at '{$user_js_class_path}' " .
|
||||
"and is NOT included in the bundle.\n\n" .
|
||||
"Either:\n" .
|
||||
"1. Add the JS file's directory to the bundle's include paths, or\n" .
|
||||
"2. Remove the custom JS implementation to use auto-generated class"
|
||||
);
|
||||
}
|
||||
|
||||
// Validate it extends the Base_ class directly
|
||||
$user_meta = $manifest_files[$user_js_class_path] ?? [];
|
||||
$user_extends = $user_meta['extends'] ?? null;
|
||||
|
||||
if ($user_extends !== $expected_base_class) {
|
||||
throw new RuntimeException(
|
||||
"JavaScript model class '{$model_name}' at '{$user_js_class_path}' " .
|
||||
"must extend '{$expected_base_class}' directly.\n" .
|
||||
"Found: extends " . ($user_extends ?: '(nothing)') . "\n\n" .
|
||||
"Correct usage:\n" .
|
||||
"class {$model_name} extends {$expected_base_class} {\n" .
|
||||
" // Your custom model methods\n" .
|
||||
"}"
|
||||
);
|
||||
}
|
||||
|
||||
console_debug('BUNDLE', "Validated {$model_name} extends {$expected_base_class}");
|
||||
} else {
|
||||
// No user-defined class - auto-generate one
|
||||
console_debug('BUNDLE', "Auto-generating concrete class for {$model_name}");
|
||||
$generated_classes[] = "class {$model_name} extends {$expected_base_class} {}";
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($generated_classes)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Write all generated classes to a single temp file using standard temp file pattern
|
||||
$content = "/**\n";
|
||||
$content .= " * Auto-generated concrete model classes\n";
|
||||
$content .= " * These classes extend the Base_* stubs to provide usable model classes\n";
|
||||
$content .= " * when no custom implementation is defined by the developer.\n";
|
||||
$content .= " */\n\n";
|
||||
$content .= implode("\n\n", $generated_classes) . "\n";
|
||||
|
||||
// Use content hash for idempotent file naming, with recognizable prefix for detection
|
||||
$hash = substr(md5($content), 0, 8);
|
||||
$temp_file = storage_path('rsx-tmp/bundle_generated_models_' . $this->bundle_name . '_' . $hash . '.js');
|
||||
file_put_contents($temp_file, $content);
|
||||
|
||||
console_debug('BUNDLE', 'Generated ' . count($generated_classes) . ' concrete model classes');
|
||||
|
||||
return $temp_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Order JavaScript files by class dependency
|
||||
*
|
||||
@@ -1109,6 +1361,29 @@ class BundleCompiler
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is a JS stub file (not in manifest, needs parsing)
|
||||
// Stub files are in storage/rsx-build/js-stubs/ or storage/rsx-build/js-model-stubs/
|
||||
if (str_contains($file, 'storage/rsx-build/js-stubs/') || str_contains($file, 'storage/rsx-build/js-model-stubs/')) {
|
||||
// Use simple regex extraction - stub files have known format and can't use
|
||||
// the strict JS parser (stubs may have code after class declaration)
|
||||
$stub_content = file_get_contents($file);
|
||||
$stub_metadata = $this->_extract_stub_class_info($stub_content);
|
||||
|
||||
if (!empty($stub_metadata['class'])) {
|
||||
$class_files[] = $file;
|
||||
$class_info[$file] = [
|
||||
'class' => $stub_metadata['class'],
|
||||
'extends' => $stub_metadata['extends'],
|
||||
'decorators' => [],
|
||||
'method_decorators' => [],
|
||||
];
|
||||
console_debug('BUNDLE_SORT', "Parsed stub file: {$stub_metadata['class']} extends " . ($stub_metadata['extends'] ?? 'nothing'));
|
||||
} else {
|
||||
$non_class_files[] = $file;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get file info from manifest
|
||||
$relative = str_replace(base_path() . '/', '', $file);
|
||||
$file_data = $manifest_files[$relative] ?? null;
|
||||
@@ -1211,6 +1486,35 @@ class BundleCompiler
|
||||
return $decorators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract class name and extends from JS stub file content
|
||||
*
|
||||
* Uses simple regex extraction since stub files have a known format and may
|
||||
* have code after the class declaration that the strict JS parser rejects.
|
||||
*
|
||||
* @param string $content The stub file content
|
||||
* @return array ['class' => string|null, 'extends' => string|null]
|
||||
*/
|
||||
protected function _extract_stub_class_info(string $content): array
|
||||
{
|
||||
// Remove single-line comments
|
||||
$content = preg_replace('#//.*$#m', '', $content);
|
||||
|
||||
// Remove multi-line comments (including JSDoc)
|
||||
$content = preg_replace('#/\*.*?\*/#s', '', $content);
|
||||
|
||||
// Match: class ClassName or class ClassName extends ParentClass
|
||||
// The first match wins - we only care about the class declaration
|
||||
if (preg_match('/\bclass\s+([A-Za-z_][A-Za-z0-9_]*)(?:\s+extends\s+([A-Za-z_][A-Za-z0-9_]*))?/', $content, $matches)) {
|
||||
return [
|
||||
'class' => $matches[1],
|
||||
'extends' => $matches[2] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
return ['class' => null, 'extends' => null];
|
||||
}
|
||||
|
||||
/**
|
||||
* Topological sort for class dependencies with decorator support
|
||||
*
|
||||
@@ -1419,7 +1723,13 @@ implode("\n", array_map(fn ($f) => ' - ' . str_replace(base_path() . '/', '',
|
||||
* Here we:
|
||||
* 1. Filter to only .js and .css files
|
||||
* 2. Order JS files by class dependency
|
||||
* 3. Add framework code (stubs, manifest, runner)
|
||||
* 3. Add framework code:
|
||||
* a. JS stubs (Base_* model classes, controller stubs, etc.)
|
||||
* b. Compiled jqhtml templates
|
||||
* c. Concrete model classes (auto-generated or validated user-defined)
|
||||
* d. Manifest definitions (registers all JS classes)
|
||||
* e. Route definitions
|
||||
* f. Initialization runner (LAST - starts the application)
|
||||
* 4. Generate final compiled output
|
||||
*/
|
||||
protected function _compile_outputs(array $types_to_compile = []): array
|
||||
@@ -1456,7 +1766,16 @@ implode("\n", array_map(fn ($f) => ' - ' . str_replace(base_path() . '/', '',
|
||||
// Other extensions are ignored - they should have been processed into JS/CSS
|
||||
}
|
||||
|
||||
// Order JavaScript files by class dependency BEFORE adding framework code
|
||||
// Add JS stubs to app bundle only (they depend on Rsx_Js_Model which is in app)
|
||||
// Add them BEFORE dependency ordering so they're properly sorted
|
||||
if ($type === 'app') {
|
||||
$stub_files = $this->_get_js_stubs();
|
||||
foreach ($stub_files as $stub) {
|
||||
$files['js'][] = $stub;
|
||||
}
|
||||
}
|
||||
|
||||
// Order JavaScript files by class dependency BEFORE adding other framework code
|
||||
if (!empty($files['js'])) {
|
||||
$files['js'] = $this->_order_javascript_files_by_dependency($files['js']);
|
||||
}
|
||||
@@ -1482,7 +1801,8 @@ implode("\n", array_map(fn ($f) => ' - ' . str_replace(base_path() . '/', '',
|
||||
}
|
||||
}
|
||||
|
||||
// Add JS stubs and framework code to app JS
|
||||
// Add framework code to app JS
|
||||
// Note: JS stubs are already added before dependency ordering above
|
||||
if ($type === 'app') {
|
||||
// Add NPM import declarations at the very beginning
|
||||
if (!empty($this->npm_includes)) {
|
||||
@@ -1492,19 +1812,19 @@ implode("\n", array_map(fn ($f) => ' - ' . str_replace(base_path() . '/', '',
|
||||
}
|
||||
}
|
||||
|
||||
// ALWAYS get JS stubs for ALL included files that have them
|
||||
// ANY file type can have a js_stub - controllers, models, custom types, etc.
|
||||
$stub_files = $this->_get_js_stubs();
|
||||
foreach ($stub_files as $stub) {
|
||||
$files['js'][] = $stub;
|
||||
}
|
||||
|
||||
// Add compiled jqhtml files AFTER JS stubs
|
||||
// Add compiled jqhtml files
|
||||
// These are JavaScript files generated from .jqhtml templates
|
||||
foreach ($this->jqhtml_compiled_files as $jqhtml_file) {
|
||||
$files['js'][] = $jqhtml_file;
|
||||
}
|
||||
|
||||
// Generate concrete model classes for PHP models in the bundle
|
||||
// This validates user-defined JS model classes and auto-generates missing ones
|
||||
$concrete_models_file = $this->_generate_concrete_model_classes($files['js']);
|
||||
if ($concrete_models_file) {
|
||||
$files['js'][] = $concrete_models_file;
|
||||
}
|
||||
|
||||
// Generate manifest definitions for all JS classes
|
||||
$manifest_file = $this->_create_javascript_manifest($files['js'] ?? []);
|
||||
if ($manifest_file) {
|
||||
@@ -2020,8 +2340,38 @@ implode("\n", array_map(fn ($f) => ' - ' . str_replace(base_path() . '/', '',
|
||||
|
||||
// Analyze each JavaScript file for class information
|
||||
foreach ($js_files as $file) {
|
||||
// Skip temp files
|
||||
// Skip most temp files, but handle auto-generated model classes
|
||||
if (str_contains($file, 'storage/rsx-tmp/')) {
|
||||
// Check if this is the auto-generated model classes file
|
||||
if (str_contains($file, 'bundle_generated_models_')) {
|
||||
// Parse simple class declarations: class Foo extends Bar {}
|
||||
$content = file_get_contents($file);
|
||||
if (preg_match_all('/class\s+([A-Za-z_][A-Za-z0-9_]*)\s+extends\s+([A-Za-z_][A-Za-z0-9_]*)/', $content, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
$class_definitions[$match[1]] = [
|
||||
'name' => $match[1],
|
||||
'extends' => $match[2],
|
||||
'decorators' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is a JS stub file (not in PHP manifest, needs direct parsing)
|
||||
// Stub files are in storage/rsx-build/js-stubs/ or storage/rsx-build/js-model-stubs/
|
||||
if (str_contains($file, 'storage/rsx-build/js-stubs/') || str_contains($file, 'storage/rsx-build/js-model-stubs/')) {
|
||||
$stub_content = file_get_contents($file);
|
||||
$stub_metadata = $this->_extract_stub_class_info($stub_content);
|
||||
|
||||
if (!empty($stub_metadata['class'])) {
|
||||
$class_definitions[$stub_metadata['class']] = [
|
||||
'name' => $stub_metadata['class'],
|
||||
'extends' => $stub_metadata['extends'],
|
||||
'decorators' => null, // Stubs don't have method decorators
|
||||
];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user