Introduce Doctrine ORM

TODOs:

- [x] EntityManager wrapper
- [ ] EntityRepository wrapper
- [ ] Query wrapper (for the DQL)
- [x] Command integration
- [ ] Psr6 Cache wrapper around the ICache
- [ ] Decide if we want a wrapper for the Mapping DTO class (lot of work
  not much beneficts)

Signed-off-by: Carl Schwan <carl@carlschwan.eu>
introduce/orm
Carl Schwan 2 years ago
parent cab0f1327e
commit e255b4b7a2
No known key found for this signature in database
GPG Key ID: C3AA6B3A5EFA7AC5

@ -115,6 +115,15 @@ if (\OC::$server->getConfig()->getSystemValue('installed', false)) {
$application->add(new OC\Core\Command\Db\Migrations\MigrateCommand(\OC::$server->get(\OC\DB\Connection::class)));
$application->add(new OC\Core\Command\Db\Migrations\GenerateCommand(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getAppManager()));
$application->add(new OC\Core\Command\Db\Migrations\ExecuteCommand(\OC::$server->get(\OC\DB\Connection::class), \OC::$server->getConfig()));
Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($application, new class implements \Doctrine\ORM\Tools\Console\EntityManagerProvider {
public function getDefaultManager(): \Doctrine\ORM\EntityManagerInterface {
return \OCP\Server::get(\OCP\DB\ORM\IEntityManager::class)->get();
}
public function getManager(string $name): \Doctrine\ORM\EntityManagerInterface {
return \OCP\Server::get(\OCP\DB\ORM\IEntityManager::class)->get();
}
});
}
$application->add(new OC\Core\Command\Encryption\Disable(\OC::$server->getConfig()));

@ -29,7 +29,7 @@
namespace OC\DB;
use Doctrine\Common\EventManager;
use Doctrine\DBAL\Configuration;
use Doctrine\ORM\Configuration;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Event\Listeners\OracleSessionInit;
use Doctrine\DBAL\Event\Listeners\SQLSessionInit;

@ -0,0 +1,88 @@
<?php
namespace OC\DB\ORM;
use Doctrine\Common\EventManager;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\ORMSetup;
use OC\DB\ConnectionAdapter;
use OCP\DB\ORM\IQuery;
use OCP\DB\ORM\IEntityManager;
use OCP\DB\ORM\IEntityRepository;
use OCP\IDBConnection;
class EntityManagerAdapter implements IEntityManager {
private EntityManager $em;
private ConnectionAdapter $connection;
public function __construct(ConnectionAdapter $connection) {
$paths = array_filter(array_map(fn ($appId) => \OC_App::getAppPath($appId) . '/lib/Entity/', \OC_App::getEnabledApps()), fn ($path) => is_dir($path));
$isDevMode = true;
$proxyDir = null;
$cache = null;
$evm = $connection->getInner()->getEventManager();
$tablePrefix = new TablePrefix('oc_');
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
// TODO actually use our cache with a psr6 cache wrapper or at least our cache config
$config = ORMSetup::createAnnotationMetadataConfiguration($paths, $isDevMode, $proxyDir, $cache);
$this->em = EntityManager::create($connection->getInner(), $config, $evm);
$this->connection = $connection;
}
public function createQuery($dql = ''): IQuery
{
return new QueryAdapter($this->em->createQuery($dql));
}
public function flush(): void {
$this->em->flush();
}
public function find(string $className, $id, ?int $lockMode = null, ?int $lockVersion = null): ?object {
return $this->em->find($className, $id, $lockMode, $lockVersion);
}
public function clear(): void {
$this->em->clear();
}
public function persist(object $entity): void {
$this->em->persist($entity);
}
public function remove(object $entity): void {
$this->em->remove($entity);
}
public function lock(object $entity, int $lockMode, $lockVersion = null): void {
$this->em->lock($entity, $lockMode, $lockVersion);
}
public function getRepository($className): IEntityRepository {
/** @var EntityRepository $internalRepo */
$internalRepo = $this->em->getRepository($className);
return new EntityRepositoryAdapter($internalRepo);
}
public function contains(object $entity): bool {
return $this->em->contains($entity);
}
public function getConnection(): IDBConnection {
return $this->connection;
}
/**
* Only for internal use
*/
public function get(): EntityManager {
return $this->em;
}
}

@ -0,0 +1,15 @@
<?php
namespace OC\DB\ORM;
use Doctrine\ORM\EntityRepository;
use OCP\DB\ORM\IEntityRepository;
class EntityRepositoryAdapter implements IEntityRepository
{
private EntityRepository $entityRepository;
public function __construct(EntityRepository $entityRepository) {
$this->entityRepository = $entityRepository;
}
}

@ -0,0 +1,116 @@
<?php
namespace OC\DB\ORM;
use OCP\ICache;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
class CacheItemAdapter implements CacheItemInterface {
private ICache $cacheAdapter;
private string $key;
private $value;
private bool $fetched = false;
private ?\DateTime $_expireAt = null;
private int $_expireAfter = -1;
public function __construct(ICache $cache, $key) {
$this->cache = $cache;
$this->key = $key;
}
private function fetch(): void {
if (!$this->fetched) {
$this->value = $this->cache->get($this->key);
$this->fetched = true;
}
}
public function getKey() {
return $this->key;
}
public function get() {
$this->fetch();
return $this->value;
}
public function isHit() {
$this->fetch();
return $this->value !== null;
}
public function set($value) {
$this->value = $value;
}
public function expiresAt($expiration) {
$this->_expireAt = $expiration;
}
public function expiresAfter($time) {
$this->_expireAfter = $time;
}
public function getExpireAt(): ?\DateTime
{
return $this->_expireAt;
}
public function getExpireAfter(): int
{
return $this->_expireAfter;
}
}
class Psr6CacheAdapter implements CacheItemPoolInterface {
private ICache $cache;
public function __construct(ICache $cache) {
$this->cache = $cache;
}
public function getItem($key) {
return new CacheItemAdapter($this->cache, $key);
}
public function getItems(array $keys = array()) {
for (int )
// TODO: Implement getItems() method.
}
public function hasItem($key)
{
// TODO: Implement hasItem() method.
}
public function clear()
{
// TODO: Implement clear() method.
}
public function deleteItem($key)
{
// TODO: Implement deleteItem() method.
}
public function deleteItems(array $keys)
{
// TODO: Implement deleteItems() method.
}
public function save(CacheItemInterface $item)
{
// TODO: Implement save() method.
}
public function saveDeferred(CacheItemInterface $item)
{
// TODO: Implement saveDeferred() method.
}
public function commit()
{
// TODO: Implement commit() method.
}
}

@ -0,0 +1,14 @@
<?php
namespace OC\DB\ORM;
use Doctrine\ORM\Query;
use OCP\DB\ORM\IQuery;
class QueryAdapter implements IQuery {
private Query $query;
public function __construct(Query $query) {
$this->query = $query;
}
}

@ -0,0 +1,34 @@
<?php
namespace OC\DB\ORM;
use \Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class TablePrefix
{
protected $prefix = 'oc_';
public function __construct($prefix)
{
$this->prefix = (string)$prefix;
}
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if (!$classMetadata->isInheritanceTypeSingleTable() || $classMetadata->getName() === $classMetadata->rootEntityName) {
$classMetadata->setPrimaryTable([
'name' => $this->prefix . $classMetadata->getTableName()
]);
}
foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) {
if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY && $mapping['isOwningSide']) {
$mappedTableName = $mapping['joinTable']['name'];
$classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName;
}
}
}
}

@ -80,6 +80,7 @@ use OC\Contacts\ContactsMenu\ContactsStore;
use OC\Dashboard\DashboardManager;
use OC\DB\Connection;
use OC\DB\ConnectionAdapter;
use OC\DB\ORM\EntityManagerAdapter;
use OC\Diagnostics\EventLogger;
use OC\Diagnostics\QueryLogger;
use OC\EventDispatcher\SymfonyAdapter;
@ -167,6 +168,7 @@ use OCP\Comments\ICommentsManager;
use OCP\Contacts\ContactsMenu\IActionFactory;
use OCP\Contacts\ContactsMenu\IContactsStore;
use OCP\Dashboard\IDashboardManager;
use OCP\DB\ORM\IEntityManager;
use OCP\Defaults;
use OCP\Diagnostics\IEventLogger;
use OCP\Diagnostics\IQueryLogger;
@ -878,6 +880,13 @@ class Server extends ServerContainer implements IServerContainer {
$connection = $factory->getConnection($type, $connectionParams);
return $connection;
});
$this->registerService(IEntityManager::class, function (ContainerInterface $c): IEntityManager {
/** @var ConnectionAdapter $connection */
$connection = $c->get(IDBConnection::class);
return new EntityManagerAdapter($connection);
});
/** @deprecated 19.0.0 */
$this->registerDeprecatedAlias('DatabaseConnection', IDBConnection::class);

@ -0,0 +1,133 @@
<?php
namespace OCP\DB\ORM;
use DateTimeInterface;
use OCP\IDBConnection;
/**
* @since 25.0.0
*/
interface IEntityManager {
/**
* Creates a new Query object.
*
* @param string $dql The DQL string.
* @since 25.0.0
*/
public function createQuery(string $dql = ''): IQuery;
/**
* Flushes all changes to objects that have been queued up to now to the database.
* This effectively synchronizes the in-memory state of managed objects with the
* database.
*
* @throws OptimisticLockException If a version check on an entity that
* makes use of optimistic locking fails.
* @throws \OCP\DB\Exception
* @since 25.0.0
*/
public function flush(): void;
/**
* Finds an Entity by its identifier.
*
* @param string $className The class name of the entity to find.
* @param mixed $id The identity of the entity to find.
* @param int|null $lockMode One of the \Doctrine\DBAL\LockMode::* constants
* or NULL if no specific lock mode should be used
* during the search.
* @param int|null $lockVersion The version of the entity to find when using
* optimistic locking.
* @psalm-param class-string<T> $className
* @psalm-param LockMode::*|null $lockMode
*
* @return object|null The entity instance or NULL if the entity can not be found.
* @psalm-return ?T
*
* @throws OptimisticLockException
* @throws \OCP\DB\Exception
*
* @template T
* @since 25.0.0
*/
public function find(string $className, $id, ?int $lockMode = null, ?int $lockVersion = null): ?object;
/**
* Clears the EntityManager. All entities that are currently managed
* by this EntityManager become detached.
*
* @throws \OCP\DB\Exception If a $entityName is given, but that entity is not
* found in the mappings.
* @since 25.0.0
*/
public function clear(): void;
/**
* Tells the EntityManager to make an instance managed and persistent.
*
* The entity will be entered into the database at or before transaction
* commit or as a result of the flush operation.
*
* NOTE: The persist operation always considers entities that are not yet known to
* this EntityManager as NEW. Do not pass detached entities to the persist operation.
*
* @param object $entity The instance to make managed and persistent.
*
* @throws \OCP\DB\Exception
* @since 25.0.0
*/
public function persist(object $entity): void;
/**
* Removes an entity instance.
*
* A removed entity will be removed from the database at or before transaction commit
* or as a result of the flush operation.
*
* @param object $entity The entity instance to remove.
*
@throws \OCP\DB\Exception
* @since 25.0.0
*/
public function remove(object $entity): void;
/**
* Acquire a lock on the given entity.
*
* @param int|DateTimeInterface|null $lockVersion
* @psalm-param LockMode::* $lockMode
*
*
* @throws OptimisticLockException
* @throws PessimisticLockException
* @since 25.0.0
*/
public function lock(object $entity, int $lockMode, $lockVersion = null): void;
/**
* {@inheritdoc}
*
* @psalm-param class-string<T> $className
*
* @psalm-return IEntityRepository<T>
*
* @template T of object
* @since 25.0.0
*/
public function getRepository($className): IEntityRepository;
/**
* Determines whether an entity instance is managed in this EntityManager.
*
* @return bool TRUE if this EntityManager currently manages the given entity, FALSE otherwise.
* @since 25.0.0
*/
public function contains(object $entity): bool;
/**
* @return IDBConnection
* @since 25.0.0
*/
public function getConnection(): IDBConnection;
}

@ -0,0 +1,8 @@
<?php
namespace OCP\DB\ORM;
interface IEntityRepository
{
}

@ -0,0 +1,8 @@
<?php
namespace OCP\DB\ORM;
interface IQuery
{
}

@ -0,0 +1,8 @@
<?php
namespace OCP\DB\ORM;
class LockMode
{
}

@ -0,0 +1,6 @@
<?php
namespace OCP\DB\ORM;
class OptimisticLockException extends \Exception {
}

@ -0,0 +1,7 @@
<?php
namespace OCP\DB\ORM;
class PessimisticLockException extends \Exception {
}
Loading…
Cancel
Save