dmaicher / doctrine-test-bundle

Symfony bundle to isolate your app's doctrine database tests and improve the test performance
MIT License
1.08k stars 60 forks source link

Connection hashing fails with "Serialization of 'Closure' is not allowed" #201

Closed Steveb-p closed 2 years ago

Steveb-p commented 2 years ago

In cases where a custom Platform is provided into Doctrine configuration, Connection hashing will fail due to an attempt to serialize a closure.

This happens because in such a case $params argument contains Platform service instance, which in turn contains event listeners that may make use of Service Subscribers (as it is for me).

Normally, if platform option is not specified, it is created by Doctrine on it's own when it is first requested, which happens later than creation of the Connection object.

Exception stacktrace (application code omitted):

Exception : Serialization of 'Closure' is not allowed
 /.../vendor/dama/doctrine-test-bundle/src/DAMA/DoctrineTestBundle/Doctrine/DBAL/AbstractStaticDriverV2.php:24
 /.../vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php:411
 /.../vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php:1951
 /.../vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php:1289
 /.../vendor/doctrine/dbal/lib/Doctrine/DBAL/Query/QueryBuilder.php:212

If the platform is unset before passing it to create hash, everything works normally.

public function connect(array $params, $username = null, $password = null, array $driverOptions = []): Connection
{
    if (!self::$keepStaticConnections) {
        return $this->underlyingDriver->connect($params, $username, $password, $driverOptions);
    }

    $serializationParams = $params;
    unset($serializationParams['platform']);
    $key = sha1(serialize($serializationParams).$username.$password);

    if (!isset(self::$connections[$key])) {
        self::$connections[$key] = $this->underlyingDriver->connect($params, $username, $password, $driverOptions);
        self::$connections[$key]->beginTransaction();
    }

    return new StaticConnection(self::$connections[$key]);
}

Alternatively, using json_encode in place of serialize also does seem to work:

public function connect(array $params, $username = null, $password = null, array $driverOptions = []): Connection
{
    if (!self::$keepStaticConnections) {
        return $this->underlyingDriver->connect($params, $username, $password, $driverOptions);
    }

    $key = sha1(json_encode($params).$username.$password);

    if (!isset(self::$connections[$key])) {
        self::$connections[$key] = $this->underlyingDriver->connect($params, $username, $password, $driverOptions);
        self::$connections[$key]->beginTransaction();
    }

    return new StaticConnection(self::$connections[$key]);
}

Although this makes it so Platform object is encoded as {}, so Connections differing only by Platform class would share the same hash - but I don't think that is an issue.