Tuto PHP-DI (Conteneur de dépendance) : BlogAction

Default
,

Bonjour,

Voila je rencontre un petit problème avec mon code.

Ce que je fais

BlogModule.php :

<?php
namespace App\Blog;

use Framework\Module;
use Framework\Router;
//use Psr\Http\Message\RequestInterface as Request;
//use Psr\Http\Message\ResponseInterface as Response;
use Framework\Renderer\RendererInterface;
use App\Blog\Actions\BlogAction;

/**
 * Configure le module
 * Ajout des définitions, Ajout de routes, Ajout de chemins...
 */
class BlogModule extends Module
{
    const DEFINITIONS = __DIR__ . '/config.php';

    private $renderer;

    public function __construct(string $prefix, Router $router, RendererInterface $renderer)
    {
        $this->renderer = $renderer;
        $this->renderer->addPath('blog', __DIR__ . '/views');
        //$router->get($prefix, [$this, 'index'], 'blog.index');
        $router->get($prefix, BlogAction::class, 'blog.index');
        //$router->get($prefix . '/{slug:[a-z0-9\-]+}', [$this, 'show'], 'blog.show');
        $router->get($prefix . '/{slug:[a-z0-9\-]+}', BlogAction::class, 'blog.show');
    }
}

BlogAction.php

 <?php
namespace App\Blog\Actions;

use Framework\Renderer\RendererInterface;
use Psr\Http\Message\ServerRequestInterface as Request;

/**
 * Assure les requêtes ou actions liées au module
 */
class BlogAction
{
    /**
     * @var RendererInterface
     */
    private $renderer;

    public function __contruct(RendererInterface $renderer)
    {
        $this->renderer = $renderer;
    }

    public function __invoke(Request $request)
    {
        $slug = $request->getAttribute('slug');
        if ($slug) {
            return $this->show($slug);
        }
        return $this->index();
    }

    /**
     * Retourne la vue par défaut : blog/index
     */
    public function index() : string
    {
        return $this->renderer->render('@blog/index');
    }

    /**
     * @param string $slug
     */
    public function show(string $slug) : string
    {
        return $this->renderer->render('@blog/show', [
            'slug' => $slug
        ]);
    }
}

Décrivez ici votre code ou ce que vous cherchez à faire

C'est le code indiqué dans la vidéo https://www.grafikart.fr/formations/mise-pratique-poo/conteneur-dependance notamment à la 29' 41

Ce que je veux

Faire fonctionner la page comme indiqué dans la vidéo

Ce que j'obtiens

Décrivez ici vos erreurs ou ce que vous obtenez à la place de ce que vous attendez :(

Fatal error: Uncaught Error: 
Call to a member function render() on null in D:\www\tuto-php\src\Blog\Actions\BlogAction.php:36
Stack trace: 
#0 D:\www\tuto-php\src\Blog\Actions\BlogAction.php(28): App\Blog\Actions\BlogAction->index()
#1 [internal function]: App\Blog\Actions\BlogAction->__invoke(Object(GuzzleHttp\Psr7\ServerRequest))
#2 D:\www\tuto-php\src\Framework\App.php(63): call_user_func_array(Object(App\Blog\Actions\BlogAction), Array)
#3 D:\www\tuto-php\web\index.php(34): Framework\App->run(Object(GuzzleHttp\Psr7\ServerRequest)) 
#4 {main} thrown in D:\www\tuto-php\src\Blog\Actions\BlogAction.php on line 36

Je ne comprend pas pourquoi le renderer n'est pas instancié...
Faut-il avoir un paramétrage spécifique dans PHP-DI pour le 'autowiring' ?
Car PHP-DI ne retrouve pas les éléments...

Merci pour votre aide.

11 Réponse

17162
,

Bonsoir.
Pourquoi est-ce que tu instancies renderer dans une propriété dans ta class BlogModule ?
Que je sache il ne le fait pas Grafikart dans cette vidéo, tout du moins pas à cette étape de la vidéo.

Default
,

Merci Lartak pour ta réponse,

Je ne le fais pas non plus. J'ai reproduit le code de Grafikart à la lettre.
Mais cela me renvoie l'erreur 'Call to a member function render() on null'.
Je cherche à comprendre pourquoi render est à null. Et ma première idée me dit que $renderer n'est pas connu. D'où l'idée de l'intancier...

L'autre piste, concerne une configuration spécifique de PHP-DI avec la fonction autowire() que j'aurais loupé dans les précédentes chapitres.

<?php
namespace App\Blog;

use Framework\Module;
use Framework\Router;
use Framework\Renderer\RendererInterface;
use App\Blog\Actions\BlogAction;

/**
 * Configure le module
 * Ajout des définitions, Ajout de routes, Ajout de chemins...
 */
class BlogModule extends Module
{
    const DEFINITIONS = __DIR__ . '/config.php';

    public function __construct(string $prefix, Router $router, RendererInterface $renderer)
    {
        $renderer->addPath('blog', __DIR__ . '/views');
        $router->get($prefix, BlogAction::class, 'blog.index');
        $router->get($prefix . '/{slug:[a-z0-9\-]+}', BlogAction::class, 'blog.show');
    }

}
21827
,

Il faut que tu spécifies à ton conteneur comment instancier la chaîne $prefix

Default
,

Merci Gorgio de t'intéresser à la question.
La chaine $prefix est instanciée avant l'apparition du BlogAction. Voilà le fichier de config ds /blog/config.php :

<?php

use App\Blog\BlogModule;
use function \DI \{autowire, get};

return [
    'blog.prefix' => '/blog',
    BlogModule::class => autowire()->constructorParameter('prefix', get('blog.prefix'))
];

Si je remets l'ancienne version de BlogModule (qui ne prend pas en compte BlogAction) :

<?php
namespace App\Blog;

use Framework\Module;
use Framework\Router;
use Psr\Http\Message\RequestInterface as Request;
use Framework\Renderer\RendererInterface;
use App\Blog\Actions\BlogAction;

/**
 * Configure le module
 * Ajout des définitions, Ajout de routes, Ajout de chemins...
 */
class BlogModule extends Module
{
    const DEFINITIONS = __DIR__ . '/config.php';

    private $renderer;

    public function __construct(string $prefix, Router $router, RendererInterface $renderer)
    {
        $this->renderer = $renderer;
        $this->renderer->addPath('blog', __DIR__ . '/views');
        $router->get($prefix, [$this, 'index'], 'blog.index');
        //$router->get($prefix, BlogAction::class, 'blog.index');
        $router->get($prefix . '/{slug:[a-z0-9\-]+}', [$this, 'show'], 'blog.show');
        //$router->get($prefix . '/{slug:[a-z0-9\-]+}', BlogAction::class, 'blog.show');
    }

    public function index(Request $request) : string
    {
        return $this->renderer->render('@blog/index');
    }

    public function show(Request $request) : string
    {
        return $this->renderer->render('@blog/show', [
            'slug' => $request->getAttribute('slug')
        ]);
    }
}

Cela fonctionne correctement.
C'est bien l'introduction du BlogAction qui génére l'erreur.

Seul la fonction 'object()' est modifiée par 'autowire()' dans mon code par rapport à celui de Grafikart.
La fonction object() a été supprimé dans PHP-DI, maintenant remplacé par cette fonction.

C'est pourquoi je recherche du côté du autowire() si un paramétrage complémentaire doit être mis en place.
Si quelqu'un a la réponse... je suis preneur.

21827
,

La question que je me pose c'est si le renderer est bien injecter dans le blog module en vrai, les messages d'erreurs de PHP-DI sont vraiment moisi je trouve

Default
,

Hum !
Je partage ton point de vue...

Pour ma part, je suis les différentes vidéos de Grafikart pour gagner en compétence, mais j'avoue découvrir PHP-DI et beaucoup de choses. Je dois remercier Grafikart de nous faire partager ses connaissances.

Default
,

Voila ce que j'ai trouvé : http://php-di.org/doc/autowiring.html
Dans certaines circonstances de dépendances la fonction autowire() gére lui-même l'instanciation des classes pour éviter de provoquer le message d'erreur (using the type hinting).
Pour plus de précisions sur le type 'hinting' : http://www.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration

Limitation (un peu plus bas dans l'article) : La classe (ou une fonction propre à la classe) s'appelle elle-même.
La solution serait de configurer ce cas dans config.php

Intancier autowire() : http://php-di.org/doc/php-definitions.html
C'est là où je me perds un peu... car c'est ce que nous avons fait.

code de config.php

<?php

use Framework\Renderer\RendererInterface;
use Framework\Renderer\TwigRendererFactory;
use Framework\Router\RouterTwigExtension;
use Framework\Router;
use function \DI \{
    autowire, factory, get
};

return [

    // Configuration générale par défaut
    'views.path' => dirname(__DIR__) . '/views',
    'twig.extensions' => [
        get(RouterTwigExtension::class)
    ],

    // ROUTER
    Router::class => autowire(),

    // RENDERER : 
    RendererInterface::class => factory(TwigRendererFactory::class),

];
Default
,

Pour info Code de TwigRendererFactory

<?php
namespace Framework\Renderer;

use Framework\Router\RouterTwigExtension;
use Psr\Container\ContainerInterface;

class TwigRendererFactory
{
    public function __invoke(ContainerInterface $container) : TwigRenderer
    {
        $viewPath = $container->get('views.path');
        $loader = new \Twig_Loader_Filesystem($viewPath);
        $twig = new \Twig_Environment($loader, []);
        //$twig->addExtension($container->get(RouterTwigExtension::class));
        if ($container->has('twig.extensions')) {
            foreach ($container->get('twig.extensions') as $extension) {
                $twig->addExtension($extension);
            }
        }
        return new TwigRenderer($loader, $twig);
    }
}

Default
,

J'ai fait le test en supprimant la dernière version de PHP-DI (6.0) et en installant la version 5.4.6.
J'obtiens la même erreur. J'ai donc bien une erreur au niveau de mon code... la fonction autowire() ne serait donc pas concerné...

J'ai réinstallé la version 6.0 de PHP-DI

Default
,

Je n'ai pas trouvé pourquoi ce comportement et l'erreur obtenue.
Le code est identique à celui de Grafikart (ormis la fonction autowire).

Si quelqu'un a le même pb et/ou une idée à suivre, je suis preneur.

Default
,

Je me permets de relancer le sujet.
Je suis toujours bloqué sur ce point.
Il est vrai que depuis quelques jours je n'ai pas eu le temps de revenir sur ce code...

J'ai ajouté un var_dump dans mon BlogModule :

    public function __construct(string $prefix, Router $router, RendererInterface $renderer)
    {
        $renderer->addPath('blog', __DIR__ . '/views');
        echo '<pre>';
        var_dump($router->get($prefix, BlogAction::class, 'blog.index'));
        $router->get($prefix, BlogAction::class, 'blog.index');
        $router->get($prefix . '/{slug:[a-z0-9\-]+}', BlogAction::class, 'blog.show');

et j'obtiens ceci :

Fatal error:  Uncaught FastRoute\BadRouteException: Cannot register two routes matching "/news" for method "GET" in D:\www\tuto-php\vendor\nikic\fast-route\src\DataGenerator\RegexBasedAbstract.php:86
Stack trace:
#0 D:\www\tuto-php\vendor\nikic\fast-route\src\DataGenerator\RegexBasedAbstract.php(30): FastRoute\DataGenerator\RegexBasedAbstract->addStaticRoute('GET', Array, '/news')
#1 D:\www\tuto-php\vendor\nikic\fast-route\src\RouteCollector.php(44): FastRoute\DataGenerator\RegexBasedAbstract->addRoute('GET', Array, '/news')
#2 D:\www\tuto-php\vendor\zendframework\zend-expressive-fastroute\src\FastRouteRouter.php(450): FastRoute\RouteCollector->addRoute(Array, '/news', '/news')
#3 D:\www\tuto-php\vendor\zendframework\zend-expressive-fastroute\src\FastRouteRouter.php(420): Zend\Expressive\Router\FastRouteRouter->injectRoute(Object(Zend\Expressive\Router\Route))
#4 D:\www\tuto-php\vendor\zendframework\zend-expressive-fastroute\src\FastRouteRouter.php(223): Zend\Expressive\Router\FastRouteRouter->injectRoutes()
#5 D:\www\tuto in D:\www\tuto-php\vendor\nikic\fast-route\src\DataGenerator\RegexBasedAbstract.php on line 86

J'aimerais avoir un retour d'expérience.

Merci