The Model-View-Controllor design pattern in PHP

Prerequisite

A few needed technologies

Hypertext Transfer Protocol

Application-level protocol for distributed systems. Generic and stateless.

generic-message = start-line
                *(message-header CRLF)
                CRLF
                [ message-body ]
start-line      = Request-Line | Status-Line

HTTP Headers and Entities

message-header = field-name : [ field-value ]
field-name     = token
field-value    = *( field-content | LWS )
field-content  = <the OCTETs making up the field-value
                 and consisting of either *TEXT or combinations
                 of token, separators, and quoted-string>

Example request headers and entities

Host: tools.ietf.org
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: UTF-8,*;q=0.5
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17

HTTP Requests

From the client to the server

Request = Request-Line
          *(( general-header
            | request-header
            | entity-header ) CRLF)
          CRLF
          [ message-body ] 

Request Line

Request-Line = Method SP Request-URI SP HTTP-Version CRLF
Example:

GET /project/65142b1a71134a60df97ad24 HTTP/2

HTTP Methods (or Verbs)

HTTP Responses

Response    = Status-Line
              *(( general-header
               | response-header
               | entity-header ) CRLF)
              CRLF
              [ message-body ]
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
Example:

HTTP/1.1 200 OK

HTTP/2 does not define Reason-Phrase : HTTP/2 200

Status Codes

Representational State Transfer

REST is a style of software architecture for distributed systems on top of HTTP.

RESTful Web Services

RESTful API and HTTP methods

Resource GET PUT POST DELETE
Collection URI List elements Replace entire collection Create new element in collection Delete collection
Element URI Retrieve one element Replace existing element *Generally not used* Delete one element

Security

Too many vulnerabilities exist… But developers are responsible for their code!

Common Attacks

Common Measures

Reduce vulnerability… Use frameworks!

MVC

The Model–View–Controller Design Pattern

Model

  • Holds the data
  • Links to persistent storage (DBMS)
  • Ignores other components

View

  • Representation of data
  • What users see
  • May know the Model

Controller

  • Handles users requests
  • Updates Model data
  • Triggers Views

MVC Schema

MCV overview

MVC Sequence

MVC Sequence

A simple PHP script

Show news and allow comments on them.

<?php
$pdo = new \PDO("mysql:host=127.0.0.1;port=3306;dbname=database", 'user', 'password');
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $news_id = $_POST['news_id'];
    $stmt = $pdo->query("INSERT INTO commentaires SET news_id='$news_id',
      auteur='" . $_POST['auteur'] . "', texte='" . $_POST['texte'] . "', date=NOW()");
    header("location: " . $_SERVER['SCRIPT_NAME'] . "?news_id=$news_id");
    exit;
} else {
    $news_id = $_GET['news_id'];
}
?>
<html><head><title>Les news</title></head>
<body>
    <div id="news">
        <?php
        $news_req = $pdo->query("SELECT * FROM news WHERE id='$news_id'");
        $news = $news_req->fetch();
        ?>
        <h1>À la une : <?php echo $news['titre'] ?></h1> 
        <h4>postée le <?php echo $news['date'] ?></h4>
        <p><?php echo $news['texte_nouvelle'] ?> </p>
        <?php
        $comment_req = $pdo->query("SELECT * FROM commentaires
      WHERE news_id='$news_id'");

        $nbre_comment = $comment_req->rowCount();

        ?>
        <h3><?php echo $nbre_comment ?> commentaires relatifs à cette news</h3>
        <ul>
        <?php while ($comment = $comment_req->fetch()) { ?>
            <li><b><?php echo $comment['auteur'] ?></b>
                a écrit le <?php echo $comment['date'] ?>
            <p><?php echo $comment['texte'] ?></p>
        <?php } ?>
        </ul>

        <form method="POST" action="<?php echo $_SERVER['SCRIPT_NAME'] ?>" name="ajoutcomment">
            <input type="hidden" name="news_id" value="<?php echo $news_id ?>">
            <label>Votre nom : </label><input type="text" name="auteur"><br />
            <label>Votre commentaire : </label> <textarea name="texte" rows="5" cols="20" >

      </textarea><br />
            <input type="submit" name="submit" value="Envoyer">
        </form>
    </div>
</body></html>

Various actions are mixed up in this file:

The Simplest MVC App

Same as previous example, with MVC pattern

The Model

<?php
function dbconnect() {
  static $connect = null;
  if ($connect === null) {
    try {
      $connect = new PDO("mysql:dbname=simplemvc;host=127.0.0.1", 'pigne', 'n2EfCJYFx6CExzSX' );
      $connect->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $e) { echo 'Connection failed :( : ' . $e->getMessage(); exit;}
  }
  return $connect;
}


function get_news($id) {
  try{
    $sql = "SELECT * FROM news WHERE id= :id";
    $sth = dbconnect()->prepare($sql);
    $sth->execute(array(':id' => $id));
    if($sth->errorCode() == 0) {
      return  $sth->fetch();
    }
    else {
      return array();
    }
  }catch (PDOException $e) { echo 'Select comments failed: ' . $e->getMessage(); exit;}
}

function get_comments($news_id) {
  try {
    $sql = "SELECT * FROM commentaires WHERE news_id= :news_id";
    $sth = dbconnect()->prepare($sql);
    $sth->execute(array(':news_id' => $news_id));
    if($sth->errorCode() == 0) {
      return  $sth->fetchAll();
    }
    else {
      return array();
    }
  }catch (PDOException $e) { echo 'Select comments failed: ' . $e->getMessage(); exit;}
}


function insert_comment($comment) {
  $connect = dbconnect();
  try{
    $sql =  "INSERT INTO commentaires SET news_id= :news_id , " .
    "auteur= :auteur , " .
    "texte= :texte , " .
    "date=NOW()";
    $sth = $connect->prepare($sql);
    $sth->execute(array(':news_id' => (int)$comment['news_id'],
      ':auteur' => $connect->quote($comment['auteur']),
      ':texte' => $connect->quote($comment['texte']),
      )
    );
  } catch(PDOException $e) { echo 'Insert failed: ' . $e->getMessage(); exit;}
}

The View

<html><head><title>Les news</title></head>
<body>
  <h1>Les News</h1>
  <div id="news">
    <h2><?php echo $news['titre'] ?> postée le <?php echo $news['date'] ?></h2>
    <p><?php echo $news['texte_nouvelle'] ?> </p>
    <h3><?php echo $nbre_comment ?> commentaires relatifs à cette nouvelle</h3>
    <dl>
    <?php foreach ($comments AS $comment) {?>
    <dt><?php echo $comment['auteur'] ?>, le <?php echo $comment['date']?>:</dt>
    <dd><?php echo $comment['texte'] ?></dd>
    <?php } ?>
    </dl>
    <h3>Un commentaire ?</h3>
    <form method="POST" action="<?php echo $_SERVER['SCRIPT_NAME'] ?>" name="ajoutcomment">
      <input type="hidden" name="news_id" value="<?php echo $news['id']?>">
      <input type="text" name="auteur" placeholder="Votre nom"><br>
      <textarea name="texte" placeholder="Saisissez votre commentaire"></textarea><br>
      <input type="submit" name="submit" value="Envoyer">
    </form>
  </div>
</body></html>

The Controller

require ('simpleModel.php');
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  insert_comment($_POST);
  header("HTTP/1.1 301 Moved Permanently");
  header("location: {$_SERVER['SCRIPT_NAME']}?news_id={$_POST['news_id']}");
  exit;
} else {
  $news = get_news($_GET['news_id']);
  $comments = get_comments($_GET['news_id']);
  $nbre_comment = sizeof($comments);
  require ('simpleView.php');
}

Next step : Object oriented MVC

PHP MVC Frameworks

Many of them