DI enableCompilation and Slim/CSRF

It seems imposible to use enableCompilation and slim/csrf together:

Fatal error: Uncaught LogicException: You cannot set a definition at runtime on a compiled container. You can either put your definitions in a file, disable compilation or ->set() a raw value directly (PHP object, string, int, …) instead of a PHP-DI definition.

Its all fine when I either comment out enableCompilation or remove the CSRF Guard from the container.

Strange enough despite the error thrown the file CompiledContainer.php gets created.

Any idea?

can you share more details

  • how do you setup the Guard into container
  • what versions (both slim and csrf)
  • code, code, code, code

Here is my current code, the relevant bits at least.

Composer dependencies:

"slim/slim": "4.3",
"slim/psr7": "0.6.0",
"slim/twig-view": "3.0.0-beta",
"php-di/php-di": "6.0",
"vlucas/phpdotenv": "^4.0",
"slim/csrf": "^1.0",
"slim/php-view": "^2.2"

Main app class:

<?php

declare(strict_types=1);

namespace Example\Main;

use DI\ContainerBuilder;
use Dotenv\Dotenv;
use Dotenv\Repository\Adapter\EnvConstAdapter;
use Dotenv\Repository\Adapter\ServerConstAdapter;
use Dotenv\Repository\RepositoryBuilder;
use Slim\Csrf\Guard;
use Slim\Factory\AppFactory;
use Slim\Psr7\Response;
use Slim\Psr7\Request;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;

class App
{

    public function __construct()
    {
    // Load the enviroment file
    $adapters = [
        new EnvConstAdapter(),
        new ServerConstAdapter(),
    ];

    $repository = RepositoryBuilder::create()
        ->withReaders($adapters)
        ->withWriters($adapters)
        ->immutable()
        ->make();

    Dotenv::create($repository, CONFIG_PATH, null)->load();

        // Set up Dependency Injection
        $containerBuilder = new ContainerBuilder();
        $containerBuilder->addDefinitions(CONFIG_PATH . DS . 'services.php');
        $containerBuilder->addDefinitions(CONFIG_PATH . DS . 'actions.php');

        /* causes fatal error in combination with CSRF guard */
        //$containerBuilder->enableCompilation(ABS_PATH . DS . 'cache');

        $container = $containerBuilder->build();

        AppFactory::setContainer($container);
        $app = AppFactory::create();

        $responseFactory = $app->getResponseFactory();

        // Register Middleware On Container
        $container->set(Guard::class, function () use ($responseFactory) {
            return new Guard($responseFactory);
        });

        $app->addRoutingMiddleware();

        // Register Twig middleware
        $app->add(TwigMiddleware::createFromContainer($app, Twig::class));

        // Enable to register CSRF middleware for all routes
        $app->add(Guard::class);

        // Include routes
        $routes = require CONFIG_PATH . DS . 'routes.php';
        $routes($app);

       $app->run();
    }
}

Services.php:

<?php

declare(strict_types=1);

use Example\TwigExtension\CsrfExtension;
use Psr\Container\ContainerInterface;
use Slim\Csrf\Guard;
use Slim\Views\PhpRenderer;
use Slim\Views\Twig;

return [
    Twig::class => function (ContainerInterface $container) {
        $twig = new Twig(ABS_PATH . '/src/example/views', [
            'cache' => false,
            'debug' => true,
            'auto_reload' => true,
        ]);

        $twig->addExtension(new Twig_Extension_Debug());
        $twig->addExtension(new CsrfExtension($container->get(Guard::class)));

        return $twig;
    },
    PhpRenderer::class => function (ContainerInterface $container) {
        return new PhpRenderer(ABS_PATH . '/src/example/views');
    },
    PDO::class => function (ContainerInterface $container) {
        $driver = $_ENV["DB_DRIVER"];
        $host = $_ENV["DB_HOST"];
        $dbname = $_ENV["DB_NAME"];
        $charset = $_ENV["DB_CHARSET"];
        $username = $_ENV["DB_USERNAME"];
        $password = $_ENV["DB_PASSWORD"];
        $dsn = "$driver:host=$host;dbname=$dbname;charset=$charset";

        $options = [
            \PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES $charset",
            \PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            \PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
            \PDO::ATTR_EMULATE_PREPARES => true,
        ];

        try {
            return new \PDO($dsn, $username, $password, $options);
        } catch (\PDOException $e) {
            throw new \PDOException($e->getMessage(), (int) $e->getCode());
        }
    },

];

Actions.php:

<?php

declare(strict_types=1);

use Example\Controllers\HelloController;
use Example\Controllers\HomeController;
use Example\Controllers\DemoController;
use Psr\Container\ContainerInterface;
use Slim\Views\Twig;

return [
    HelloController::class => function (ContainerInterface $container) {
        $view = $container->get(Twig::class);
        return new HelloController($view);
    },
    HomeController::class => function (ContainerInterface $container) {
        $view = $container->get(Twig::class);
        return new HomeController($view);
    },
    DemoController::class => function (ContainerInterface $container) {
        $view = $container->get(Twig::class);
        $product = $container->get(Product::class);
        return new DemoController($view, $product);
    }
];

The container defintions are closures and already cached by the PHP OPcache. Does it makes sence and is it really faster to cache the same thing twice? I’m not sure.

@odan is that mean that better to configure dependencies using function (Container $container) {} instead of create($class)->constructor(...$args) or both is cached by opcache?

As long as it is a PHP file, everything is cached in the OPCache, even the PHP-DI cache file (because it is a PHP file). Manual caching of a closures is difficult / tricky in PHP and is already solved by the OPcache.

But anyway, I just wanted to say that if the PHP-DI cache causes too many problems, then just don’t enable it, because the PHP OPCache is pretty good (and in this case less error-prone). If the “extra” caching level works for you, then use it of course! Sorry for the confusion :wink:

1 Like

I didn’t know about OPcache learned something new.