È un buon modo per associare l'URI alla class / metodo in PHP per MVC

Sono nuovo di MVC quindi questo è il mio primo tentativo e sono sicuro che voi ragazzi potete darmi dei miglioramenti su questo, grazie per eventuali suggerimenti o aiuto!

Di seguito è quello che ho trovato per un sistema router / dispatcher per il mio quadro personale su cui sto lavorando, è il mio primo tentativo di utilizzare il pattern MVC.

Il primo block di codice è solo il mio file .htaccess che indirizza tutte le richieste attraverso il mio file index.php.

Il secondo block di codice è il mio arrays di "Routes" che dirà all'object Router, quale class e metodo call oltre a qualsiasi ID o numero di paging se esistono.

Il terzo block di codice è la class del router.

Quarto block sta semplicemente facendo funzionare la class

Quindi la class del router deve usare la regex per far corrispondere l'URI con una rotta nella mappa del path, in teoria, questo suona come una ctriggers prestazione quando c'è un elenco di oltre 50 routes su cui la regex deve essere eseguita, dovrei fare questo diversamente? Il motivo principale per cui utilizzo la regex è quello di far corrispondere numbers di pagina e numbers di ID quando sono presenti nel path.

Inoltre, per favore, non dirmi semplicemente di usare un framework, lo sto facendo per impararlo meglio, imparo meglio in questo modo e preferisco semplicemente non usare un framework esistente in questo momento, ho studiato tutti i principali e alcuni less comuni quelli per le idee già.

1) Quindi, la domanda principale, fa qualcosa che non sembra giusto?

2) Esiste un modo migliore per rilevare cosa c'è nell'URI rispetto all'utilizzo della regex su un arrays come sto facendo, considerarlo su un sito ad alto traffico?

3) Dal momento che tutto viene instradato attraverso il file index.php con questo, come dovrei fare per gestire le richieste AJAX?

Scusate se questo è fonte di confusione, sono un po 'confuso!


.htaccess file

RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php?uri=$1 [NC,L,QSA] 

Array di mappe ()

 /** * Map URI to class/method and ID and Page numbers * Must be an arrays */ $uri_route_map = arrays( //forums 'forums/' => arrays( 'controller' => 'forums', 'method' => 'index', 'id_number' => '', 'page_number' => ''), 'forums/viewforum/(?<id_number>\d+)' => arrays( 'controller' => 'forums', 'method' => 'viewforum', 'id_number' => isset($id_number), 'page_number' => ''), 'forums/viewthread/(?<id_number>\d+)' => arrays( 'controller' => 'forums', 'method' => 'viewthread', 'id_number' => isset($id_number), 'page_number' => ''), 'forums/viewthread/(?<id_number>\d+)/page-(?<page_number>\d+)' => arrays( 'controller' => 'forums', 'method' => 'viewthread', 'id_number' => isset($id_number), 'page_number' => isset($page_number)), // user routes // account routes // blog routes // mail routes // various other routes ); 

Classe del router che legge e corrisponde all'arrays Map sopra

 /** * Run URI against our Map arrays to get class/method/id-page numbers */ class Router { private $_controller = ''; private $_method = ''; public $page_number = ''; public $id_number = ''; public function __construct($uri, arrays $uri_route_map) { foreach ($uri_route_map as $rUri => $rRoute) { if (preg_match("#^{$rUri}$#Ui", $uri, $uri_digits)) { //if page number and ID number in uri then set it locally $this->page_number = (isset($uri_digits['page_number']) ? $uri_digits['page_number'] : null); $this->id_number = (isset($uri_digits['id_number']) ? $uri_digits['id_number'] : null); $this->_controller = $rRoute['controller']; $this->_method = $rRoute['method']; // just for debug and testing while working on it / will be removed from final code echo '<hr> $page_number = ' . $this->page_number . '<br><br>'; echo '<hr> $id_number = ' . $this->id_number . '<br><br>'; echo '<hr> $controller = ' . $this->_controller . '<br><br>'; echo '<hr> $method = ' . $this->_method . '<br><br>'; break; }else{ $this->page_number = ''; $this->id_number = ''; $this->_controller = '404'; $this->_method = '404'; } } } public function getController() { return $this->_controller; } public function getMethod() { return $this->_method; } public function getPageNumber() { return $this->page_number; } public function getIDNumber() { return $this->id_number; } /** * Call our class and method from values in the URI */ public function dispatch() { if (file_exists('controller' . $this->_controller . '.php')) { include ('controller' . $this->_controller . '.php'); $controllerName = 'Controller' . $this->_controller; $controller = new $controllerName($this->getIDNumber(),$this->getPageNumber()); $method = $this->_method; if (method_exists($this->_controller, $this->_method)) { return $controller->$method(); } else { // method does not exist } } else { // Controller does not exist } } } 

Eseguirlo

 /** * Testing the class */ $uri = isset($_GET['uri']) ? $_GET['uri'] : null; $router = new Router($uri, $uri_route_map); $router->dispatch(); ?> 

1) Va bene a me. Il codice sembra un po 'disordinato però.

2) Sì, c'è un modo migliore. Stai facendo la regex perché vuoi abbinare parti dell'URL che non conosci. Perché non fare $parts = explode("/", $uri) quindi vedere se riesci a trovare la pagina che stai cercando? Dovrai definire quanti parametri ti aspetti per each pagina o non saprai se scegliere i forums con arrays("viewform", 123) parametri arrays("viewform", 123) o forums/viewforum con arrays(123) parametri arrays(123) .

explode sente molto meglio di una regex. Aggiunge anche il vantaggio di una migliore gestione degli errori. Cosa succede se l'argomento passato a viewforum non è un numero? Sicuramente puoi fare meglio di "404" 😉

3) Crea un gestore di ajax separato. Ajax è comunque nascosto alla vista, quindi non devi preoccuparti di fornire URL semantici.

Esempio:

 function find_route($parts) { foreach ($uri_route_map as $route => $route_data) { $route_check = implode("/", arrays_slice($parts, 0, count($parts) - $route_data['num_arguments'])); if ($route_check === $route) { return $route_data; } } throw new Exception("404?"); } $uri = "forum/viewforum/522"; $parts = explode("/", $uri); $route = find_route($parts); $arguments = arrays_slice($parts, count($parts) - $route['num_arguments']); $controller = $rRoute['controller']; $method = $rRoute['method']; $controller_instance = new $controller(); call_user_func_arrays(arrays($controller_instance, $method), $arguments); 

(non testato)

plugin

A causa di $ uri_route_map non puoi "dynamicmente" registrare più plugin o pagine o "routes". Aggiungerei una function per aggiungere più routes dynamicmente al Router .

Inoltre si potrebbe prendere in considerazione uno schema di auto-discovery che, ad esempio, controllerà i plugins/ cartella plugins/ per le cartelle con un file chiamato "manifest.php" che, quando chiamato, aggiungerà facoltativamente più route al Router.

1), 2) Non penso che sia una buona idea inserire id_number e page_number nel router, perché in futuro si possono incontrare molti altri parametri per l'url. Meglio usare il controller e il metodo e definire nel controller cosa fare con altri parametri o creare un'altra class Request che si occupa delle informazioni richieste.

3) Per ajax usa url come ajax / module / azione. E crea un controller ajax che esegua operazioni di sicurezza ajax di base, come il controllo su XSRF e poi decide quali controller eseguire e azioni da call.

1) e 2) Non dirò, non è giusto, ma perché non usare le rotte predefinite? Il più delle volte un path come

regolatore / azione / param1 / param2

è abbastanza buono per la maggior parte della tua pagina.

Probabilmente potresti fare qualcosa del genere per definire i routes predefiniti:

 $this->controller = 'index'; $this->action = 'index'; private function getDefaultRoutes() { $url = $_SERVER['REQUEST_URI']; $tabUrl = explode('/',$url); if(!empty($tabUrl)) { $this->controller = arrays_shift($tabUrl); $this->action = arrays_shift($tabUrl); $this->params = $tabUrl; } } 

E poi se hai bisogno di routes più specifici puoi definirli in un arrays o qualunque cosa tu voglia. Nel tuo router devi solo controllare se l'URI corrente corrisponde a routes specifici o routes predefiniti. Facendo ciò diminuirai il numero di rotte da abbinare e aumenterai la velocità del tuo router.

3) Il tuo router è probabilmente instanciato dal tuo indice, senza indice senza root, quindi sfortunatamente probabilmente non puoi evitare di usarlo. Questo è il motivo per cui è molto importnte evitare azioni costose nel tuo indice. In genere, non avviare la connessione al database nell'indice se tutte le pagine non ne hanno bisogno.

Inoltre, per favore, non dirmi semplicemente di usare un framework

Non dimenticare di scaricare alcuni famosi framework e guarda il loro codice. È il modo migliore per imparare. In tal modo, probabilmente troverai molte buone pratiche e risposte.

1) Quindi, la domanda principale è che qualcosa non sembra giusto?

Personalmente, vedo che questo diventa più complicato man mano che il tuo sito cresce. Un framework MVC, come mi è stato insegnato, dovrebbe essere praticamente "Impostalo e dimenticalo" – stai separando il gestore di richieste (controller) dall'interrogazione del database e dal business end (model) e dagli elementi di visualizzazione (vista).

[ NB: potresti aver bisogno di altri aspetti fondamentali. Il mio framework standard include alcuni elementi fondamentali che portno la session attraverso le varie parti, oltre a gestire gli aspetti fondamentali del lavoro del sito – Ad esempio, mentre i templates sono responsabili per effettuare le giuste chiamate al database come indicato dal controller, ci sono core funziona in un file sql.class.php che mi fornisce un insieme standardizzato di methods per effettuare tali chiamate e consegnare o memorizzare nella cache i risultati secondo necessità.]

Il tuo metodo di invio è sulla strada giusta con questo: stai estraendo dall'URI il nome del controller (Forum, Profili, ecc.). Hai bisogno di una mappa uri? Sento che stai creando una situazione non necessaria in cui devi aggiornare questa mappa each volta, piuttosto che semplicemente creare un nuovo controller quando hai bisogno di nuove funzionalità e registrarlo con il database. Non sto dicendo che ti sbagli di per sé, ma non credo che l'avrei fatto in quel modo.

2) Esiste un modo migliore per rilevare cosa c'è nell'URI rispetto all'utilizzo della regex su un arrays come sto facendo, considerarlo su un sito ad alto traffico?

Controlla il risultato (nessun gioco di parole, dal momento che è il controller che fa il lavoro qui). Considerare questo approccio e vedere come funziona per voi:

Il tuo file index.php (alias "Main Controller") cattura l'URI ed esplode i valori lungo "/" in bit. bit [0] è l'ID del controller – questo dice "Voglio usare il controller denominato bit [0] => valore". Questo è fatto come:

 require_once( dirname( __FILE__ )."/controllers/".$bit[0]."controller.php" ); 

Personalmente, essendo un po 'strano quando si tratta di strutture di directory, uso il bit [0] per identificare la directory in cui si trova controller.php, poiché potrei avere sub controller.

È questo file controller che uso per analizzare altri bit. Per questo, userò un esempio:

Supponiamo che il bit [0] abbia il valore "forum". Potrei passare, se è impostato, bit [1] a un'istruzione switch. Per impostazione predefinita, desidero sempre elencarlo, ma potrei specificarlo direttamente in "elenco", "visualizzazione" o "post" nel bit [1]. Questo mi dirà nella class controller quale metodo call. Il metodo mi dirà quindi di call il model "forum" associato se devo eseguire query e memorizzare nella cache l'elenco dei forum, ad esempio.

I "bit" estranei possono fare una di queste due cose: possono essere passati come semplici argomenti al metodo su quali dati richiedere dal model, o il bit [1] può essere abbastanza complesso da giustificare un sub controller, e il successivo i bit saranno passati a quel controller per determinare l'azione appropriata, come è stato fatto con il controller del forum.

Regex, essendo lento, dovrebbe essere evitato quando ansible. Dato che potremmo avere un URI di /forums/view/102305 possiamo presumere che il controller del forums trasmetterà 102305 al metodo associato all'argomento della view (il metodo è qualcosa come private function displayPost( $id ) where $id è 102305 ). Non è necessaria alcuna regex poiché possiamo semplicemente far esplodere i valori lungo un delimitatore comune previsto.

3) Dal momento che tutto viene instradato attraverso il file index.php con questo, come dovrei fare per gestire le richieste AJAX?

Non molto difficile. Se il controller è impostato su, ad esempio, AJAX, è ansible ribuild l'URL e accedervi direttamente. Puoi scrivere le esclusioni nel file .htaccess ( RewriteRule ^(AJAX)($|/) - [L] ). Oppure (non ideale, ma una soluzione subdolo) è quello di aggiungere ../ al tuo URI AJAX per riportre l'URI alla radice – non sta più tentando di accedere a index.php quindi la regola di riscrittura non si applica.

modificare

Supponiamo che stiamo usando un URI di /forums/id-1234/page-4 per il tuo esempio. Di nuovo, assumiamo come ho detto sopra che i forums riferiscono al controller da utilizzare e each altro / delimita argomenti (cosa che mi piace call "drill down"). Quindi, nel nostro file del controller del forum (chiamiamolo forumcontroller.php , potremmo avere qualcosa come questo (estremamente semplificato) constructor:

 // $registry is a class containing fundamental methods, and is meant to exemplify all // classs tied to the main controller "index.php". Keep in mind, I'm assuming we've // found the right controller by exploding the URI, and passed the remainder as bits // to the constructor. public function __construct( registry $registry ) { $this->registry = $registry; //tying this controller to main controller. // For ease and clarity, we're assuming there's no case in which you wouldn't have // bits set. Error checking is easy. $bits = $this->registry->getURLBits; switch( $bits[0] ) { case 'view': $this->showForumEntry( $bits[1], (isset( $bits[2] ) ? $bits[2] : '' ); break; case 'edit': $this->editForumEntry( $bits[1] ); break; case 'post': $this->postForumEntry(); break; default: $this->listForumEntries(); break; } } private function showForumEntry( $thread, $offset ) { // Because you wanted to prepend id to the id element, we can use this for // cheekiness in the query if our DB is well designed. $data = explode('-', $thread); // Select all from forums where id = 1234 $sql = "SELECT * FROM forums WHERE $data[0] = $data[1]"; if( $offset != '' ) { $page = explode('-', $offset); $offset = $page[1] * 25; // Or whatever your max per page is. Make it dynamic. $max = $offset+25; $sql .= " LIMIT $offset, $max"; } // You see where I'm going with this... } 

Il punto è che hai il controllo di ciò che viene passato e di come viene gestito. Controlla gli URI e puoi semplificare la loro elaborazione.

Modifica 2 Leggendo di nuovo, ci sono alcuni concetti che penso ti aiuteranno e che dovresti familiarizzare con:

Visualizza il model "Factory" qui (Il mio registro $ è, nel suo cuore, un insieme di fabbriche): http://php.net/manual/en/language.oop5.patterns.php

Una buona ripartizione di MVC graficamente: http://best-practice-software-engineering.ifs.tuwien.ac.at/patterns/images/mvc3.jpg

Maggiori informazioni sui methods di fabbrica: http://www.devshed.com/c/a/PHP/Design-Patterns-in-PHP-Factory-Method-and-Abstract-Factory/

Un'altra nota, e questa è un'osservazione personale dopo aver lavorato con Joomla, Drupal, WordPress e varie diverse soluzioni CMS e BBS aziendali: progettate esclusivamente pensando a voi. Quando inizi a cercare di diventare "qualcosa per tutti", ti ritroverai con un sacco di inutili gonfiamenti che vengono caricati con each pagina e utilizzati 1 volta su 100. MVC è un model di progettazione, e usarlo come un model ti dirà sbarazzarsi di eccesso in each aspetto, compresi gli URI. Elaborazione /controller/arg1-Identifier/arg2-offset non è necessario e si può facilmente uscire con /controller/id/offset (ad es. /forums/1234/4 ). Se vuoi renderlo SEO friendly, aggiungi il titolo del thread, non un tag che identifichi l'ID (ad esempio /forums/1234-This-Is-A-Topic/4 ).

Ora, segnaliamo anche l'ovvio riguardo alla mia precedente modifica: si tratta di un controller destinato esclusivamente all'elemento forum. Ogni elemento del tuo sito (es. Forum, gallerie, profili, ecc.) Dovrebbe avere il proprio controller. Perché? Perché ognuno sta facendo cose completamente diverse sulle sue pagine. Quindi, utilizzatelo: non è necessario utilizzare una mappa URI se si capisce che si sta dirigendo verso il controller e il controllore sta delegando responsabilità al model e ai sottoprogrammi eventualmente necessari.

Spero davvero che aiuti.

1) Funziona? Se sì, allora si. Poiché il codice sopra riportto contiene solo arrays, espressioni regolari e validation per questo, non penso che ci sia un problema con il tuo codice. Finché funziona Ma se chiedi, 'quel codice è scalabile?' quindi la risposta sarebbe varia, e tutto dipende dai tuoi scopi MVC Framework (ad esempio, è quella struttura per usi generali, ad esempio: blog, o il suo particolare per il provider API REST. E così via …)

2) Sì. Kohana, Zend, CI e altri popolari (e altamente ottimizzati) framework PHP usano (arrays + regex sul router).

3) Penso che potresti dargli una bandiera nel block / sezione del path e rendere questo flag disponibile come variabile globale. In questo modo, nel controller, è ansible decidere quale risposta submit per un tipo di richiesta diverso (ajax / non-ajax) controllando tale flag (ad esempio, è ansible fornire $this->is_ajax come metodo globale disponibile in ambito Controller).

Se posso aggiungere un paio di punti:

  • Rimuovere id_number e id_number dal router – basta passare tutto ciò che è stato abbinato a un controller, dopotutto, è un lavoro di controller per elaborare quei dati, non il router

  • Non passare $uri a un constructor, passarlo invece a un dispatch() .

  • Perché quei isset() -s in $uri_route_map ? Ovviamente sarebbero false , poiché $uri_route_map è definita prima che l'object Router() sia istanziato.

  • Suggerirei di aggiungere più logica alla routine di corrispondenza: nel tuo caso corrente sitename/forums non corrisponderà a nulla risultante in 404 (nessuna barra finale)

  • Puoi anche definire i parametri di default nella tua $uri_route_map , e quindi arrays_merge con parametri abbinati. Quindi, ad esempio, quando non viene specificato alcun numero di pagina page_number sarà uguale a 1

  • Se sei preoccupato per la perfomance sul sito web ad alto traffico, puoi memorizzare i routes. Dopo tutto, forums/viewforum/100 punta sempre allo stesso controller / metodo.

E perché sei preoccupato di submit richieste AJAX al tuo file index.php? Qual è il problema con quello?