MVC Structure in PHP

Web applications can become more complex. The more functionality you add, the more difficult the code is to maintain and understand. It is for this reason that structuring your code in an organized way is crucial.

images/articles/php/mvc-structure-in-php.jpg

The MVC (model-view-controller) approach renders development more efficient by breaking the application into three distinct components: the model, the view, and the controller. Doing so allows for each component to be created and maintained in isolation, thereby minimizing the residual effects.

Models manage the data and the business logic, views contain the templates for responses (for example, HTML pages), and controllers orchestrate requests, deciding what data to use and how to render the appropriate template.

The following example works through a typical scenario involving the converter application, highlighting the role of each MVC component:

  1. The user interacts with the view to specify which type of conversion to carry out, for instance converting an input temperature from Fahrenheit to Celsius.
  2. The controller responds by identifying the appropriate conversion action, gathering the input, and supplying it to the model.
  3. The model converts the value from Fahrenheit to Celsius and returns the result to the controller.
  4. The controller calls the appropriate view, passing along the calculated value. The view renders and returns the result to the user. 

MVC applications usually have one file that gets all the requests, and routes them to the specific controller depending on the URL. The main purpose of a web application is to process HTTP requests coming from the client and return a response. The main elements for this are the request and the router.

The request object

A request is basically a message that goes to a URL, and has a method - GET or POST. The URL is composed of two parts: the domain of the web application, that is, the name of your server, and the path of the request inside the server. You can get this information from the global array $_SERVER that PHP populates for each request.

Our Request class should have a property for each of these three elements, followed by a set of getters and some other helpers that will be useful for the user. Also, we should initialize all the properties from $_SERVER in the constructor.

class Request {
const GET = 'GET';
const POST = 'POST';
private $domain;
private $path;
private $method;
public function __construct() {
$this->domain = $_SERVER['HTTP_HOST'];
$this->path = $_SERVER['REQUEST_URI'];
$this->method = $_SERVER['REQUEST_METHOD'];
}
public function getUrl(): string {
return $this->domain . $this->path;
}

public function getDomain(): string {
return $this->domain;
}
public function getPath(): string {
return $this->path;
}
public function getMethod(): string {
return $this->method;
}
public function isPost(): bool {
return $this->method === self::POST;
}
public function isGet(): bool {
return $this->method === self::GET;
}
}

The properties are coming from the values of the $_SERVER array: HTTP_HOST, REQUEST_URI, and REQUEST_METHOD.

Filtering parameters from requests

Another important part of a request is the information that comes from the user, that is, the GET and POST parameters, and the cookies. As with the $_SERVER global array, this information comes from $_POST, $_GET, and $_COOKIE, but it is always good to avoid using them directly, without filtering, as the user could send malicious code.

class FilteredMap {
private $map;
public function __construct(array $baseMap) {
$this->map = $baseMap;
}
public function has(string $name): bool {
return isset($this->map[$name]);
}
public function get(string $name) {
return $this->map[$name] ?? null;
}
}

public function getInt(string $name) {
return (int) $this->get($name);
}
public function getNumber(string $name) {
return (float) $this->get($name);
}
public function getString(string $name, bool $filter = true) {
$value = (string) $this->get($name);
return $filter ? addslashes($value) : $value;
}

These three methods allow the user to get parameters of a specifc type. Let's say that the developer needs to get the ID from the request. The best option is to use the getInt method to make sure that the returned value is a valid integer, and not some malicious code that can mess up the database.

Also note the function getString, where we use the addSlashed method. This method adds slashes to some of the suspicious characters, such as slashes or quotes, trying to prevent malicious code with it.

Now we are ready to get the GET and POST parameters as well as the cookies from the Request class using FilteredMap. The new code would look like the following:

class Request {
// ...
private $params;
private $cookies;
public function __construct() {
$this->domain = $_SERVER['HTTP_HOST'];
$this->path = explode('?', $_SERVER['REQUEST_URI'])[0];
$this->method = $_SERVER['REQUEST_METHOD'];
$this->params = new FilteredMap(
array_merge($_POST, $_GET)
);
$this->cookies = new FilteredMap($_COOKIE);
}
// ...
public function getParams(): FilteredMap {
return $this->params;
}
public function getCookies(): FilteredMap {
return $this->cookies;
}
}

With this new addition, a developer could get the POST parameter price with the following line of code:

$price = $request->getParams()->getNumber('price');

This is way safer than the usual call to the global array:

$price = $_POST['price'];