r * @return self */ public function preparefrontendHandlers() { $handler = $this->handler; $endpointsUrl = $this->app->config->get('app.slug') . '/__endpoints'; if (get_option('permalink_structure')) { $url = $this->app->request->url(); } else { $url = $this->app->request->query('rest_route'); } if ( !str_contains($url ?? '', $endpointsUrl) || $handler instanceof Closure ) { return $this; } [$controller, $cb] = Str::parseCallback($this->parseAction($handler)); $this->endpointSignature = [$controller, "_{$cb}"]; $controller = str_replace('\\', '.', $controller); // @phpstan-ignore-next-line $endpoints = $this->app->endpoints; $endpoints[$controller]["_{$cb}"] = [ 'uri' => $this->uri, 'methods' => explode(',', $this->method), 'policy' => $this->getPolicyName() ]; // @phpstan-ignore-next-line $this->app->endpoints = $endpoints; return $this; } /** * Get a display name for the route's policy handler. * * @return string|null */ protected function getPolicyName() { if (!$this->policyHandler) { return null; } if ($this->policyHandler instanceof Closure) { return 'Closure'; } $name = $this->policyHandler; if (is_string($name) && !$this->app->hasNamespace($name)) { $name = $this->app->__namespace__ . '\\App\\Http\\Policies\\' . $name; } return $name; } /** * Parse the action from the handler. * * @param mixed $handler * @return string */ protected function parseAction($handler) { $action = $this->app->parseRestHandler($handler, $this->namespace); $action = trim($action, '\\'); if (!str_contains($action, '@')) { $action .= '@__invoke'; } return $action; } /** * Alternative constructor * * @param \FluentCrm\Framework\Foundation\Application $app * @param string $namespace * @param string $uri * @param string $handler * @param string $method * @return self */ public static function create($app, $namespace, $uri, $handler, $method) { return new static($app, $namespace, $uri, $handler, $method); } /** * Set route meta * * @param string $key * @param mixed $value * @return self */ public function meta($key, $value = null) { $meta = is_array($key) ? $key : [$key => $value]; $this->meta = array_merge($this->meta, $meta); return $this; } /** * Get route meta * * @param string $key * @return mixed */ public function getMeta($key = '') { if (isset($this->meta[$key])) { return $this->meta[$key]; } return $this->meta; } /** * Get route options * * @return mixed */ public function getOptions() { return $this->getOption(); } /** * Get route options * * @param string $key * @return mixed */ public function getOption($key = null) { return $key ? $this->options[$key] : $this->options; } /** * Get route action information * @param string $key * @return mixed */ public function getAction($key = '') { if ($key && array_key_exists($key, $this->actionInfo)) { return $this->actionInfo[$key]; } return $this->actionInfo; } /** * Set a where constrain into the route * * @param string $identifier * @param string $value * @return self */ public function where($identifier, $value = null) { if (!is_null($value)) { $this->wheres[$identifier] = $this->getValue($value); } else { foreach ($identifier as $key => $value) { $this->wheres[$key] = $this->getValue($value); } } return $this; } /** * Add an integer type route constraint * * @param string $identifiers * @return self */ public function int($identifiers) { $identifiers = is_array($identifiers) ? $identifiers : func_get_args(); foreach ($identifiers as $identifier) { $this->wheres[$identifier] = '[0-9]+'; } return $this; } /** * Add an alpha type route constraint * * @param string $identifiers * @return self */ public function alpha($identifiers) { $identifiers = is_array($identifiers) ? $identifiers : func_get_args(); foreach ($identifiers as $identifier) { $this->wheres[$identifier] = '[a-zA-Z]+'; } return $this; } /** * Add an alphanum type route constraint * * @param string $identifiers * @return self */ public function alphaNum($identifiers) { $identifiers = is_array($identifiers) ? $identifiers : func_get_args(); foreach ($identifiers as $identifier) { $this->wheres[$identifier] = '[a-zA-Z0-9]+'; } return $this; } /** * Add an alphanumdash type route constraint * * @param string $identifiers * @return self */ public function alphaNumDash($identifiers) { $identifiers = is_array($identifiers) ? $identifiers : func_get_args(); foreach ($identifiers as $identifier) { $this->wheres[$identifier] = '[a-zA-Z0-9-_]+'; } return $this; } /** * Set the route before middleware * * @param array|string $middleware * @return self */ public function before(...$middleware) { return $this->middleware('before', ...$middleware); } /** * Set the route after middleware * * @param array|string $middleware * @return self */ public function after(...$middleware) { return $this->middleware('after', ...$middleware); } /** * Set the route middleware * @param array $middleware * @return self */ public function middleware($type = 'before', ...$middleware) { if (is_array($middleware[0])) { $middleware = reset($middleware); } $this->middleware[$type] = array_merge( $this->middleware[$type], $middleware ); return $this; } /** * Set the default route policy. * * @return self */ public function withDefaultPolicy() { return $this->withPolicy( // @phpstan-ignore-next-line $this->app->__namespace__.'\\App\\Http\\Policies\\Policy' ); } /** * Set the route policy * * @param mixed $handler * @param string|null $method * @return self */ public function withPolicy($handler, $method = null) { if (is_array($handler = $method ? func_get_args() : $handler)) { $handler = implode('@', $handler); } $this->policyHandler = $handler; if (is_string($handler) && !$this->app->hasNamespace($handler)) { $this->setPolicyHandlerWithNamespace( debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 4) ); } return $this->addRouteInfo($handler); } /** * Check if the request is from CLI; * * @return bool */ protected function fromCli() { $hash = $this->app->request->header('X-From-CLI'); $slugHash = md5($this->app->config->get('app.slug')); return $hash === $slugHash; } /** * Add route information for CLI command. * * @param mixed $handler * @return self */ protected function addRouteInfo($handler) { if (!$this->fromCli()) { return $this; } if ($handler instanceof Closure) { $policyHandler = 'Closure'; } else { $policyHandler = $this->resolvePolicyHandler(); if (is_array($policyHandler)) { $policyHandler = implode('@', $policyHandler); } } $this->injectProp('policy', $policyHandler); return $this; } /** * Inject property into route infio. * * @param string $key * @param mixed $value * @return void */ public function injectProp($key, $value) { if (!$this->endpointSignature) { return; } [$controller, $cbKey] = $this->endpointSignature; $controllerKey = str_replace('\\', '.', $controller); // @phpstan-ignore-next-line $endpoints = $this->app->endpoints; if (isset($endpoints[$controllerKey][$cbKey])) { $endpoints[$controllerKey][$cbKey][$key] = $value; // @phpstan-ignore-next-line $this->app->endpoints = $endpoints; } } /** * Resolve and set policy with namespace for add-ons * * @param array $backTrace * @return void */ protected function setPolicyHandlerWithNamespace($backTrace) { $last = end($backTrace); if (!isset($last['class'])) return; $class = $last['class']; $namespace = substr(__NAMESPACE__, 0, strpos(__NAMESPACE__, '\\')); $calledClassNamespace = substr($class, 0, strpos($class, '\\')); if ($namespace != $calledClassNamespace) { $ns = $calledClassNamespace . '\\App\\Http\\Policies\\'; $this->policyHandler = $ns . $this->policyHandler; } } /** * Set the name for the route. * * @param string $name * @return self */ public function name($name) { if (!$this->name) { $this->name = $name; } else { $this->name .= $name; } // @phpstan-ignore-next-line return $this->app->router->setNamedRoute($this->name, $this); } /** * Set the name for the route. * * @param string $name * @return null */ public function withName($name) { $this->name = implode('', $name); } /** * Set the namespace for controller/action. * * @param string $ns * @return null */ public function withNamespace($ns) { if (is_array($ns)) { $this->namespace = implode('\\', $ns); } else { $this->namespace = trim($ns, '\\'); } } /** * Sign the route. * * @return $this */ public function signed() { $this->signed = true; return $this; } /** * Apply rate limit to the route. * * @param int $limit Number of allowed requests. * @param int $interval Time interval in seconds. * * @return $this */ public function rateLimit($limit, $interval) { // Since the rate limiter is applied twice because // WordPress sends an extra request for every // request, so we need to double the limit. $rateLimiter = new RateLimiter($limit * 2, $interval); $this->middleware('before', $rateLimiter); return $this; } /** * Apply a rate limit to the route for per minute. * * @param int $limit The maximum number of requests allowed per minute. * @return $this */ public function rateLimitPerMinute($limit) { return $this->rateLimit($limit, MINUTE_IN_SECONDS); } /** * Apply an hourly rate limit to the route. * * @param int $limit The maximum number of requests allowed per hour. * @return $this */ public function rateLimitHourly($limit) { return $this->rateLimit($limit, HOUR_IN_SECONDS); } /** * Apply a daily basis (24 hours) rate limit to the route. * * @param int $limit The maximum number of requests allowed per day. * @return $this */ public function rateLimitDaily($limit) { return $this->rateLimit($limit, DAY_IN_SECONDS); } /** * Register the rest endpoint * * @return null */ public function register() { $this->updateRouteOptions(); return register_rest_route( $this->restNamespace, $this->getRouteUri(), $this->getOptions(), $this->shouldOverride ); } /** * Update route options before registering. * * @return void */ protected function updateRouteOptions() { $this->setOptions(); } /** * Get normalized uri for the current route. * * @return string */ protected function getRouteUri() { return '/' . trim($this->compileRoute($this->uri), '/'); } /** * Mark this route to override any existing route at the same URI. * * @return $this */ public function override() { $this->shouldOverride = true; return $this; } /** * Set route options * * @return null */ protected function setOptions() { $this->options = array_merge( $this->options, $this->getDefaultOptions() ); } /** * Get default options. * * @return array */ protected function getDefaultOptions() { return [ [ 'methods' => $this->method, 'callback' => [$this, 'callback'], 'permission_callback' => [$this, 'permissionCallback'], 'args' => [], ], ]; } /** * Generate and return the schema for the route. * * @return self * @see https://developer.wordpress.org/rest-api/extending-the-rest-api/schema */ public function schema($schema) { $this->options['schema'] = fn() => $schema; return $this; } /** * Get item from predefined regex * @param string $value * @return string */ protected function getValue($value) { if (array_key_exists($value, $this->predefinedNamedRegx)) { return $this->predefinedNamedRegx[$value]; } return $value; } /** * Compikle the rest route to regex * * @param string $uri * @return string compiled rest endpoint */ protected function compileRoute($uri) { $params = []; $compiledUri = preg_replace_callback('#/{(.*?)}#', function ($match) use (&$params, $uri) { // Default regx $regx = '[^\s(?!/)]+'; $param = trim($match[1]); if ($isOptional = strpos($param, '?')) { $param = trim($param, '?'); } if (in_array($param, $params)) { throw new InvalidArgumentException( "Duplicate parameter name '{$param}' found in {$uri}.", 500 ); } $params[] = $param; if (isset($this->wheres[$param])) { $regx = $this->wheres[$param]; } $pattern = "/(?P<" . $param . ">" . $regx . ")"; if ($isOptional) { $pattern = "(?:" . $pattern . ")?"; } $this->options['args'][$param]['required'] = !$isOptional; return $pattern; }, $uri); return $this->compiled = $compiledUri; } /** * Route handler * * @return \WP_REST_Response */ public function callback() { try { $this->response = $this->handleAfterMiddleware( $this->dispatchRouteAction() ); return $this->handleResponse($this->response); } catch (ValidationException $e) { return $this->app->response->sendError( $e->errors(), $e->getCode() ); } catch (ModelNotFoundException $e) { return $this->app->response->sendError([ 'message' => $e->getMessage() ], 404); } catch (Throwable $e) { return $this->handleUnknownException( $e, $this->response ? $this->response->get_headers() : [] ); } } /** * Handle response from route. * * @param \WP_REST_Response $response * @return \WP_REST_Response */ protected function handleResponse($response) { if ($response->get_status() >= 400) { $this->fireExceptionEvent( new Exception( $this->extractErrorMessage($response), $response->get_status() ) ); } return $response; } /** * Extract error message from response data. * * @param \WP_REST_Response $response * @return string */ protected function extractErrorMessage($response) { $data = $response->get_data(); if (is_string($data)) { return $data; } if (is_array($data) && isset($data['message'])) { return $data['message']; } if ($data instanceof WP_Error) { return $data->get_error_message(); } return 'Unknown error'; } /** * Throw an exception based on the status code. * * @param string $message * @param int $status * @return null * @throws \Exception */ protected function throwException($message, $status) { $class = sprintf( 'WpOrg\Requests\Exception\Http\Status%d', $status ); if (!class_exists($class)) { $class = 'WpOrg\Requests\Exception\Http'; } throw new $class($message, $status); } /** * Handle exception and send error response. * * @param Throwable $e * @return \WP_REST_Response */ protected function handleUnknownException(Throwable $e, $headers = []) { $data = []; $this->fireExceptionEvent($e); if ($this->app->isDebugOn()) { $data = [ 'file' => $e->getFile(), 'line' => $e->getLine(), ]; } return $this->app->response->sendError([ 'code' => 'plugin_exception', 'data' => $data, 'message' => $e->getMessage(), ], $e->getCode() ?: 500, $headers); } /** * Dispatch the route action. * * @return \WP_REST_Response */ protected function dispatchRouteAction() { $response = $this->app->call( $this->action, $this->getControllerParameters() ); if ($response instanceof WPFluentResponse) { $response = $response->toArray(); } elseif (!($response instanceof WP_REST_Response)) { $response = !is_wp_error($response) ? $this->app->response->sendSuccess($response) : $this->app->response->wpErrorToResponse($response); } return $response; } /** * Handle after middleware if any. * * @param mixed $response * @return mixed */ protected function handleAfterMiddleware($response) { if (!$this->skipMiddleware) { $response = $this->app->make(Pipeline::class) ->send(new WPFluentResponse($response)) ->through($this->collectMiddleWare('after')) ->then(function($response) { return $this->normalize($response); }); if (!$response) { $response = $this->app->request->abort(); } } return $response; } /** * Normalize the response. * * @param mixed $response * @return mixed */ protected function normalize($response) { if ($response instanceof WPFluentResponse) { $response = $response->toArray(); } if (!$response instanceof WP_REST_Response) { return new WP_REST_Response($response); } return $response; } /** * Fire exception action hook. * * @param Exception $exception * @return void */ protected function fireExceptionEvent($exception) { if ($this->app->isDebugOn() || defined('FLUENT_BRIDGE_SECRET')) { // $message = sprintf( // "%s in %s:%d\nStack trace:\n%s\n", // $exception->getMessage(), // $exception->getFile(), // $exception->getLine(), // $exception->getTraceAsString() // ); // // error_log($message); $this->app->doAction('fluent_exception', $exception); } } /** * Permission callback for route * @param \WP_REST_Request $wpRestRequest * @return mixed */ public function permissionCallback($wpRestRequest) { try { $this->parameters = null; $this->substitutedParameters = null; $this->app->instance('route', $this); $this->app->instance('wprestrequest', $wpRestRequest); $this->app->request->mergeInputsFromRestRequest($wpRestRequest); $this->prepareCallbacks($this->app->request); if (!$this->isThisValidSignedRoute()) { throw new Exception('Invalid Signature', 403); } $response = $this->app->make(Pipeline::class) ->send($this->app->request) ->through($this->collectMiddleWare('before')) ->then(function ($request) { if ($request && $request instanceof Request) { return $this->dispatchPermissionHandler(); } }); if (is_wp_error($response)) { throw new Exception( $response->get_error_message(), is_int($code = $response->get_error_code()) ? $code : 403 ); } if ($response instanceof WP_REST_Response) { $data = $response->get_data(); throw new Exception( $data['message'] ?? $response->get_status(), $response->get_status() ); } return $response; } catch (Exception $e) { return new WP_Error( 'Permission Callback Error', $e->getMessage(), [ 'status' => $e->getCode() ?: 403 ] ); } } /** * Checks if the route is signed and needs validation. * * @return boolean [description] */ protected function isThisValidSignedRoute() { if (!$this->signed) return true; $request = $this->app->make('request'); if ($this->app->make('url')->validate($request->getFullUrl())) { parse_str($this->app->make('encrypter')->decrypt( $this->app->request->get('_data') ), $query); $this->app->request->merge( Arr::except($query, ['expires_at']) ); $this->app->request->forget('_data'); return true; } } /** * Dispatches the permission handler. * * @return bool|null */ protected function dispatchPermissionHandler() { if (!$this->permissionHandler) { return true; } $isValid = $this->app->call( $this->permissionHandler, $this->getControllerParameters() ); if (is_object($isValid)) { if ($this->isUser($isValid)) { $isValid = $isValid->id(); } else { $this->throwInvalidPolicy(); } } if (!is_bool($isValid) && !is_int($isValid) && !is_null($isValid)) { $this->throwInvalidPolicy(); } return (bool) $isValid; } /** * Checks if the user is an instance of WPUserProxy. * * @param WPUserProxy $user * @return bool */ protected function isUser($user) { return $user instanceof WPUserProxy; } /** * Throw invalid policy handling exception. * * @return InvalidArgumentException */ protected function throwInvalidPolicy() { throw new InvalidArgumentException( 'The policy must return a boolean, integer, null, or a WPUserProxy instance.', 500 ); } /** * Gether route params after substituted the params * * @return array */ protected function getControllerParameters() { $routeParameters = []; if (!$this->substitutedParameters) { if ($routeParameters = $this->getParameter()) { $routeParameters = $this->substituteParameters($routeParameters); } } else { $routeParameters = $this->substitutedParameters; } return $routeParameters; } /** * Added the ability to add middleware so we can intercept * the request without modifying the source code again * and again. The middleware class will implement * the handle method as given below: * * public function handle($request, $next) * * And must return $next($request) to handle the request. * Otherwise return nothing to abort the request. * Optionally, you may call the abort method: * return $request->abort(code, message); * * @param string $type * @return array */ protected function collectMiddleWare($type = 'before') { $middleware = $this->app['config']->get('middleware', []); $callableMiddleware = Arr::get($middleware, "global.{$type}", []); $routeArray = []; if (isset($middleware['route'])) { $routeArray = $middleware['route']; if (isset($routeArray[$type])) { $routeArray = $routeArray[$type]; } } foreach ($this->middleware[$type] as $routeMiddleware) { if (is_object($routeMiddleware)) { $handler = $routeMiddleware; } elseif (class_exists($routeMiddleware)) { $handler = $this->resolveMiddlewareFrom($routeMiddleware); } else { $pieces = explode(':', $routeMiddleware); $handler = Arr::get($routeArray, $key = reset($pieces)); if (isset($pieces[1])) { $handler = $this->resolveMiddleware($handler, $pieces); } } if (isset($handler)) { $this->addMiddlewareInTheStack($callableMiddleware, $handler); } else { if (isset($key)) { $mpath = 'config.middleware.route.' . $type; $msg = "No middleware is assigned for the key: {$key} in {$mpath} array."; } else { $msg = "Could't resolve middleware."; } throw new InvalidArgumentException($msg); } } return $callableMiddleware; } /** * Resolve a middleware from a class. * * @param string $class * @return \Closure */ protected function resolveMiddlewareFrom($class) { return static function ($r, $next, ...$params) use ($class) { return (new $class)->handle($r, $next, ...$params); }; } /** * Resolve the middleware * * @param mixed $handler * @param array $pieces * @return object */ protected function resolveMiddleware($handler, $pieces) { if (is_object($handler)) { $handler = $this->wrapMiddleware($handler, $pieces); } elseif (is_string($handler)) { $handler = $handler . ':' . str_replace(' ', '', end($pieces)); } return $handler; } /** * Create a class to wrap the middleware * * @param mixed $handler * @param array $pieces * @return object */ protected function wrapMiddleware($handler, $pieces) { $params = str_replace(' ', '', end($pieces)); $params = explode(',', $params); return new class ($handler, $params) { protected $handler, $params = null; public function __construct($handler, $params) { $this->handler = $handler; $this->params = $params; } public function handle($r, $next) { if (is_callable($this->handler)) { return ($this->handler)($r, $next, ...$this->params); } else { if (!method_exists($this->handler, 'handle')) { $class = get_class($this->handler); throw new InvalidArgumentException( "The {$class} must implement the handle method." ); } return $this->handler->handle($r, $next, ...$this->params); } } }; } /** * Add the middleware in the stack * * @param array &$stack All callable middleware for the route * @param string $middleware * @return void */ protected function addMiddlewareInTheStack(&$stack, $middleware) { if (!in_array($middleware, $stack)) { $stack[] = $middleware; } } /** * Resolve the policy handler * * @param string $policyHandler * @return mixed */ protected function getPolicyHandler($policyHandler) { if (!$policyHandler) { return [$this, 'defaultPolicyHandler']; } if (is_callable($policyHandler)) { return $policyHandler; } if (is_string($policyHandler)) { if (function_exists($policyHandler)) { return $policyHandler; } $policyHandlerFunction = substr( $policyHandler, strrpos($policyHandler, '\\') + 1 ); if (function_exists($policyHandlerFunction)) { return $policyHandlerFunction; } } if ($this->isPolicyHandlerParseable($policyHandler)) { return $policyHandler; } if (is_string($policyHandler) && $this->handler instanceof Closure) { if (class_exists($policyHandler)) { $reflection = new ReflectionClass($policyHandler); if ($reflection->hasMethod('verifyRequest')) { return $policyHandler . '@' . 'verifyRequest'; } } elseif (function_exists($policyHandler)) { return $policyHandler; } throw new InvalidArgumentException( 'Explicit policy handler is required while using a closure as route callback.' ); } if ($policyHandler && !function_exists($policyHandler)) { [$_, $method] = is_array($this->handler) ? [$this->handler[0], $this->handler[1] ?? '__invoke'] : Str::parseCallback($this->handler, '__invoke'); $policyHandler .= '@' . $method; } return $policyHandler ?: [$this, 'defaultPolicyHandler']; } /** * Check if the policy handler is parseable. * * @param string $policyHandler * @return boolean */ protected function isPolicyHandlerParseable($policyHandler) { return (strpos($policyHandler, '@') === true || strpos($policyHandler, '::') === true); } /** * Default/Fallback policy handler for the route * * @return bool */ public function defaultPolicyHandler() { return true; } /** * Parse the rest and permission/policy handlers * * @param \WP_REST_Request $request * @return null * @throws \BadMethodCallException */ public function prepareCallbacks($request) { $handler = $this->app->parseRestHandler($this->handler, $this->namespace); [$action, $controller] = $this->resolveHandlerDetails($handler); $policyHandler = $this->resolvePolicyHandler(); $this->actionInfo = [ 'handler' => is_object($handler) ? $action : $handler, 'controller' => $controller, 'method' => $this->getMethodName($action, $handler), 'path' => $this->uri, 'http_method' => $request->get_method(), 'full_uri' => $request->get_route(), 'permission_callback' => $policyHandler, 'compiled_url' => $this->compiled ]; $this->action = $handler; if ($routeParameters = $this->getParameter()) { $this->substitutedParameters = $this->substituteParameters($routeParameters); } return $this->action; } /** * Get the method name to build action info. * * @param mixed $action * @param mixed $handler * @return string|null */ protected function getMethodName($action, $handler) { $method = is_array($action) ? $action[1] ?? '__invoke' : null; if (is_null($method) && is_object($handler)) { $method = '__invoke'; } return $method; } /** * Resolve the handler details. * * @param mixed $handler * @return array */ protected function resolveHandlerDetails($handler) { if ($handler instanceof Closure) { return ['Closure', null]; } if (is_object($handler)) { $class = get_class($handler); return [$class, $class]; } $handler = trim($handler, '\\'); [$controller, $method] = Str::parseCallback($handler, '__invoke'); $controllerName = $this->extractControllerName($controller); return [[$controller, $method], $controllerName]; } /** * Extract the controller name from the FQCN. * * @param string $fqcn * @return string */ protected function extractControllerName($fqcn) { $parts = explode('\\', $fqcn); return end($parts); } /** * Parse and validate the policy handler. * * @return array */ protected function resolvePolicyHandler() { try { $policyHandler = $this->app->parsePolicyHandler( $this->getPolicyHandler($this->policyHandler) ); if ($policyHandler) { $this->permissionHandler = $policyHandler; // Adjust method if explicitly given in string policy handler if (is_string($this->policyHandler) && is_array($policyHandler) && isset($policyHandler[1])) { $pieces = explode('@', $this->policyHandler); if (isset($pieces[1])) { $this->permissionHandler[1] = $pieces[1]; } } if (!is_callable($this->permissionHandler)) { throw new Exception; } } } catch (Exception $e) { throw $this->invalidPolicyHandlerException(); } // Convert object controller to class string for endpoint metadata if (is_array($policyHandler) && is_object($policyHandler[0])) { $policyHandler[0] = get_class($policyHandler[0]); } return $policyHandler; } /** * Build and throw an exception for invalid policy handlers. * * @throws \BadMethodCallException */ protected function invalidPolicyHandlerException() { $pHandler = $this->policyHandler; if (is_array($this->permissionHandler) && $this->permissionHandler) { $pHandler = is_object($this->permissionHandler[0]) ? get_class($this->permissionHandler[0]) . ':' . $this->permissionHandler[1] : $this->permissionHandler[0] . ':' . $this->permissionHandler[1]; } return new BadMethodCallException( "The permission callback {$pHandler} is invalid or not callable." ); } /** * Get one or more route parameters * @param string $key * * @return mixed */ public function getParameter($key = null) { if (is_null($this->parameters)) { $this->parameters = $this->app->request->get_url_params(); } return $key ? $this->parameters[$key] : $this->parameters; } /** * Get the name of the route. * * @return string */ public function getName() { return $this->name; } /** * Get the url of the route. * * @return string */ public function getUrl() { return $this->uri; } /** * Get the url of the route. * * @return string */ public function uri() { return $this->getUrl(); } /** * Dynamically access a route parameter. * * @param string $key * @return mixed */ public function __get($key) { return $this->getParameter($key); } }