Door
Roy B
op 26-05-2014 16:05
gewijzigd op 26-05-2014 16:27
5.067 views
Kan iemand een voorbeeldje geven van een simpel begin met MVC?
Ik heb al vele tutorials bekeken, maar kom er niet helemaal uit hoe te beginnen.
Ik wil een nieuwssysteem maken.
<?php
// newsitem.controller.php
class NewsitemController
{
private $_model;
public function __construct($model)
{
$this->_model = $model;
}
public function create($id)
{
$this->_model->create($id);
}
public functin getAll()
{
$this->_model->getAll();
}
}
// newsitem.model.php
class NewsitemModel
{
private $_database;
public function __construct($database)
{
$this->_database = $database;
}
public function create($data)
{
}
public function getAll()
{
// hier komt de database actie..
}
}
// index.php
$model = new NewsitemModel($database);
$controller = new NewsitemController($model);
$controller->create($_GET["id"]);
?>
// newsitem.html
<h1><?php echo $title; ?></h1>
<p><?php echo $message; ?></p>
1. Je hebt nu een config.php die een PDO-object $pdo definieert.
2. Die $pdo injecteer je in een BaseModel en alle Model-klassen die dit BaseModel extenden.
3. Tot slot gebruik je bijvoorbeeld binnen een class NewsitemModel extends BaseModel code die alleen werkt bij PDO.
Die basisopzet schaalt niet lekker:
• Je opent per request altijd een databaseverbinding, zelfs als je die helemaal niet nodig hebt. Daarmee verbrand je bij een populaire site resources die je hard nodig hebt.
• Je kunt PDO niet soepel vervangen door iets anders, omdat je daarvoor meerdere klassen moet aanpassen.
• Je kunt de databaseverbinding (ongeacht of dat PDO of iets anders is) niet gemakkelijk vervangen, waardoor de applicatie zich bijvoorbeeld niet laat cachen. Je applicaties leunen nu altijd op de database terwijl dat misschien helemaal niet hoeft.
• Je zeult nu één PDO-object $pdo achter je aan door de hele applicatie. Dat kan zowel een voordeel als een nadeel zijn. Je weet het verschil echter pas als je het overdenkt of test.
Dit is, kortom, het eerste onderdeel dat ik nog eens zou overdenken.
@Ward van der Put,
Klopt! De connectie met de database is een uitdaging voor later.
Hiervoor wil ik een klasse maken, maar voor nu is dat nog even niet nodig.
Heb je misschien nog tips over de vraag die ik stelde?
Ik wil mijn templates compleet afhankelijk maken van de Controller met enkel variabelen daarin. Dat is nu nog niet. Ik denk dat ik dan moet gaan werken met een Router.
<?
// newsitems.php
includes "../includes/config.php";
$model = new Model($pdo);
$controller = new Controller($model);
if(isset($_GET["title"]) AND trim($_GET["title"]) != "")
{
$newsitems = $controller->getByTitle($_GET["title"]);
}
?>
<!DOCTYPE html>
<head>
<title><?php echo $newsitem["title"]; ?></title>
...
Als je veel queries van het type SELECT * FROM x of SELECT * FROM x WHERE y = z gebruikt, dan zou ik serieus een Data Access Object (DAO) pattern overwegen.
Dat lost in je huidige opzet meteen twee problemen op: de $pdo die je achter je aansleept en controllers die via/via aan het model hangen.
Je hebt een te lange aaneenschakeling van onderling afhankelijke objecten. Moeilijk te verwoorden, maar je hebt te veel objecten "in" elkaar zitten; die zou je ze meer "naast" elkaar moeten zetten.
Je opent al in config.php met PDO een databaseverbinding $pdo. Die injecteer je vervolgens in het model NewsitemModel($pdo). Dat model injecteer je tot slot in de controller NewsitemController($model), waarmee je eigenlijk dus NewsitemController(NewsitemModel($pdo)) doet.
Die $pdo hoort daar niet in thuis. Alleen het model weet waar Abraham de mosterd haalt. De NewsitemController is nu nog geen controller, maar meer een "wrapper", een extra en overbodige schil rondom het model:
<?php
class NewsitemController
{
private $_model;
public function __construct($model)
{
$this->_model = $model;
}
public function getByTitle($title)
{
$title = str_replace("-", " ", $title);
return $this->_model->getByTitle($title);
}
}
?>
Al in de constructor maak je zo van de controller een container van het $model. Vervolgens is de methode getByTitle() van de controller slechts een herhaling van getByTitle() van het model. Kortom: deze controller is een kopie van het model.
Begin inderdaad bij de basis, bijvoorbeeld met een Data Access Object (DAO) pattern. In de configuratie declareren we alleen globale constanten en we gebruiken één basisklasse voor alle databaseconnectiviteit. Bijvoorbeeld:
<?php
/* Configuratie in bijvoorbeeld config.php */
define('DB_HOST', 'localhost');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_NAME', 'test');
/* Abstract Data Access Object (DAO) */
abstract class AbstractBaseDAO
{
private $Connection;
public function __construct()
{
$this->connect();
}
private function connect()
{
$this->Connection = new \MySQLi(DB_HOST, DB_USERNAME, DB_PASSWORD);
$this->Connection->select_db(DB_NAME);
}
/**
* @param void
* @return array|null
*/
public function fetchAll()
{
$query = 'SELECT * FROM ' . $this->TableName;
if ($result = $this->Connection->query($query)) {
if ($result->num_rows == 0) {
return null;
} else {
while ($row = $result->fetch_assoc()) {
$rows[] = $row;
}
return $rows;
}
} else {
return null;
}
}
}
?>
Nu zeul je geen $pdo met geopende databaseverbinding achter je aan in alle modelklassen. Wil je de database-opzet veranderen, dan hoef je slechts op één plaats de code te herschrijven, hier in de methode connect().
Zoals gezegd, weet alleen het model waar Abraham de mosterd haalt. En die models kunnen we nu ook sterk vereenvoudigen, want daaruit verdwijnt alle PDO-specifieke PHP-code. Je kunt met deze eerste opzet bijvoorbeeld al alle nieuwsberichten ophalen via fetchAll(). Het enige model dat je daarvoor nodig hebt, is:
<?php
/* Model */
class NewsitemModel extends AbstractBaseDAO
{
protected $TableName = 'newsitems';
}
?>
That's it. De abstract class AbstractBaseDAO is er voor de database en de class NewsitemModel extends AbstractBaseDAO voor een specifieke databasetabel.
Er zijn meer design patterns; dit is slechts een voorbeeld. Je kunt het op verschillende manieren aanpakken, bijvoorbeeld ook met een data mapper pattern of met een active record pattern.
Dan nog even heel iets anders tussendoor..
Alle URL's worden doorgestuurd naar mijn index.php d.m.v. htaccess.
In mijn index.php haal ik de URL op met $_SERVER["REQUEST_URI"].
Stel dat mijn URL /nieuws/dit-is-een-nieuwsitem is. Dan moet de NewsitemController worden aangeroepen en vervolgens de actie $controller->getByTitle($title) worden uitgevoerd. Maar is mijn URL /nieuws moet de actie $controller->getAll() worden uitgevoerd.
Als je uitsluitend nieuws wilt tonen, dan kun je direct de controller inzetten en daarmee op basis van de URL de methode getAll() of getByTitle() aanroepen.
Maar waarschijnlijk handelt index.php in de root als de "front controller" meer af dan alleen /nieuws/ en dan kun je het beste een apart object voor MVC-routing gebruiken dat beslist welke controllers worden aangesproken.
In plaats van $_SERVER['REQUEST_URI'] zou ik zelf een aparte variabele gebruiken, bijvoorbeeld $_GET['route'] met:
@Ward van der Put,
Mijn index.php handelt inderdaad meer af dan alleen /nieuws. Alle URL's worden hier afgehandeld, zoals dat hoort bij een MVC heb ik gelezen. Ik heb ook iets gelezen over een Router. Kun je mij op weg helpen hiermee?