From 7bd2757dc5c717364dc0e5750ad4ba655600d1fa Mon Sep 17 00:00:00 2001 From: Yura Rodchyn Date: Wed, 5 Nov 2025 16:16:07 +0200 Subject: [PATCH] Add few interfaces for better extensibility and clean code a little Signed-off-by: Yura Rodchyn --- src/Database.php | 160 ++++++++----------------- src/Profiler.php | 15 +-- src/Profiler/BlackholeProfiler.php | 4 - src/Profiler/QueryProfile.php | 65 +--------- src/Profiler/QueryProfileInterface.php | 26 ++++ src/ProfilerInterface.php | 18 +++ src/Statement.php | 28 +---- 7 files changed, 102 insertions(+), 214 deletions(-) create mode 100644 src/Profiler/QueryProfileInterface.php create mode 100644 src/ProfilerInterface.php diff --git a/src/Database.php b/src/Database.php index 868945a..4352826 100644 --- a/src/Database.php +++ b/src/Database.php @@ -17,21 +17,15 @@ use Access\Driver\DriverInterface; use Access\Driver\Mysql; use Access\Driver\Sqlite; -use Access\Entity; -use Access\Exception; use Access\Exception\ClosedConnectionException; -use Access\Lock; -use Access\Presenter; use Access\Presenter\EntityPresenter; -use Access\Profiler; -use Access\Query; use Access\Query\IncludeSoftDeletedFilter; -use Access\Repository; -use Access\Statement; -use Access\StatementPool; -use Access\Transaction; use DateTimeImmutable; +use Generator; +use PDO; use Psr\Clock\ClockInterface; +use ReflectionException; +use ReflectionMethod; /** * An Access database @@ -42,38 +36,16 @@ */ class Database { - /** - * PDO connection - * - * @var \PDO|null $connection - */ - private ?\PDO $connection; + private ?PDO $connection; - /** - * Driver - * - * @var DriverInterface $driver - */ private DriverInterface $driver; - /** - * Statement pool - * - * @var StatementPool $statementPool - */ private StatementPool $statementPool; - /** - * Profiler - * - * @var Profiler $profiler - */ - private Profiler $profiler; + private ProfilerInterface $profiler; /** * Clock used for the timestamps - * - * @var ClockInterface $clock */ private ClockInterface $clock; @@ -85,14 +57,14 @@ class Database private IncludeSoftDeletedFilter $includeSoftDeletedFilter = IncludeSoftDeletedFilter::Auto; /** - * Create a Access database with a PDO connection + * Create an Access database with a PDO connection * - * @param \PDO $connection A PDO connection - * @param Profiler $profiler A custom profiler, optionally + * @param PDO $connection A PDO connection + * @param ?ProfilerInterface $profiler A custom profiler, optionally */ public function __construct( - \PDO $connection, - ?Profiler $profiler = null, + PDO $connection, + ?ProfilerInterface $profiler = null, ?ClockInterface $clock = null, ) { $this->statementPool = new StatementPool($this); @@ -103,19 +75,19 @@ public function __construct( } /** - * Create a access database with a PDO connection string + * Create an access database with a PDO connection string * * @param string $connectionString A PDO connection string - * @param Profiler $profiler A custom profiler, optionally - * @return self A Access database object + * @param ?ProfilerInterface $profiler A custom profiler, optionally + * @return self An Access database object */ public static function create( string $connectionString, - ?Profiler $profiler = null, + ?ProfilerInterface $profiler = null, ?ClockInterface $clock = null, ): self { try { - $connection = new \PDO($connectionString); + $connection = new PDO($connectionString); return new self($connection, $profiler, $clock); } catch (\Exception $e) { throw new Exception("Invalid database: {$connectionString}", 0, $e); @@ -125,9 +97,9 @@ public static function create( /** * Get the PDO connection * - * @return \PDO A PDO connection + * @return PDO A PDO connection */ - public function getConnection(): \PDO + public function getConnection(): PDO { if ($this->connection === null) { throw new ClosedConnectionException(); @@ -141,8 +113,6 @@ public function getConnection(): \PDO * * Note that in order for the connection to be closed, all it's instances * must be set to null - * - * @return void */ public function closeConnection(): void { @@ -153,18 +123,18 @@ public function closeConnection(): void /** * Set a new PDO connection * - * @param \PDO $connection A new PDO connection + * @param PDO $connection A new PDO connection */ - final public function setConnection(\PDO $connection): void + final public function setConnection(PDO $connection): void { // make sure we don't have any link to the old connection $this->statementPool->clear(); - $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->connection = $connection; /** @var string $driverName */ - $driverName = $connection->getAttribute(\PDO::ATTR_DRIVER_NAME); + $driverName = $connection->getAttribute(PDO::ATTR_DRIVER_NAME); $this->driver = match ($driverName) { Mysql::NAME => new Mysql(), Sqlite::NAME => new Sqlite(), @@ -172,11 +142,6 @@ final public function setConnection(\PDO $connection): void }; } - /** - * Get the statement pool - * - * @return StatementPool - */ public function getStatementPool(): StatementPool { return $this->statementPool; @@ -184,10 +149,8 @@ public function getStatementPool(): StatementPool /** * Get the profiler for some timings - * - * @return Profiler */ - public function getProfiler(): Profiler + public function getProfiler(): ProfilerInterface { return $this->profiler; } @@ -213,11 +176,6 @@ public static function getDriverOrDefault(?DriverInterface $driver): DriverInter return $driver ?? new Mysql(); } - /** - * Begin a transaction - * - * @return Transaction - */ public function beginTransaction(): Transaction { $transaction = new Transaction($this); @@ -228,8 +186,6 @@ public function beginTransaction(): Transaction /** * Create a lock object - * - * @return Lock */ public function createLock(): Lock { @@ -247,11 +203,11 @@ public function createLock(): Lock */ public function getRepository(string $klass): Repository { - $this->assertValidEntityClass($klass); + self::assertValidEntityClass($klass); $repositoryClassName = $klass::getRepository(); - $this->assertValidRepositoryClass($repositoryClassName); + self::assertValidRepositoryClass($repositoryClassName); /** @var Repository $repository * @psalm-suppress UnsafeInstantiation */ @@ -269,7 +225,6 @@ public function getRepository(string $klass): Repository * * @param string $klass Entity class name * @param int $id ID of the entity - * @return ?Entity */ public function findOne(string $klass, int $id): ?Entity { @@ -285,7 +240,6 @@ public function findOne(string $klass, int $id): ?Entity * * @param string $klass Entity class name * @param array $fields List of fields with values - * @return ?Entity */ public function findOneBy(string $klass, array $fields): ?Entity { @@ -298,14 +252,14 @@ public function findOneBy(string $klass, array $fields): ?Entity * @psalm-template TEntity of Entity * @psalm-param class-string $klass * @psalm-suppress InvalidReturnType TODO remove when psalm supports this - * @psalm-return \Generator - yields Entity + * @psalm-return Generator - yields Entity * * @param string $klass Entity class name * @param array $fields List of fields with values - * @param ?int $limit A a limit to the query - * @return \Generator - yields Entity + * @param ?int $limit A limit to the query + * @return Generator - yields Entity */ - public function findBy(string $klass, $fields, ?int $limit = null): \Generator + public function findBy(string $klass, array $fields, ?int $limit = null): Generator { yield from $this->getRepository($klass)->findBy($fields, $limit); } @@ -316,14 +270,14 @@ public function findBy(string $klass, $fields, ?int $limit = null): \Generator * @psalm-template TEntity of Entity * @psalm-param class-string $klass * @psalm-suppress InvalidReturnType TODO remove when psalm supports this - * @psalm-return \Generator - yields Entity + * @psalm-return Generator - yields Entity * * @param string $klass Entity class name * @param int[] $ids List of ids - * @param ?int $limit A a limit to the query - * @return \Generator - yields Entity + * @param ?int $limit A limit to the query + * @return Generator - yields Entity */ - public function findByIds(string $klass, array $ids, ?int $limit = null): \Generator + public function findByIds(string $klass, array $ids, ?int $limit = null): Generator { yield from $this->getRepository($klass)->findByIds($ids, $limit); } @@ -334,18 +288,18 @@ public function findByIds(string $klass, array $ids, ?int $limit = null): \Gener * @psalm-template TEntity of Entity * @psalm-param class-string $klass * @psalm-suppress InvalidReturnType TODO remove when psalm supports this - * @psalm-return \Generator - yields Entity + * @psalm-return Generator - yields Entity * * @param string $klass Entity class name - * @param ?int $limit A a limit to the query + * @param ?int $limit A limit to the query * @param string $orderBy The order to use to find all entities - * @return \Generator - yields Entity + * @return Generator - yields Entity */ public function findAll( string $klass, ?int $limit = null, string $orderBy = 'id ASC', - ): \Generator { + ): Generator { yield from $this->getRepository($klass)->findAll($limit, $orderBy); } @@ -353,16 +307,16 @@ public function findAll( * Execute a select query with a entity provider * * @psalm-template TEntity of Entity - * @psalm-return \Generator - yields Entity + * @psalm-return Generator - yields Entity * * @param EntityProvider $entityProvider Creator the empty entity shells * @param Query\Select $query Select query to be executed - * @return \Generator - yields Entity + * @return Generator - yields Entity */ public function selectWithEntityProvider( EntityProvider $entityProvider, Query\Select $query, - ): \Generator { + ): Generator { $oldIncludeSoftDeleted = $query->setIncludeSoftDeleted($this->includeSoftDeletedFilter); try { @@ -390,15 +344,15 @@ public function selectWithEntityProvider( * * @psalm-template TEntity of Entity * @psalm-param class-string $klass - * @psalm-return \Generator - yields Entity + * @psalm-return Generator - yields Entity * * @param string $klass Entity class name * @param Query\Select $query Select query to be executed - * @return \Generator - yields Entity + * @return Generator - yields Entity */ - public function select(string $klass, Query\Select $query): \Generator + public function select(string $klass, Query\Select $query): Generator { - $this->assertValidEntityClass($klass); + self::assertValidEntityClass($klass); $entityProvider = new EntityProvider($klass); @@ -410,7 +364,6 @@ public function select(string $klass, Query\Select $query): \Generator * * @param string $klass Entity class name * @param Query\Select $query Select query to be executed - * @return ?Entity * * @psalm-template TEntity of Entity * @psalm-param class-string $klass @@ -433,13 +386,10 @@ public function selectOne(string $klass, Query\Select $query): ?Entity * Insert a model * * The ID is set to the returned model - * - * @param Entity $model - * @return Entity */ public function insert(Entity $model): Entity { - $this->assertValidEntityClass(get_class($model)); + self::assertValidEntityClass(get_class($model)); $values = $model->getInsertValues($this->clock); @@ -448,7 +398,7 @@ public function insert(Entity $model): Entity $stmt = new Statement($this, $this->profiler, $query); $gen = $stmt->execute(); - $model->setId(intval($gen->getReturn())); + $model->setId((int)$gen->getReturn()); // set default values/timestamps $model->markUpdated($values); @@ -459,12 +409,11 @@ public function insert(Entity $model): Entity /** * Send changes in model to database * - * @param Entity $model * @return bool Was something actually updated */ public function update(Entity $model): bool { - $this->assertValidEntityClass(get_class($model)); + self::assertValidEntityClass(get_class($model)); $id = $model->getId(); $values = $model->getUpdateValues($this->clock); @@ -488,8 +437,6 @@ public function update(Entity $model): bool * Save a model to the database * * Delegates to insert when no id is available, update otherwise - * - * @param Entity $model */ public function save(Entity $model): void { @@ -510,7 +457,7 @@ public function save(Entity $model): void */ public function delete(Entity $model): bool { - $this->assertValidEntityClass(get_class($model)); + self::assertValidEntityClass(get_class($model)); $transaction = $this->beginTransaction(); @@ -548,8 +495,8 @@ public function softDelete(Entity $model): bool } try { - $setDeletedAt = new \ReflectionMethod($model, 'setDeletedAt'); - } catch (\ReflectionException $e) { + $setDeletedAt = new ReflectionMethod($model, 'setDeletedAt'); + } catch (ReflectionException $e) { throw new Exception('Entity is not soft deletable', 0, $e); } @@ -583,7 +530,6 @@ public function softDelete(Entity $model): bool * * Has no return value, not suited for select queries * - * @param Query $query * @throws Exception when $query is a Query\Select */ public function query(Query $query): void @@ -616,11 +562,10 @@ public function query(Query $query): void * * @param string $presenterKlass Class to present the entity with * @param Entity $entity Entity to present - * @return array|null */ public function presentEntity(string $presenterKlass, Entity $entity): ?array { - $this->assertValidPresenterClass($presenterKlass); + self::assertValidPresenterClass($presenterKlass); $presenter = $this->createPresenter(); @@ -636,7 +581,6 @@ public function presentEntity(string $presenterKlass, Entity $entity): ?array * * @param string $presenterKlass Class to present the collection with * @param Collection $collection Collection to present - * @return array */ public function presentCollection(string $presenterKlass, Collection $collection): array { @@ -644,7 +588,7 @@ public function presentCollection(string $presenterKlass, Collection $collection } /** - * Create an presenter instance + * Create a presenter instance * * @return Presenter An presenter instance */ diff --git a/src/Profiler.php b/src/Profiler.php index 19d8363..2eea45c 100644 --- a/src/Profiler.php +++ b/src/Profiler.php @@ -14,14 +14,13 @@ namespace Access; use Access\Profiler\QueryProfile; -use Access\Query; /** * A simple collection of query profiles to keep some timings * * @author Tim */ -class Profiler +class Profiler implements ProfilerInterface { /** * @var QueryProfile[] $queryProfiles @@ -30,9 +29,6 @@ class Profiler /** * Create a query profile for query - * - * @param Query $query - * @return QueryProfile */ public function createForQuery(Query $query): QueryProfile { @@ -53,8 +49,6 @@ public function clear(): void /** * Get the total duration in seconds - * - * @return float */ public function getTotalDuration(): float { @@ -69,8 +63,6 @@ public function getTotalDuration(): float /** * Get the total duration with hydrate in seconds - * - * @return float */ public function getTotalDurationWithHydrate(): float { @@ -85,8 +77,6 @@ public function getTotalDurationWithHydrate(): float /** * Return number of query profiles - * - * @return int */ public function count(): int { @@ -95,8 +85,7 @@ public function count(): int /** * Get a flat export of query profiles - * - * @return array + * * @psalm-return array{duration: float, durationWithHydrate: float, queries: array} */ public function export(): array diff --git a/src/Profiler/BlackholeProfiler.php b/src/Profiler/BlackholeProfiler.php index 7f0169e..1032674 100644 --- a/src/Profiler/BlackholeProfiler.php +++ b/src/Profiler/BlackholeProfiler.php @@ -14,7 +14,6 @@ namespace Access\Profiler; use Access\Profiler; -use Access\Profiler\QueryProfile; use Access\Query; /** @@ -30,9 +29,6 @@ class BlackholeProfiler extends Profiler * Create a query profile for query * * Does not keep it in history - * - * @param Query $query - * @return QueryProfile */ public function createForQuery(Query $query): QueryProfile { diff --git a/src/Profiler/QueryProfile.php b/src/Profiler/QueryProfile.php index 883cd10..46d9a54 100644 --- a/src/Profiler/QueryProfile.php +++ b/src/Profiler/QueryProfile.php @@ -20,51 +20,24 @@ * * @author Tim */ -class QueryProfile +class QueryProfile implements QueryProfileInterface { - /** - * @var Query $query - */ private Query $query; - /** - * @var float - */ private float $prepareDurationStart = 0.0; - /** - * @var float - */ private float $prepareDurationEnd = 0.0; - /** - * @var float - */ private float $executeDurationStart = 0.0; - /** - * @var float - */ private float $executeDurationEnd = 0.0; - /** - * @var float - */ private float $hydrateDurationStart = 0.0; - /** - * @var float - */ private float $hydrateDurationEnd = 0.0; - /** - * @var int|null - */ private ?int $numberOfResults = null; - /** - * @param Query $query - */ public function __construct(Query $query) { $this->query = $query; @@ -72,25 +45,17 @@ public function __construct(Query $query) /** * Get the query for this profile - * - * @return Query */ public function getQuery(): Query { return $this->query; } - /** - * Start of prepare - */ public function startPrepare(): void { $this->prepareDurationStart = microtime(true); } - /** - * End of prepare - */ public function endPrepare(): void { $this->prepareDurationEnd = microtime(true); @@ -98,25 +63,17 @@ public function endPrepare(): void /** * Get prepare duration in seconds - * - * @return float */ public function getPrepareDuration(): float { return $this->prepareDurationEnd - $this->prepareDurationStart; } - /** - * Start of execute - */ public function startExecute(): void { $this->executeDurationStart = microtime(true); } - /** - * End of execute - */ public function endExecute(): void { $this->executeDurationEnd = microtime(true); @@ -124,25 +81,17 @@ public function endExecute(): void /** * Get execute duration in seconds - * - * @return float */ public function getExecuteDuration(): float { return $this->executeDurationEnd - $this->executeDurationStart; } - /** - * Start of hydrate - */ public function startHydrate(): void { $this->hydrateDurationStart = microtime(true); } - /** - * End of hydrate - */ public function endHydrate(): void { $this->hydrateDurationEnd = microtime(true); @@ -150,8 +99,6 @@ public function endHydrate(): void /** * Get hydrate duration in seconds - * - * @return float */ public function getHydrateDuration(): float { @@ -160,8 +107,6 @@ public function getHydrateDuration(): float /** * Set number of results - * - * @param int|null $numberOfResults */ public function setNumberOfResults(?int $numberOfResults): void { @@ -174,8 +119,6 @@ public function setNumberOfResults(?int $numberOfResults): void * This might not be accurate if the looping over the results of the query * is cut short for any reason, it's the number of records that have been * yielded by the statement - * - * @return int|null */ public function getNumberOfResults(): ?int { @@ -188,12 +131,10 @@ public function getNumberOfResults(): ?int * - Prepare duration * - Execute duration * - * Acutal fetching of data is not included, because we yield all records + * Actual fetching of data is not included, because we yield all records * directly to the caller, skewing the time it takes to fetch. You can get * the duration of the fetching with `getHydrateDuration` or * `getTotalDurationWithHydrate` - * - * @return float */ public function getTotalDuration(): float { @@ -206,8 +147,6 @@ public function getTotalDuration(): float * - Prepare duration * - Execute duration * - Hydrate duration - * - * @return float */ public function getTotalDurationWithHydrate(): float { diff --git a/src/Profiler/QueryProfileInterface.php b/src/Profiler/QueryProfileInterface.php new file mode 100644 index 0000000..a4146ed --- /dev/null +++ b/src/Profiler/QueryProfileInterface.php @@ -0,0 +1,26 @@ +db = $db; $this->statementPool = $db->getStatementPool(); @@ -135,8 +113,6 @@ public function execute(): \Generator /** * Get the return value based in query type - * - * @return ?int */ private function getReturnValue(): ?int {