Home > Backend Development > PHP Tutorial > An Introduction to the Front Controller Pattern, Part 2

An Introduction to the Front Controller Pattern, Part 2

Lisa Kudrow
Release: 2025-02-26 09:55:45
Original
815 people have browsed it

An Introduction to the Front Controller Pattern, Part 2

Core points

  • The front-end controller acts as a centralized proxy for the application, assigning commands to predefined handlers, such as page controllers or REST resources.
  • The front-end controller can maintain a compact structure, route and dispatch incoming requests, and can also be extended to a fully functional RESTful controller, parse HTTP verbs, adapt to pre/post dispatch hooks, etc.
  • This article demonstrates how to deploy a small but scalable HTTP framework that works with front-end controllers, standalone routers, and schedulers while handling request/response cycles independently.
  • The author also introduced the process of building a front-end controller from scratch, including defining classes to simulate data and behaviors of typical HTTP request/response cycles, building routing modules, and setting up schedulers.
  • Front-end controller mode has the advantages of centralized control, reducing code duplication, and improving modularity and separation of concerns, but it may not be suitable for all web applications and may result in single point of failure if implemented improperly.

The front-end controller is like a centralized proxy in an application, and its main area of ​​focus is to statically or dynamically assign commands to predefined handlers such as page controllers, REST resources, or anything else that comes to mind. Building at least one simple front-end controller is a very useful experience to understand its details, and to promote this idea from a pragmatic point of view, I introduced in an introductory post the implementation of a artificial front-end controller that will route and all the logic required to dispatch requests is packaged within the boundaries of a class. One of the best aspects of front-end controllers is that you can keep them as compact structures, route and dispatch incoming requests only, or you can show your creativity and implement a fully functional RESTful controller capable of parsing HTTP Verbs, adaptation pre/post dispatch hooks, etc., all behind a unified API. However, while this approach is attractive, it violates the single responsibility principle (SRP) and the nature of OOP itself proactively delegating different tasks to multiple fine-grained objects. So, does this mean I am just another sinful soul who dares to break the SRP precepts? In a sense, yes. So I want to eliminate my sin by showing you how to easily deploy a small but scalable HTTP framework that is able to work with front-end controllers with standalone routers and schedulers. In addition, the entire request/response cycle will be handled independently by several reusable classes that can naturally be adjusted at will. Given that there are a large number of fully-featured components-packaged HTTP frameworks available, it seems ridiculous to implement a front-end controller that routes and dispatches requests from scratch even though these classes retain the nature of SRP. To avoid being judged for reinventing the wheel, some parts of my custom implementation will be inspired by the clever EPHPMVC library written by Lars Strojny.

Analyze request/routing/scheduling/response cycle

The task we should solve first is to define several classes responsible for simulating data and behavior of typical HTTP request/response cycles. This is the first class, and the interface it implements:

<code>class Request {

  public function __construct($uri, $params) { 
    $this->uri = $uri;
    $this->params = $params;
  }

  public function getUri() {
    return $this->uri;
  }

  public function setParam($key, $value) {
    $this->params[$key] = $value;
    return $this;
  }

  public function getParam($key) {
    if (!isset($this->params[$key])) {
      throw new \InvalidArgumentException("The request parameter with key '$key' is invalid."); 
    }
    return $this->params[$key];
  }

  public function getParams() {
    return $this->params;
  }
}</code>
Copy after login
The

The Request class encapsulates the incoming URI and parameter array and simulates an extremely simple HTTP request. For brevity, additional data members such as the method set associated with the relevant request have been intentionally excluded. If you want to add them to the class, continue. Having a thin HTTP request wrapper that exists independently is good, but it will end up being useless without coupling with the corresponding parts of the data and behavior that simulates a typical HTTP response. Let's fix and build this supplemental component:

<code>class Response {
  public function __construct($version) {
    $this->version = $version;
  }

  public function getVersion() {
    return $this->version;
  }

  public function addHeader($header) {
    $this->headers[] = $header;
    return $this;
  }

  public function addHeaders(array $headers) {
    foreach ($headers as $header) {
      $this->addHeader($header);
    }
    return $this;
  }

  public function getHeaders() {
    return $this->headers;
  }

  public function send() {
    if (!headers_sent()) {
      foreach($this->headers as $header) {
        header("$this->version $header", true);
      }
    } 
  }
}</code>
Copy after login

The Response class is undoubtedly more active than its partner Request. It acts as a base container that allows you to stack HTTP headers at will and is able to send them to the client. Since these classes perform their operations independently, it's time to start building the next part of the front-end controller. In a typical implementation, the routing/dispatch process is mostly encapsulated in the same approach, which is frankly not too bad at all. However, in this case, it is better to break down these processes and delegate them to different classes. In this way, things will be more balanced in terms of equal responsibility. Here are the batches of classes that make the routing module run:

<code>class Route {

  public function __construct($path, $controllerClass) {
    $this->path = $path;
    $this->controllerClass = $controllerClass;
  }

  public function match(RequestInterface $request) {
    return $this->path === $request->getUri();
  }

  public function createController() {
   return new $this->controllerClass;
  }
}


class Router {
  public function __construct($routes) {
    $this->addRoutes($routes);
  }

  public function addRoute(RouteInterface $route) {
    $this->routes[] = $route;
    return $this;
  }

  public function addRoutes(array $routes) {
    foreach ($routes as $route) {
      $this->addRoute($route);
    }
    return $this;
  }

  public function getRoutes() {
    return $this->routes;
  }

  public function route(RequestInterface $request, ResponseInterface $response) {
    foreach ($this->routes as $route) {
      if ($route->match($request)) {
        return $route;
      }
    }
    $response->addHeader("404 Page Not Found")->send();
    throw new \OutOfRangeException("No route matched the given URI.");
  }
}</code>
Copy after login

As one might expect, there are many options when implementing functional routing mechanisms. At least in my opinion, the above method is both practical and direct. It defines a separate Route class that binds the path to a given operation controller, and a simple router whose responsibilities are limited to checking whether the stored route matches the URI associated with a specific request object. To finally solve the problem, we need to set up a quick scheduler that can be used side by side with the previous class. This is how the following class does:

<code>class Dispatcher {

  public function dispatch($route, $request, $response)
    $controller = $route->createController();
    $controller->execute($request, $response);
  }
}</code>
Copy after login

Scan the Dispatcher and you will notice two things. First, it does not carry any state. Second, it implicitly assumes that each operation controller will run under the surface of the execute() method. This can be refactored to a slightly more flexible pattern if you want (the first thing that comes to mind is to tweak the implementation of the Route class), but for simplicity, I'll keep the scheduler unchanged. So far, you might be wondering how to place a front-end controller that is able to combine all previous classes together. Don't worry, next is!

(Due to space limitations, subsequent content will be truncated. Please provide the rest of the content and I will continue to complete the pseudo-original.)

The above is the detailed content of An Introduction to the Front Controller Pattern, Part 2. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template