1

I'm trying to use https://github.com/php-middleware/phpdebugbar in a clean Zend Expressive Skeleton application.

I know the instructions on this page suggest using this DI configuration (if using pimple):

$container[Psr\Http\Message\ResponseInterface::class] = new Zend\Diactoros\ResponseFactory();
$container[Psr\Http\Message\StreamFactoryInterface] = new Zend\Diactoros\StreamFactory();

So I tried using this (I'm using zend service manager):

return [
    'dependencies' => [
        'factories' => [
            Psr\Http\Message\ResponseInterface::class => new Zend\Diactoros\ResponseFactory(),
           'Psr\Http\Message\StreamFactoryInterface' => new Zend\Diactoros\StreamFactory(),
        ],
    ],
];

But I'm running into the following error:

PHP Fatal error:  Uncaught Zend\ServiceManager\Exception\ServiceNotFoundException: Unable to resolve service "Psr\Http\Message\ResponseInterface" to a factory; are you certain you provided it during configuration? in /www/develop.expressive.centralsemi.com/htdocs/vendor/zendframework/zend-servicemanager/src/ServiceManager.php:687

I also tried this:

return [
    'dependencies' => [
        'factories' => [
            Psr\Http\Message\ResponseInterface::class => new Zend\Diactoros\ResponseFactory(),
            Psr\Http\Message\StreamFactoryInterface::class => new Zend\Diactoros\StreamFactory(),
        ],
    ],
];

and this:

return [
    'dependencies' => [
        'factories' => [
            Psr\Http\Message\ResponseInterface::class => Zend\Diactoros\ResponseFactory::class,
            Psr\Http\Message\StreamFactoryInterface::class => Zend\Diactoros\StreamFactory::class,
        ],
    ],
];

But still no luck.

Admittedly, I'm not familiar with Zend/Diactoros, but I don't understand how Zend\Diactoros\ResponseFactory can be a factory, it doesn't have an __invoke() method. So I feel like that is the core of my issue. Am I supposed to create my own factory for this? I feel like that is not the intended way to do this.

Note, I've also tried following these instructions. And while there is no error, it doesn't seem like it's showing up at all: https://docs.zendframework.com/zend-expressive/v3/cookbook/debug-toolbars/

I'm sure I'm missing some key part, but what is it that I'm missing?

My composer.json:

{
    "name": "zendframework/zend-expressive-skeleton",
    "description": "Zend expressive skeleton. Begin developing PSR-15 middleware applications in seconds!",
    "type": "project",
    "homepage": "https://github.com/zendframework/zend-expressive-skeleton",
    "license": "BSD-3-Clause",
    "keywords": [
        "skeleton",
        "middleware",
        "psr",
        "psr-7",
        "psr-11",
        "psr-15",
        "zf",
        "zendframework",
        "zend-expressive"
    ],
    "config": {
        "sort-packages": true
    },
    "extra": {
        "zf": {
            "component-whitelist": [
                "zendframework/zend-expressive",
                "zendframework/zend-expressive-helpers",
                "zendframework/zend-expressive-router",
                "zendframework/zend-httphandlerrunner",
                "zendframework/zend-expressive-fastroute",
                "zendframework/zend-expressive-twigrenderer"
            ]
        }
    },
    "support": {
        "issues": "https://github.com/zendframework/zend-expressive-skeleton/issues",
        "source": "https://github.com/zendframework/zend-expressive-skeleton",
        "rss": "https://github.com/zendframework/zend-expressive-skeleton/releases.atom",
        "slack": "https://zendframework-slack.herokuapp.com",
        "forum": "https://discourse.zendframework.com/c/questions/expressive"
    },
    "require": {
        "php": "^7.1",
        "zendframework/zend-component-installer": "^2.1.1",
        "zendframework/zend-config-aggregator": "^1.0",
        "zendframework/zend-diactoros": "^1.7.1 || ^2.0",
        "zendframework/zend-expressive": "^3.0.1",
        "zendframework/zend-expressive-helpers": "^5.0",
        "zendframework/zend-stdlib": "^3.1",
        "zendframework/zend-servicemanager": "^3.3",
        "zendframework/zend-expressive-fastroute": "^3.0",
        "zendframework/zend-expressive-twigrenderer": "^2.0"
    },
    "require-dev": {
        "filp/whoops": "^2.1.12",
        "php-middleware/php-debug-bar": "^3.0",
        "phpunit/phpunit": "^7.0.1",
        "roave/security-advisories": "dev-master",
        "squizlabs/php_codesniffer": "^2.9.1",
        "zendframework/zend-expressive-tooling": "^1.0",
        "zfcampus/zf-development-mode": "^3.1"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/App/src/",
            "PhpDebugBar\\": "src/PhpDebugBar/src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "AppTest\\": "test/AppTest/"
        }
    },
    "scripts": {
        "post-create-project-cmd": [
            "@development-enable"
        ],
        "development-disable": "zf-development-mode disable",
        "development-enable": "zf-development-mode enable",
        "development-status": "zf-development-mode status",
        "expressive": "expressive --ansi",
        "check": [
            "@cs-check",
            "@test"
        ],
        "clear-config-cache": "php bin/clear-config-cache.php",
        "cs-check": "phpcs",
        "cs-fix": "phpcbf",
        "serve": "php -S 0.0.0.0:8080 -t public/",
        "test": "phpunit --colors=always",
        "test-coverage": "phpunit --colors=always --coverage-clover clover.xml"
    }
}
d.lanza38
  • 2,525
  • 7
  • 30
  • 52

3 Answers3

2

I figured it out. The instructions at https://docs.zendframework.com/zend-expressive/v3/cookbook/debug-toolbars/ are correct. The only additional step which is required is adding invokables and aliases entries to the configuration.

Mine were in /config/autoload/zend-expressive-tooling-factories.global.php:

return [
    'dependencies' => [
        'invokables' => [
            Zend\Diactoros\ResponseFactory::class => Zend\Diactoros\ResponseFactory::class,
            Zend\Diactoros\StreamFactory::class => Zend\Diactoros\StreamFactory::class,
        ],
        'aliases' => [
            Psr\Http\Message\ResponseFactoryInterface::class => Zend\Diactoros\ResponseFactory::class,
            Psr\Http\Message\StreamFactoryInterface::class => Zend\Diactoros\StreamFactory::class,
        ]
    ],
];

I originally just had the aliases section and not the invokables section. Once I added that, everything just worked.

d.lanza38
  • 2,525
  • 7
  • 30
  • 52
1

try creating an alias first and then provide it to a factory

return [
    'dependencies' => [
        'factories' => [
            Psr\Http\Message\ResponseInterface::class => Zend\Diactoros\ResponseFactory::class,
           'Psr\Http\Message\StreamFactoryInterface' => Zend\Diactoros\StreamFactory::class,
        ],
        'aliases' => [
           ClassThatImplementsResponseInterface::class => Psr\Http\Message\ResponseInterface::class,
           ClassThatImplementsStreamFactoryInterface::class => 'Psr\Http\Message\StreamFactoryInterface',
        ]
    ],
];

UPDATE: As @d.lanza38 discovered this is the wright configuration

return [
    'dependencies' => [
        'invokables' => [
            Zend\Diactoros\ResponseFactory::class => Zend\Diactoros\ResponseFactory::class,
            Zend\Diactoros\StreamFactory::class => Zend\Diactoros\StreamFactory::class,
        ],
        'aliases' => [
            Psr\Http\Message\ResponseFactoryInterface::class => Zend\Diactoros\ResponseFactory::class,
            Psr\Http\Message\StreamFactoryInterface::class => Zend\Diactoros\StreamFactory::class,
        ]
    ],
];
  • Thank you @Razvan. What am I supposed to use in place of `ClassThatImplementsResponseInterface::class` and `ClassThatImplementsStreamFactoryInterface::class`? Am I supposed to write my own implementation? I thought that was what Zend\Diactoros was supposed to provide. – d.lanza38 Apr 15 '19 at 16:27
  • 1
    @d.lanza38 I thought that this is what you intended to do. Tell me what you intend to do so I can better understand. If you just want to emit a custom response (e.g. different code: $this->responseFactory->createResponse(404)) try using this https://docs.zendframework.com/zend-servicemanager/reflection-abstract-factory/ so you won't have to worry about custom factories and provide the Zend\Diactoros\ResponseFactory to the class constructor from where you want to emit the response – Razvan Ionascu Apr 16 '19 at 12:45
  • Sorry if I'm a little unclear, what I'm trying to do is use `php-middleware/phpdebugbar` in a fresh Zend Expressive skeleton application. I'm half confused by the two completely different sets of instructions I've found which explain how to do so. I'm not trying to serve a 404 response or anything like that, just trying to get 'php-middleware/phpdebugbar' working. In your answer, I'm not sure what my equivalent of `ClassThatImplementsResponseInterface` and `ClassThatImplementsStreamFactoryInterface` are though. – d.lanza38 Apr 16 '19 at 16:38
1

I am writing an other answer as it is easier to read. OK. I just did a fresh install of Zend Expressive 3 and https://github.com/php-middleware/phpdebugbar:

  1. composer require --dev php-middleware/php-debug-bar
  2. composer dump-autoload
  3. Created /config/autoload/debugbar.local.php
return array_merge(PhpMiddleware\PhpDebugBar\ConfigProvider::getConfig(), [
    'phpmiddleware' => [
        'phpdebugbar' => [
            'javascript_renderer' => [
                'base_url' => '/razvan.ionascu/ze-api/public',
            ],
            'collectors' => [
                DebugBar\DataCollector\ConfigCollector::class, // Service names of collectors
            ],
            'storage' => null, // Service name of storage
        ],
    ],
    'dependencies' => [

        'factories'  => [
            \PhpMiddleware\PhpDebugBar\PhpDebugBarMiddleware::class => \PhpMiddleware\PhpDebugBar\PhpDebugBarMiddlewareFactory::class,
            \DebugBar\DataCollector\ConfigCollector::class => \PhpMiddleware\PhpDebugBar\ConfigCollectorFactory::class,
            \PhpMiddleware\PhpDebugBar\ConfigProvider::class => \PhpMiddleware\PhpDebugBar\ConfigProvider::class,
            \DebugBar\DebugBar::class => \PhpMiddleware\PhpDebugBar\StandardDebugBarFactory::class,
            JavascriptRenderer::class => NewJavascriptRendererFactory::class,
        ],
    ],
]);

For this to work you need to create a new JavascriptRendererFactory. The current one seaches for the wrong config key, ConfigProvider::class instead of 'config' :

class NewJavascriptRendererFactory
{
    public function __invoke(ContainerInterface $container): JavascriptRenderer
    {
        $debugbar = $container->get(DebugBar::class);
        $config = $container->get('config');
        $rendererOptions = $config['phpmiddleware']['phpdebugbar']['javascript_renderer'];


        $renderer = new JavascriptRenderer($debugbar);
        $renderer->setOptions($rendererOptions);

        return $renderer;
    }
}
  1. added the following aliases and factories to /confog/atuoload/dependencies.global.php
'aliases'            => [
            \Psr\Http\Message\ResponseFactoryInterface::class              => ResponseFactory::class,
            \Psr\Http\Message\StreamFactoryInterface::class                => StreamFactory::class,
        ],
'factories' => [
...
\DebugBar\JavascriptRenderer::class => JavascriptRendererFactory::class,
...
],
  1. Added a new pipeline to /config/pipeline.php
//only works in development mode
if (!empty($container->get('config')['debug'])) {
        $app->pipe(\PhpMiddleware\PhpDebugBar\PhpDebugBarMiddleware::class);
    }

As I mentioned in an earlier comment, I have also configured https://docs.zendframework.com/zend-servicemanager/reflection-abstract-factory/ , so I don't need to create a factory for each service()

See the working debug bar here

  • Thank you very much @Razvan Ionascu for the detailed answer. I'm still running into issues, but let me look at it a bit and figure out what it is I might have different. But real quick, I'd just like to confirm a few things though. I should change `'base_url' => '/razvan.ionascu/ze-api/public',` to `'base_url' => '/my/path/to/public',`? I should change `JavascriptRenderer::class => NewJavascriptRendererFactory::class,` to `\DebugBar\JavascriptRenderer::class => PhpDebugBar\Message\NewJavascriptRendererFactory::class,`? – d.lanza38 Apr 17 '19 at 17:17
  • And this `$renderer = new JavascriptRenderer($debugbar);` is referring to `DebugBar\JavascriptRenderer`. And in `/config/autoload/dependencies.global.php` the `ResponseFactory::class` and `StreamFactory::class` are referring to the ones in `Zend\Diactoros\`, correct? – d.lanza38 Apr 17 '19 at 17:17
  • Please note: I created a module `PhpDebugBar\Message` which contains `NewJavascriptRendererFactory` as you have defined it. The module is registered and everything. – d.lanza38 Apr 17 '19 at 17:18
  • 1
    yes, change the base url to your public or the assets from the debug bar won't work. I think that if you change what you said you will change it should work fine. If id doesn't let me know :) . Good luck – Razvan Ionascu Apr 18 '19 at 12:11
  • I created a fresh Zend Expressive 3 project, followed your steps exactly and I'm still getting the same error of "Unable to resolve service "Zend\Diactoros\ResponseFactory" to a factory; are you certain you provided it during configuration?". I have a feeling my issue revolves around PSR-17 and one package expecting old school factories using `__invoke` and another expecting PSR-17 type of factories. I'll post my composer.json in my question for reference. Could you let me know if there are any differences between mine and yours? – d.lanza38 Apr 22 '19 at 17:08
  • 1
    sure. You could also try using this: https://docs.zendframework.com/zend-servicemanager/reflection-abstract-factory/ so you won't have any headaches related to the factories. – Razvan Ionascu Apr 23 '19 at 13:13
  • Thank you for the tip. I'll have to look into this so I can use it going forward. Also, it seems my issue all long was resolved with a variant of you original answer. If you just update it with the code in the answer I provided then i'll accept it as the correct answer. – d.lanza38 Apr 23 '19 at 18:36