Piyasada kullanmamız için daha önceden yapılmış açık kod kaynaklı MVC ve frameworklar var ama bir tanede kendimiz yapmak istersen ne olur! Aşağıdaki örnekte bu işlemin composer.json ve PSR-4 namespace yapısı ile nasıl yapılacağını göreceğiz. Bu bize 'require' ve 'include' kelimelerinin gereğinden fazla kullanılmalarını önlemeye yardımcı olacak. Gerekli olan tüm classları yaratmayacağım ama siz kendiniz istediğiniz gibi ekleme ve çıkarma yapabilirsiniz. Bu konuda yeterince bilginiz olduğunu tahmin ediyorum! Ayrıca bu bir "bitmiş" örnek değil, sadece size fikir verir.


Dosya ve klasör yapısı


"System" ile işaretlenmiş olan dosya ve klasörlerin korunması lazım ama değişiklik yapılabilir. Klasörlerden src olanına dikkat edin çünkü dosya ve klasör isimleri büyük-küçük harf duyarlıdır.


mvc                                     # Name of the application (can be anything)
app # System directory
config.php # System file
asset # Where you keep assets (you can restructure it)
css # Example directory
site.css # Example file
image # Example directory
image.jpg # Example file
js # Example directory
site.js # Example file
src # System directory
Controller # System directory
AbstractController.php # System file
Home.php # Example file
League.php # Example file
Core # System directory
Request.php # System file
Template.php # System file
Helper # Where you put helper classes
Model # Where you put model classes
Repository # Where you put repository classes
View # System directory
Home # Example directory
index.html # Example file
League # Example directory
index.html # Example file
base.html # System file
autoload.php # System file
vendor # System directory generated by composer.json
.htaccess # System file
composer.json # System file
index.php # System file

Kurulum


# Install composer on system
$ curl -sSk https://getcomposer.org/installer | php -- --disable-tls
All settings correct for using Composer
Downloading 1.2.1...

Composer successfully installed to: /vagrant/composer.phar
Use it: php composer.phar
You have instructed the Installer not to enforce SSL/TLS security on remote HTTPS requests.
This will leave all downloads during installation vulnerable to Man-In-The-Middle (MITM) attacks

# Globally enable composer
$ mv composer.phar /usr/local/bin/composer
$ composer self-update
You are already using composer version 1.2.1 (stable channel).

# Generate composer autoloader for the application
mvc $ composer dump-autoload -o
Generating optimized autoload files

Nasıl çalışır


Adressteki birinci segment "controller" ismidir ve ikincisi ise o controller içindeki "method" ismidir.


# Logic
1. User goes to: http://www.yourdomain/
2. Request gets picked up by index.php
3. Relevant controller and its method gets triggered
4. Response is send back

# Example 1
1. Request: http://www.yourdomain/league
2. Process: index.php hits "indexMethod" method of League.php controller
3. Content: Template.php reads "Home\index.html" under "View"
4. Response send back

# Example 2
1. Request: http://www.yourdomain/user/full-detail
2. Process: index.php hits "fullDetailMethod" method of User.php controller
3. Content: Template.php reads "User\fullDetail.html" under "View"
4. Response send back

# Example (Error)
1. Request: http://www.yourdomain/league/non-existing
2. Error gets triggered as:
404
Method cannot be found: [Controller\league:nonExistingMethod]
/var/www/html/mvc/src/Core/Request.php:88

Yapı


app/config.php


define('APP_NAME', 'Simple MVC');
define('APP_DOMAIN', 'http://192.168.50.20');
define('APP_INNER_DIRECTORY', '/mix/mvc');
define('APP_ROOT', __DIR__.'/..');

define('APP_CONTROLLER_NAMESPACE', 'Controller\\');
define('APP_DEFAULT_CONTROLLER', 'Home');
define('APP_DEFAULT_CONTROLLER_METHOD', 'index');
define('APP_CONTROLLER_METHOD_SUFFIX', 'Method');

define('DB_HOST', 'localhost');
define('DB_NAME', 'simplemvc');
define('DB_USER', 'root');
define('DB_PASS', 'root');

src/Controller/AbstractController


namespace Controller;

use Core\Template;

class AbstractController
{
private $template;

public function __construct()
{
$this->template = new Template();
}

protected function getView($controller, array $variables = [])
{
return $this->template->getView($controller, $variables);
}
}

src/Controller/Home


namespace Controller;

use Core\Template;

class Home extends AbstractController
{
public function __construct()
{
parent::__construct(new Template());
}

public function indexMethod()
{
return parent::getView(
__METHOD__,
[
'title' => APP_NAME.' - Home',
'header' => 'Welcome to '.APP_NAME
]
);

}
}

src/Controller/League


namespace Controller;

use Core\Template;

class League extends AbstractController
{
public function __construct()
{
parent::__construct(new Template());
}

public function indexMethod()
{
return parent::getView(
__METHOD__,
[
'title' => APP_NAME.' - Home',
'header' => 'League list',
'league_list' => 'A B C',
]
);

}
}

src/Core/Request


namespace Core;

use Exception;

class Request
{
private $server;
private $post;
private $get;
private $files;

public function __construct(
array $server = [],
array $post = [],
array $get = [],
array $files = []
) {
$this->server = $server;
$this->post = $post;
$this->get = $get;
$this->files = $files;
}

public function getServer($index = null)
{
return !is_null($index) && isset($this->server[$index]) ? $this->server[$index] : $this->server;
}

public function getPost()
{
return $this->post;
}

public function getGet()
{
return $this->get;
}

public function getFiles()
{
return $this->files;
}

public function getController()
{
$urlParts = $this->getUrlParts();

// If controller name is not set in URL return default one
if (!isset($urlParts[0])) {
return APP_CONTROLLER_NAMESPACE.APP_DEFAULT_CONTROLLER;
}

// If controller exists in system then return it
if (class_exists(APP_CONTROLLER_NAMESPACE.$urlParts[0])) {
return APP_CONTROLLER_NAMESPACE.$urlParts[0];
}

// Otherwise
http_response_code(404);
throw new Exception(sprintf('Controller cannot be found: [%s]', APP_CONTROLLER_NAMESPACE.$urlParts[0]), 404);
}

public function getMethod($controller)
{
$urlParts = $this->getUrlParts();

// If controller method is not set in URL return default one
if (!isset($urlParts[1])) {
return APP_DEFAULT_CONTROLLER_METHOD.APP_CONTROLLER_METHOD_SUFFIX;
}

// If controller method name pattern is invalid
if (!preg_match('/^[a-z\-]+$/', $urlParts[1])) {
http_response_code(400);
throw new Exception(sprintf('Invalid method: [%s]', $urlParts[1]), 400);
}

// If controller method exists in system then return it
$method = $this->constructMethod($urlParts[1]);
if (method_exists($controller, $method)) {
return $method;
}

// Otherwise
http_response_code(404);
throw new Exception(sprintf('Method cannot be found: [%s:%s]', $controller, $method), 404);
}

private function getUrlParts()
{
$url = str_replace(APP_INNER_DIRECTORY, null, $this->getServer('REQUEST_URI'));
$urlParts = explode('/', $url);
$urlParts = array_filter($urlParts);
$urlParts = array_values($urlParts);

return $urlParts;
}

private function constructMethod($part)
{
$method = null;

$parts = explode('-', $part);
foreach ($parts as $part) {
if (!$method) {
$method = $part;
} else {
$method .= ucfirst($part);
}
}

return $method.APP_CONTROLLER_METHOD_SUFFIX;
}
}

src/Core/Template


namespace Core;

use Exception;

class Template
{
private $viewPath = '%s/src/View';
private $baseView = 'base.html';
private $reservedVariables = ['application_name', 'body'];

public function __construct()
{
$this->viewPath = sprintf($this->viewPath, APP_ROOT);
}

public function getView($controller, array $variables = [])
{
$variables = $this->validateVariables($variables);

$parts = explode('::', $controller);
$directory = $this->getDirectory($parts[0]);
$file = $this->getFile($parts[1]);

$viewPath = $this->viewPath.'/'.$directory.'/'.$file.'.html';
if (file_exists($viewPath)) {
$baseView = file_get_contents($this->viewPath.'/'.$this->baseView);
$body = file_get_contents($viewPath);
$view = str_replace('{{ body }}', $body, $baseView);

foreach ($variables as $key => $value) {
$view = str_replace('{{ '.$key.' }}', $value, $view);
}

return $view;
}

http_response_code(404);
throw new Exception(sprintf('View cannot be found: [%s]', $viewPath), 404);
}

private function validateVariables(array $variables = [])
{
foreach ($variables as $name => $value) {
if (in_array($name, $this->reservedVariables)) {
http_response_code(404);
throw new Exception(sprintf('Unacceptable view variable given: [%s]', $name), 409);
}
}

$variables['application_name'] = APP_NAME;

return $variables;
}

private function getDirectory($controller)
{
$parts = explode('\\', $controller);

return end($parts);
}

private function getFile($controller)
{
return str_replace(APP_CONTROLLER_METHOD_SUFFIX, null, $controller);
}
}

src/View/Home/index.html


Welcome to our {{ application_name }} application! It shows us how to create a simple MVC.

src/View/League/index.html


See the list of Football Leagues: {{ league_list }}

src/View/base.html


<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<div>
<ul>
<li><a href="home">HOME</a></li>
<li><a href="league">LEAGUE</a></li>
</ul>
</div>
<hr />
<div>
<h3>{{ header }}</h3>
</div>
<hr />

{{ body }}

<hr />
<div id="footer">
<h3>© All rights reserved. {{ application_name }}</h3>
</div>
</body>
</html>

src/autoload.php


require_once __DIR__.'/../vendor/autoload.php';

.htaccess


# This will prevent generating "Not Found" error in browser
# Then we will manually do the rest to produce page for user
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . index.php [L]
</IfModule>

composer.json


{
"autoload": {
"psr-4": {
"": "src/"
}
}
}

index.php


require_once __DIR__.'/app/config.php';
require_once __DIR__.'/src/autoload.php';

use Core\Request;

$request = new Request($_SERVER, $_POST, $_GET, $_FILES);

try {
$controller = $request->getController();
$method = $request->getMethod($controller);

$controller = new $controller;
echo $controller->$method();
} catch (Exception $e) {
echo sprintf(
'<h3>%s</h3><h4>%s</h4><h5>%s:%s</h5>',
$e->getCode(),
$e->getMessage(),
$e->getFile(),
$e->getLine()
);
}