Logger in router.php - How to

Hello,

there has been a lot of time questions on how to use logger (Monolit) in the router.php file, that comes out the box when running slim from Docker, under shinsenter/slim:latest image.

The answers on the web are sometimes confusing and sometimes pointing to different versions. After reading and doing some trial and error, here is my fast and dirty solution to this problem.

<?php

declare(strict_types=1);

use App\Application\Actions\DB\DB;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\App;
use Slim\Interfaces\RouteCollectorProxyInterface as Group;
use Psr\Log\LoggerInterface;

return function (App $app) {

    $container = $app->getContainer();
    $logger = $container->get(LoggerInterface::class);

    $app->get('/', function (Request $request, Response $response) use ($logger) {
        $response->getBody()->write('Hello world!');

        $logger->info("/ GET was called.");

        return $response;
    });

when using RouteCollectorProxyInterface as $group, you can use this:

<?php

declare(strict_types=1);

use App\Application\Actions\DB\DB;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\App;
use Slim\Interfaces\RouteCollectorProxyInterface as Group;
use Psr\Log\LoggerInterface;

return function (App $app) {

    $app->group('/api', function (Group $group) use ($app) {

        $container = $app->getContainer();
        $logger = $container->get(LoggerInterface::class);

        $group->options('/{routes:.*}', function (Request $request, Response $response) {
            // CORS Pre-Flight OPTIONS Request Handler
            return $response;
        });

        $group->get('/', function (Request $request, Response $response) use ($logger) {
            $response->getBody()->write('Hello world!');

            $logger->info("/api/ GET was called");

            return $response;
        });

I just hope it helps someone, as it took me some time to figure this out.

Thanks for sharing this. Note that it is also possible to fetch dependencies directly within the route callback.

$app->get('/', function (Request $request, Response $response) {
    $response->getBody()->write('Hello world!');

    $logger = $this->get(LoggerInterface::class);
    $logger->info("/ GET was called.");

    return $response;
});
1 Like

Hi,
sorry to coming to this after some months, but is there a way to call loggerInterface from a PHPMailer Support Class, like presented in this post: PHPMailer Example?

I tried your initial injection, and it didn’t work. I must say, that this injections stuff is not very intuitive to me.

Thx

I tried your initial injection, and it didn’t work.

Sorry to hear that. Could you share more details?

I must say, that this injections stuff is not very intuitive to me.

The concept of dependency injection is to declare all required classes and interfaces within the class constructor. The DI container will then create and inject these objects. It’s a straightforward and efficient way to manage object dependencies.

Using functions (closures) as routing handlers means that you cannot benefit from the concept of DI. The non-class based approach (closures as routing handler) can become unmaintainable in the long run as you have to manually create, wire, and pass everything together.

Hi, thanks for taking the time.

Here is what I tried:

<?php

namespace App\Application\Actions\Support;

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
use Psr\Log\LoggerInterface;
use Slim\App;

class Mailer
{
    private $host;
    private $username;
    private $password;
    private $port;

    private $from_email;
    private $from_name;

    private $mail;
    private $logger;

    public function __construct()
    {
        $this->mail = new PHPMailer(true);
        $this->logger = new LoggerInterface();
        $this->logger->info("mail was initialiazed");
    }

The error here is: “Cannot instantiate interface Psr\Log\LoggerInterface”, which makes sense, I guess.

I also tried this:

    [...]

    public function __construct()
    {
        $this->mail = new PHPMailer(true);
        $this->logger = $this->get(LoggerInterface::class);
        $this->logger->info("/ GET was called.");
    }

Here the msg is: Call to undefined method App\Application\Actions\Support\Mailer::get(), which also makes sense.

The DI container will then create and inject these objects.

When you say inject, do you mean initialize it in the background and make them available? Are they then treated as static classes overall? and who is the ‘parent’ that manages the DI, is it Slim\App? If you have other sources or code that I can look at, you can also point me to that.

Ok, what ended up working, but I would like to know if this is SlimPHP Best Practice is this:

// routes.php
$container = $app->getContainer();
$logger = $container->get(LoggerInterface::
...
$mail = new Mailer($logger);

// Mailer.php
private LoggerInterface $logger;
public function __construct(LoggerInterface $_logger)
{
   $this->mail = new PHPMailer(true);
   $this->logger = $_logger;
   $this->logger->debug("Mailer Contructor was called.");
}

Which I guess is DI on constructor level… I have always done this, but just today learned that this is DI.

Is this correct and intended?

The DI container itself also acts as a “factory” that can create these objects either automatically (autowiring) or manually using factoring definitions (plain functions).

In your case I would recommend to create a DI container definition for Psr\Log\LoggerInterface::class and PHPMailer::class.

See here: PHP-DI - The Dependency Injection Container for humans

use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;

return [
    LoggerInterface:class => function (ContainerInterface $container) {
        return new Logger(...);
    },

    PHPMailer::class => function (ContainerInterface $container) {
        $mailer = new PHPMailer(...);
        // ...
        return $mailer;
    }),
];

Note that the PHPMailer is now “shared”. So when you need to send multiple mails at once, I would recommend to implement a custom MailerFactory instead.

Then just declare these dependencies within a Service class when needed:

final class MyService
{
    private LoggerInterface $logger;
    private PHPMailer $mailer;

    public function __construct(PHPMailer $mailer, LoggerInterface $logger)
    {
       $this->mailer = $mailer;
       $this->logger = $logger;
    }

    // your business logic
}

Here you can find more information: