MVC – Model, View, Controller

הסבר פשוט על תבנית העיצוב הפופולרית עם דוגמה בשפת התכנות PHP

MVC הוא מונח שהפך להיות מאד בסיסי בתכנות ובמדעי המחשב. לאלו מכם שלמדו מדעי המחשב הפוסט הזה הוא מאד בסיסי וחסר פואנטה. אך אם לא למדתם מדעי המחשב בצורה מסודרת, שווה להקדיש מספר דקות ללמידת MVC, זה שימושי וזה כדאי.

אז מה זה MVC? מדובר בתבנית עיצוב. מה זו תבנית עיצוב, אתם שואלים? תבנית עיצוב זו תיאור דרך לפתרון בעיה מסוימת בתוכנה. למשל, אם אני רוצה לבנות אתר או אפליקציה מסוימת שכבר בנינו בעבר, אנחנו יכולים לקחת תרשים מוכן של האפליקציה הקודמת המתאר איך בדיוק האפליקציה הזו בנויה ולבנות את האפליקציה לפי התרשים והתיאור. כך אנחנו חוסכים לעצמנו המון המון זמן שצריך להיות מוקדש לתכנון. תבניות עיצוב הן בעצם תבניות מוכנות של אפליקציות שונות ומשונות שאנשים יצרו בעבר. כשאני משתמש בתבנית עיצוב של מישהו אחר, אני בעצם חוסך המון המון זמן שבו הייתי שובר את הראש על בעיה שמישהו אחר כבר פתר – בדיוק כמו לעשות copy&paste לקוד שמצאנו באינטרנט.

מן הסתם יש המון תבניות עיצוב, יש גם ספרים שלמים שמתארים תבניות כאלו המותאמות למגוון של בעיות. אך ישנן מספר תבניות כלליות שמתארות פתרונות כלליים לכלל התוכנות. תבניות עיצוב אלו נחשבות כבסיסיות ועדיף לכל מתכנת להכיר אותן. המפורסמת והחשובה שבהן היא MVC שמורכבת מראשי התיבות של Model, View, Controller או בעברית מודל, מבט, בקר.

עכשיו אני בעצם צריך ללכת לערך של MVC בויקיפדיה העברית ולהתחיל לקשקש על תיאוריה. אבל כאן אנחנו מדברים על ת'כלס ואנחנו נדגים באמצעות קוד פרופר של PHP. אני מניח שאתם יודעים תכנות מונחה עצמים (לפחות בערך). לשם ההדגמה, אני משתמש בקוד ה-PHP שיש בהדגמה הזו של PHP ו-MVC באנגלית.

אז בואו נתחיל, ב-MVC יש לנו שלושה חלקים:
מודל\model – מטפל בקריאה ובכתיבת המידע למסד הנתונים ובכל הפעולות שקשורות לליבה העסקית של האפליקציה: חישובים למיניהם, אבטחה וסיסמאות וכל מה שהוא לא תצוגה.
מבט\view – כל מה שקשור לתצוגה – הוא זה שמדפיס את ה-HTML ומחליט איפה לתקוע את המשתנים.
בקר\controller – זה שמתווך בין השניים.

בפועל מה שקורה הוא שהמשתמש נכנס ל-index.php מסוים שמפעיל את הבקר, הבקר בודק פרמטרים מסוימים בקריאה של המשתמש (למשל פרמטרים של GET) ומבצע קריאה למודל ומעביר לו את הפרמטרים. המודל מחזיר לקונטרולר שלנו מידע. את המידע הזה הקונטרולר שולח למבט. המבט מחזיר לו HTML והמודל מחזיר את זה למשתמש.

נשמע מסובך? ממש לא. קודם כל בואו ונראה את מבנה הקבצים של האפליקציה שלנו שתפקידה הוא להציג ספרים:

מבנה קבצי אפליקציה המבוססת על MVC
מבנה קבצי אפליקציה המבוססת על MVC

נקודת הכניסה היא Index.php, הקובץ הידוע שנמצא ב-root של האפליקציה שלנו. הקובץ הזה יקרא לבקר שלנו והוא זה שיקח פיקוד מעכשיו והלאה. זה מה שמכיל ה-index.php שלנו:


	// index.php file
	include_once("controller/Controller.php");

	$controller = new Controller();
	$controller->invoke();

לא צריך להיות מתכנת דגול על מנת להבין מה עשינו פה – Include לקובץ של controller יצירת אובייקט מקלאס מסוים ואז שימוש במתודת invoke, בואו ולך לקובץ controller.php כדי לראות איך הוא נראה:


include_once("model/Model.php");

class Controller {
     public $model;	

     public function __construct()
     {
          $this->model = new Model();
     } 

     public function invoke()
     {
          if (!isset($_GET['book']))
          {
               // no special book is requested, we'll show a list of all available books
               $books = $this->model->getBookList();
               include 'view/booklist.php';
          }
          else
          {
               // show the requested book
               $book = $this->model->getBook($_GET['book']);
               include 'view/viewbook.php';
          }
     }
}

גם כאן לא צריך להיות מתכנת מומחה כדי להבין מה קורה. קודם כל עושים include למודל שלנו ובקונסטרקטור אנו יוצרים אובייקט בשם model מהקלאס שנמצא במודל (נגיע אליו עוד מעט). במתודת invoke אנו בודקים אם יש בקשת GET של book.
במידה ולא, נכניס לתוך משתנה בשם books את כל מה שמתודת getBookList של המודל מחזירה לנו ונעשה include לקובץ מבט בשם booklist.php.
במידה ויש בקשת GET, ניקח אותה ונשלח אותה באמצעות מתודת getBook של המודל ונכניס את התוצאה לתוך משתנה שנקרא book ונעשה include לקובץ מבט בשם viewbook.php.

או קיי, מה בעצם קורה פה? הבקר בודק אם יש בקשת GET – אם אין, אנו מניחים שאנחנו בעמוד הראשי ובעצם קוראים לרשימת ספרים מהמודל שתוצג באמצעות booklist.php שנמצא במבט. במידה ויש בקשת GET, אנו מניחים שמדובר בעמוד ספר ספציפי ומבקשים את פרטי הספר מהמודל ומציגים אותו באמצעות viewbook.php שנמצא במבט.

או קיי, אז יש לנו כמה קריאות למודל, בואו ונסתכל על קובץ המודל שלנו. קובץ המודל הוא בעצם החלק המרכזי של האפליקציה שלנו, כיוון שבו יש את כל ההגיון העסקי ואובייקטים שונים שמטפלים בבקשות שונות.
באפליקציה טובה, יהיה כימוס נכון של כל הקלאסים והאובייקטים. כך למשל הקלאס שמטפל בכתיבה וקריאה ממסד הנתונים ישים את פרטי מסד הנתונים ואת מתודות החיבור ב-private על מנת לשמור על ריבוד נכון של האפליקציה.

אז הנה model.php


include_once("model/Book.php");

class Model {
	public function getBookList()
	{
		// here goes some hardcoded values to simulate the database
		return array(
			"Jungle Book" => new Book("Jungle Book", "R. Kipling", "A classic book."),
			"Moonwalker" => new Book("Moonwalker", "J. Walker", ""),
			"PHP for Dummies" => new Book("PHP for Dummies", "Some Smart Guy", "")
		);
	}

	public function getBook($title)
	{
		// we use the previous function to get all the books and then we return the requested one.
		// in a real life scenario this will be done through a db select command
		$allBooks = $this->getBookList();
		return $allBooks[$title];
	}

}


כאן יש לנו בעצם include ל-book.php שמכיל את הקלאס של ה-book ויש את שתי המתודות החביבות שראינו בבקר: getBookList שמחזירה רשימה של ספרים. בדוגמה הזו אין קריאה למסד נתונים ובלגנים אלא פשוט החזרה של רשימה שכתבנו מראש.
getBook פשוט מחזירה מידע על ספר ספציפי.

book.php הוא פשוט קלאס מאד מאד פשוט שאחראי לבניית אובייקט הספר:


class Book {
	public $title;
	public $author;
	public $description;

	public function __construct($title, $author, $description)
    {
        $this->title = $title;
	    $this->author = $author;
	    $this->description = $description;
    }
}


כפי שאנחנו יכולים לראות כאן המודל אחראי לאסיפת המידע, הכנסתו לאובייקט מסודר והחזרתו אל הבקר.

אז אחרי שהבנו שבעצם המודל מחזיר אובייקט של ספרים או ספר אחד אל תוך הבקר – או למשתנה book או למשתנה booklist, בואו ונראה איך בדיוק המבט עוסק בהם.

אם אתם זוכרים, בקוד של הבקר היה לנו include לקובץ של viewbook.php במידה ומדובר בספר אחד, ואז משתנה book היה זמין עבורו (המידע לתוך משתנה book הגיע מהמודל) או include לקובץ של booklist.php במידה ומדובר ברשימת ספרים ואז משתנה books היה זמין עבורו.

כל view לוקח את המשתנה שלו ודואג להדפיס אותו בהתאם לצורך – אם זה ספר אחד או אם זה רשימת ספרים:

viewbook.php


<html>
<head></head>

<body>

	<?php 

		echo 'Title:' . $book->title . '<br/>';
		echo 'Author:' . $book->author . '<br/>';
		echo 'Description:' . $book->description . '<br/>';

	?>

</body>
</html>

booklist.php



<html>
<head></head>

<body>

	<table>
		<tbody><tr><td>Title</td><td>Author</td><td>Description</td></tr></tbody>
		<?php 

			foreach ($books as $title => $book)
			{
				echo '<tr><td><a href="index.php?book='.$book->title.'">'.$book->title.'</a></td><td>'.$book->author.'</td><td>'.$book->description.'</td></tr>';
			}

		?>
	</table>

</body>
</html>


מדובר בקבצים שפשוט מטפלים בהדפסה, זה הכל – פשוט ולעניין!

עד כאן בנוגע ל-MVC, האפליקציה הפשוטה מאד שעשינו כאן זהה לכל אפליקציה שבנויה לפי תבנית העיצוב הזו. כמובן שרוב האפליקציות יהיו מורכבות הרבה יותר, אבל בפועל יש לכל אפליקצית MVC את שלושת הרכיבים – בקר, מודל ומבט. חשוב שכל אחד מהרכיבים יהיה מופרד לוגית ומעשית מהשני. לא עושים חישובים שונים בקובץ view, לא מבצעים קריאה למסדי נתונים ואינטרפולציות שונות בקובץ של controller, ולא משנה עד כמה האפליקציה מסועפת ומורכבת.

גם אם לא הבנתם בתחילה את המאמר הזה עד הסוף, חשוב להסתכל על הקוד ולנסות להבין אותו בכל זאת. הבנה של תבניות עיצוב בכלל, ו-MVC (ותבניות נוספות) בפרט הן קריטיות למי שעוסק בתוכנה – ולא משנה אם בפיתוח בק אנד או פרונט אנד.









פוסטים נוספים שכדאי לקרוא

פתרונות ומאמרים על פיתוח אינטרנט

נגישות טכנית – פודקאסט ומבוא

פרק בפודקאסטעל נגישות בעברית שצולל לכלים האוטומטיים ולפן המאד מאד טכני של הנגישות.

גלילה לראש העמוד