Bonjour,

Voila je rencontre un petit problème avec mon code. Je suis entraine de suivre la formation php pdo. J'ai commencé mon projet. J'ai un problème J'utilise cette librairie dans mon router de mon framework : https://packagist.org/packages/zendframework/zend-expressive-fastroute.

Ce que je fais

Dans mon code router j'ai une fonction match() qui vérifie si une route match et une fonction get() qui permet de faire une route avec la méthode GET

class Router
 /**
     * @param string $path
     * @param callable $callable
     * @param string $name
     */
    public function get(string $path, callable $callable, string $name)
    {
        $this->router->addRoute(new ZendRoute($path, $callable, ['GET'], $name));
    }

Ce que je veux

Les testes passe au vert :

// RouterTest
/**
     * @var Router
     */
    private $router;

    public function setUp()
    {
        $this->router = new Router();
    }

    public function testGetMethod()
    {
        $request = new ServerRequest('GET', '/blog');
        $this->router->get('/blog', function () { return "Hello"; }, 'blog');
        $route = $this->router->match($request);
        $this->assertEquals('blog', $route->getName());
        $this->assertEquals('Hello', call_user_func_array($route->getCallback(), [$request]));
    }

    public function testGetMethodIfUrlDoesNotExits()
    {
        $request = new ServerRequest('GET', '/blog');
        $this->router->get('/asssas', function () { return "Hello"; }, 'blog');
        $route = $this->router->match($request);
        $this->assertEquals(null, $route);
    }

    public function testGetMethodWithParameters()
    {
        $request = new ServerRequest('GET', '/blog/mon-slug-8');
        $this->router->get('/posts', function () { return "Hello"; }, 'posts');
        $this->router->get('/blog/{slug:[a-z0-9\-]+}-{id:\d+}', function () { return "Hello"; }, 'posts.show');
        $route = $this->router->match($request);
        $this->assertEquals('posts.show', $route->getName());
        $this->assertEquals('Hello', call_user_func_array($route->getCallback(), [$request]));
        $this->assertEquals(['slug' => 'mon-slug', 'id' => '8'], $route->getParameters);
    }

Ce que j'obtiens

Mes teste ne passe pas et j'ai cette execption de ZendFramework qui s'affiche :

1) Tests\ParticlesFramework\RouterTest::testGetMethod
Zend\Expressive\Router\Route will not accept anything other than objects implementing the MiddlewareInterface starting in version 3.0.0; we detected usage of "Closure" for path "/blog" using methods GET. Please update your code to create middleware instances implementing MiddlewareInterface; use decorators for callable middleware if needed.

2) Tests\ParticlesFramework\RouterTest::testGetMethodIfUrlDoesNotExits
Zend\Expressive\Router\Route will not accept anything other than objects implementing the MiddlewareInterface starting in version 3.0.0; we detected usage of "Closure" for path "/asssas" using methods GET. Please update your code to create middleware instances implementing MiddlewareInterface; use decorators for callable middleware if needed.

3) Tests\ParticlesFramework\RouterTest::testGetMethodWithParameters
Zend\Expressive\Router\Route will not accept anything other than objects implementing the MiddlewareInterface starting in version 3.0.0; we detected usage of "Closure" for path "/posts" using methods GET. Please update your code to create middleware instances implementing MiddlewareInterface; use decorators for callable middleware if needed.

L'exeption d'écrit qu'il faut que ma classe implémente une interface MiddlewareInterface. J'ai regardé le controller principal de FastRouteRouter il implémente une interface RouterInterface avec les méthodes addRoute(), match(), et generateUri(). Ma classe Router utilise ces même fonction, j'ai ésayé defaire ériter de l'interface RouterInterface. Ça fonctione pas, j'ai toujours cette même exception qui s'attend à avoir une MiddlewareInterface.

Dans ma classe Route qui représente une Route matché j'ai des méthodes suivante :

// Route.php
/**
     * @var FastRouteRouter
     */
    private $router;

    /**
     * Router constructor.
     */
    public function __construct()
    {
        $this->router = new FastRouteRouter();
    }

    /**
     * @param string $path
     * @param callable $callable
     * @param string $name
     */
    public function get(string $path, callable $callable, string $name)
    {
        $this->router->addRoute(new ZendRoute($path, $callable, ['GET'], $name));
    }

    /**
     * @param ServerRequestInterface $request
     * @return null|Route
     */
    public function math(ServerRequestInterface $request): ?Route
    {
        $result = $this->router->match($request);
        if ($result->isSuccess()) {
            //result->getMatchedMiddleware()
            return new Route(
                $result->getMatchedRouteName(),
                $result->getMatchedRoute()->getMiddleware(),
                $result->getMatchedParams()
            );
        }
        return null;
    }

Merci de votre aide

23 réponses


Gorgio
Réponse acceptée

Si tu es dans la formation de Grafikart (https://www.grafikart.fr/tutoriels/router-919) il dit clairement sous la vidéo de limiter la version de Fast Route:
composer require zendframework/zend-expressive-fastroute:1.2.0

Aussi ne remonte pas un sujet aussi vieux c'est pas le plus judicieux imho

Bonjour,
En fait j'ai le même soucis, mais c'est pas une erreur du code originale de Grafikart, c'est parce que Zend à mis à jour son code.
Du coup sa fonctionne plus il dit qu'il faut implémenter le middleware interface à la place du [GET].
Je n'ai pour l'instant pas réussi à le faire fonctionner donc je peu pas trop aider mais peut être qu'un autre membre comprendras le problème et pourras venir à notre aide ;)

Si vous souhaitez, vous pouvez revenir à une version antérieure avec un composer require zendframework/zend-expressive-fastroute 2.1.0 ou en modifiant directement dans le composer.json et ensuite faire un composer update.

Bon j'avance un peu.
Dans ZendRoute il s'attend à voir une instance MiddlewareInterface de Webimpress sinon il renvoie sur la function triggerDeprecationForNonMiddlewareImplementation(). Le petit soucis c'est que nous utilisons pas le MiddlewareInterface de Webimpress du coup ca génère une erreur (enfin je pense).
Pour l'instant j'ai réussi à tout refaire fonctionner en commentant la ligne qui fait appelle à la fonction triggerDeprecationForNonMiddlewareImplementation() dans le fichier route.php de zend-expressive-router.
Mais ce n'est pas la meilleur solution (juste temporaire le temp de trouver mieux).

C'est quand même bizare que zend continue à utilisé webimpress et interop alors que cela ont été abandoné au profit de psr

Bonjour,
Il faut apparement explicitement implementer la version interop du MiddlewareInterface

<?php
class ActionTestClass implements \Interop\Http\Server\MiddlewareInterface
{
    public function __invoke(ServerRequestInterface $request)
    {
        return 'hello';
    }

//  implementation de Interop\Http\Server\MiddlewareInterface 
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler)
    {
        return $this->__invoke($request);
    }
}
?>

On passe une instance au lieu d'un callable dans le test

 class RouterTest extends TestCase
 {
    public function testGetMethod()
    {
    $request = new ServerRequest('GET', '/blog/mon-slug-8');
    $this->router->get('/posts', new ActionTestClass(), 'posts');
    }
 }
Bonne journée

Bonjour,
je rencontre exactement le même problème et ne parviens pas à redescendre vers une version de zend-expressive-router qui "fonctionne" comme précédemment... En effet, dans la mesure ou zend-expressive-router est une dépendance de zend-expressive-fastroute, la version est gérée par celui-ci et je me retrouve donc avec la version 2.4.1 du router qui affiche le message d'erreur...

De plus, je n'arrive pas à voir comment implémenter la solution de @philippe74, si on doit créer une nouvelle instance de la classe, on perd tout l'intérêt du conteneur d'injection de dépendances dans la mesure on on doit passer toutes les dépendences en paramètre du constructeur... Quelqu'un sait-il m'éclairer?

Merci d'avance,
Nicwalle

Bonjour Nicwalle tu peux aller sur le tuto chapitre router, Spyke à donner une solution avec Aura.Router et tinmar81 une pour garder fasteroute (elle rejoint un peu ce que propose philippe74) et je bloque toujours sur la solution fasteroute.
Sinon pour revenir sur l'ancienne version tu dois noter le numéro de la version dans composer et enlever le ^

zendframework/zend-expressive-fastroute": "2.1.2"

tu peux aussi ajouter le zend router

zendframework/zend-expressive-router": "2.3.0"

puis un coup de composer update
Pour l'instant j'ai la dernière version j'ai commenter la ligne indiquer plus haut ça fonctionne même si ça me plaît pas

Bonjour Fred,
Merci pour ta réponse rapide !
En ce qui concerne la version, j'obtenais des erreurs (dues à des conflits) au niveau de composer.
Sinon, j'ai fait comme toi et j'ai modifié la ligne de vérification d'implémentation.

Je vais essayer cet autre router donc, merci !

Nicwalle

Bonjour,
J'ai modifié le code de Grafikart pour utiliser la version 3.0 de zend-expressive-fastroute
Jai utilisé le CombinedMiddleware de Grafikart pour encapsuler le callable (video 28)

class BlogModule 

public function __construct(ContainerInterface $container) {

        $router->get($'/blog', new CombinedMiddleware($container, [PostIndexAction::class]), 'blog.posts.index');
}

La nouvelle version du MiddlewareInterface permet d'avoir un CombineMiddleware qui fait aussi Delegate.
Cela donne

class CombinedMiddleware implements RequestHandlerInterface, MiddlewareInterface
{
   public function __construct(ContainerInterface $container, array $middlewares)
    {
        $this->middelewares = $middlewares;
        $this->container = $container;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $this->handler = $handler;
        return $this->handle($request);
    }

        public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $middleware = $this->getMiddleware();
        if (is_null($middleware)) {
            return $this->handler->handle($request);
        } elseif (is_callable($middleware)) {
            $response = call_user_func_array($middleware, [$request, [$this, 'process']]);
            if (is_string($response)) {
                return new Response(200, [], $response);
            }
            return $response;
        } elseif ($middleware instanceof MiddlewareInterface) {
            return $middleware->process($request, $this);
        }
    }
}

Au niveau du dispatcher il faut legerement modifier (par rapport à la video (28-modification de profil)
if ($callback instanceof CombinedMiddleware) {
return $callback->process($request, $handler);
}

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $route = $request->getAttribute(Route::class);
        if (is_null($route)) {
            return $handler->handle($request);
        }
        $callback = $route->getCallback();

        if ($callback instanceof CombinedMiddleware) {
            return $callback->process($request, $handler);
        }
        if (!is_array($callback)) {
            $callback = [$callback];
        }
        return (new CombinedMiddleware($this->container, $callback))->process($request, $handler);
    }

et enfin au niveau du router, il n'y a que l'appel deprecié a modifier dans la fonction match
$result->getMatchedRoute()->getMiddleware(),

    public function match(ServerRequestInterface $request): ? Route
    {
        $result = $this->router->match($request);
        if ($result->isSuccess()) {
            return new Route(
                $result->getMatchedRouteName(),
                $result->getMatchedRoute()->getMiddleware(),
                $result->getMatchedParams()
            );
        }
        return null;
    }

En esperant que cela puisse aider.
Ca marche bien chez moi

Afin de pouvoir continuer à utiliser zend fast router voilà comment j'ai fait.
Tout d'abord créé une nouvelle classe dans src/Framework/Router/MiddlewareApp.php
ensuite à l'intérieur de cette classe, tu mets le code suivant :

<?php
namespace Framework\Router;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class MiddlewareApp implements MiddlewareInterface {

    /**
     * @var callable
     */
    private $callback;

    public function __construct(callable $callback)
    {
        $this->callback = $callback;
    }

    /**
     * @param ServerRequestInterface $request
     * @param RequestHandlerInterface|null $handler
     * @return ResponseInterface
     */
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler = null): ResponseInterface
    {
        return $this->process($request, $handler);
    }

    /**
     * @return callable
     */
    public function getCallback(): callable
    {
        return $this->callback;
    }

}

Une fois cela fait, tu modifie le code de la fonction get de la façon suivante :

    public function get(string $path, callable $callable, ?string $name = null)
    {
        $this->router->addRoute(new ZendRoute($path, new MiddlewareApp($callable), ['GET'],$name));
    }

puis dans la fonction match le code est le suivant

    public function match(ServerRequestInterface $request): ?Route
    {
        $result = $this->router->match($request);
        if ($result->isSuccess()) {
            return new Route(
                $result->getMatchedRouteName(),
                $result->getMatchedRoute()->getMiddleware()->getCallback(),
                $result->getMatchedParams()
            );
        }
        return null;
    }

et finalement pour tester dans la class RouterTest.php tu essayes avec le code suivant :

    public function testGetMethod()
    {
        $request = new Request('GET', '/blog');
        $this->router->get('/blog', function () { return 'Hello'; }, 'blog');
        $route = $this->router->match($request);
        $this->assertEquals('blog', $route->getName());
        $this->assertContains('Hello', call_user_func_array($route->getCallback(),[$request]));
    }

et normalement tout devrait rentrer dans l'ordre.

Si cela comporte des risques ou erreurs merci de me le signaler :)

Salut
merci pour ton code
j'ai ça comme erreur:

Fatal error: Uncaught Error: Class 'Framework\MiddlewareApp' not found in D:\Labo\dev\MonFramework\src\Framework\Router.php:33 Stack trace: #0 D:\Labo\dev\MonFramework\src\Blog\BlogModule.php(13): Framework\Router->get('/blog', Array, 'blog.index') #1 D:\Labo\dev\MonFramework\src\Framework\App.php(32): App\Blog\BlogModule->construct(Object(Framework\Router)) #2 D:\Labo\dev\MonFramework\public\index.php(5): Framework\App->construct(Array) #3 {main} thrown in D:\Labo\dev\MonFramework\src\Framework\Router.php on line 33

bonne journée.

Salut, c'est certainement que tu n'as pas importé le namespace Framework\Router\MiddlewareApp dans ta classe de test

Salut à tous,
Lucas merci pour ton code je viens d'essayer mais j'ai l'erreur suivante :

Argument 1 passed to App\Middlewares\MiddlewareApp::__construct() must be callable, string given

J'enregistre mes routes de la manière suivante :

$this->router->get('/login', Actions\AuthAction::class, 'login');

Comment puis-je passer un type callable à la place de Actions\AuthAction::class ?

Merci d'avance

Salut alors de tête, tu peux passer à la place de

Actions\AuthAction::class

le callable de la façon suivante, c'est une fonction où le premier paramètre est un tableau avec ta classe et la méthode que tu veux appeler pour le callback, et le deuxième tableau représente les paramètres de ta function :

call_user_func_array([taClass,"methodAAppeller"],[parameters]);

Bonjour je me permet de déterrer cet ancien sujet mais j'aimerais savoir comment avez-vous fait afin de palier ce problème et continuer de suivre le tuto dans de meilleures conditions. Merci

Bonjour
J'ai toujours un problème avec Zend Expressive Fast Route. J'ai essayé de faire comme Lucas Tambarin, mais il y a un problème qui se pose. Dans la fonction macth, la méthode getCallback n'existe pas:

$result->getMatchedRoute()->getMiddleware()->getCallback()

J'utilise la version 3.0.2
Si quelqu'un peut bien m'aider

lakamark
Auteur

Je vais fermer le sujet, car j'ai plus le temps pour travailler sur mon framework.

Merci de votre aide.

lakamark
Auteur

Je vais fermer le sujet, car j'ai plus le temps pour travailler sur mon framework.

Sinon il y a altorouter qui est vraiment très très bien :)

Desolée de relancer le sujet merci a @Lucas Tambarin.
pour l'aide.

pour les personne qui on l'erreur:

Warning: call_user_func_array() expects parameter 1 to be a valid callback, no array or string given

Le probleme vient qui la function call_user_func_array qui a besoin d'une closure. dans le fichier App ligne 56 on lui donne une instance de MiddlewareApp et non une closure.

donc:

pour la function match :

public function match(ServerRequestInterface $request): ?Route
    {
        $result = $this->router->match($request);
        if ($result->isSuccess()) {

            return new Route(
                $result->getMatchedRouteName(),
                $result->getMatchedRoute()->getMiddleware(),
                $result->getMatchedParams()
            );
        }
        return null;
    }

et pour la function run :

        $Middleware = $route->getCallback();
        $callback = $Middleware->getCallback();
        if (is_string($callback)) {
            $callback = $this->container->get($callback);
        }
        $response = call_user_func_array($callback, [$request]);

La proposition de Lucas Tambarin ne peut fonctionner qu'avec l'introduction du container qui n'intervient que plus tard dans la formation (voir son implémentation ici : https://github.com/kelinox/Framework)
A l'heure d'aujourd'hui, le Zend Expressive Router n'est plus maintenu et l'auteur suggère d'utiliser Mezzio Router et son FastRouteRouter.

Lucas Tambarin merci