Guzzle 6 Logging with Symfony: what's the right way to create a callback service?

The latest version of Guzzle uses PSR-7. Functionality that previously was based on events is now implemented as middleware. The log subscriber is not supported, and the logging middleware is part of the main Guzzle package.

This is how I implemented a Guzzle service with logging with Symfony's container.

parameters:
    guzzle.class: GuzzleHttp\Client
    guzzle.stack.class: GuzzleHttp\HandlerStack
    guzzle.middleware.class: GuzzleHttp\Middleware
    guzzle.message_formatter.class: GuzzleHttp\MessageFormatter

services:
    guzzle:
        class: %guzzle.class%
        arguments:
            -
                handler: @guzzle.stack

    guzzle.stack:
        public: false
        class: %guzzle.stack.class%
        factory: [ %guzzle.stack.class%, create ]
        calls:
            - [ push, [ @guzzle.logger ] ]

    guzzle.message_formatter:
        public: false
        class: %guzzle.message_formatter.class%

    guzzle.logger:
        public: false
        class: callback
        arguments: [@logger, @guzzle.message_formatter]
        factory: [GuzzleHttp\Middleware, log]

On the guzzle.logger service, the callback class is actually invalid, but without a class, it complains:

[Symfony\Component\DependencyInjection\Exception\RuntimeException] Please add the class to service "guzzle.logger" even if it is constructed by a factory since we might need to add method calls based on compile-time checks.

Nonetheless, when the dependencies are not public, Symfony compiles the Guzzle client service with a logger correctly.

<?php

use Symfony\Component\DependencyInjection\Container;

class appDevDebugProjectContainer extends Container
{
    protected function getGuzzleService()
    {
        $a = \GuzzleHttp\HandlerStack::create();
        $a->push(\GuzzleHttp\Middleware::log(
            $this->get('logger'),
            new \GuzzleHttp\MessageFormatter()
        ));

        return $this->services['guzzle'] = new \GuzzleHttp\Client(array(
            'handler' => $a
        ));
    }
}

Given that Symfony services must be objects with classes, what is the right way to create a service with callbacks without using a factory? The trick is to create the service without a factory, because if the service is constructed entirely within the container, it could be configured or extended with container extensions and compiler passes.

Tags: PHP, Guzzle, Symfony