web-dev-qa-db-fra.com

Comment fonctionne le routage MVC?

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.

21
Madara's Ghost

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 Dispatcherclass 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
_
29
w00

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;
  }
2
JvdBerg