Handle a route's callback

This handles common exceptions and their output
to keep the "dispatch()" method DRY

@param Route $route
@param RouteCollection $matched
@param array $methods_matched
@return void
181611
protected function handleRouteCallback(Route $route, RouteCollection $matched, array $methods_matched)
    {
        // Handle the callback
        $returned = call_user_func(
            $route->getCallback(), // Instead of relying on the slower "invoke" magic
            $this->request,
            $this->response,
            $this->service,
            $this->app,
            $this, // Pass the Klein instance
            $matched,
            $methods_matched
        );

        if ($returned instanceof AbstractResponse) {
            $this->response = $returned;
        } else {
            // Otherwise, attempt to append the returned data
            try {
                $this->response->append($returned);
            } catch (LockedResponseException $e) {
                // Do nothing, since this is an automated behavior
            }
        }
    }
859931
private function executeRouteMiddleware(Route &$route, $position, $method)
    {
        $method = strtoupper($method);

        /** @var Middleware[] $middleware */
        $middleware = array();
        $continue   = true;

        // Get all global middleware's.
        if (isset(
                $this->middleware['global'],
                $this->middleware['global'][$position])
            && count($this->middleware['global'][$position]) > 0) {

            $middleware = array_merge($middleware, $this->middleware['global'][$position]);
        }

        // Group middleware if defined.
        if ($route->getGroup() !== null
            &&  isset($this->middleware[$route->getGroup()],
                $this->middleware[$route->getGroup()][$position])
            && count($this->middleware[$route->getGroup()][$position]) > 0) {
            $middleware = array_merge($middleware, $this->middleware[$route->getGroup()][$position]);
        }


        // Will execute all middleware in order..:
        foreach ($middleware as $trigger)
        {
            if (count($trigger->getMethods()) === 0
            ||  in_array(strtoupper($method), $trigger->getMethods())) {

                if ($this->executeMiddleware($trigger) === false) {
                    $continue = false;
                }
            }
        }

        return $continue;
    }
271569
private function call(string $method, string $pattern, string $className, string $function, array $options = []): void
    {
        if (empty($className) || empty($function)) {
            throw new Exception('Missing params, we need 2 parameters');
        }

        $route = [
            'method' => $method,
            'pattern' => $pattern,
            'className' => $className,
            'function' => $function,
        ];
        $this->collections[] = $route;

        if (array_key_exists('options', $options)) {
            $this->setOptions($route, $options);
        }

        return;
    }
397183
public function dispatchMatch(MatchContextInterface $match)
    {
        // store the previous request context.
        $request = $this->url->getRequestContext();

        $this->url->setRequestContext($match->getRequest());
        $this->routeStack->push($match->getName());

        $response = $this->mapper->mapResponse($this->dispatcher->dispatch($match));

        $this->routeStack->pop();

        // restore the previous request context.
        if (null !== $request) {
            $this->url->setRequestContext($request);
        }

        return $response;
    }
69215
public function handle(ParsedRoute $route)
    {
        $segments = explode("::", $route->controller());
        $controller = $segments[0];
        $action = count($segments) > 1 ? $segments[1] : "index";
        if (method_exists($controller, $action)) {
            $params = [$route->params()];
            return call_user_func_array([new $controller, $action], $params);
        }

        throw new RuntimeException("No method {$action} in controller {$segments[0]}");
    }
79558
public function routeMatched(ApiRoute $route, Request $request): void
	{
		if (($format = $request->getParameter(self::API_DOCU_STARTER_QUERY_KEY_GENERATE)) !== null) {
			$this->generator->generateAll($this->router);

			exit(0);
		}

		if (($format = $request->getParameter(self::API_DOCU_STARTER_QUERY_KEY_TARGET)) !== null) {
			$this->generator->generateTarget($route, $request);

			exit(0);
		}
	}
1154666
public function addRoute($method, $route, $callback)
    {
        return $this->routes[] = new Route(strtoupper($method), $route, $callback);
    }
700206
private function addRoutes(FastRoute\RouteCollector $route) : void
    {
        $routeIndex=0;
        foreach ($this->routes as $definedRoute) {
            $definedRoute[3] = $definedRoute[3] ?? $this->defaultReturnType;
            $routeName = 'routeClosure'.$routeIndex;
            $route->addRoute(strtoupper($definedRoute[0]), $definedRoute[1], $routeName);
            $routeIndex++;
        }
    }
158576
public function apply(Request $request, Match $match, array $middlewares = [])
    {
        // get the pipe of route selected by router
        $pipe = $match->getOption('pipe');

        // check if pipe is valid
        if (!is_array($pipe)) {
            // early return
            return $this->resolve($request, $match);
        }
        // filter the middlewares using the pipe of match
        $piping = array_filter($middlewares, function ($middleWare) use ($pipe) {
            // check if alias of middleware is in pipe
            return in_array($middleWare->alias(), $pipe);
        });

        // check if exists valid middlewares
        if (!count($piping)) {
            // early return
            return $this->resolve($request, $match);
        }

        // inject the handler into the HttpResponse middleware
        $that = $this;
        $piping[] = new HttpResponse(function () use ($that, $request, $match) {
            // resolve the request using the match
            return $that->resolve($request, $match);
        });

        // create a delegate with the middlewares
        $delegate = new Delegate($piping);

        // start the pipe
        return $delegate->process($request);
    }
1193521
protected function checkFallbacks($routes, $httpMethod)
    {
        $additional = array(Route::ANY);

        if ($httpMethod === Route::HEAD) {
            $additional[] = Route::GET;
        }

        foreach ($additional as $method) {
            if (isset($routes[$method])) {
                return $method;
            }
        }

        $this->matchedRoute = $routes;

        throw new HttpMethodNotAllowedException('Allow: ' . implode(', ', array_keys($routes)));
    }
516781
public function dispatch(\Parable\Routing\Route $route)
    {
        $this->dispatchedRoute = $route;

        $this->hook->trigger(self::HOOK_DISPATCH_BEFORE, $route);
        $controller = null;

        // Start output buffering and set $returnContent to null
        $returnContent = null;
        $this->response->startOutputBuffer();

        // Build the parameters array
        $parameters = [];
        foreach ($route->getValues() as $value) {
            $parameters[] = $value;
        }

        // Call the relevant code
        if ($route->hasControllerAndAction()) {
            $controller = \Parable\DI\Container::get($route->getController());
            $returnContent = $controller->{$route->getAction()}(...$parameters);
        } elseif ($route->hasCallable()) {
            $callable = $route->getCallable();
            $returnContent = $callable(...$parameters);
        }

        $this->hook->trigger(self::HOOK_DISPATCH_TEMPLATE_BEFORE, $route);

        // If the route has no template path, attempt to build one based on controller/action.phtml
        if (!$route->hasTemplatePath() && $route->hasControllerAndAction()) {
            $reflection = new \ReflectionClass($controller);
            $controllerName = str_replace('\\', '/', $reflection->getName());
            $controllerName = str_replace('Controller/', '', $controllerName);

            $templatePathGenerated = $this->path->getDir(
                "app/View/{$controllerName}/{$route->getAction()}.phtml"
            );

            if (file_exists($templatePathGenerated)) {
                $route->setTemplatePath($templatePathGenerated);
            }
        }

        // And check again, now that we might have a magic template path
        if ($route->hasTemplatePath()) {
            $templatePath = $this->path->getDir($route->getTemplatePath());
            $this->view->setTemplatePath($templatePath);
            $this->view->render();
        }

        // If the callback or controller/action returned content, we need to handle it
        if ($returnContent) {
            if ($this->response->getOutput()->acceptsContent($returnContent)) {
                $this->response->setContent($returnContent);
            } else {
                $type   = gettype($returnContent);
                $output = get_class($this->response->getOutput());

                // Stop the output buffer we've started above and throw
                $this->response->stopOutputBuffer();
                throw new \Parable\Framework\Exception(
                    "Route returned value of type '{$type}', which output class '{$output}' cannot handle."
                );
            }
        }

        // Any rendered content was from before the returnContent was set, so we prepend it if there's any
        $renderedContent = $this->response->returnOutputBuffer();
        if ($renderedContent) {
            $this->response->prependContent($renderedContent);
        }

        $this->hook->trigger(self::HOOK_DISPATCH_TEMPLATE_AFTER, $route);
        $this->hook->trigger(self::HOOK_DISPATCH_AFTER, $route);
        return $this;
    }
1096357
private function getMatchedVars(RouteInterface $route, array $matches)
    {
        $vars = $route->getContext()->getVars();
        $params = array_merge(
            $route->getDefaults(),
            array_filter(array_map(
                [$this, 'getValue'],
                array_intersect_key(
                    $matches,
                    $t = array_combine($vars, array_pad([], count($vars), null))
                )
            ))
        );

        return array_intersect_key($params, $t);
    }
675414
public function dispatchRoute(Parsed $route, array $parameters = [])
    {
        $handler = $route->getHandler();
        if ($this->isClosure($handler)) {
            $paramMap  = $this->getFunctionArgumentsNames($handler);
            $arguments = $this->getFunctionArguments(
                $paramMap,
                $route->getParameters(),
                $route->getPlaceholders()
            );

            return call_user_func_array($handler, $arguments);
        }

        return $this->dispatchNotFound();
    }
747072
static public function serve(array $route) {
        if (!isset($route['path']) || !isset($route['callback']) || 
            (isset($route['method']) && is_string($route['method']))) {
            throw new \Lollipop\Exception\HTTP\Router('Invalid route');
        }
        
        // Default path to '/'
        $path = Utils::fuse($route['path'], '');
        $path = trim($path, '/');

        if (!isset($route['method'])) {
            $route['method'] = [];
        }
        
        if (!isset($route['middlewares'])) {
            $route['middlewares'] = [];
        }
        
        if (!isset($route['arguments'])) {
            $route['arguments'] = [];
        }
        
        // Report duplicated path
        // if (isset(self::$_stored_routes[$path])) {
        //     throw new \Lollipop\Exception\HTTP\Router("Duplicate path: $path");
        // }

        // if (isset($route['name']) && isset(self::$_stored_names[$route['name']])) {
        //     throw new \Lollipop\Exception\HTTP\Router('Duplicate name: ' . $route['name']);
        // }

        // Store route
        foreach ($route['method'] as $method) {
            self::$_stored_routes[$method][$path] = $route;
        }

        if (isset($route['name'])) {
            self::$_stored_names[$route['name']] = $path;
        }

        // Register dispatcher once this function was called
        self::_registerDispatch();
    }
154467
private static function treat(Route $route, $params)
    {
        // in case of get request or post request
        if (($route->getMethod() == 'post' && Request::isPost()) || ($route->getMethod() == 'get') || ($route->getMethod() == 'resource') || ($route->getMethod() == 'call')) {
            return static::execute($route, $params);
        }

        return false;
    }
1308670
public function execute(Routes $routes): void
	{
		$routeCollection = [];

		// Build table rows

		foreach($routes->getRoutes() as $route)
		{
			// Normalize action name

			$action = ($route->getAction() instanceof Closure) ? 'Closure' : $route->getAction();

			// Build table row

			$routeCollection[] =
			[
				$route->getRoute(),
				implode(', ', $route->getMethods()),
				$action,
				implode(', ', $route->getMiddleware()),
				implode(', ', $route->getConstraints()),
				(string) $route->getName(),
			];
		}

		// Draw table

		$headers =
		[
			'<green>Route</green>',
			'<green>Allowed methods</green>',
			'<green>Action</green>',
			'<green>Middleware *</green>',
			'<green>Constraints **</green>',
			'<green>Name</green>',
		];

		$this->table($headers, $routeCollection);

		$this->write('<green>*</green> <faded>Global middleware is not listed.</faded>');
		$this->write('<green>**</green> <faded>Global constraints are not listed.</faded>');
	}
1237165
private function executeController($controller, Route $method, $matches) {

		if(false === is_object($controller)) {
			return trigger_error(sprintf('Argument 1 passed to %s() must be of the type object, "%s" given', __METHOD__, gettype($controller)), E_USER_ERROR);
		}

		if(false === is_array($matches)) {
			return trigger_error(sprintf('Argument 2 passed to %s() must be of the type array, "%s" given', __METHOD__, gettype($matches)), E_USER_ERROR);
		}

		//Load the module dispatch file if exists and readable
		$this -> loadBoot($method -> getModule());

		//Execute start method if controller supports it
		if(true === is_callable([$controller, '__start'])) {
			call_user_func_array([$controller, '__start'], []);
		}

		//Execute main function
		$action = Application :: get(['prefix', 'action']) . $method -> getAction();

		//Trigger error if main function does not exists
		if(false === is_callable([$controller, $action])) {
			return trigger_error(sprintf('Method "%s" does not exists in "%s" controller', Application :: get(['prefix', 'action']) . ucfirst($method -> getAction()), $method -> getController()), E_USER_ERROR);
		}

		call_user_func_array([$controller, $action], $matches);

		//Execute end method if controller supports it
		if(true === is_callable([$controller, '__end'])) {
			call_user_func_array([$controller, '__end'], []);
		}
	}