123]); * * // Internal call with specific API key * $result = API::execute('DataApi.export', [ * '_api_key' => 'secret_key', * 'format' => 'csv' * ]); * * // Nested internal calls work automatically * // UserApi.get_full_profile might internally call: * // - PostApi.get_user_posts * // - CommentApi.get_user_comments * // Each with proper session isolation * ``` * * TODO: Implement session stack when authentication system is ready * TODO: Add API token/key authentication mechanism * TODO: Create API::execute() static helper method * TODO: Add request/response interceptors for internal calls * TODO: Implement circuit breaker for recursive call protection */ class ApiHandler { /** * API version header name * * @var string */ protected static $version_header = 'X-API-Version'; /** * Handle API request * * @param mixed $handler The handler (controller/method) * @param Request $request * @param array $params * @param array $attributes Route attributes * @return \Illuminate\Http\Response */ public static function handle($handler, Request $request, array $params = [], array $attributes = []) { // Add API-specific parameters $params = static::__prepare_api_params($params, $request); // Check API version if specified $version = static::__get_api_version($request, $attributes); if ($version) { $params['_api_version'] = $version; } // Execute the handler $result = static::__execute_handler($handler, $request, $params); // ALWAYS return JSON for external API requests return static::__create_json_response($result, $request); } /** * Execute API handler internally (for future internal API calls) * * @param string $endpoint Format: "ControllerClass.method" * @param array $params * @param array $options * @return mixed Raw PHP response (not JSON) */ public static function execute_internal($endpoint, array $params = [], array $options = []) { // Parse endpoint list($class, $method) = static::__parse_endpoint($endpoint); // TODO: Push new session context onto stack // $this->push_session_context($options); try { // Build handler callable if (!class_exists($class)) { throw new \InvalidArgumentException("API class not found: {$class}"); } $handler = [$class, $method]; if (!is_callable($handler)) { throw new \InvalidArgumentException("API method not callable: {$endpoint}"); } // Add internal execution flag $params['_internal_call'] = true; $params['_caller_session'] = $options['session'] ?? null; // Execute handler directly $result = call_user_func($handler, $params); // For internal calls, return raw data (not JSON response) if ($result instanceof JsonResponse) { return json_decode($result->getContent(), true); } return $result; } finally { // TODO: Pop session context from stack // $this->pop_session_context(); } } /** * Parse endpoint string into class and method * * @param string $endpoint * @return array [class, method] */ protected static function __parse_endpoint($endpoint) { $parts = explode('.', $endpoint); if (count($parts) !== 2) { throw new \InvalidArgumentException("Invalid endpoint format. Expected: ControllerClass.method"); } $class = $parts[0]; $method = $parts[1]; // Add namespace if not present if (!str_contains($class, '\\')) { // Check common API namespaces using Manifest $namespaces = [ 'App\\Http\\Controllers\\Api\\', 'App\\RSpade\\Api\\', 'App\\Api\\' ]; $found = false; foreach ($namespaces as $namespace) { try { $full_class = $namespace . $class; $metadata = Manifest::php_get_metadata_by_fqcn($full_class); $class = $metadata['fqcn']; $found = true; break; } catch (\RuntimeException $e) { // Try next namespace } } if (!$found) { // Try without namespace prefix try { $metadata = Manifest::php_get_metadata_by_class($class); $class = $metadata['fqcn']; } catch (\RuntimeException $e) { // Class will be used as-is, may fail later } } } return [$class, $method]; } /** * Execute the API handler * * @param mixed $handler * @param Request $request * @param array $params * @return mixed */ protected static function __execute_handler($handler, Request $request, array $params) { // Handle different handler types if (is_string($handler) && str_contains($handler, '@')) { // Laravel style "Controller@method" list($class, $method) = explode('@', $handler); $handler = [new $class, $method]; } if (!is_callable($handler)) { throw new \RuntimeException("API handler is not callable"); } // Call handler with request and params $result = call_user_func($handler, $request, $params); // Ensure we have a result if ($result === null) { $result = ['success' => true]; } return $result; } /** * Create JSON response for API result * * @param mixed $data * @param Request $request * @return JsonResponse */ protected static function __create_json_response($data, Request $request) { $response = new JsonResponse($data); // Pretty print in development for debugging if (app()->environment('local', 'development')) { $response->setEncodingOptions(JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } // Always set JSON content type explicitly $response->headers->set('Content-Type', 'application/json'); return $response; } /** * Prepare API-specific parameters * * @param array $params * @param Request $request * @return array */ protected static function __prepare_api_params(array $params, Request $request) { // Add pagination parameters if ($request->has('page')) { $params['_page'] = (int) $request->get('page', 1); $params['_per_page'] = (int) $request->get('per_page', 25); } // Add sorting parameters if ($request->has('sort')) { $params['_sort'] = $request->get('sort'); $params['_order'] = $request->get('order', 'asc'); } // Add field filtering if ($request->has('fields')) { $params['_fields'] = explode(',', $request->get('fields')); } // Add search parameter if ($request->has('q') || $request->has('search')) { $params['_search'] = $request->get('q') ?: $request->get('search'); } // Add API key if present if ($request->has('api_key')) { $params['_api_key'] = $request->get('api_key'); } elseif ($request->header('X-API-Key')) { $params['_api_key'] = $request->header('X-API-Key'); } return $params; } /** * Get API version from request or attributes * * @param Request $request * @param array $attributes * @return string|null */ protected static function __get_api_version(Request $request, array $attributes) { // Check route attribute foreach ($attributes as $attr) { if ($attr instanceof \App\RSpade\Core\Attributes\ApiVersion) { return $attr->version; } } // Check header if ($request->hasHeader(static::$version_header)) { return $request->header(static::$version_header); } // Check URL path (e.g., /api/v2/...) $path = $request->path(); if (preg_match('#/v(\d+)/#', $path, $matches)) { return $matches[1]; } return null; } }