J'ai donc commencé à étudier un peu plus en profondeur MVC (vrai MVC, pas framework MVC) et j'essaye de développer un petit framework. Je travaille en lisant d'autres frameworks tels que Symphony et Zend, en voyant comment ils font leur travail et en essayant de l'implémenter moi-même.
L'endroit où je suis resté coincé était le système de routage d'URL:
<?php
namespace Application\Common;
class RouteBuilder {
public function create($name, $parameters) {
$route = new Route($name);
$route->resource = array_keys($parameters)[0];
$route->defaults = $parameters["defaults"];
$notation = $parameters["notation"];
$notation = preg_replace("/\[(.*)\]/", "(:?$1)?", $notation);
foreach ($parameters["conditions"] as $param => $condition) {
$notation = \str_replace($param, $condition, $notation);
}
$notation = preg_replace("/:([a-z]+)/i", "(?P<$1>[^/.,;?\n]+)", $notation);
//@TODO: Continue pattern replacement!!
}
}
/* How a single entry looks like
* "main": {
"notation": "/:action",
"defaults": {
"resource" : "Authentication",
},
"conditions": {
":action" : "(login)|(register)"
}
},
*/
Je n'arrive tout simplement pas à m'enrouler correctement la tête. Quel est le flux de travail de l'application à partir d'ici?
Le motif est généré, probablement un objet Route
à conserver sous l'objet Request
ou quelque chose, alors quoi? Comment ça marche?
P.S. Vous cherchez une réponse réelle et bien expliquée ici. Je veux vraiment comprendre le sujet. J'apprécierais que quelqu'un prenne le temps d'écrire une vraie réponse élaborée.
Une classe MVC Router
(qui fait partie d'un plus large Front Controller) décompose l'URL d'une requête HTTP, en particulier le composant de chemin (et potentiellement la chaîne de requête).
Router
tente de faire correspondre la ou les deux premières parties du composant de chemin à une combinaison d'itinéraire correspondante (Controller
/Action [ method], ou juste un Controller
qui exécute une action par défaut ( méthode).
Une action, ou commande, est simplement une méthode hors d'un Controller
spécifique.
Il y a généralement un _abstract Controller
_ et de nombreux enfants de Controller
, un pour chaque page Web (en général).
Certains pourraient dire que Router
transmet également des arguments à la méthode Controller
désirée, s'il y en a dans l'URL.
Note : Les puristes de la programmation orientée objet, suivant le principe de responsabilité unique, pourraient affirmer que routage composants d'une URL et dispatching une classe Controller
sont deux responsabilités distinctes. Dans ce cas, une Dispatcher
class instanciera en fait la Controller
et transmettra à l'une de ses méthodes tous les arguments dérivés de la requête HTTP du client.
Exemple 1: Controller
, mais pas d'action ni d'arguments.
_http://localhost/contact
_ Sur une requête GET, cela peut afficher un formulaire.
Contrôleur = Contrat
Action = Par défaut (généralement une méthode index()
)
=======================
Exemple 2: Controller
et action, mais pas d'arguments.
_http://localhost/contact/send
_ Sur une requête POST), cela peut déclencher la validation côté serveur et tenter d'envoyer un message.
Contrôleur = Contrat
Action = envoyer
=======================
Exemple 3: Controller
, action et arguments.
_http://localhost/contact/send/sync
_ Sur une requête POST), cela peut déclencher la validation côté serveur et tenter d'envoyer un message. Cependant, dans ce cas, JavaScript n'est peut-être pas actif. Ainsi, pour prendre en charge dégradation gracieuse, vous pouvez dire au ContactController
d'utiliser un View
qui prend en charge le rafraîchissement d'écran et répond avec un en-tête HTTP de _Content-Type: text/html
_, au lieu de _Content-Type: application/json
_. sync
serait passé comme argument à ContactConroller::send()
. Notez que mon exemple de sync
était totalement arbitraire et inventé, mais je pensais que cela convenait!
Contrôleur = Contrat
Action = envoyer
Arguments = _[sync]
_ // Oui, passez des arguments dans un tableau!
Une classe Router
instancie l'enfant concret demandé Controller
, appelle la méthode demandée à partir de instance de contrôleur, et passe la méthode du contrôleur son arguments (le cas échéant).
1) Votre classe Router
doit d'abord vérifier s'il existe un Controller
concret qu'elle peut instancier (en utilisant le nom tel qu'il se trouve dans l'URL, plus le mot "contrôleur"). Si le contrôleur est trouvé, test de la présence de la méthode demandée (action).
2) Si le Router
ne peut pas trouver et charger le nécessaire PHP à l'exécution (en utilisant un - autoloader est conseillé) pour instancier un enfant Controller
concret, il devrait alors vérifier un tableau (généralement trouvé dans un autre nom de classe Route
) pour voir si l'URL demandée - correspond , en utilisant des expressions régulières, l'un des éléments contenus à l'intérieur. Un squelette de base d'une classe Route
suit.
Remarque: _.*?
_ = zéro, ou plus, de tout caractère, non capturant.
_class Route
{
private $routes = [
['url' => 'nieuws/economie/.*?', // regular expression.
'controller' => 'news',
'action' => 'economie'],
['url' => 'weerbericht/locatie/.*?', // regular expression.
'controller' => 'weather',
'action' => 'location']
];
public function __contstruct()
{
}
public function getRoutes()
{
return $this->routes;
}
}
_
Pourquoi utiliser une expression régulière? Il est peu probable que vous obteniez une correspondance fiable pour les données après la deuxième barre oblique dans l'URL.
_/controller/method/param1/param2/...
_, où param [x] pourrait être n'importe quoi!
Avertissement: Il est recommandé de modifier le délimiteur de modèle d'expression régulière par défaut ('/') lorsque les données de ciblage contiennent le délimiteur de modèle (dans ce cas, des barres obliques "/". Presque tout caractère d'URL non valide serait un excellent choix.
Une méthode de la classe Router
effectuera une itération sur le tableau _Route::routes
_ pour voir s'il existe une correspondance d'expression régulière entre l'URL cible et la string
valeur associé à un index de 2e niveau url
. Si une correspondance est trouvée, le Router
sait alors quel Controller
concret instancier et la méthode suivante à appeler. Les arguments seront transmis à la méthode si nécessaire.
Méfiez-vous toujours des cas Edge, tels que les URL représentant les éléments suivants.
_`/` // Should take you to the home page / HomeController by default
`''` // Should take you to the home page / HomeController by default
`/gibberish&^&*^&*%#&(*$%&*#` // Reject
_
La classe de routeur, de mon cadre. Le code raconte l'histoire:
class Router
{
const default_action = 'index';
const default_controller = 'index';
protected $request = array();
public function __construct( $url )
{
$this->SetRoute( $url ? $url : self::default_controller );
}
/*
* The magic gets transforms $router->action into $router->GetAction();
*/
public function __get( $name )
{
if( method_exists( $this, 'Get' . $name ))
return $this->{'Get' . $name}();
else
return null;
}
public function SetRoute( $route )
{
$route = rtrim( $route, '/' );
$this->request = explode( '/', $route );
}
private function GetAction()
{
if( isset( $this->request[1] ))
return $this->request[1];
else
return self::default_action;
}
private function GetParams()
{
if( count( $this->request ) > 2 )
return array_slice ( $this->request, 2 );
else
return array();
}
private function GetPost()
{
return $_SERVER['REQUEST_METHOD'] == 'POST';
}
private function GetController()
{
if( isset( $this->request[0] ))
return $this->request[0];
else
return self::default_controller;
}
private function GetRequest()
{
return $this->request;
}