2013-05-20 5 views
5

Mi rendo conto che questo argomento è stato chiesto e affrontato ripetutamente e, nonostante abbia letto innumerevoli domande simili e abbia letto innumerevoli articoli, non riesco ancora a cogliere alcuni problemi chiave. Sto cercando di costruire il mio framework MVC per scopi di apprendimento e per familiarizzare meglio con OOP. Questo è per uso privato personale, non per implicare che sia una scusa per essere pigro, ma piuttosto non sono così preoccupato di avere tutti i suoni e le campane di quadri più robusti.Comprensione/miglioramento di un framework MVC barebone

La mia struttura di directory è la seguente:

public 
- index.php 
private 
- framework 
    - controllers 
    - models 
    - views 
    - FrontController.php 
    - ModelFactory.php 
    - Router.php 
    - View.php 
- bootstrap.php 

Ho un file .htaccess con indirizza tutte le richieste di index.php, questo file contiene le impostazioni di configurazione di base, come fuso orario e globali costanti, e poi carichi il file bootstrap.php. Il bootstrap contiene il mio autoloader per le classi, avvia la sessione, definisce le funzioni globali da utilizzare nel mio progetto e quindi chiama il router. Il router seleziona la richiesta dall'URL, la convalida tramite ReflectionClass ed esegue la richiesta nel formato example.com/controller/method/params.

Tutti i miei regolatori di estendere il FrontController.php:

<?php 
namespace framework; 

class FrontController 
{ 
    public $model; 
    public $view; 
    public $data = []; 

    function __construct() 
    { 
     $this->model = new ModelFactory(); 
     $this->view = new View(); 
    } 

    // validate user input 
    public function validate() {} 

    // determines whether or not a form is being submitted 
    public function formSubmit() {} 

    // check $_SESSION for preserved input errors 
    public function formError() {} 
} 

Questo front controller carica il ModelFactory:

<?php 
namespace framework; 

class ModelFactory 
{ 
    private $db  = null; 
    private $host  = 'localhost'; 
    private $username = 'dev'; 
    private $password = '********'; 
    private $database = 'test'; 

    // connect to database 
    public function connect() {} 

    // instantiate a model with an optional database connection 
    public function build($model, $database = false) {} 
} 

e base Vista:

<?php 
namespace framework; 

class View 
{ 
    public function load($view, array $data = []) 
    { 
     // calls sanitize method for output 
     // loads header, view, and footer 
    } 

    // sanitize output 
    public function sanitize($output) {} 

    // outputs a success message or list of errors 
    // returns an array of failed input fields 
    public function formStatus() {} 
} 

Infine, ecco un controller di esempio per dimostrare come viene elaborata una richiesta attualmente:

<?php 
namespace framework\controllers; 

use framework\FrontController, 
    framework\Router; 

class IndexController extends FrontController implements InterfaceController 
{ 
    public function contact() 
    { 
     // process form if submitted 
     if ($this->formSubmit()) { 
      // validate input 
      $name = isset($_POST['name']) && $this->validate($_POST['name'], 'raw') ? $_POST['name'] : null; 
      $email = isset($_POST['email']) && $this->validate($_POST['email'], 'email') ? $_POST['email'] : null; 
      $comments = isset($_POST['comments']) && $this->validate($_POST['comments'], 'raw') ? $_POST['comments'] : null; 

      // proceed if required fields were validated 
      if (isset($name, $email, $comments)) { 
       // send message 
       $mail = $this->model->build('mail'); 
       $to = WEBMASTER; 
       $from = $email; 
       $subject = $_SERVER['SERVER_NAME'] . ' - Contact Form'; 
       $body = $comments . '<br /><br />' . "\r\n\r\n"; 
       $body .= '-' . $name; 

       if ($mail->send($to, $from, $subject, $body)) { 
        // status update 
        $_SESSION['success'] = 'Your message was sent successfully.'; 
       } 
      } else { 
       // preserve input 
       $_SESSION['preserve'] = $_POST; 

       // highlight errors 
       if (!isset($name)) { 
        $_SESSION['failed']['name'] = 'Please enter your name.'; 
       } 
       if (!isset($email)) { 
        $_SESSION['failed']['email'] = 'Please enter a valid e-mail address.'; 
       } 
       if (!isset($comments)) { 
        $_SESSION['failed']['comments'] = 'Please enter your comments.'; 
       } 
      } 
      Router::redirect('contact'); 
     } 

     // check for preserved input 
     $this->data = $this->formError(); 

     $this->view->load('contact', $this->data); 
    } 
} 

Da quello che sono in grado di capire, la mia logica è fuori per i seguenti motivi:

  • di convalida dovrebbe essere fatto nel modello, non il controller. Tuttavia, un modello non dovrebbe avere accesso alle variabili $ _POST, quindi non sono completamente sicuro se sto facendo questa parte correttamente o no? Mi sembra che questo è quello che chiamano un "controller grasso" che è male, ma non sono sicuro di cosa debba cambiare ...
  • Il controller non deve inviare dati alla vista; la vista dovrebbe invece avere accesso al modello per richiedere i propri dati. Quindi spostare la proprietà $data da FrontController e in ModelFactory e chiamare la vista dal controller senza passare i dati risolve questo problema? Tecnicamente, quindi, aderirebbe al diagramma di flusso MVC, ma la soluzione proposta sembra un dettaglio così insignificante o addirittura banale, assumendo che sia così semplice, che probabilmente non lo è.
  • La parte che mi ha messo in dubbio per tutta la mia implementazione è che ho un oggetto Utente che viene istanziato con ruoli e permessi degli utenti corrispondenti, e ho cercato di capire come o più specificamente dove creare un metodo isAllowed() che può essere chiamato sia dal Controller che dalla Vista. Avrebbe senso quindi inserire questo metodo nel Modello, poiché sia ​​il Controller che la Vista dovrebbero avere accesso al Modello?

In generale, sono sulla buona strada qui o quali problemi evidenti devo affrontare per andare sulla giusta strada? Sto davvero sperando in una risposta personale specifica ai miei esempi piuttosto che in un "go read this" .. Apprezzo qualsiasi feedback e aiuto sincero.

+1

Questa domanda sembra essere off-topic perché avrebbe dovuto essere in http://codereview.stackexchange.com –

risposta

0

La convalida deve essere eseguita nel modello, non nel controller. Tuttavia, un modello non dovrebbe avere accesso alle variabili $ _POST, quindi non sono del tutto sicuro se sto facendo questa parte o no correttamente? Mi sento come se questo è quello che chiamano un "regolatore di grasso", che è male, ma non sono sicuro di quello che deve cambiare ...

che è giusto, modello dovrebbe sapere nulla della richiesta, quindi, devi passare $ _POST ai modelli, ma non saprà che sono i parametri di richiesta.

Una cosa: la convalida che non ha nulla a che fare con la logica aziendale dovrebbe rimanere nel controller. Supponiamo che tu crei un token CSRF per i tuoi moduli per motivi di sicurezza, questo valore dovrebbe trovarsi all'interno del controller, perché gestisce le richieste.

Il controllore non deve inviare dati alla vista; la vista dovrebbe invece avere accesso al modello per richiedere i propri dati. Quindi spostare la proprietà $ data da FrontController e in ModelFactory e quindi chiamare la vista dal controller senza passare i dati risolve questo problema? Tecnicamente sarebbe poi aderire al diagramma di flusso MVC, ma la soluzione proposta sembra un tale dettaglio insignificante o addirittura banale, ammesso che sia così semplice, che probabilmente non è ..

Questo non è necessariamente vero. Questo approccio è chiamato Active Model e normalmente si utilizza il modello Observer, con i modelli osservati dalle viste. Se qualche modello cambia, notifica la vista che si aggiornerà da sé. Questo approccio è più adatto per le applicazioni desktop, non per quelle basate sul web. Nelle applicazioni Web, il più comune è disporre di controller come intermediari tra modello e vista (modello passivo ). Non c'è un giusto approccio, dovresti scegliere quello che ti piace di più.

La parte che mi ha in discussione tutta la mia implementazione è che ho un oggetto d'uso che viene creata un'istanza con gli utenti corrispondenti ruoli e permessi, e ho cercato di capire come o più specificamente dove creare un Metodo isAllowed() che può essere chiamato sia da Controller che da View. Avrebbe senso quindi inserire questo metodo nel Modello, poiché sia ​​il Controller che la Vista dovrebbero avere accesso al Modello?

Bene, questo non c'è modo, dovrò dirvi di leggere su ACL.

+0

i controllori non sono responsabili per la convalida né per il controllo di accesso. –

+0

Non ho detto che fossero responsabili per il controllo degli accessi, ma è in realtà responsabile della convalida che ha a che fare con la richiesta stessa, ma non con la logica. –

+0

@Downvoter, commento mentale? –

1
  • I $_POST superglobals dovrebbero essere astratta da un'istanza richiesta, come spiegato in this post.

  • La convalida dell'input non è responsabilità dei controllori. Invece dovrebbe essere maniglie da domain objects all'interno del livello del modello.

  • La fabbrica modello non è un modello.

  • La definizione della visibilità del parametro di classe come public interrompe l'incapsulamento dell'oggetto.

  • L'intestazione della posizione HTTP (reindirizzamento) è una forma di risposta. Pertanto, dovrebbe essere gestito dall'istanza della vista.

  • Nella sua forma attuale, i controller controllano direttamente i superglobali. Ciò causa un accoppiamento stretto con lo stato globale.

  • I controlli di autorizzazione devono essere eseguiti outside controller. Non dentro di esso.

  • La "fabbrica modello" deve essere invece una fabbrica di servizi, che viene iniettata sia in controller che in vista. Garantire che ogni servizio venga istanziato una sola volta e quindi consentire ai controller di lavorare con lo stesso stato del livello del modello.

+0

Noto che sei sempre stato tra i primi a rispondere alle domande contrassegnate da "MVC", e non ho dubbi che tu abbia capito molto bene il modello. Finché ho lavorato su questo, mi verrebbe davvero l'idea di sprofondare in un altro guaio indovinando. Non credo che potrei convincerti ad aggiornare gli esempi forniti nella mia domanda per mostrarmi come dovrebbe essere fatto? –

1

In primo luogo, penso che sia bello che si stia tentando di creare il proprio framework. Molti dicono che tutti dovrebbero farlo, se non altro per scopi di apprendimento.

In secondo luogo, suggerirei di leggere questo Wikipedia article sui framework. Molte persone non si rendono conto che esistono diversi modelli per il routing (invio di url, attraversamento) e visualizzazioni (push, pull).

Personalmente, non vedo la necessità di astrarre i super globali poiché sono già astrazioni (da php) dall'input raw (php: // input) e possono essere modificati. Solo la mia opinione.

Hai ragione che la convalida deve essere fatta dal Modello. Non convalidi i moduli, convalidi i dati. Per quanto riguarda la visualizzazione dell'accesso ai dati, ciò dipende dal modello scelto. È possibile inviare i dati alla vista oppure la vista può estrarre i dati.

Se siete curiosi, il mio tentativo di MVC basic framework è in github. I suoi 4 file, meno 2K di codice (il livello del DB è 1K). Implementa il routing traversale (componente) e recupera i dati, c'erano già molti framework che implementano i pattern alternativi.

Problemi correlati