MVC Uygulaması İçin İlk Adımlar: App Sınıfı

İkinci yazıdan merhaba arkadaşlar. Bu yazıda çalışmalara başlayıp ortaya güzel şeyler çıkaracağız. Evvela MVC uygulamasında kendimize uygun bir dizin yapısı belirlememiz gerekiyor. Bizim yapacağımız sistem şu şekilde olacak:

/app
  /controllers
    defaultController.php
    postController.php
  /core
    app.php
    controller.php
    model.php
    view.php
  /models
    post.php
  /views
    -görünüm dosyaları-
index.php

Evvela, /core dizinindeki app.php dosyası ile başlıyoruz. app sınıfı, bizim temel işlemlerimizi yapacak olan sınıftır. Kabaca anlatmak gerekirse bir kullanıcı siteye girdiği vakit ilk olarak app sınıfından geçecek ve kullanıcının ne istediğini app sınıfında yapacağımız işlemlerle anlayıp yönlendireceğiz. app sınıfı ile controller dosyalarını tetikleyecek, controller dosyaları ile model ve view katmanlarını kullanacağız.

/app/core/app.php:

class app
{
	/**
	 * Sınıf içerisinde tutulacak değerler
	 * __construct metodu ile belirleyip
	 * run metodu ile kullanacağız
	 */
	public $controller, $action, $params;

	/**
	 * Controller ve Action'ı belirleyen başlatıcı metod
	 */
	public function __construct()
	{
		/**
		 * Eğer url sorgusu varsa, başındaki ve
		 * sonundaki / işaretlerini siliyoruz
		 * yoksa geçerli olarak default/index
		 */
		$url = isset($_GET['url']) && !empty($_GET['url']) ? 
			trim($_GET['url'], '/') : 'default/index';

		/**
		 * URL dizgesini / karakterleriyle bölüyoruz
		 * Böylelikle her bölüme ulaşabileceğiz
		 */
		$url = explode('/', $url);

		/**
		 * Controller ve Action'ı belirliyoruz
		 * Eğer $url[0] varsa onu $url[0].'Controller' yani $url[0]'ın default olduğunu varsayalım
		 * indexController olacaktır, eğer yoksa defaultController olarak ayarla dedik
		 * Aynı işlemi action için de yapıyoruz. Action $url[1]'de yer alıyor
		 */
		$this->controller = isset($url[0]) ? $url[0].'Controller' : 'defaultController';
		$this->action = isset($url[1]) ? $url[1].'Action' : 'indexAction';

		/**
		 * array_shift fonksiyonu, dizedeki ilk elemanı siler/kaldırır
		 */
		array_shift($url);
		array_shift($url);

		/**
		 * $url[0] ve $url[1]'i aldık, gerisi parametre. 
		 * Yani default/index/1/2/3'ün 1/2/3 olan yeri. 
		 */
		$this->params = $url;
	}
}

Dikkat edilmesi gereken nokta $_GET['url'] kodunu kullanarak neler yaptığımız. Öncelikle varlığını sorguluyor varsa sağ ve solunda bulunan / karakterlerini trim fonksiyonu ile siliyoruz. Eğer yoksa, geçerli olarak hangi controller’ı alacağını belirliyoruz.

Uygulamada geçerli controller katmanı defaultController’dır. Geçerli aksiyon ise indexAction’dır. Aldığımız URL’i explode ile parçalara ayırıyoruz ve sınıf içerisinde bulunan değişkenlere veriyoruz. Çünkü daha sonrasında bunları run() metodunda kullanacağız.

Şimdi gelelim run() metoduna. Burada URL barından aldığımız değerlere göre hareket edip controller dosyalarını çağıracağız ve bunlara göre işlemler yapacağız. Haydi.

/app/core/app.php (devamı):

class app
{
	/**
	 * Sınıf içerisinde tutulacak değerler
	 * __construct metodu ile belirleyip
	 * run metodu ile kullanacağız
	 */
	public $controller, $action, $params;

	/**
	 * Controller ve Action'ı belirleyen başlatıcı metod
	 */
	public function __construct()
	{
		/**
		 * Eğer url sorgusu varsa, başındaki ve
		 * sonundaki / işaretlerini siliyoruz
		 * yoksa geçerli olarak default/index
		 */
		$url = isset($_GET['url']) && !empty($_GET['url']) ? 
			trim($_GET['url'], '/') : 'default/index';

		/**
		 * URL dizgesini / karakterleriyle bölüyoruz
		 * Böylelikle her bölüme ulaşabileceğiz
		 */
		$url = explode('/', $url);

		/**
		 * Controller ve Action'ı belirliyoruz
		 * Eğer $url[0] varsa onu $url[0].'Controller' yani $url[0]'ın default olduğunu varsayalım
		 * indexController olacaktır, eğer yoksa defaultController olarak ayarla dedik
		 * Aynı işlemi action için de yapıyoruz. Action $url[1]'de yer alıyor
		 */
		$this->controller = isset($url[0]) ? $url[0].'Controller' : 'defaultController';
		$this->action = isset($url[1]) ? $url[1].'Action' : 'indexAction';

		/**
		 * array_shift fonksiyonu, dizedeki ilk elemanı siler/kaldırır
		 */
		array_shift($url);
		array_shift($url);

		/**
		 * $url[0] ve $url[1]'i aldık, gerisi parametre. 
		 * Yani default/index/1/2/3'ün 1/2/3 olan yeri. 
		 */
		$this->params = $url;
	}

	/**
	 * Uygulamayı başlatır
	 */
	public function run()
	{
		// Eğer Controller dosyası varsa $file değişkenini yol olarak belirle
		if (file_exists($file = CDIR."/{$this->controller}.php")) {
			// Dosyayı sistemimize dahil edelim
			require_once $file;
			// Eğer sınıf yaratılmışsa/varsa controller'ımızı çağıralım
			if (class_exists($this->controller)) {
				// controller'ı çağıralım:
				$controller = new $this->controller;
				// Eğer metod varsa ve yaratılmışsa
				if (method_exists($controller, $this->action)) {
					// call_user_func ile controller ve metodu çağırıyoruz
					call_user_func_array([$controller, $this->action], $this->params);
				// Eğer method yoksa programdan çık
				} else {
					exit("Metod mevcut değil: {$this->action}");
				}
			// Sınıf yoksa ve yaratılmamışsa programdan çık
			} else {
				exit("Sınıf mevcut değil: $this->controller");
			}
		// Controller dosyası yoksa programdan çık
		} else {
			exit("Controller dosyası mevcut değil: {$this->controller}.php");
		}
	}
}

__construct() metodunda topladığımız bilgilerle birkaç kontrol sağlayarak işe başlıyoruz. file_exists fonksiyonu ile dosyanın var olup olmadığını soruyoruz. Burada CDIR isimli bir sabit var, bunu daha sonra index.php’de Controller dizinini işaret eden sabit olarak ayarlayacağız. Controller varsa sayfayı dahil ediyoruz, sonra sınıf ve sonrasında ise metod kontrolü yapıyoruz. Eğer her şey yolundaysa temel noktaya geliyoruz. call_user_func_array() fonksiyonu ile daha önceden belirlenmiş olan controller, action ve parametreleri çağırıyoruz. Sonucunda sistemin yapı taşını oturtmuş oluyoruz.

Örnek call_user_func_array() Fonksiyonu Kullanımı

Bu fonksiyon oldukça iş gören bir fonksiyondur. Basit bir örnekle açıklayayım:

function printMessage($name)
{
  echo "Merhaba {$name}!";
}

call_user_func_array('printMessage', ['Durruti']);

Buradaki fonksiyonu printMessage('Durruti') şeklinde de yazdırabilirdik yalnız bazı spesifik durumlarda bunu kullanamıyoruz o yüzden bu fonksiyona ihtiyaç duyabiliyoruz. Sınıflarla ise, şu şekilde çalışıyor:

class Alert
{
  public function name($name)
  {
    return "Dikkat et {$name}!";
  }
}

echo call_user_func_array([new Alert, 'name'], ['Onur']);

Son örneğimizde ise, daha kolay anlaşılabilmesi adına uygulamada kullandığımıza yakın bir örnek vereceğim. Yalnız bu sadece ufak bir senaryo, bu şekilde kullanmıyoruz:

class postController
{
 public function getAction($id)
 {
 echo 'Gösterilecek gönderi ID: ' . $id;
 }
}

if ($_GET['controller'] === 'post' && $_GET['action'] === 'get') {
 call_user_func_array([new postController, 'getAction'], [5]);
}

Umarım aklınızda yer etmiştir. Bu konuyu biraz daha internette araştırarak yeni şeyler katabilirsiniz kendinize. Bu bölüm şimdilik bu kadar. Önümüzdeki yazıda, kullanıcının ilk karşılaşacağı index.php sayfasını işleyeceğiz.

Görüşmek üzere.

Adım adım:

  1. Yeni Başlayanlar İçin PHP’de MVC
  2. MVC Uygulaması İçin İlk Adımlar: App Sınıfı (Şu an buradasınız)
  3. MVC’de Giriş Sayfası, Ayarlamalar ve İlk Controller
  4. Controller ve View Çekirdek Sınıflarını Oluşturmak
  5. Model Katmanı ve Veritabanı İşlemlerini Kolaylaştırmak
  6. Proje/Uygulamanın Deposu
  7. Uygulamanın bitmiş halini indirin
  • Selman Tunç

    bunu composer ile yap çok daha kolay örnek olarak https://github.com/stnc/stnc-framework

    • Merhaba, elbette bir paket olarak sunulabilir bu tarz uygulamalar. Ama bu yazı dizisinde yapmaya çalıştığım namespace nedir bilmeyen insanların MVC ile tanışmasını sağlamak. Yoksa MVC’nin binlerce farklı yöntemi ve bu yazılarda bahsettiğim uygulamanın yüzlerce eksiği var. -Fark ettiysen- her şeyin en minimal haliyle yazdım. Ayrıca bir adet composer konfigürasyon dosyasıyla composer hazır hale gelebilir, çok da problem değil yani. 😊

      Mesele MVC framework’lerin nasıl çalıştığını, temelini kavramak 😉

    • Ömer Faruk UYDURAN

      Merhaba söz konusu proje sizin projeniz ise bu derslerden sonra incelemeyi düşünüyorum. Verdiğiniz github linkinden projeye ulaştım. Ancak dökümantasyon linki ölü bilginiz olsun. Lİnki yenilerseniz incelemek isterim. Selametle.

      • Selman Tunç

        Tamam yarın güncel dokümantasyon koyayım ve bu konuda bir makale de yazacağım, selametle

      • Selman Tunç

        Dokümantasyonu proje içerisine ekledim , ama henüz yazı yazacak zamanım olmadı ,oradan inceleyebilirsiniz.

  • Ömer Faruk UYDURAN

    Dersleri yeni gördüm kafamda bir çok şey oluşmaya başladı. Emeğine sağlık. Ancak soracağım bir nokta var. App class’ında neden 2 defa ardı ardına array_shift($url); kullanıldı amacı nedir?

    • Merhaba, teşekkür ederim.

      $url dizgesini explode fonksiyonu ile ayırıyoruz. Aşağıdaki senaryoda görsel olarak açıklayayım:

      $url = 'controller/aksiyon/parametre1/parametre2';
      $url = explode('/', $url); // array('controller', 'aksiyon', 'parametre1', 'parametre2') halini aldı

      Burada $url[0] ‘controller’ ı ifade ediyor. Bu ilk iki dizi elemanını kullandıktan sonra diziden çıkarmak için array_shift fonksiyonunu iki kere kullandım. İki kere array_shift kullanmak yerine şunu da yapabilirdim:

      unset($url[0]);
      unset($url[1]);

      Umarım yardımcı olur, kolay gelsin.

  • Muhammet Ali ÖZKAYA

    Merhaba
    Makalelerinizi itinayla takip ediyorum. PHP konusunda yeniyim ve MVC patern ile kendi freamwork’umu yazma konusunda çabalıyorum. İçerik bana oldukça yol gösterdi. Anacak sanki 1. konu ile 2. konu yer değişmesi gerekiyor gibi. index.php’yi şekillendirmeden buradaki bazı tanımları kullanarak app.php’yi yazmak biraz tersten gitmek olmuş. bu biraz kafa karıştırıcı. Anlam veremedim neden böyle yaptığınıza. Bir sebebi var mıdır?
    Teşekkürler

    • İkisi de birbirine ihtiyaç duyduğundan hangisinin ilk yazıldığının bir önemi yok. Ama şöyle düşünmekte fayda var; araba sınıfını yazmadan arabanın anahtar kodlarını yazmak yanlış olur. İlk önce arabanın gövdesini yazdım, motorunu ve bağlantılarını yaptım sonrasında index.php’de sınıfı çağırıp çalıştırdım.

  • Berkay Karatas

    merhaba, sınıfınızda reflection injection ve file inclusion açığı var.

    call_user_func_array call_user_func_array([$controller, $merhod], $params);
    bu kısım linkten reflection injection açığı oluşturuyor.

    if (file_exists($file = CDIR.”/{$this->controller}.php”)) {
    bu kısımı da linkten aldığınız için, file inclusion açığı ortaya çıkıyor.

    bu açıkları fix etmenin yöntemi, çağırılacak sayfayı veya çalıştırılacak methodu önceden belirlemek, uymuyorsa exit; yapmak. fakat sınıfınızın genel yapısı nedeniyle, esnek bir şekilde linkten gelen veriyi dahil edip çalıştırdığı için fix etmek imkansız.