getFileName(); $compiledAt = $classFilePath !== false ? filemtime($classFilePath) : time(); return new CompiledComponentMetadata( className: $className, componentName: $this->extractComponentName($reflection), properties: $this->extractProperties($reflection), actions: $this->extractActions($reflection), constructorParams: $this->extractConstructorParams($reflection), attributes: $this->extractAttributes($reflection), compiledAt: $compiledAt ); } /** * Extract component name from attribute */ private function extractComponentName(ReflectionClass $reflection): string { // Try to get from #[LiveComponent] attribute $attributes = $reflection->getAttributes(); foreach ($attributes as $attribute) { $attributeName = $attribute->getName(); if (str_ends_with($attributeName, 'LiveComponent')) { $args = $attribute->getArguments(); return $args['name'] ?? $args[0] ?? $this->classNameToComponentName($reflection->getShortName()); } } // Fallback: derive from class name return $this->classNameToComponentName($reflection->getShortName()); } /** * Convert class name to component name */ private function classNameToComponentName(string $className): string { // CounterComponent → counter // UserProfileComponent → user-profile $name = preg_replace('/Component$/', '', $className); $name = preg_replace('/([a-z])([A-Z])/', '$1-$2', $name); return strtolower($name); } /** * Extract public properties * * @return array */ private function extractProperties(ReflectionClass $reflection): array { $properties = []; foreach ($reflection->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { $type = $property->getType(); $typeName = 'mixed'; if ($type instanceof ReflectionNamedType) { $typeName = $type->getName(); } $properties[$property->getName()] = new ComponentPropertyMetadata( name: $property->getName(), type: $typeName, isPublic: $property->isPublic(), isReadonly: $property->isReadOnly(), defaultValue: $property->hasDefaultValue() ? $property->getDefaultValue() : null, hasDefaultValue: $property->hasDefaultValue() ); } return $properties; } /** * Extract public action methods * * @return array */ private function extractActions(ReflectionClass $reflection): array { $actions = []; foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { // Skip magic methods and constructor if ($method->isConstructor() || str_starts_with($method->getName(), '__')) { continue; } // Skip inherited methods if ($method->getDeclaringClass()->getName() !== $reflection->getName()) { continue; } $parameters = []; foreach ($method->getParameters() as $param) { $type = $param->getType(); $typeName = 'mixed'; if ($type instanceof ReflectionNamedType) { $typeName = $type->getName(); } $parameters[$param->getName()] = $typeName; } $returnType = $method->getReturnType(); $returnTypeName = null; if ($returnType instanceof ReflectionNamedType) { $returnTypeName = $returnType->getName(); } $actions[$method->getName()] = new ComponentActionMetadata( name: $method->getName(), parameters: $parameters, returnType: $returnTypeName, isPublic: $method->isPublic() ); } return $actions; } /** * Extract constructor parameters * * @return array Parameter name => type */ private function extractConstructorParams(ReflectionClass $reflection): array { $constructor = $reflection->getConstructor(); if ($constructor === null) { return []; } $params = []; foreach ($constructor->getParameters() as $param) { $type = $param->getType(); $typeName = 'mixed'; if ($type instanceof ReflectionNamedType) { $typeName = $type->getName(); } $params[$param->getName()] = $typeName; } return $params; } /** * Extract component attributes */ private function extractAttributes(ReflectionClass $reflection): array { $attributes = []; foreach ($reflection->getAttributes() as $attribute) { $attributes[$attribute->getName()] = $attribute->getArguments(); } return $attributes; } /** * Batch compile multiple components * * @param array $classNames * @return array */ public function compileBatch(array $classNames): array { $compiled = []; foreach ($classNames as $className) { $compiled[$className] = $this->compile($className); } return $compiled; } }