Een tijdje geleden ben ik bij het werk wat ik doe bij iemand op een site een MVC pattern tegengekomen. Aangezien ik toen nog niet veel werkte met mvc's was ik erg onder de indruk van de werking ervan. Ik heb een tijdje gezocht naar wat tutorials over hoe je een dergelijk systeem op zou moeten zetten, ik vond deze tutorial die mijnsinziens erg handig was: http://www.onlamp.com/pub/a/php/2005/09/15/mvc_intro.html.
Nadat ik me redelijk ingelezen had in de werking van OOP, ben ik begonnen met het bedenken van een eigen framework. Inmiddels is dit al een tijdje af, en werkt het voor 90% precies zoals ik zou willen. Toch ik heb ik het idee dat ik het niet helemaal 'the right way' doe. Ik vraag me af, omdat ik nu aan een nieuw project ben begonnen of het niet simpeler en efficienter kan.
Bij werkt het nu ongeveer zo:
Bij een request wordt alles naar index.php geredirect, dat is mijn controller. Hierin bevint zich een __autoload functie. Mbv die functie wordt de gewenste file geinclude en een class geladen. Deze class extend weer een class, en die misschien ook. En op die manier wordt door telkens de parent class constructor te callen de database connection en authentication classes geladen. Daarna wordt binnen die gerequeste klasse het event gecalled uit de request (anders het default event (__default)).
Daarna geeft de controller het hele object van die class mee aan de class presenter die dan vervolgens een layout required waar op diverse plaatsen templates in worden geladen. dit wordt allemaal teruggegeven aan de client.
Het is misschien wat vaag, maar het verschilt in iedergeval een hoop met een normaal MVC patroon. Zo heb ik bijvoorbeeld geen getters en setters, geen singleton, en dus eigenlijk geen models. Kortom ik zit een beetje vast in de ontwikkeling ervan. Eigenlijk werkt het prima, maar het voelt niet echt goed aan omdat ik weet dat het niet the way to go is.
Ik kan me goed voorstellen dat het niet helemaal duidelijk is voor iedereen, dus mochten jullie meer willen weten, of willen jullie bijvoorbeeld de controller zien, dan zet ik het er graag op.
De factory haalt de rows uit de database en stopt ze in instanties van het model. Een model representeert dus een enkele row. Select-queries komen dus waarschijnlijk niet voor in je model. Een update en een delete query mogen er wel weer in voorkomen. Echter slikken die geen argumenten - alle benodigde gegevens zijn immers al beschikbaar in het object.
Dat met die selects en joins is inderdaad een lastig punt wanneer je je database abstracter wil maken. Ik gebruik zelf PDO, en PDO kent setFetchMode(PDO::FETCH_CLASS, 'jouwModel'). Op die manier worden alle resultaten in instanties van je model gepropt. Zo kan je wel gewoon je eigen queries maken, en wordt je resultaat mooi in je models gepropt. Je bent welliswaar wat 'gelimiteerd' in welke velden je kan ophalen - maar je mag nu zelf weten waar de data vandaan komt. Joins, onmogelijk ingewikkelde WHERE-statements. Verbetering ten opzichte van je standaard fetch_assoc() aanpak is dat je nu geen update en delete queries meer 'handmatig' uitvoert.
Wanneer je setters & getters gebruikt heb je de mogelijkheid om het aangepaste properties van een object te controleren alvorens de weiziging echt wordt doorgevoerd. En je kan database-resultaten omzetten in iets wat meer bruikbaar is in PHP. Stel dat je bijvoorbeeld een model Post hebt, dan kan je daar een method getUser() aan hangen. Deze geeft dan een instantie van het model User terug. Zie je, heel je database-gedrag wordt zo abstract en op de achtergrond gehouden.
Maak overigens bij voorkeur je properties protected in plaats van private. Zo kan je bij het extenden van de klasse ook bij de properties. Vooral bij models zijn er tal van situaties waarin je direct bij de properties wil en niet via de getters en setters wil praten intern.
Jij zegt dat je de SubController uit zou bouwen op de MainController. Is een subcontroller een maincontroller? Volgens mij niet -> dus niet extenden.
Ik zou beginnen met de subcontroller -> maak een klasse met methods, maar verzin alleen de namen. Geef ze nog geen invulling. Alleen de methods die leeg zijn, en welke argumenten ze accepteren. Eventueel laat je ze nog even een lege view returnen. Dan ga je bezig met het maken van een (simpele) router. Dan maak je een bestandje, een scriptje, dat de router gebruikt om de controller aan te roepen, en teken je de teruggegeven view. (Een view-klasse maken is ongeveer 5 minuten werk. Gewoon 2 methods: construct & draw. construct slikt als argument het template, draw roept include $this->template aan.)
Denk je later dat je nog een ander type router zou willen maken, of views wat flexibeler wil maken, dan kan je er nog voor kiezen om ook dit scriptje abstract onder te brengen in een klasse. Maar dat hoeft in principe niet.
Zo ik ben begonnen met een opzetje, ik heb tot nu toe de router gemaakt, die heb ik overigens in de mainController geintergreerd, ik hoop dat dat geen doodszonde is.
Daarnaast heb ik nu een mainView, die door de methode delegate in de mainController wordt aangemaakt. daarna maakt delegate een controller aan obv de request en roept de juiste methode aan.
Ik vraag me nu even af wat de beste manier is om de layouts views en templates aan elkaar te hangen. Ik heb me nog maar niet aan de models gewaagd, maar ik vermoed dat ik daar ook nog wel wat te vragen over heb.
Je neemt gewoon 1 index.phtml-layout, een pagina waarin je logo zit, je stylesheet, en je footer. Voor het content-gedeelte plak je er weer een subview in, namelijk eentje afkomstig uit de aangeroepen controller. Compositie - ieder stukje verantwoordelijkheid in z'n eigen view (template?)
Je kan zelf kiezen of je een template-engine als Smarty wil gebruiken, of dat je via DOM een heel xhtml document in elkaar gaat laten draaien, of dat je ingewikkelde klassen gaat maken die allemaal HTML uitpoepen, voor ieder stukje weer een aparte klasse (hier probeer ik stilletjes aan te geven dat ik die laatste manier zelf nooit heb zien zitten)
Oke oke ik ben weer wat verder. Ik heb het nu zo opgelost dat de view class een property heeft; location. en in mijn layout loop ik dan door een array met alle views en kijk ik of ze de juiste location hebben (dus bijv. links-onder). en vervolgens call ik dan draw() op die view.
Ik ben nu begonnen met het eerste model, artikel. Alle getters en setters netjes opgesteld, en de factory class gemaakt. Nu vraag ik me alleen af moet ik daar nu via de setters alle properties in gaan zetten of passen jullie gewoon een array met de hele bups naar een method van het model die daar dan alle setters callt?
Waarschijnlijk maakt het niet heel veel uit, maar ik wil wel graag weten dat ik het via de standaard doe.
Ik doe het gewoon door voor iedere property de setter aan te roepen. Maar dat is alleen als ik iets 'handmatig' wijzig. Komt de invoer gewoon uit de database (dus wanneer je je lijst met models aan het maken bent) dan dump ik gewoon de rauwe database-data in de properties van het model. Dat kan je doen door een optioneel argument bij de constructor toe te voegen, of via een static (weer een soort van factory) method:
<?php
class Post {
static public function initializeWithData(array $data)
{
$instance = new self();
foreach($data as $key => $value) {
$instance->$key = $value;
}
return $instance;
}
protected
$id,
$author_id,
$content;
public function __construct()
{
}
public function id() //als het goed is heb je hier geen setter voor nodig
{
return $this->id;
}
public function getAuthor()
{
return Author::findById($this->author_id);
}
public function setAuthor(Author $author)
{
$this->author_id = $author->id();
}
}
$post = Post::initializeWithData(array(
'id' => 24,
'author_id' => 5,
'content' => 'Hello World'
));
?>
Of zoals ik al eerder als voorbeeld gaf, ik laat het PDO doen:
<?php
$storage = new PDO('mysql:host...');
$stmt = $storage->prepare('SELECT * FROM Posts');
$stmt->setFetchMode(PDO::FETCH_CLASS, 'Post');
$post->$stmt->fetch();
?>
Oke nou we gaan weer lekker verder. Klopt het trouwens dat je de singleton niet kan callen van binnen de klasse, dat hij dan wel een nieuwe instantie aanmaakt?
Ik zou niet weten waarom het niet zou kunnen. Moet je zelf even proberen. Wat wel zo is is dat je Singleton niet kan afdwingen binnen een klasse. private methods, zoals de __construct bij Singleton is vanuit de klasse zelf immers altijd aanspreekbaar.
Maar goed ook trouwens, want als je een method niet vanuit de klasse zelf kan aanmaken, hoe maak je dan die ene singleton instance in de eerste plaats :)
Maar wat bedoel je dan met afdwingen? Dit is er nu aan de hand, ik maak in mn index.php mainController aan. Als ik het daarna nog een keer in index.php doe, (dus via de singleton methode) dan gaat het goed, hij retourneerd dezelfde instantie.
Als ik echter in mn mainView (die in mainController->delegate() wordt aangemaakt, weer de singleton methode aanroep, maakt hij een nieuwe instantie (althans, de construct functie wordt opnieuw gecalled)
Hier even mijn singleton method: getInstance();
function getInstance() {
static $instance;
if(!isset($instance)) {
$instance = new self();
}
return $instance;
}
Als je de constructor private maakt, kan je alleen maar instanties aanmaken van binnenuit de klasse.
Wanneer je mainController extend met subController, dan wordt subController::getInstance() inderdaad gezien als een nieuwe method, en dus is static instance een andere instance dan die van maincontroller. Dit kan je oplossen door instance in de klasse op te slaan in plaats van de method:
<?php
class x {
static private $instance;
static public function getInstance() {
if(!isset(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
static public function getOtherInstance()
{
static $instance;
if(!isset($instance)) {
$instance = new self();
}
return $instance;
}
public function __construct()
{
echo 'lang leve de constructor' . PHP_EOL;
}
public function test()
{
var_dump(self::getInstance());
var_dump(x::getInstance());
}
}
class y extends x {
public function __construct() {
echo 'b' . PHP_EOL;
}
}
y::getInstance();
?>
Als je getInstance gebruikt gaat het "goed". y::getInstance geeft de instance van X terug. Zou je in plaats van getInstance overal getOtherInstance gebruiken, dan geven alle x varianten een x terug, en alle y varianten een y.
Oke, maar het vreemde is dat ik geen extend gebruik.
Ik heb net uitgevogeld wat het is. Het probleem is dat ik een aantal functies in de construct call, en die functies callen weer via via getInstance() dus voordat de construct functie doorlopen is, wordt er opnieuw getInstance gecalled. en dan maakt ie natuurlijk weer een nieuwe instantie aan. en Apache crashed, want het is een eindeloze loop.