Skriven av: Re-JeeP
Datum: 2007-04-01
http://www.phpportalen.net/article.php?id=12

Introduktion av MVC i PHP

Introduktion av MVC i PHP

Nedan följer en kort introduktion till objekt orienterad programmering i PHP5 med MVC som design modell. OOP är inget som kommer att förklaras utan detta handlar om att lära sig använda MVC som design modell.
Dock används OOP PHP5 i exemplet. Läsaren bör ha någorlunda kunskap inom OOP för att fullt förstå syftet med modellen.

Vad är MVC?

MVC står för Model View Controller och är en designmetod som används inom systemutveckling. Den grundläggande tanken med MVC är att separera funktionalitet från designen och sedan sedan styra dessa med hjälp av en annan komponent. I MVC heter de olika komponenterna Model, View och Controller, där i stora drag Model sköter uträkningar, View sköter design och Controller sköter kopplingen mellan dom.

Det finns olika sätt att använda MVC men principen att skilja på funktionalitet och design är densamma. Här förklaras den förmodligen vanligaste metoden.
Att designa sina hemsidor med MVC ger en bra struktur där det är enkelt att göra ändringar utan att ställa till det i andra delar. "Divide and conquer" är ett uppskattat sätt att programmera på som passar bra in på MVC modellen. Med det menar man att man enklare löser ett stort problem genom att dela upp det i många små och mindre problem.

Flödesschema

En kort förklaring av hur ett förlopp kan se ut kommer nu att demonstreras. Senare kommer samma förlopp att demonstreras men då mer grundligt och med ett exempel till grund.
Det första som händer är att Controller får information om att något har hänt. Det kan tex. vara en knapptryckning. Controller säger då till Model att göra beräkningar med ny data som skickas med från Controller. Model gör sina beräkningar och sparar datan. När Modell är klar med beräkningarna kommer Controller säga till View att uppdatera sig. Hur View ser ut beror på den information som hämtas från Modell. Med andra ord om Modell på order av Controller uppdaterar sina värden kommer då också designen att ändras när View hämtar den nya datan.
Lägg märke till att Model inte har någon som helst kunskap om View eller Controller. Allt Model gör är att utföra beräkningar och lagra resultaten.

Exempel

Nedan demonstreras ett exempel för att göra det lättare att förstå.
Exemplet går ut på att man har två tal som det räknas med på olika sätt. Tal1 har värde 100 och tal2 har värde 30. Genom att klicka på fyra olika länkar kan man byta operatorn mellan talen. De fyra operatorerna är addition (tal1 + tal2), subtraktion (tal1 - tal2), multiplikation (tal1 * tal2) och division (tal1 / tal2). Beroende på vilken länk man klickar på kommer operatorn och svaret att ändras och sedan presenteras.
I koden finns en mindre mängd kommentarer för att lättare förstå vad som händer.

Exemplet består av fyra filer: index.php, calModel.php, calView.php och calController.php. Notera namnen på filerna.

PHP: index.php
1:
<?php
2:
 
3:
// Inkluderar filerna med klasserna
4:
require_once('calView.php');
5:
require_once('calModel.php');
6:
require_once('calController.php');
7:
 
8:
// Skapar ett objekt av modellen
9:
$cModel = new calModel();
10:
 
11:
// Skapar ett objekt av vyn
12:
$cView = new calView($cModel);
13:
 
14:
// Skapar ett objekt av kontrollen
15:
$cController = new calController($cView, $cModel, $_GET);
16:
 
17:
?>
PHP: calModel.php
1:
<?php
2:
 
3:
// Detta är modellen som sköter alla beräkningar.
4:
class calModel
5:
{
6:
    // Tal1
7:
    private $a = 100;
8:
 
9:
    // Tal2
10:
    private $b = 30;
11:
 
12:
    // Resultatet av tal1 operator tal2
13:
    private $res = "?";
14:
 
15:
    // Operatorn
16:
    private $op;
17:
 
18:
    // Adderar tal1 och tal2
19:
    public function add()
20:
    {
21:
        $this->res = $this->a + $this->b;
22:
    }
23:
 
24:
    // Subtraherar tal2 från tal1
25:
    public function sub()
26:
    {
27:
        $this->res = $this->a - $this->b;
28:
    }
29:
 
30:
    // Multiplicerar tal1 med tal2
31:
    public function mul()
32:
    {
33:
        $this->res = $this->a * $this->b;
34:
    }
35:
 
36:
    // Dividerar tal1 med tal2
37:
    public function div()
38:
    {
39:
        $this->res = $this->a / $this->b;
40:
    }
41:
 
42:
    // Retunerar värdet på tal1
43:
    public function getA()
44:
    {
45:
        return $this->a;
46:
    }
47:
 
48:
    // Retunerar värdet på tal2
49:
    public function getB()
50:
    {
51:
        return $this->b;
52:
    }
53:
 
54:
    // Retunerar resultatet av tal1 operator tal2
55:
    public function getRes()
56:
    {
57:
        return $this->res;
58:
    }
59:
 
60:
    // Retunerar operatorn
61:
    public function getOp()
62:
    {
63:
        return $this->op;
64:
    }
65:
 
66:
    // Sätter en operator
67:
    public function setOp($op)
68:
    {
69:
        $words = array("add", "sub", "mul", "div");
70:
        $chars = array("+", "-", "*", "/");
71:
 
72:
        $this->op = str_replace($words, $chars, $op);
73:
    }
74:
}
75:
 
76:
?>
PHP: calView.php
1:
<?php
2:
 
3:
// Detta är vyn (Det grafiska vi ser (html)).
4:
class calView
5:
{
6:
    // Referens till modellen
7:
    private $cModel;
8:
 
9:
    // En konstruktor som skapar ett objekt av modellen
10:
    public function __construct($cModel)
11:
    {
12:
        $this->cModel = $cModel;
13:
    }
14:
 
15:
    // Skriver ut hela operationen a op b = ?. Skriver även ut länkar så att
16:
man kan välja operator.
17:
    public function display()
18:
    {
19:
        echo $this->cModel->getA()." ".$this->cModel->getOp()."
20:
".$this->cModel->getB()." = ".$this->cModel->getRes();
21:
        echo "
22:
            <ul>
23:
                <li><a href='?op=add'>Addition</a></li>
24:
                <li><a href='?op=sub'>Subtraktion</a></li>
25:
                <li><a href='?op=mul'>Multiplikation</a></li>
26:
                <li><a href='?op=div'>Division</a></li>
27:
            </ul>
28:
        ";
29:
    }
30:
}
31:
 
32:
?>
PHP: calController.php
1:
<?php
2:
 
3:
// Detta är våran kontroller.
4:
class calController
5:
{
6:
    // Referens till vyn.
7:
    private $cView;
8:
 
9:
    // Referens till modellen
10:
    private $cModel;
11:
 
12:
    // Konstruktor med vyn, modellen och värdet på $_GET som parametrar.
13:
    public function __construct($cView, $cModel, $get)
14:
    {
15:
        $this->cView = $cView;
16:
        $this->cModel = $cModel;
17:
 
18:
        // Kollar värdet på $get['op'] och väljer operator utifrån det.
19:
        if($get['op'] == 'add') {
20:
            $this->cModel->add();
21:
        } else if($get['op'] == 'sub') {
22:
            $this->cModel->sub();
23:
        } else if($get['op'] == 'mul') {
24:
            $this->cModel->mul();
25:
        } else if($get['op'] == 'div') {
26:
            $this->cModel->div();
27:
        } else {
28:
            $get['op'] = 'op';
29:
        }
30:
 
31:
        // Sätter operatorn.
32:
        $this->cModel->setOp($get['op']);
33:
 
34:
        // Uppdaterar vyn.
35:
        $this->cView->display();
36:
    }
37:
}
38:
 
39:
?>

Detaljerat flödesschema

Nedan beskrivs ett mer detaljerat flöde än ovan. Scenariot är att man går in på sidan och trycker på länken "addition".

När man besöker sidan kommer index.php att laddas. index.php laddar in de andra filerna in och skapar objekt av klasserna.
När objekten skapas skickas ett Model objekt till View. View måste ju ha kunskap om Model för att kunna hämta sin data. När Controller skapas skickas det med ett objekt av både Model och View. Controller måste ju ha kunskap om Model för att säga till den att utföra nya beräkningar och måste även ha kunskap om View för att kunna säga till den att uppdatera designen efter vad Model har kommit fram till i de nya beräkningarna. Till Controller skickas det även med en $_GET variabel som innehåller information om vilken länk användaren klickade på. Denna informationen måste Controller ha för att kunna avgöra vad den ska skicka för instruktioner till Model. I detta fall kommer den att innehålla informationen om vilken operation som ska utföras (addition, subtraktion, multiplikation eller division).

När sidan laddas för första gången kommer $_GET inte innehålla något värde (undantag är om man skriver in ett get-värde i url'en med en gång men det bortser vi ifrån här). I Controller kommer då else satsen att uppfyllas och $_GET['op'] kommer att vara en tom sträng. Controller kommer att uppdatera Model med informationen om vilken operator som ska användas. Controller anropar sedan funktionen display() i View som i sin tur uppdaterar designen efter värden från Model. Vi kommer då att få presenterat för oss "100 op 30 = ?". Anledningen till att vi ser värden är för att de har initierats med värden i Model från start.
Vi klickar nu på Addition. Det som händer är att index.php kommer att uppdateras och allt kommer att göras igen på precis samma sätt som innan med skillnaden att $_GET kommer att ha annan information i sig. I detta fallet kommer $_GET['op'] innehålla värdet "add". Denna gång kommer inte else-satsen att köras. Istället kommer Controller att uppdatera Model med den nya operatorn addition. Controller anropar sedan display() i View för att designen ska uppdatera sig med den nya datan från Model. Det vi denna gång kommer att få presenterat är "100 + 30 = 130".

Dom av er som kan OOP men inte MVC förstår säkert flödet med kanske undrar varför göra det så omständigt för sig när man istället kan lösa det på 28 rader.

PHP:
1:
<?php
2:
 
3:
$a = 100;
4:
$b = 30;
5:
 
6:
if(isset($_GET['op']))
7:
{
8:
    if($_GET['op'] == "add") {
9:
        echo $a." + ".$b." = ".($a + $b);
10:
    } else if($_GET['op'] == "sub") {
11:
        echo $a." - ".$b." = ".($a - $b);
12:
    } else if($_GET['op'] == "mul") {
13:
        echo $a." * ".$b." = ".($a * $b);
14:
    } else if($_GET['op'] == "div") {
15:
        echo $a." / ".$b." = ".($a / $b);
16:
    }
17:
} else {
18:
    echo $a." op ".$b." = ?";
19:
}
20:
 
21:
?>
22:
 
23:
<ul>
24:
    <li><a href='?op=add'>Addition</a></li>
25:
    <li><a href='?op=sub'>Subtraktion</a></li>
26:
    <li><a href='?op=mul'>Multiplikation</a></li>
27:
    <li><a href='?op=div'>Division</a></li>
28:
</ul>

Svaret på detta går inte alltid att motivera. Exemplet vi använt oss av är ju tämligen enkelt så om det var det enda sidan skulle innehålla är det enkla sättet den bästa lösningen. Men om en sida är uppdelad i flera olika
delar och exemplet skulle vara en liten del av sidan skulle en MVC-lösningen vara bättre. Varför? Jo, för att i MVC ska alla grafiska komponenter i ett program eller på en hemsida vara uppdelade i en Model, en View och en Controller.
En stor hemsida har ofta om man inte följer någon bra design modell en enorm index fil vilket gör det svårt att veta vad som är vad i den. En snyggt strukturerad kod enligt MVC gör det helare mycket enklare.

Tillvägagångsätt

Hur går man då tillväga när man designar en sida i MVC som har mer än en komponent? Och hur binder man ihop dom olika komponenterna?

Detta är oftast den svåraste biten. Ett bra sätt är att använda sig av UML (Unified Modeling Language). UML är ett objektorienterat generellt språk för modellering av alla typer av system (citat wikipedia).
Det första man gör är att ta reda på vilka olika komponenter man kommer att ha på sin sida. Det kan vara en nyhets del, en meny, en statusbar, osv...
Man tar sen fram papper och penna och ritar ett UML diagram över komponenterna och alla kopplingar mellan dom. När man känner att man har en bra struktur är det dags för att dela in det i MVC delarna. Viktigt är nu att man inte bara delar upp alla komponenter i MVC samtidigt, utan man tar en komponent i taget och ritar ut dom nya kopplingarna. Detta gör man tills man gjort alla komponenter.
Om man inte har designat alldeles fel borde man få en bild över hur det hela kommer att se ut.
I stort sett kommer varje del i UML-diagrammet vara en klass. För varje klass måste man nu ta reda vilka instansvariabler och funktioner man ska ha.
Ett bra sätt för att ta reda på det är att använda "use cases". Ett use case är en icke detaljerad beskrivning av vad en användare kan göra på sidan. I exemplet ovan hade de olika use casen kunnat se ut såhär:

1. Användaren går in på sidan.
2. Användaren trycker på addition.
3. Användaren trycker på subtraktion.
4. Användaren trycker på multiplikation.
5. Användaren trycker på division.

Man ska egentligen inte gå in så mycket på detaljer som att användaren klickar på en knapp men i det här lilla exemplet är det okej.
Man går sedan igenom alla use casen och fyller under tiden i vad man kommer att behöva för instanser och funktioner i klasserna.

För det andra use caset hade man kunnat resonera såhär:
index.php laddas. Här måste vi skapa objekt av klasserna. View måste ha kunskap om Model så vi skickar med ett Model objekt som argument. Controller måste ha kunskap om både View och Model så vi skickar med objekt av båda som argument. Controller måste veta att användaren klickade på "addition" så vi skickar med även med $_GET variabeln som argument.
Controller måste nu skicka information till Model om att göra uträkningen med operatorn addition. Vi måste alltså ha en metod som sätter den nya operatorn till addition. Vi skapar funktionen setOp(). Vi skapar även instansvariabeln op för att kunna spara vilken operator som ska användas.
Vi måste ha en funktion som räknar ut det nya resultatet med operatorn addition. Vi skapar funktionen add(). add() måste komma åt dom båda talen och även lagra resultatet. Vi skapar instansvariablerna a, b och res.
Nu måste vi säga till View att uppdatera sig. Detta gör vi genom att skapa en funktionen display() i View. Nu måste View kunna hämta informationen från Model. Den behöver hämta värdet på första talet, andra talet, resultatet och operatorn. Vi skapar funktionerna getA(), getB(), getRes() och getOp() i Model. Nu visas uträkningen med operatorn addition. Vi är nu klara med vårat andra use case.

Detta gör man med alla use case och när man är klar har man förhoppningsvis fått ihop det mesta (man glömmer alltid något).

Detta är bara ett av många tillvägagångsätt. Det viktigaste är att man har en tydlig design på det man ska göra innan man börjar koda.

Sammanfattning

I stort används MVC för att separera funktionalitet från design. Genom att göra det får man en bra struktur och en ren kod att arbeta med.
När man ska ändra designen arbetar man bara i View och slipper därför att oroa sig för att några uträkningar ska ändras. Samma gäller när man ska ändra funktionaliteten. Man slipper tänka på att designen kommer att ändras eftersom man bara ändrar i Model.
sidb
Mycket mer finns att säga om MVC men det lämnas upp till läsaren att forska vidare om själv. Lycka till!

Kommentera denna artikel