English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Scrivere a mani nude un framework PHP: un'analisi approfondita del flusso di lavoro MVC

1 Cos'è MVC 

Il modello MVC (Model-View-Controller) è un modello di architettura software nell'ingegneria del software, che suddivide il sistema software in tre parti fondamentali: modello (Model), vista (View) e controller (Controller). 

Il modello MVC in PHP, noto anche come Web MVC, è evoluto dalla fine del XX secolo. Lo scopo del modello MVC è di realizzare una progettazione dinamica del programma, facilitando la modifica e l'estensione successiva del programma e rendendo possibile il riutilizzo di una parte del programma. Inoltre, semplificando la complessità, questo modello rende la struttura del programma più intuitiva. Il sistema software, separando i suoi componenti fondamentali, ha anche attribuito alle parti fondamentali le funzioni appropriate. 

Le funzioni di ciascuna parte del MVC:
 • Modello Model – Gestisce la maggior parte della logica aziendale e tutta la logica del database. Il modello fornisce uno strato astratto per connettersi e operare il database.
 • Controller – Responsabile di rispondere alle richieste dell'utente, preparare i dati e determinare come presentare i dati.
 • Visualizzazione View – Responsabile della presentazione dei dati, presentati all'utente tramite HTML. 

Un flusso tipico di Web MVC:
 1. Il Controller intercetta le richieste dell'utente;
 2. Il Controller chiama il Model per completare le operazioni di lettura e scrittura di stato;
 3. Il Controller trasmette i dati al View;
 4. Visualizza i risultati finali e li presenta all'utente. 

2. Perché sviluppare una propria MVC framework 

Sul web ci sono molte excellenti framework MVC disponibili, questa guida non è volta a sviluppare una soluzione MVC completa e definitiva, ma è vista come una buona opportunità per imparare PHP internamente, durante questo processo, imparerai programmazione orientata agli oggetti e design pattern MVC, e imparerai alcune cose da considerare durante lo sviluppo. 

Più importante ancora, puoi controllare completamente il tuo framework e integrare le tue idee nel framework che stai sviluppando. Anche se non è necessariamente il migliore, puoi sviluppare funzioni e moduli nel tuo modo. 

3. Inizia a sviluppare la tua own MVC framework 

3.1 Preparazione della directory 

Prima di iniziare a sviluppare, diamo prima avvio al nostro progetto, supponiamo che il progetto che stiamo creando sia todo, il framework MVC può essere chiamato FastPHP, quindi il primo passo è configurare la struttura della directory.

 

Nonostante non utilizzeremo tutte le directory in questa guida, è molto importante configurare la directory del programma all'inizio per garantire l'estensibilità del programma in futuro. Di seguito, esamineremo l'uso di ciascuna directory:
 •application – Codice dell'applicazione
 •config – Configurazione del programma o configurazione del database
 •fastphp – Directory del core della framework
 •public – File statici
 •runtime – Directory dei dati temporanei
 •scripts – Strumenti della riga di comando 

3.2 Norme del codice

Dopo aver configurato la directory, dobbiamo stabilire alcune norme per il codice:
 1. Il nome delle tabelle MySQL deve essere in minuscolo, ad esempio: item, car
 2. Il nome del modulo (Models) deve iniziare con una lettera maiuscola e aggiungere "Model" alla fine, ad esempio: ItemModel, CarModel
 3. I controller (Controllers) devono avere la lettera maiuscola iniziale e aggiungere “Controller” nel nome, ad esempio: ItemController, CarController
 4. La struttura di distribuzione delle viste (Views) è di tipo “nome_del_controllore/nome_dellazione”, ad esempio: item/view.php, car/buy.php 

Alcune delle regole sopra sono per facilitare le chiamate reciproche nel programma. Ora iniziamo la programmazione PHP MVC vera e propria. 

3.3 Reindirizzamento 

Reindirizza tutte le richieste di dati al file index.php, crea un file .htaccess nella directory todo, il contenuto del file è: 

<IfModule mod_rewrite.c>
  RewriteEngine On
  # Assicurati che il percorso della richiesta non sia un nome di file o una directory
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteCond %{REQUEST_FILENAME} !-d
  # Reindirizza tutte le richieste a index.php?url=PATHNAME
  RewriteRule ^(.*)$ index.php?url=$1 [PT,L]
</IfModule>

La ragione principale per farlo è:
 1. Il programma ha un singolo punto di ingresso;
 2. Oltre ai programmi statici, tutti gli altri programmi vengono reindirizzati a index.php;
 3. Può essere utilizzato per generare URL favorevoli all'SEO, per una configurazione migliore dell'URL, potrebbe essere necessario il routing dell'URL in futuro, questa parte non sarà spiegata ora. 

3.4 File di ingresso 

Dopo aver completato le operazioni sopra, dovrebbe essere chiaro cosa dobbiamo fare, giusto! Aggiungi il file index.php nella directory public, il contenuto del file è:

 <?php
// La directory dell'applicazione è la directory corrente
define('APP_PATH', __DIR__.'/');
// Abilita la modalità di debug
define('APP_DEBUG', true);
// URL radice del sito
define('APP_URL', 'http://localhost/fastphp');
// Carica la struttura
require './fastphp/FastPHP.php';

Attenzione, il codice PHP sopra non aggiunge il simbolo di fine PHP”?>”, la ragione principale è che per i file che contengono solo codice PHP, il segno di fine (“?>”) è meglio non esistere, PHP non richiede un simbolo di fine, e non aggiungere il simbolo di fine può prevenire in gran parte l'inserimento di contenuti di intrusione extra alla fine, rendendo il programma più sicuro. 

3.5 File di configurazione e richiesta principale 

Nel file index.php, abbiamo inviato una richiesta al file FastPHP.php nella cartella FastPHP, quindi cosa contiene esattamente il file di avvio FastPHP.php?

<?php
// Inizializzare le costanti
defined('FRAME_PATH') or define('FRAME_PATH', __DIR__.'/');
defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']).'/');
defined('APP_DEBUG') or define('APP_DEBUG', false);
defined('CONFIG_PATH') or define('CONFIG_PATH', APP_PATH.'config/');
defined('RUNTIME_PATH') or define('RUNTIME_PATH', APP_PATH.'runtime/');
// Include il file di configurazione
require APP_PATH . 'config/config.php';
// Include la classe del framework principale
require FRAME_PATH . 'Core.php';
// Eseguire l'istanziazione della classe principale
$fast = new Core;
$fast->run();

Questi file possono essere inclusi direttamente nel file index.php, e le costanti possono essere definite direttamente in index.php. Facciamo così per rendere più facile la gestione e l'espansione in futuro, quindi mettiamo insieme i programmi che devono essere caricati e eseguiti all'inizio in un file separato per il riferimento.

Prima di tutto, vediamo il file config.php nel percorso config del file, il principale scopo di questo file è impostare alcune opzioni di configurazione del programma e la connessione del database, il contenuto principale è: 

 <?php
/** Configurazione delle variabili **/
define('DB_NAME', 'todo');
define('DB_USER', 'root');
define('DB_PASSWORD', 'root');
define('DB_HOST', 'localhost');

Dovrebbe essere detto che config.php coinvolge relativamente poco contenuto, sono solo alcune impostazioni di base del database, e ora vediamo come scrivere il file di ingresso comune del framework under fastphp, Core.php.
 

<?php
/**
 * Frame di lavoro FastPHP
 */
class Core
{
  // Esecuzione del programma
  function run()
  {
    spl_autoload_register(array($this, 'loadClass'));
    $this->setReporting();
    $this->removeMagicQuotes();
    $this->unregisterGlobals();
    $this->Route();
  }
  // Gestione delle rotte
  function Route()
  {
    $controllerName = 'Index';
    $action = 'index';
    if (!empty($_GET['url'])) {
      $url = $_GET['url'];
      $urlArray = explode('/', $url);
      // Prelievo del nome del controller
      $controllerName = ucfirst($urlArray[0]);
      // Prelievo del nome dell'azione
      array_shift($urlArray);
      $action = empty($urlArray[0]) ? 'index' : $urlArray[0];
      // Prelievo dei parametri dell'URL
      array_shift($urlArray);
      $queryString = empty($urlArray) ? array() : $urlArray;
    }
    // Gestione dei dati vuoti
    $queryString = empty($queryString) ? array() : $queryString;
    // Istanziazione del controller
    $controller = $controllerName . 'Controller';
    $dispatch = new $controller($controllerName, $action);
    // Se il controller e l'azione esistono, questa viene chiamata e i parametri dell'URL vengono trasmessi
    if ((int)method_exists($controller, $action)) {
      call_user_func_array(array($dispatch, $action), $queryString);
    } else {
      exit($controller . "Controller non esistente");
    }
  }
  // Verifica l'ambiente di sviluppo
  function setReporting()
  {
    if (APP_DEBUG === true) {
      error_reporting(E_ALL);
      ini_set('display_errors','On');
    } else {
      error_reporting(E_ALL);
      ini_set('display_errors','Off');
      ini_set('log_errors', 'On');
      ini_set('error_log', RUNTIME_PATH. 'logs/error.log');
    }
  }
  // Remove sensitive characters
  function stripSlashesDeep($value)
  {
    $value = is_array($value) ? array_map('stripSlashesDeep', $value) : stripslashes($value);
    return $value;
  }
  // Detect and remove sensitive characters
  function removeMagicQuotes()
  {
    if ( get_magic_quotes_gpc()) {
      $_GET = stripSlashesDeep($_GET );
      $_POST = stripSlashesDeep($_POST );
      $_COOKIE = stripSlashesDeep($_COOKIE);
      $_SESSION = stripSlashesDeep($_SESSION);
    }
  }
  // Detect and remove custom global variables (register globals)
  function unregisterGlobals()
  {
    if (ini_get('register_globals')) {
      $array = array('_SESSION', '_POST', '_GET', '_COOKIE', '_REQUEST', '_SERVER', '_ENV', '_FILES');
      foreach ($array as $value) {
        foreach ($GLOBALS[$value] as $key => $var) {
          if ($var === $GLOBALS[$key]) {
            unset($GLOBALS[$key]);
          }
        }
      }
    }
  }
  // Automatic loading of controller and model classes 
  static function loadClass($class)
  {
    $frameworks = FRAME_PATH . $class . '.class.php';
    $controllers = APP_PATH . 'application/controllers/' . $class . '.class.php';
    $models = APP_PATH . 'application/models/' . $class . '.class.php';
    if (file_exists($frameworks)) {
      // Carica le classi core del framework
      include $frameworks;
    } elseif (file_exists($controllers)) {
      // Carica la classe controller dell'applicazione
      include $controllers;
    } elseif (file_exists($models)) {
      // Carica la classe modello dell'applicazione
      include $models;
    } else {
      /* Codice di errore */
    }
  }
}

Ecco un'analisi dettagliata del metodo di richiesta principale callHook(), prima di tutto, vorremmo vedere come il nostro URL sarà:
yoursite.com/controllerName/actionName/queryString

La funzione callHook() ha il ruolo di ottenere l'URL dal variabile globale G ET[ ′ url ′ ]e dividerlo in tre parti: ottenere l'URL dal variabile globale G ET[′url′]e dividerlo in tre parti: controller, action e queryString. 

Ad esempio, se l'URL è: todo.com/item/view/1/first-item, allora
 • $controller è: item
 • $action è: view
 • La stringa di ricerca Query String è: array(1, first-item) 

Dopo la divisione, viene istanziato un nuovo controller: $controller.'Controller' (dove “.” è il trattino), e viene chiamato il metodo $action. 

3.6 Classe base / Controller 

Le operazioni successive consistono nel creare le classi base necessarie per il programma in fastphp, inclusi le classi base dei controller, dei modelli e delle viste. 

Nuova classe base del controller creata come Controller.class.php, la funzione principale del controller è la gestione di base, i dettagli specifici sono come segue:
 

<?php 
/**
 * Classe base del controller
 */
class Controller
{
  protected $_controller;
  protected $_action;
  protected $_view;
  // Costruttore, inizializzazione delle proprietà e istanziazione del modello corrispondente
  function __construct($controller, $action)
  {
    $this->_controller = $controller;
    $this->_action = $action;
    $this->_view = new View($controller, $action);
  }
  // Assegnazione di variabili
  function assign($name, $value)
  {
    $this->_view->assign($name, $value);
  }
  // Renderizzazione della vista
  function __destruct()
  {
    $this->_view->render();
  }
}
 

La classe Controller implementa la comunicazione tra tutti i controller, modelli e viste (classe View). Durante l'esecuzione del costruttore distruttore, possiamo chiamare render() per visualizzare il file di vista (view).

3.7 Modello Model base

Ecco il modello base Model.class.php, il modello base Model.class.php è il seguente:

 <?php
class Model extends Sql
{
  protected $_model;
  protected $_table;
  function __construct()
  {
    // Connettione al database
    $this->connect(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
    // Ottenere il nome del modello
    $this->_model = get_class($this);
    $this->_model = rtrim($this->_model, 'Model');
    // Il nome della tabella del database è uguale al nome della classe
    $this->_table = strtolower($this->_model);
  }
  function __destruct()
  {
  }
}

 Considerando che il modello deve gestire il database, è stato creato un database base, Sql.class.php, il modello base eredita Sql.class.php, il codice è il seguente:

 <?php
class Sql
{
  protected $_dbHandle;
  protected $_result;
  // Connettione al database
  public function connect($host, $user, $pass, $dbname)
  {
    try {
      $dsn = sprintf("mysql:host=%s;dbname=%s;charset=utf8", $host, $dbname);
      $this->_dbHandle = new PDO($dsn, $user, $pass, array(PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC));
    }
      exit('Error: ' . $e->getMessage());
    }
  }
  // Query all
  public function selectAll()
  {
    $sql = sprintf("select * from `%s`", $this->_table);
    $sth = $this->_dbHandle->prepare($sql);
    $sth->execute();
    return $sth->fetchAll();
  }
  // Query based on condition (id)
  public function select($id)
  {
    $sql = sprintf("select * from `%s` where `id` = '%s'", $this->_table, $id);
    $sth = $this->_dbHandle->prepare($sql);
    $sth->execute();
    return $sth->fetch();
  }
  // Delete based on condition (id)
  public function delete($id)
  {
    $sql = sprintf("delete from `%s` where `id` = '%s'", $this->_table, $id);
    $sth = $this->_dbHandle->prepare($sql);
    $sth->execute();
    return $sth->rowCount();
  }
  // Custom SQL query, return the number of affected rows
  public function query($sql)
  {
    $sth = $this->_dbHandle->prepare($sql);
    $sth->execute();
    return $sth->rowCount();
  }
  // Add new data
  public function add($data)
  {
    $sql = sprintf("insert into `%s` %s", $this->_table, $this->formatInsert($data));
    return $this->query($sql);
  }
  // Modify data
  public function update($id, $data)
  {
    $sql = sprintf("update `%s` set %s where `id` = '%s'", $this->_table, $this->formatUpdate($data), $id);
    return $this->query($sql);
  }
  // Convert array to insert format SQL statement
  private function formatInsert($data)
  {
    $fields = array();
    $values = array();
    foreach ($data as $key => $value) {
      $fields[] = sprintf("`%s`", $key);
      $values[] = sprintf("'%s'", $value);
    }
    $field = implode(',', $fields);
    $value = implode(',', $values);
    return sprintf("(%s) values (%s)", $field, $value);
  }
  // Convertire l'array in una stringa SQL di aggiornamento
  private function formatUpdate($data)
  {
    $fields = array();
    foreach ($data as $key => $value) {
      $fields[] = sprintf("`%s` = '%s'", $key, $value);
    }
    return implode(',', $fields);
  }
}

Dovrebbe essere detto che Sql.class.php è una parte fondamentale del framework. Perché? Perché attraverso di esso, abbiamo creato uno strato di astrazione SQL, che può ridurre notevolmente il lavoro di programmazione del database. Anche se l'interfaccia PDO è già abbastanza sintetica, l'astrazione rende il framework ancora più flessibile. 

3.8 视图View类 

视图类 View.class.php 内容如下:

 <?php
/**
 * 视图基类
 */
class View
{
  protected $variables = array();
  protected $_controller;
  protected $_action;
  function __construct($controller, $action)
  {
    $this->_controller = $controller;
    $this->_action = $action;
  }
  /** 分配变量 **/
  function assign($name, $value)
  {
    $this->variables[$name] = $value;
  }
  /** 渲染显示 **/
  function render()
  {
    extract($this->variables);
    $defaultHeader = APP_PATH . 'application/views/header.php';
    $defaultFooter = APP_PATH . 'application/views/footer.php';
    $controllerHeader = APP_PATH . 'application/views/' . $this->_controller . '/header.php';
    $controllerFooter = APP_PATH . 'application/views/' . $this->_controller . '/footer.php';
    // 文件页头
    if (file_exists($controllerHeader)) {
      include ($controllerHeader);
    } else {
      include ($defaultHeader);
    }
    // 文件页内容
    include (APP_PATH . 'application/views/' . $this->_controller . '/' . $this->_action . '.php');
    // 文件页脚
    if (file_exists($controllerFooter)) {
      include ($controllerFooter);
    } else {
      include ($defaultFooter);
    }
  }
}
 

这样我们的核心的PHP MVC框架就编写完成了,下面我们开始编写应用来测试框架功能。

4 应用

4.1 部署数据库

在 SQL 中新建一个 todo 数据库,使用下面的语句增加 item 数据表并插入2条记录:

CREATE DATABASE `todo` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `todo`;
CREATE TABLE `item` (
  `id` int(11) NOT NULL auto_increment;
  `item_name` varchar(255) NOT NULL;
  PRIMARY KEY (`id`)
ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `item` VALUES(1, 'Hello World.');
INSERT INTO `item` VALUES(2, 'Lets go!'); 

4.2 Distribuzione del modello 

Poi, dobbiamo creare un modello ItemModel.php nella directory models, contenente il seguente codice:

 <?php
class ItemModel extends Model
{
  /* Implementazione della logica di business */
}

Il contenuto del modello è vuoto. Poiché il modello Item eredita da Model, possiede tutte le funzionalità di Model.

4.3 Distribuzione del controller 

Crea un controller ItemController.php nella directory controllers, contenente il seguente codice:

 <?php
class ItemController extends Controller
{
  // Metodo首页,test framework custom DB query
  public function index()
  {
    $items = (new ItemModel)->selectAll();
    $this->assign('title', 'Tutti gli elementi');
    $this->assign('items', $items);
  }
  // Aggiungi il record, test framework DB record create (Create)
  public function add()
  {
    $data['item_name'] = $_POST['value'];
    $count = (new ItemModel)->add($data);
    $this->assign('title', 'Aggiunta con successo');
    $this->assign('count', $count);
  }
  // Visualizza il record, test framework DB record read (Read)
  public function view($id = null)
  {
    $item = (new ItemModel)->select($id);
    $this->assign('title', 'Visualizzazione in corso' . $item['item_name']);
    $this->assign('item', $item);
  }
  // Aggiorna il record, test framework DB record update (Update)
  public function update()
  {
    $data = array('id' => $_POST['id'], 'item_name' => $_POST['value']);
    $count = (new ItemModel)->update($data['id'], $data);
    $this->assign('title', 'Modifica avvenuta con successo');
    $this->assign('count', $count);
  }
  // Eliminazione dei record, test della funzionalità di eliminazione del framework DB (Delete)
  public function delete($id = null)
  {
    $count = (new ItemModel)->delete($id);
    $this->assign('title', 'Eliminazione avvenuta con successo');
    $this->assign('count', $count);
  }
}

4.4 Implementazione delle viste 

Creare due modelli di intestazione e piè di pagina header.php e footer.php nella directory views, con il seguente contenuto. 

header.php, contenuto:

 <html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title><?php echo $title ?></title>
  <style>
    .item {
      width:400px;
    }
    input {
      color:#222222;
      font-family:georgia,times;
      font-size:24px;
      font-weight:normal;
      line-height:1.2em;
      color:black;
    }
    a {
      color:blue;
      font-family:georgia,times;
      font-size:20px;
      font-weight:normal;
      line-height:1.2em;
      text-decoration:none;
     }
    a:hover {
      text-decoration:underline;
    }
    h1 {
      color:#000000;
      font-size:41px;
      letter-spacing:-2px;
      line-height:1em;
      font-family:helvetica,arial,sans-serif;
      border-bottom:1px dotted #cccccc;
    }
    h2 {
      color:#000000;
      font-size:34px;
      letter-spacing:-2px;
      line-height:1em;
      font-family:helvetica,arial,sans-serif;
    }
  </style>
</head>
<body>
  <h1><?php echo $title ?></h1>
footer.php, contenuto:
 </body>
</html>

Poi, crea i seguenti file di vista nella directory views/item. 

index.php, esplora tutti i record della tabella item nel database, contenuto:

 <form action="<?php echo APP_URL ?>/item/add" method="post">
  <input type="text" value="Clicca per aggiungere" onclick="this.value=''" name="value">
  <input type="submit" value="Aggiungi">
</form>
<br/><br/>
<?php $number = 0?>
<?php foreach ($items as $item): ?>
  <a class="big" href="<?php echo APP_URL ?>/item/view/<?php echo $item['id'] ?>" title="[#1#]">
    <span class="item">
      <?php echo ++$number ?>
      <?php echo $item['item_name'] ?>
    </span>
  </a>
  ----
  <a class="big" href="<?php echo APP_URL ?>/item/delete/<?php echo $item['id']?>">Elimina</a>
<br/>
<?php endforeach ?>

add.php, aggiungi registrazione, contenuto:
 <a class="big" href="<?php echo APP_URL ?>/item/index">Aggiunto con successo <?php echo $count ?> record, clicca per tornare</a> 

view.php, visualizza una registrazione singola, contenuto:

 <form action="<?php echo APP_URL ?>/item/update" method="post">
  <input type="text" name="value" value="<?php echo $item['item_name'] ?>">
  <input type="hidden" name="id" value="<?php echo $item['id'] ?>">
  <input type="submit" value="Modifica">
</form>
<a class="big" href="<?php echo APP_URL ?>/item/index">Torna indietro</a>

update.php, modifica i record, contenuto:
 <a class="big" href="<?php echo APP_URL ?>/item/index">Modificati <?php echo $count ?> elementi, clicca per tornare</a> 

delete.php, elimina i record, contenuto:
 <a href="<?php echo APP_URL ?>/item/index">Eliminati <?php echo $count ?> elementi, clicca per tornare</a> 

4.5 Test di applicazione 

In questo modo, visitando il programma todo nel browser: http://localhost/todo/item/index/, puoi vedere l'effetto. 

Tutto il codice è stato pubblicato su github, le parti chiave sono state commentate, l'indirizzo del repository:https://github.com/yeszao/fastphp, benvenuti a clonare e inviare contributi.

Per progettare un MVC migliore o utilizzare in modo più standardizzato, vederePrincipi di assegnazione delle responsabilità dell'architettura MVC .

Questo è tutto il contenuto dell'articolo, speriamo che sia utile per la tua apprendimento, e ti preghiamo di sostenere e fare eco della guida.

Dichiarazione: il contenuto di questo articolo è stato tratto da Internet, il copyright è dell'autore originale, il contenuto è stato contribuito autonomamente dagli utenti di Internet e caricato autonomamente, il sito web non detiene i diritti di proprietà, non è stato editato manualmente e non assume responsabilità legali correlate. Se trovi contenuti sospetti di violazione del copyright, ti preghiamo di inviare una e-mail a notice#oldtoolbag.com (sostituisci # con @) per segnalare il problema e fornire prove pertinenti. Una volta verificata, il sito eliminerà immediatamente il contenuto sospetto di violazione del copyright.

Ti potrebbe interessare