BG MVC Model View Controller eğitim serisi yayında...

Ana sayfa > Programlama > Bgmvc > Sistem akışı

Sistem akışı

Temel işlem mantığı

Web tarayıcı satırından girilen değere aşağıda gösterilen sıra ile işlem yapılır:

  1. Web tarayıcı satırından hangi ifade girilirse girilsin, bu değere önce ana dizinde bulunan .htaccess dosyası işlem yapar ve akışı yine ana dizinde bulunan index.php dosyasına yönlendirir.
  2. index.php dosyası içinde bazı işlemler yapıldıktan sonra, akış ön denetleyici adı verilen bir dosyaya yönlendirilir.
  3. Ön denetleyici dosyasında, tarayıcı satırında sadece web sitesinin adresi yazılıysa, önceden tanımlı bir denetleyici sınıfı ve bu sınıf içinde yer alan bir hareket (Action) fonksiyonu, web sitesinin adresi ile birlikte denetleyici ve hareket tanımlıysa, tanımlanan denetleyici sınıfı ve bu sınıf içinde yer alan hareket (Action) fonksiyonu tahsis edilir.
  4. Akış, tahsis edilen hareket fonksiyonu içinden belirlenmiş görüntüye yönlendirilir.

Her bir hareket (Action) için, ait olduğu denetleyici sınıfı içinde tanımlanmış olan bir fonksiyon vardır. Bu fonksiyon, kendisine tahsis edildiği hareket devreye girdiğinde, veritabanı ile ilgili olanlar da dahil, gerekli tüm işlemleri yapar ve sonuçları ilgili görüntüye yönlendirir.

Ayrıntılı dizin ve dosya yapısı

BG MVC uygulamasında kullanılan dizin ve dosya yapısının ayrıntıları aşağıda gösterilmektedir:

Yan tarafında mavi nokta ile gösterilen dizin ve dosyalar, haricen yüklenmiş yazılımları göstermektedir.

Web tarayıcı satırından girilen değere aşağıda gösterilen sıra ile işlem yapılır:

.htaccess dosyası

Web tarayıcı satırından hangi ifade girilirse girilsin, bu değere önce ana dizinde bulunan .htaccess dosyası işlem yapar ve akışı yine ana dizinde bulunan index.php dosyasına yönlendirir.

C:\wamp\www\bgmvc\.htaccess


Options -Indexes

RewriteEngine On 

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f

RewriteRule ^(.*)$ index.php/$1 [L]

index.php dosyası

.htaccess dosyasından gelen sistem akışı ana dizinde bulunan index.php dosyasında devam eder.

C:\wamp\www\bgmvc\index.php


<?php 
session_start(); // Oturum başlatma

// Sınıfı kullanımları
use \Core\{Config, Router, H};
use App\Models\Users;
use Dotenv\Dotenv;

// Sabit bildirimleri
define('FROOT', __DIR__); // Fiziksel dosya yol tanımlaması (C:\wamp\www\bgmvc)
define('DS', DIRECTORY_SEPARATOR);

// Composer tarafından yüklenen bileşenleri otomatik olarak yükleme
require_once('./vendor/autoload.php');

// Dotenv bileşeni oluşturma
$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->safeLoad();

// Otomatik sınıf dosyası yükleme fonksiyonu
spl_autoload_register(function($className){
    $parts = explode('\\', $className);
    $class = end($parts);
    array_pop($parts);
    $path = strtolower(implode(DS, $parts));
    $path = FROOT . DS . $path . DS . $class . '.php';
    if(file_exists($path)) {
       include($path);
    } 
});

// Kullanıcı bağlı ise kullanıcı bilgilerini içeren bir nesne, değilse false değeri geri döndürür.
$current_user = Users::get_current_user();

// Config.php dosyasındaki Config sınıfı içindeki get() fonksiyonu ile 
// ana dizinde bulunan env dosyasındaki root_dir değerini okuma
$root_dir = Config::get('root_dir'); // '/bgmvc/'

// Tanımlanan ROOT sabitine Lokal sunucu ise '/bgmvc/' değerini, uzak sunucu ise '/' değerini atar.
define('ROOT', ($_SERVER['SERVER_NAME']==='localhost') ? $root_dir : '/'); // Lokal='/bgmvc/' Live='/'

// Lokal sunucu ise '/bgmvc/' değerini, uzak sunucu ise '/' değerini URL değerinin başından siler.
// Lokal: '/bgmvc/' -> '', '/bgmvc/home' -> 'home' Live: '/' -> '' '/home' -> 'home'
$url = H::remove_root($_SERVER['REQUEST_URI']); 

// var_dump($url); die();

// İlk giriş ise sadece boş bir dizi, değilse denetleyici (controller), hareket (action) ve parametreler içerir.
$current_page = $url; // '', 'home', '/home/details/5', 'activities/software', 'about'

// '/' değeri ile ayrılmış olan denetleyici, hareket ve parametre değerleri bir diziye atanır. 
$url = explode ('/', $url); 

Router::route($url); // Router'a yönlendirme


// C:\wamp\www\bgmvc\core\H.php
public static function remove_root($url) {
	// '/bgmvc/' -> ''
	// '/bgmvc/home' -> 'home'
	// '/bgmvc/home/details/5' -> '/home/details/5'
	if(ROOT != '/') { // En soldaki '/bgmvc/' (Lokal sunucu) veya '/' (Uzak sunucu) değerini kaldırma
	   $url = str_replace(ROOT, '', $url);
	} 
	else { // '/' değerini kaldırma (Canlı sunucu) $url = '/' -> $url = ''
	   $url = ltrim($url, '/'); 
	}	

	// $url değerinin en sağında yer alabilecek id=21&md=34 gibi değerleri silmek için
	$url = preg_replace('/(\?.+)/', '', $url); 

	return $url;
}

index.php dosyasında sırayla aşağıdaki işlemler gerçekleştirilir:

  1. session_start() komutu ile oturum başlatılır.
  2. use komutları ile kullanılacak sınıflar dosyaya dahil edilir.
  3. define() fonksiyonu ile, dizin ayırıcı DS sabitine ve uygulamamızın fiziksel ana dizin yolu FROOT sabitine atanır.
  4. Composer tarafından yüklenen bileşenleri otomatik olarak yüklemek için autoload.php dosyası çalıştırılır.
  5. Dotenv bileşeni oluşturulur.
  6. Nesneye yönelik uygulamalarda, tanımladığımız her sınıf için bir dosya oluştururarak, bu dosyaları gerekli oldukları PHP dosyalarının başına include() veya require() fonksiyonları ile tek tek dahil etmek yerine, henüz tanımlanmamış bir sınıfı kullanmak istediğimizde otomatik olarak çağrılan spl_autoload_register() fonksiyonu ile, core, controllers ve models dizinleri altında bulunan ve içinde sınıf tanımlamalarının yapıldığı .php uzantılı dosyaları otomatik olarak tanımlamış oluruz.
  7. Users sınıfındaki get_current_user() ile $currentUser değişkenine, kullanıcı bağlı ise kullanıcı bilgilerini içeren bir nesne, değilse false değer atanır.
  8. Config.php dosyasındaki Config sınıfı içindeki get() fonksiyonu ile ana dizinde bulunan env dosyasındaki root_dir değeri $root_dir değişkenine atanır.
  9. define() fonksiyonu ile anımlanan ROOT sabitine lokal sunucu ise '/bgmvc/' değerini, uzak sunucu ise '/' değerini atar.
  10. H sınıfı içindeki remove_root() fonksiyonu çağrılarak, lokal sunucu ise '/bgmvc/' değerini, uzak sunucu ise '/' değerini URL değerinin başından siler.
    • Eğer ROOT sabiti '/' değerinden farklı ise (Lokal sunucu), '/bgmvc/' değeri $url ($_SERVER['REQUEST_URI']) değişken değerinin başlangıcından silinir. [] değerini alır. Değilse (Uzak sunucu), '/' değeri $url ($_SERVER['REQUEST_URI']) değişken değerinin başlangıcından silinir.
    • preg_replace() fonksiyonu ile $url değerinin en sağında yer alabilecek parametreler (id=21&md=34 gibi) silinir.
  11. $url değişkeninde bulunan istek yapılan sayfanın denetleyici, hareket ve parametre değerleri $current_page değişkenine aktarılır. (home/details/5)
  12. $url değişkeninde '/' değeri ile ayrılmış olan denetleyici, hareket ve parametre değerleri explode() fonksiyonu ile ayrılarak bir yine $url değişkenine bir dizi olarak atanır.
  13. Router::route($url); satırı çalıştığında ise, spl_autoload_register() fonksiyonu ile yapılan otomatik sınıf yükleme nedeniyle, core dizini altında bulunan Router.php dosyası otomatik olarak yüklenerek içindeki sınıf tanımlaması uygulamamıza dahil edilir ve Router sınıfı içindeki route() fonksiyonu $url değişkeni ile çağrılır.

Router.php dosyası

C:\wamp\www\bgmvc\core\Router.php


<?php 
namespace Core;

use Core\Session;
use App\Models\Users;

class Router {

    public static function route($url) {
		$error = false;
		// $url[0] = 'Denetleyici'
		$controller = !empty($url[0]) ? ucwords($url[0]) : Config::get('default_controller'); // $controller = 'Home'
		$controller_name = $controller; // $controller_name = 'Home'
        $controller = '\App\Controllers\\' . $controller . 'Controller'; // $controller = '\App\Controllers\HomeController'
		
		// URL satırında sadece denetleyici adı girilmişse ve bu denetleyiciye ait index.php dosyası varsa, 
        // index değerini action olarak URL'ye eklemek için (İçinde index.php olmayan denetleyici dizinlerine girişi bloke etmek için)		
		$index_exist = file_exists(FROOT . DS . 'app' . DS . 'views' . DS . lcfirst($controller_name) . DS . 'index.php');

        // $url içinde denetleyici ve hareket değeri mevcutsa, denetleyici değerini siler, hareket değeri dizi başına gelir.
		array_shift($url); // $url[0] = 'Hareket' 
		
		// Seçilen yol içinde action var ise atanır, yoksa $controller_name'e ait index.php dosyası varsa 'index' atanır.
		// Böylece diğer dizinler için mevcut bir index.php yoksa $action boş kalır ve method_exists() koşulunu geçemez.
		$action = !empty($url[0]) ? $url[0] : ($index_exist ? 'index' : ''); // $action = 'index'
		$action_name = $action; // $action_name = 'index'
        $action .= "Action"; // $action = 'indexAction'
		
		// $url içinde hareket değeri mevcutsa, hareket değerini siler, parametre değeri dizi başına gelir.
		array_shift($url); // $url[0] = 'Parametre 1', $url[1] = 'Parametre 2', ... 

		// Denetleyici sınıfı veya hareket metodu mevcut değilse, $url dizisine sayfanın mevcut olmadığını gösteren bir değer atar.
		if(!class_exists($controller) || !method_exists($controller, $action)) {
		   $url = ["nopage"];
		   $error = true;
        }
		else {
		   // acl kontrolü (acl.json dosyası ile menüde gösterilmeyen sayfalara erişimi engeller)
		   // Menüde gösterilen sayfalara erişimi engellemek için ayrıca işlem gerekir (if($current_user->acl=='admin')). 
		   $grant_access = self::has_access($controller_name, $action_name);

		   // Kullanıcının giriş izni yoksa, $url dizisine giriş izni olmadığını gösteren bir değer atar.
		   if(!$grant_access) {
			  $url = ["noaccess"];
			  $error = true;
		   }
		}		
		
		// Sınıf veya metod olmamasından (nopage) veya giriş izni olmamasından (noaccess) kaynaklı hata varsa,
        // sayfayı hata gösterimine yönlendirecek şekilde, denetleyici ve hareket adını değiştirir.
		if($error) {
		   $controller = str_replace($controller_name, Config::get('exceptions'), $controller);  
		   $controller_name = Config::get('exceptions'); // $controller_name = 'Exceptions'
		   $action_name = 'index'; // $action_name = 'index'
		   $action = $action_name . 'Action'; // $action = 'indexAction' 
		}

	    // HomeController sınıfı cinsinden bir nesne oluşturur. Bu işlemi yaparken, HomeController sınıfının türetildiği Controller 
		// sınıfının __construct() fonksiyonunu otomatik olarak çağırarak, $controller_name ve $action_name değerlerini geçirir.  
		// $controller = '\App\Controllers\HomeController', $controller_name = 'Home', $action_name = 'index'
		$controller_class = new $controller($controller_name, $action_name);
		
		// HomeController sınıfındaki $action fonksiyonunu (indexAction) $url parametreleri ile çağırma
		call_user_func_array([$controller_class, $action], $url); // $action = 'indexAction', $url = params
    }
	
    public static function redirect($location) {
        if(!headers_sent()) {
           header('Location: ' . ROOT . $location);
        } 
		else {
           echo '<script type="text/javascript">';
           echo 'window.location.href = "'. ROOT . $location .'"';
           echo '</script>';
           echo '<nosript>';
           echo '<meta http-equiv="refresh" content="0;url=' . ROOT . $location . '" />';
           echo '</nosript>';
        }
        exit();
    }

    public static function perm_redirect($perm, $redirect, $msg = "Bu sayfaya giriş yapamazsınız.") {
        $user = Users::get_current_user();
        $allowed = $user && $user->has_permission($perm);
        if(!$allowed) {
           Session::msg($msg);
           self::redirect($redirect);
        } 
    }
	
    public static function get_menu($menu) {
      $menu_array  = [];
      $menu_file = file_get_contents(FROOT . DS . 'app/views/inc' . DS . $menu . '.json');
      $acl = json_decode($menu_file, true);
	  
      foreach($acl as $key => $val) {
        if(is_array($val)) {
		   $sub = [];
           foreach($val as $k => $v) {
			 if(substr($k,0,9) == 'separator' && !empty($sub)) {
                $sub[$k] = '';
                continue;
             }
			 else if($final_val = self::get_link($v)) {
                $sub[$k] = $final_val;
             }
           }
           if(!empty($sub)) {
              $menu_array[$key] = $sub;
           }
        } 
		else {
		   if($final_val = self::get_link($val)) {
              $menu_array[$key] = $final_val;
           }
        }
      }
	  
      return $menu_array;
    }	
	
    private static function get_link($val) {
	  
	  // Harici bağlantı ise
      if(preg_match('/https?:\/\//', $val) == 1) {
	     return $val;
      } 
	  else {
         $uAry = explode('/', $val);
         $controller_name = ucwords($uAry[0]);
         $action_name = (isset($uAry[1]))? $uAry[1] : '';
		 
		 if(self::has_access($controller_name, $action_name)) {            
			return $val;
         }
         
		 return false;
      }
    }	
	
    public static function has_access($controller_name, $action_name='index') {
      $acl_file = file_get_contents(FROOT . DS . 'app/views/inc' . DS . 'acl.json');
      $acl = json_decode($acl_file, true);
      $current_user_acls = ["Guest"];
      $grant_access = false;
	  
      if(Session::exists('logged_in_user')) {
         $current_user_acls[] = "LoggedIn";
		 $current_user_acls[] = ucwords(Users::get_current_user()->acl); // Veritabanındaki users tablosunda acl değeri tek ise
      }
	  
      foreach($current_user_acls as $level) { // 'Guest', 'LoggedIn' ve 'Admin'
		if(array_key_exists($level, $acl) && array_key_exists($controller_name, $acl[$level])) {
		   if(in_array($action_name, $acl[$level][$controller_name]) || in_array("*", $acl[$level][$controller_name])) {
			  $grant_access = true;
              break;
           }
        }
      }
	  
      // Girişi bloke edilen menü seçenekleri
      foreach($current_user_acls as $level) {
        $denied = $acl[$level]['denied'];
        if(!empty($denied) && array_key_exists($controller_name, $denied) && in_array($action_name, $denied[$controller_name])) {
           $grant_access = false;
           break;
        }
      }
	  
	  return $grant_access;
    }
}

Router.php dosyasında sırayla aşağıdaki işlemler gerçekleştirilir:

  1. Eğer $url dizisi boş değilse, $url dizisinin ilk elemanı olan denetleyici adı baş harfi büyütülerek, boş ise Config sınıfından okunan ön tanımlı denetleyici adı ('Home') $controller değişkenine atanır.
  2. $controller değişken değeri $controller_name değişkenine atanır.
  3. $controller değişkeninin başına denetleyicilerin yol tanımlaması sonuna ise 'Controller' ifadesi eklenir.
  4. URI satırında sadece denetleyici adı girilmişse ve bu denetleyiciye ait index.php dosyası varsa, 'index' değerini action olarak URI'ye eklemek için, dosyanın olup olmadığı kontrol edilerek sonuç $index_exist değişkenine atanır (İçinde index.php olmayan denetleyici dizinlerine girişi bloke etmek için)
  5. array_shift($url) fonksiyonu ile $url içinde denetleyici ve hareket değeri mevcutsa, denetleyici değerini siler, hareket değeri dizi başına gelir.
  6. Seçilen yol içinde action var ise atanır, yoksa $controller_name'e ait index.php dosyası varsa 'index' atanır. Böylece, diğer dizinler için mevcut bir index.php yoksa $action boş kalır ve method_exists() koşulunu geçemez.

  7. array_shift($url) fonksiyonu ile $url içinde hareket değeri mevcutsa, hareket değerini siler, parametre değeri dizi başına gelir.
  8. class_exists() ve method_exists() fonksiyonlarını kullanarak, denetleyici sınıfı veya hareket metodu mevcut değilse, $url dizisine sayfanın mevcut olmadığını gösteren bir değer atar, mevcut ise işlem devam eder.

  9. acl.json dosyasının içeriğini kontrol ederek, kullanıcının sayfaya erişim yetkisi olup olmadığını belirler ve sonucu $grant_access değişkenine atar. Kullanıcının giriş izni yoksa, $url dizisine giriş izni olmadığını gösteren bir değer atar.
  10. Sınıf veya metod olmamasından (nopage) veya giriş izni olmamasından (noaccess) kaynaklı hata varsa, sayfayı hata gösterimine yönlendirecek şekilde, denetleyici ve hareket adını değiştirirerek akışı hata sayfasına yönlendirir.
  11. HomeController sınıfı cinsinden bir nesne oluşturur. Bu işlemi yaparken, HomeController sınıfının türetildiği Controller sınıfının __construct() fonksiyonunu otomatik olarak çağırarak, $controller_name ve $action_name değerlerini geçirir. Çağrılan __construct() fonksiyonu ile:
    • $controller_name ve $action_name değerleri Controller sınıfının sırasıyla $_controller_name ve $_action_name değişkenlerine atanır.
    • $controller ve $action değerleriyle View sınıfından bir nesne oluşturularak, Controller sınıfı $view değişkenine atanır.
    • Config sınıfından okunan default_layout ('default') değeri View nesnesinin setLayout() fonksiyonu ile yine View nesnesinin $_layout değişkenine atanır.
    • Request sınıfından bir nesne oluşturularak, Controller sınıfı $request değişkenine atanır.
  12. call_user_func_array() fonksiyonunu kullanarak, HomeController sınıfındaki $action fonksiyonunu (indexAction) $url parametreleri ile çağırır.

Controller.php dosyası

core dizini altındaki Controller.php dosyası içinde tanımlanan Controller sınıfı, app/controllers dizini altındaki denetleyici dosyalarında yer alan tüm sınıfların türetildiği bir sınıftır.

HomeController sınıfındaki indexAction() fonksiyonu çalıştığında, Controller sınıfından türetilen HomeController sınıfı nesnesi yoluyla erişim sağladığı, Controller sınıfının __construct() fonksiyonu ile oluşturulan ve aynı sınıf içinde yer alan $view değişkenine aktarılan View sınıfından üretilen, nesne yoluyla View sınıfı içindeki render() fonksiyonunu kullanarak ilgili görüntü dosyasını ve şablon dosyasını tarayıcıda gösterir.

C:\wamp\www\bgmvc\core\Controller.php


<?php 
namespace Core;

use Core\{View, Config, Request};

class Controller {
    private $_controller_name, $_action_name;
    public $view, $request;

    public function __construct($controller, $action) {        
		$this->_controller_name = $controller;
        $this->_action_name = $action;
        $view_path = strtolower($controller) . '/' .$action;
        $this->view = new View($view_path);
        $this->view->set_layout(Config::get('default_layout'));
        $this->request = new Request();
        $this->onConstruct();
    }

    public function onConstruct(){}
}

Ön tanımlı denetleyici dosyası oluşturma

HomeController sınıfındaki indexAction() fonksiyonu çalışır. Controller sınıfından türetilen HomeController sınıf nesnesi yoluyla erişim sağladığı Controller sınıfı içindeki $view değişkeni yoluyla View sınıfı içindeki render() fonksiyonunu kullanarak ilgili görüntü dosyasını ve şablon dosyasını tarayıcıda gösterir.

C:\wamp\www\bgmvc\app\controllers\HomeController.php


<?php 

namespace App\Controllers;

use Core\{DB, Controller, H, Router, Session};
use App\Models\{Articles, Users};

class HomeController extends Controller {

    public function indexAction($pageno=''){
		$params = [
            'columns' => "*",
            'order' => 'articles.id DESC'
        ];  
        $this->view->total = Articles::find_total($params); // articles tablosundaki toplam kayıt sayısı		
		[$params, $page] = Articles::merge_with_pagination($params, $pageno, $this->view->total);
			
  	    $this->view->limit = $params['limit'];
	    $this->view->page = $page;
		
        $this->view->articles = Articles::find($params); // articles tablosundaki tüm kayıt bilgileri bir nesne dizisine aktarılır.
		$this->view->heading = "Son makaleler";
        $this->view->set_site_title('Son makaleler');
		
		$this->view->render();
    }

    public function detailsAction($id) {
        $params = [
            'columns' => "*",
			'conditions' => "articles.id = :id",
            'bind' => ['id' => $id]
        ];
		
		if($this->request->isPost()) {
		   $params['bind']['id'] = $this->request->get('article_id');
		   
		   $article = Articles::find_first($params);		  
		   
		   echo(json_encode($article));
		}	 		
		else {
		   $article = Articles::find_first($params);
		   if(!$article) Router::redirect('exceptions/index/noarticle');
		   $this->view->article = $article;

		   unset($params['conditions']);
		   unset($params['bind']);
		   $params['order'] = 'articles.id DESC';
		   
		   $this->view->articles = Articles::find($params);

		   $this->view->render();
		}
    }
    
}

HomeController.php dosyasındaki HomeController sınıfının indexAction fonksiyonunda sırayla aşağıdaki işlemler gerçekleştirilir:

  1. Tüm sütunlar seçilerek kayıtlar id değeriyle büyükten küçüğe doğru sıralanacak şekilde $params dizisine değer atanır.
  2. Articles sınıfı yoluyla Model sınıfının find_total() fonksiyonuna $params değişkeni parametre olarak geçirilerek, articles tablosundaki toplam kayıt sayısı alınır ve Controller sınıfı $view nesnesinin total adlı değişkenine atanır.
  3. Articles sınıfı yoluyla Model sınıfının merge_with_pagination() fonksiyonuna $params, $pageno, $this->view->total değerleri parametre olarak geçirilerek, $params dizisine 'limit' ve 'offset' değerleri eklenir, $pageno değerine göre $page değeri belirlenir.
  4. $params['limit'] değeri ile limit adlı ve $page değeri ile page adlı iki adet değişken, Controller sınıfı $view nesnesi yoluyla, oluşturulur.
  5. Articles sınıfı yoluyla Model sınıfının find() fonksiyonuna $params değişkeni parametre olarak geçirilerek, tüm kayıt bilgileri Controller sınıfı $view nesnesinin articles adlı dizisine aktarılır.
  6. Sayfa başlığı değeri Controller sınıfı $view nesnesinin heading adlı değişkenine atanır.
  7. Web sayfası başlığı, set_site_title() fonksiyonu ile Controller sınıfı $view nesnesinin $_site_title değişkenine atanır.
  8. Controller sınıfı $view nesnesi yoluyla View sınıfı render() fonksiyonu çağrılır.

View.php dosyası

Controller.php içindeki Controller sınıfından türetilen herhangi bir denetleyici sınıfı içindeki bir hareket fonksiyonundan, yapılan görüntü işlemlerini gerçekleştirmek için, View.php dosyasında tanımlı View sınıfı oluşturulur.


<?php 
namespace Core;

use Core\{Config, Router};

class View {
    public $articles, $total, $heading;
	public $errors, $user, $header, $users, $article;
	public $msg, $msg_txt, $limit, $page, $url;
	
	private $_site_title = '', $_content = [], $_current_content, $_buffer, $_layout;
    private $_default_view_path;

    public function __construct($path = '') {
       $this->_default_view_path = $path;
       $this->_site_title = Config::get('default_site_title');
    }

    public function set_layout($layout) {
        $this->_layout = $layout;
    }

    public function set_site_title($title) {
        $this->_site_title = $title;
    }

    public function get_site_title() {
        return $this->_site_title;
    }

    public function render($path = '') {
        if(empty($path)) {
           $path = $this->_default_view_path;
        }
        $layout_path = FROOT . DS . 'app' . DS . 'views' . DS . 'layouts' . DS . $this->_layout . '.php';
        $full_path = FROOT . DS . 'app' . DS . 'views' . DS . $path . '.php';
		
		if(!file_exists($full_path)) {
           Router::redirect('exceptions/index/noview');
        }
        if(!file_exists($layout_path)) {
           Router::redirect('exceptions/index/notemplate');
        }
        
		include($full_path);
        include($layout_path);
    }

    public function start($key) {		
		if(empty($key)) {
		   Router::redirect('exceptions/index/nokey');	
        }
        $this->_buffer = $key;
		
        ob_start();
    }

    public function end() {
        if(empty($this->_buffer)) {
		   Router::redirect('exceptions/index/nostart');	
        } 
        $this->_content[$this->_buffer] = ob_get_clean();
        $this->_buffer = null;
    }

    public function content($key) {
        if(array_key_exists($key, $this->_content)) {
           echo $this->_content[$key];
        } 
		else {
           echo '';
        }
    }

    public function inc($path) {
		$full_path = FROOT . DS . 'app' . DS . 'views' . DS . $path . '.php';

		if(file_exists($full_path)) {           
		   include($full_path);
        }
    }
}

Görüntü sınıfı kullanımı

1. Controller.php içindeki Controller sınıfından türetilen herhangi bir denetleyici sınıfı içindeki bir hareket fonksiyonundan, Controller sınıfı içinde tanmlı view değişkeni yoluyla, View.php dosyasında tanımlı View sınıfı içindeki render() fonksiyonu çağrılır.

$this->view->render('home/index');

2. render() fonksiyonu, kendisine geçirilen parametre değerine (home/index) karşılık gelen dosya ile şablon dosyası mevcut ise, sırayla görünüme dahil eder:

  1. C:\wamp\www\bgmvc\app\views\home\index.php
  2. C:\wamp\www\bgmvc\app\views\layouts\default.php

index.php görüntü dosyası

  • View sınıfı içindeki start() fonksiyonu 'content' parametresi ile çağrılarak, ob_start() PHP fonksiyonu ile, $this->start('content') ve end() fonksiyonları arasında yer alan verileri aktarmak için, bir tampon bellek oluşturulur.
  • default.php şablon dosyasında $this->content('content') fonksiyonu ile eklenecek ifadeler yazılır.
  • View sınıfı içindeki end() fonksiyonu ile, oluşturulan tampon bellek içeriği, ob_get_clean() PHP fonksiyonu ile View sınıfı içindeki _content dizisine aktarılır.

C:\wamp\www\bgmvc\app\views\home\index.php


<?php 
  use Core\{H, Config};
  use App\Models\{Users};
?>

<?php $this->start('content'); ?>

<?php 
if(Config::get('main_slide')) { 
   $this->inc('inc/main_slide');
}
?>

<div class="p-3">
	<nav class="navbar navbar-expand-lg bg-header">
	   <div class="container-fluid p-2">
	   
		  <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#MainNavbar" aria-controls="MainNavbar" aria-expanded="false" aria-label="Toggle navigation">
			<span class="navbar-toggler-icon"></span>
		  </button>
	   
		  <div class="collapse navbar-collapse" id="MainNavbar">
			
			<ul class="navbar-nav">
				<li><?=$this->heading?></li>
			</ul>
		  
			<ul class="navbar-nav ms-auto mb-2 mb-lg-0 bg-fontsm">
			    <?php // Sayfada gösterilecek kayıt sayısı seçimi ?>
				<form class="mt-2" action="<?=ROOT?><?=H::get_action()?>" method="post">
					<label class="me-2" for="records_limit">Kayıt sayısı</label>
					<select name="records_limit" id="records_limit" class="custom-select">
						<?php foreach([4, 6, 8, 10] as $limit) : ?>
						<option
							<?php if(isset($_SESSION['records_limit']) && $_SESSION['records_limit'] == $limit) echo 'selected'; ?>
							value="<?= $limit; ?>">
							<?= $limit; ?>
						</option>
						<?php endforeach; ?>
					</select>
				</form>
			</ul>
			
		  </div>
	   </div>
	</nav>
	
	<?php // Makalelerin gösterimi  ?>
	<div class="row row-cols-1 row-cols-md-3 row-cols-lg-4 row-cols-xl-5 row-cols-xxl-6 g-4">
	   <?php foreach($this->articles as $article): ?>
		 <div class="col">
			<div class="card h-100 shadow">
			  <div class="card-body">
				<h5 class="card-title"><?= $article->title ?></h5>
				<div class="bg-card-text mb-2"><?= html_entity_decode($article->body) ?></div>
				<a href="<?=ROOT?>home/details/<?=$article->id?>" class="text-info">Detaylar</a>
			  </div>
			</div>
		 </div>
       <?php endforeach; ?>
	</div>	
	
	<?php // Sayfa yapısı oluşturma ?>
	<?php $this->inc('inc/pager'); ?>

</div>

<?php $this->end(); ?>

default.php dosyası

Bu dosya web sitesinin temel HTML dosyasıdır.

Dosya içinde, aşağıdaki işlemler sırasıyla gerçekleştirilir:

  1. .css dosyaları sisteme dahil edilir.
  2. Görüntü dosyası index.php içinde set_site_title() fonksiyonu ile belirlenen içerik, $this->content('head'); ifadesinin yerini alır.
  3. main_menu.php dosyası dahil edilir.
  4. Session sınıfındaki display_session_alerts() fonksiyonu ile, eğer varsa, oturum uyarı mesajları ekrana yazılır.
  5. content() fonksiyonu 'content' parametresi ile çağrılarak ($this->content('content')), _content dizisi içeriği, ekrana yazılır.
  6. Javascript dosyaları sisteme dahil edilir.

C:\wamp\www\bgmvc\app\views\layouts\default.php


<?php use Core\{Config, Session}; ?>
<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= $this->get_site_title(); ?></title>

    <link rel="stylesheet" href="<?=ROOT?>public/css/bootstrap.min.css" media="screen" title="no title" charset="utf-8">
	<link href="<?=ROOT?>public/css/main.css?v=<?=Config::get('version');?>" rel="stylesheet" type="text/css" media="screen" title="no title" charset="utf-8">
	
<?php $this->content('head'); ?>
</head>
<body>
    
	<?php $this->inc('inc/main_menu'); ?>
	
    <div class="container-fluid p-0">
        <?=Session::display_session_alerts(); ?>
        <?php $this->content('content'); ?>
    </div>

	<script src="<?=ROOT?>public/js/jquery-3.7.1.min.js"></script>
	<script src="<?=ROOT?>public/js/popper.min.js"></script>
	<script src="<?=ROOT?>public/js/bootstrap.bundle.min.js"></script>
	<script src="<?=ROOT?>public/js/main.js"></script>
	
</body>
</html>

Menü dosyası

default.php dosyasından çağrılan main_menu.php dosyasında:

  1. Router::get_menu('menu_acl') fonksiyonu ile menu_acl.json dosyası okunur.
  2. Elde edilen menü ile navigasyon çubuğu oluşturulur.

C:\wamp\www\bgmvc\app\views\layouts\main_menu.php


<?php 
  use Core\{H, Router};
  global $current_user;
  $menu = Router::get_menu('menu_acl');
?>

<nav class="navbar navbar-expand-lg navbar-dark bg-dark p-4">
  <div class="container-fluid">
  
      <a class="navbar-brand" href="<?=ROOT?>">BG MVC</a>
      <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#TopNavbar2" aria-controls="TopNavbar2" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>

	  <div class="collapse navbar-collapse" id="TopNavbar2">
		<ul class="navbar-nav me-auto mb-2 mb-lg-0">
		  <?php foreach($menu as $key => $val) :
			 if(is_array($val)): ?>			
				<li class="<?php echo H::active_class($key,'nav-item dropdown'); ?>">
					<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" aria-expanded="false"><?=$key?></a>
					<ul class="dropdown-menu">
						<?php foreach($val as $k => $v):
							     echo H::nav_item($v, $k, true);
						      endforeach;
						?>
					</ul>
				</li>					
			<?php else: 
				echo H::nav_item($val, $key);
			 endif; 
			?>
		  <?php endforeach; ?>
		</ul>
		
		<ul class="navbar-nav d-flex">
		  <?php if(!$current_user): ?>
			  <?= H::nav_item('register/login', 'Giriş yap'); ?>
		  <?php endif; ?>

		  <?php if($current_user): ?>
			  <li class="nav-item dropdown">
				  <a href="#" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Merhaba <?= $current_user->fname;?></a>
				  <ul class="dropdown-menu dropdown-menu-right">							
					<?= H::nav_item('register/logout', 'Çıkış', true); ?>
				  </ul>
			  </li>
		  <?php endif; ?>
		</ul>			

	  </div>  
  </div>
</nav>

Menü json dosyası

main_menu.php dosyasından çağrılan menu_acl.json dosyasındaki içerik çağrılan kod satırına dahil edilir.

C:\wamp\www\bgmvc\app\menu_acl.json


{
  "Ana sayfa" : "home",
  "Faaliyetlerimiz" : {
	"Yazılım" : "activities/software",
	"Kodlama" : "activities/code",
	"Tasarım" : "activities/design"
  },   
  "Kod örnekleri" : "codes",
  "Hakkımızda" : "about"
}

Sistem akışı

Web sayfasına ilk girişte sistemin akışı aşağıdaki sıra ile gerçekleşmektedir:

.htaccess

Web tarayıcı satırından hangi ifade girilirse girilsin, bu değere önce ana dizinde bulunan .htaccess dosyası işlem yapar ve akışı yine ana dizinde bulunan index.php dosyasına yönlendirir.


RewriteRule ^(.*)$ index.php/$1 [L]

index.php

Aşağıda gösterilen sınıf fonksiyonları ile işlem yapıldıktan sonra route() fonksiyonu çağrılır:

  • Dotenv
  • Users
  • Config
  • H

Router::route($url);

core/Router.php

acl.json dosyasından erişim izni kontrolü yapılır.

call_user_func_array() fonksiyonunu kullanarak, HomeController sınıfındaki $action fonksiyonunu (indexAction) $url parametreleri ile çağırır.


// HomeController sınıfındaki indexAction fonksiyonunu çağırma
call_user_func_array([$controller_class, $action], $url); // $action = 'indexAction', $url = params

app/controllers/HomeController.php

indexAction() fonksiyonu içinde, aşağıda gösterilen sınıf fonksiyonları ile işlem yapıldıktan sonra View sınıfı render() fonksiyonu çağrılır:

  • View (Controller yoluyla)
  • Model (Articles yoluyla)

$this->view->render();

core/View.php

render() fonksiyonu içinde, app/views/home/index.php ve app/views/layout/default.php dosyaları sırasıyla görünüme dahil edilir.


include($full_path);   // app/views/home/index.php
include($layout_path); // app/views/layout/default.php

app/views/home/index.php

View sınıfındaki start() ve end() fonksiyonları ile, default.php şablon dosyasında yer alacak olan içerik oluşturulur.

pager.php dosyası ile sayfalama sistemi oluşturulur.


$this->start('content');
.
.
.
$this->end();

app/views/layout/default.php

Sistemin ana HTML sayfası oluşturulur. .css ve .js dosyaları sisteme dahil edilir.

main_menu.php dosyası dahil edilir.

app/views/home/index.php dosyasında oluşturulan içerik, View sınıfındaki content() fonksiyonu ile görünüme dahil edilir.


$this->inc('inc/main_menu');

app/views/inc/main_menu.php

Router::get_menu() fonksiyonu ile menu_acl.json dosyası okunarak, sayfanın en yukarısında yer alan menü oluşturulur.


Router::get_menu('menu_acl');

app/views/inc/menu_acl.json

Menü değerlerini içerir.

Sistemin çalışma sırası aşağıdaki şekilde gösterilmektedir: