Written by
Yoann Pigné
Updated on
Updated on
The Model-View-Controllor design pattern in PHP
Prerequisite
A few needed technologies
- HTTP
- REST
- Security
Hypertext Transfer Protocol
Application-level protocol for distributed systems. Generic and stateless.
- Request message (from client to server)
- Response message (from server to client)
- Text-based messages / Multipurpose Internet Mail Extensions (MIME) format
- Messages
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)
"OPTIONS"
: information about the communication options available"GET"
: retrieve whatever information is identified by theRequest-URI
"HEAD"
: same as"GET"
message-body
in the response"POST"
: append entity to the existingRequest-URI
"PUT"
: store entity as the newRequest-URI
"DELETE"
: delete existingRequest-URI
"TRACE"
: see what is being received at the other end of the request chain"CONNECT"
: for use with a proxy that can dynamically switch to being a tunnel (e.g. SSL tunneling).
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
1xx
: Informational - Request received, continuing process2xx
: Success - The action was received, understood, and accepted3xx
: Redirection - Further action must be taken to complete the request4xx
: Client Error - The request contains bad syntax or cannot be fulfilled5xx
: Server Error - The server failed to fulfill an apparently valid request
Representational State Transfer
REST
is a style of software architecture for distributed systems on top of HTTP.
- Each resource are accessed through one unique request (URI).
- Requests are stateless (identification within the URI)
- Resources are accessed one by one or as collections
- RESTful web service (RESTful web API)
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 |
- Plural may indicate a collection e.g.
http://example.com/emails/
- An id may indicate an element e.g.
http://example.com/email/17/
- URN can be prefixed with API version e.g.
https://api.twitter.com/1.1/statuses/home_timeline.json
Security
Too many vulnerabilities exist… But developers are responsible for their code!
Common Attacks
- Cross-Site Request Forgery (CSRF)
- Cross-site scripting (XSS)
- SQL injection
Common Measures
- Anti-CSRF tokens
- Forms Timeouts
- Escape users inputs
Reduce vulnerability… Use frameworks!
MVC
The Model–View–Controller Design Pattern
- Improve the separation of concerns
- Facilitate automated unit testing
- Facilitate team work
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
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:
- Request handling
- Database update
- Database lookup
- Visualization (style?)
- Security
- Routing
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
- Laravel
- Symfony
- CodeIgniter
- Zend Framework / Laminas Project
- Yii (Framework)
- CakePHP
- Slim
- Phalcon
- FuelPHP
- Fat-Free Framework