phoenix icon indicating copy to clipboard operation
phoenix copied to clipboard

Please give ConnectionFactory an interface

Open uncaught opened this issue 1 month ago • 2 comments

Feature Request

What

With your version 3 you made ConnectionFactory final, forcing us to wrap the service instead of extending it. This works fine, but there is no interface we could be using.

Why

We use a custom ConnectionFactory to inject additional services into our custom connection that we need.

Also we register some additional and complex database types in there that are not compatible with ConnectionFactory::initializeTypes.

How

Implementing an interface like this would already suffice:


interface ConnectionFactoryInterface {
    public function createConnection(array $params, ?Configuration $config = null, array $mappingTypes = []): Connection;
}

Thank you

uncaught avatar Nov 24 '25 07:11 uncaught

What is your use case for replacing the ConnectionFactory ?

stof avatar Nov 24 '25 08:11 stof

As I said, for one we need to inject services into our custom connection. The bundle allows to override the connection's class with wrapper_class, but not as service with extra dependencies to my knowledge.

And we register special types, e.g. a native \UnitEnum type which becomes a MySQL enum.

E.g. from our user entity, this UserType is a native \UnitEnum that is automatically injected as doctrine type:

  #[Column(type: UserType::class, options: ['default' => 'regular'])]
  private UserType $type = UserType::regular;
if you are interested, this is our implementation
<?php

declare(strict_types=1);

namespace Octaved\Doctrine\DBAL;

use Doctrine\DBAL\Configuration;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Types\Type;
use Octaved\Doctrine\DBAL\Type\IEnumSet;
use Octaved\Doctrine\DBAL\Type\JsonDecoratorType;
use Octaved\Doctrine\DBAL\Type\NativeEnumSetType;
use Octaved\Doctrine\DBAL\Type\NativeEnumType;
use Octaved\Doctrine\DeadlockHandler;
use Octaved\Utilities\Decorator\StdClassDecorator;

class ConnectionFactory {
  /**
   * Static because we have two connections with the same types and Type::addType is static anyway.
   */
  private static bool $typesAdded = false;

  public function __construct(
    private readonly \Doctrine\Bundle\DoctrineBundle\ConnectionFactory $inner,
    private readonly DeadlockHandler $deadlockHandler,
    /** @var string[] */
    private readonly array $packageTypes,
    /** @var string[] */
    private readonly array $fqnTypes,
  ) {
  }

  public function createConnection(array $params, ?Configuration $config = null, array $mappingTypes = []): Connection {
    $this->registerPackageTypes();
    $connection = $this->inner->createConnection($params, $config, $mappingTypes);
    if ($connection instanceof Con) {
      $connection->setDeadlockHandler($this->deadlockHandler);
    }

    return $connection;
  }

  private function setType(string $type, string $class): void {
    if (Type::hasType($type)) {
      Type::overrideType($type, $class);
    } else {
      Type::addType($type, $class);
    }
  }

  private function registerPackageTypes(): void {
    if (!self::$typesAdded) {
      foreach ($this->packageTypes as $type => $class) {
        $this->setType($type, $class);
      }
      foreach ($this->fqnTypes as $fqnType) {
        if (is_subclass_of($fqnType, \UnitEnum::class)) {
          NativeEnumType::register($fqnType, $fqnType);
        } elseif (is_subclass_of($fqnType, IEnumSet::class)) {
          NativeEnumSetType::register($fqnType, $fqnType::getEnumClass());
        } elseif (is_subclass_of($fqnType, StdClassDecorator::class)) {
          JsonDecoratorType::register($fqnType);
        } elseif (is_subclass_of($fqnType, Type::class)) {
          $this->setType($fqnType, $fqnType);
        }
      }
      self::$typesAdded = true;
    }
  }
}

uncaught avatar Nov 24 '25 10:11 uncaught