Parcourir ce qu'il y a dans une closure (function anonyme)

Le problème exposé dans ce sujet a été résolu.

Bonjour à tous, je viens demander de l'aide Svp, car je galère à finir mon router pour un frameworks MVC maison.

Mon router fonctionne avec 2 classes (mais je vais ajouter une 3è classe pour les routeGroup) : une classe "Router", et une classe "Route". Elles font entre 150 et 200 lignes de code chaqune, donc je ne vais peut être pas balancer tout le code ici. Je pense que c'est surtout une aide théorique que j'ai besoin, après pour le codage, j'essayerai de me débrouiller tout seul.

Actuellement mon router fonctionne pour des routes comme ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
$router->get(
    '/contact',
    'App\Controllers\Pages\ContactController@getContact',
    'contact'
);

$router->post(
    '/contact',
    'App\Controllers\Pages\ContactController@postContact'
);

Mais j'aimerai bien ajouter des routesGroup. Par exemple pour les routes de mon admin, j'aimerai remplacer ceci:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
$router->get(
    '/admin/edit/{id}',
    'App\Controllers\Admin\ArticleController@edit',
    'admin_article_edit'
);

$router->post(
    '/admin/edit/{id}',
    'App\Controllers\Admin\ArticleController@update'
);

Par ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
$router->group('/admin', function() {
    $router->get(
        '/edit/{id}',
        'App\Controllers\Admin\ArticleController@edit',
        'admin_article_edit'
    );

    $router->post(
        '/edit/{id}',
        'App\Controllers\Admin\ArticleController@update'
    );
});

Mais je galère.

Donc dans ma classe Router, j'ai commencé par ajouter cette function :

1
2
3
4
5
6
<?php
public function group($path, $callable)
{
    $group = new RouteGroup($path, $callable);
    $this->routeGroups[] = $group;
}

Et j'ai donc créé une 3ème classe "RouteGroupe", qui pour le moment contient ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
/**
* Fonctionne avec la classe Router
* Permet de représenter un routeGroup
*/
class RouteGroup
{
    private $path;          // chemin du group (préfix avant les routes)
    private $callable;      // closure avec function anonyme

    public function __construct($path, $callable) {
        $this->path = trim($path, '/');
        $this->callable = $callable;

        var_dump($this->path);
        var_dump($this->callable);
    }

}  // END class

Et je ne sais pas trop quoi faire ensuite. Les var_dump m'affichent ceci:

1
2
3
4
<?php
_ var_dump($this->path) : C:\wamp64\www\MVC\core\Routing\RouteGroup.php:23:string 'bbb' (length=3)
_ var_dump($this->callable) : C:\wamp64\www\MVC\core\Routing\RouteGroup.php:24:
object(Closure)[4]

Je pense que la 1ère étape, serai que je parcours les routes qui sont dans ma function anonnye de group(), (donc ce qu'il y a dans $this->callable). Comment puije-faire ceci SVP ?

Merci beaucoup.

+0 -0

Salut, à mon avis, y a un problème dans l'architecture générale. Personnellement, ton router devrait construire une liste des routes, et cette construction devrait se produire dans tes méthodes get et post. Donc si tu fais comme ça, tu n'as pas besoin de savoir ce que contient ton callable, tu as juste à l'exécuter dans le router en enregistrant quelque part le prefix de ton routergroup, et après l'appel, tu retires ce prefix…

Moi je comprends le fonctionnement de ta classe router, dis moi si je me trompe ?

Merci pour ta réponse. Voici ma classe Router :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
<?php

namespace Routing;

use Config\Config;
use Errors\Error;
use Classes\Response;

/**
* Router de l'appliction
* Fonctionne avec class Route
*/
class Router
{
    private static $instance;

    private $url;               // si $_GET['url'] existe, prendra sa valeur
    private $routes = [];       // toute les routes
    private $namedRoute = [];   // pour si la route est nommée



    /**
    * Singleton
    */
    public static function getInstance()
    {
        if (self::$instance === null) {
            self::$instance = new Router();
        }

        return self::$instance;
    }


    /**
    * private - car n'est pas autorisé à etre appelée de l'extérieur
    */
    private function __construct()
    {
        $this->urlGet();
    }


    /**
    * Verif si il y a bien un $_GET['url'], si non ça veut dire que c'est la page d'accueil
    * Si on trouve ?url= dans l'url, alors on fait une redirection 301 vers le contenu de la variable GET
    */
    private function urlGet()
    {
        if (isset($_GET['url'])) {
            $this->url = $_GET['url'];

            if (strpos($_SERVER['REQUEST_URI'], '?url=') !== false) {      
                Response::redirect301($this->url);
            }
        } else {
            $this->url = '/';
        }
    }


    /**
    * Add GET route
    */
    public function get($path, $callable, $name=null)
    {
        return $this->add($path, $callable, 'GET', $name);
    }

    /**
    * Add POST route
    */
    public function post($path, $callable, $name=null)
    {
        return $this->add($path, $callable, 'POST', $name);
    }

    /**
    * Add PUT route
    */
    public function put($path, $callable, $name=null)
    {
        return $this->add($path, $callable, 'PUT', $name);
    }

    /**
    * Add PATCH route
    */
    public function patch($path, $callable, $name=null)
    {
        return $this->add($path, $callable, 'PATCH', $name);
    }

    /**
    * Add DELETE route
    */
    public function delete($path, $callable, $name=null)
    {
        return $this->add($path, $callable, 'DELETE', $name);
    }

    /**
    * Add OPTIONS route
    */
    public function options($path, $callable, $name=null)
    {
        return $this->add($path, $callable, 'OPTIONS', $name);
    }

    /**
    * Add HEAD route
    */
    public function head($path, $callable, $name=null)
    {
        return $this->add($path, $callable, 'HEAD', $name);
    }


    /**
    * Ajouter une route (avec get() ou post()...)
    * @param $path - chemin d'url
    * @param $callable - controller@method, ou closure avec function anonyme
    * @param $method - Method en GET ou en POST
    * @param optional $name - Pour éventuellement nommer la route
    */
    private function add($path, $callable, $method, $name)
    {
        $route = new Route($path, $callable);
        $this->routes[$method][] = $route;

        if ($name) {
            $this->namedRoute[$name] = $route;
        }

        return $route;  // pour que si dans routes on fait un ->with(param, [regex]) - faire appelle à Route
    }


    /**
    * Executer rooting
    */
    public function run()
    {
        if (!isset($this->routes[$_SERVER['REQUEST_METHOD']])) {
            $this->ifException('REQUEST_METHOD does not exist');
        }

        // parcourir ensemble des routes
        foreach ($this->routes[$_SERVER['REQUEST_METHOD']] as $route) {
            if ($route->match($this->url)) {
                return $route->call();   // si ça match -> appeler callable
            }
        }

        // si URL correspond à aucune route
        $this->ifException('No matching routes for URL "'.$this->url.'"');
    }


    /**
    * Afficher url avec le nom d'un route
    */
    public function url($name, $params=[])
    {
        if (!isset($this->namedRoute[$name])) {
            // si aucune route correspond à ce nom
            $this->ifException('No route have this name "'.$name.'"');
        }

        return $this->namedRoute[$name]->getUrl($params);
    }

}   // END class

Oui donc comme je le pensais, tu as un tableau de route.

Dans ce cas pourquoi veux tu parcourir ta closure, il te suffit de l'executer ? Pour le prefixe, moi je mettrais une variable dans ton router que tu updates avant l'appel de ta fonction et tu le retires (Attention si tu souhaite gerer des imbrications de plusieurs routergroup, il faut alors gerer la concatenation de prefix ou avec un tableau) après l'appel.

Dis moi si tu n'as pas compris ?

Merci pour ta réponse, oui je veux aussi pouvoir géré l'imbrication de plusieurs routesGroup comme ceci par exemple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
$router->group('/admin', function() {
    $router->group('/article', function() {
        $router->get(
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@edit',
            'admin_article_edit'
        );

        $router->post(
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@update'
        );
    });
});

J'avoue, je comprend pas trop, c'est peut etre un peu trop poussé pour moi. D'après toit, je n'est pas besoin de créer une 3è class "RouteGroup" ? Je peut y faire dans ma class "Router". Merci

+0 -0

Je suis pas sûr de comprendre l'intérêt de la syntaxe que tu proposes pour les groupes. Pourquoi pas une syntaxe de ce style :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
$router->group('/admin', [
    '/article' => [
        'get' => [
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@edit',
            'admin_article_edit'
        ],

        'post' => [
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@update'
        ],
    ],
});
+0 -0

Pour moi tu peux completement faire sans troisieme classe.

Dans le code au dessus, tu appelles ta method group. Moi dans cette methode, je concaternerais un \$prefix ("/admin", "/article"…) a une variable membre qui gère le prefix de tes url (vide par defaut), puis j'apelle ton $callable, la il faut modifier ta methode add pour prendre en compte le prefix si la variable n'est pas vide. puis une fois l'appel de ton \$callable terminé, tu retires de ta variable membre qui gère le prefix, le prefix qui avait ete passé en parametre de ta methode ("/admin", "/article"…)

1
2
3
4
5
6
7
<?php
    public function group($prefix, $callable)
    {
        $m_prefix .= $prefix;
        $callable();
        $m_prefix = substr($m_prefix, 0, strlen($m_prefix) - strlen($prefix));
    }

Un exemple vite fait, ca fait longtemps que j'ai pas refait de php, meme pas sur que les noms de fonction soit les bons ;)

Victor a raison, c'est generalement plutot une syntaxe a base de tableaux que l'on voit dans les frameworks, ca simplifie un peu l'ecriture et le principe est identique à la difference que tu n'ajoutes pas les prefix de ta route mais tu les verifies au moment de la requete

+0 -0

Merci beaucoup! Je viens de tester, et c'est effectivement fonctionnel. Je m'attendais à un truc beaucoup + compliqué que ceci. Si je pensais être obligé de créer une 3è classe "RouteGroup", c'est parce-que j'avais fouillé dans le code source du framework Slim 3, et eux procèdent ainsi.

Par contre je ne comprend pas trop pourquoi faire ceci ? :

1
2
<?php
$m_prefix = substr($m_prefix, 0, strlen($m_prefix) - strlen($prefix));

Je pense que :

1
2
<?php
$m_prefix = NULL;

Suffit, avec un peut moins de code. Ce qui me fait donc ceci à ajouter dans ma classe Router :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php
/**
* Pour éventuellement ajouter des routeGroup (prefix)
*/
public function group($prefix, $callable)
{
    $this->prefix .= $prefix;

    $callable();

    $this->prefix = null;
}

Gràce à cette function, je peut maintenant imbriquer plusieurs routeGroups comme ceci:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
Route::group('/admin', function() {
    Route::group('/article', function() {
        Route::get(
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@edit',
            'admin_article_edit'
        );

        Route::post(
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@update'
        );
    });
});

Merci.

PS: Par contre, je suis maintenant "obligé" par un système de Façade

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php

namespace Facades;

use Routing\Router;

/**
* Facade pour les routes du Router
*/
class Route
{
    private static $router;

    /**
    * @param $method - Nom de la method à appeler
    * @param array $arguments - Paramètres dans method
    */
    public static function __callStatic($method, array $arguments)
    {
        if (self::$router === null) {
            self::$router = new Router();
        }

        return call_user_func_array([self::$router, $method], $arguments);
    }

}

Si non, ça va m'obliger à chaque fois de mettre ma variable "router" en global…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
$router->group('/admin', function() {
    global $router;
    $router->group('/article', function() {
        global $router;
        $router->get(
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@edit',
            'admin_article_edit'
        );

        $router->post(
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@update'
        );
    });
});

J'espère juste que le système de Façade va pas trop me faire perdre en performance par rapport au Singleton que j'utilisais… Car si il y a 300 routes dans mon application, ça ba appeler 300 fois ma Façade… Mais Laravel 5 utilise ce système, donc ça devrais pas être trop gênant ?

+0 -0

Niveau performance je peux pas trop te dire. la solution a base de tableazu sera beaucoup moins gourmande problablement.

Pour le substr, cela me semble necessaire car lorsque tu imbriques tes deux groups: - tu ajoute d'abord /admin (prefix vaut "/admin"), - puis tu apelles ton deuxieme groupe, - tu ajoutes /article (prefix vaut "/admin/article"), - tu ajoute tes deux routes - et la, toi tu supprimes completement ton prefix.

Ce système ne fonctionnera pas si ta première closure contient l'appel a une methode group suivi de l'ajout d'une route, cette route ne sera alors pas prefixé comme elle le devrait puisque que situé dans ton premier groupe.

Essaie ca pour t'en convaincre:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
$router->group('/admin', function() {
    global $router;
    $router->group('/article', function() {
        global $router;
        $router->get(
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@edit',
            'admin_article_edit'
        );

        $router->post(
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@update'
        );
    });
    $router->post(
        '/list',
        'App\Controllers\Admin\ArticleController@list'
    );
});

En remettant le prefix a null, tu auras une url /list au lieu de /admin/list

J'ai pas compris ton problème de façade (interface ?), il est evident que ton router doit etre un singleton.

+0 -0

Ok merci. Oui je vais remettre le substr. J'ai fais quelques var_dump histoire de bien m'en rendre compte.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php
/**
* Pour éventuellement ajouter des routeGroups (prefixs)
*/
public function group($prefix, $callable)
{
    $this->prefix .= $prefix;

    $callable();

    $this->prefix = substr($this->prefix, 0, strlen($this->prefix) - strlen($prefix));
}

Ma façade n'est pas une interface. Facade ou Singleton, Dans les 2 cas le Router est instancié qu'une seule fois à chaque rafraichissement de page.

Le "désavantage" de la Façade, c'est que ça me fait charger une class en +. Mais le Framework Laravel 5 utilise ce système plutot qu'un Singleton.

Nan mais je ne vois pas la différence entre une Façade et le singleton que tu utilisais avant ? Je n'ai pas compris quel problème posait l'utilisation du singleton sans encapsulation ?

Personnellement je ne me fie pas a ce que fait telle ou telle application quand je developpe ma propre solution, sans savoir pourquoi cela a été fait de cette façon et pas autrement.

Personnellement je ne me fie pas a ce que fait telle ou telle application quand je developpe ma propre solution, sans savoir pourquoi cela a été fait de cette façon et pas autrement.

grugru

Oui exact, mais l'utilisation de la Façade me permet justement de ne pas être obliger dans la liste de mes routes d'ajouter des affreux "global" dans mes group. Donc voilà pourquoi je préfère maintenant passer par un système de Façade. Ma Façade me permet de faire des appelles static, tout en gardant qu'une seule instance du Router.

Car ceci marchais pas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<?php

$router = Router::getInstance();

$router->group('/admin', function() {
    $router->group('/article', function() {
        $router->get(
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@edit',
            'admin_article_edit'
        );

        $router->post(
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@update'
        );
    });
});

Wamp me renvoyais une erreur comme quoi variable router indéfini…

ça m’obligeais de faire ceci pour que ça marche (à ajouter des affreux global dans les group) :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php

$router = Router::getInstance();

$router->group('/admin', function() {
    global $router;
    $router->group('/article', function() {
        global $router;
        $router->get(
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@edit',
            'admin_article_edit'
        );

        $router->post(
            '/edit/{id}',
            'App\Controllers\Admin\ArticleController@update'
        );
    });
});
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte