Skip to content

Commit

Permalink
added reflection for Table, Column, Index, ForeignKey
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed May 7, 2024
1 parent 57fa78c commit 1d9e286
Show file tree
Hide file tree
Showing 9 changed files with 624 additions and 106 deletions.
6 changes: 6 additions & 0 deletions src/Database/Connection.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ public function getSupplementalDriver(): Driver
}


public function getReflection(): Reflection
{
return new Reflection($this->getDriver());
}


public function setRowNormalizer(?callable $normalizer): static
{
$this->rowNormalizer = $normalizer;
Expand Down
2 changes: 1 addition & 1 deletion src/Database/Drivers/SqlsrvDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public function getForeignKeys(string $table): array
fk.name AS name,
cl.name AS local,
tf.name AS [table],
cf.name AS [column]
cf.name AS [foreign]
FROM
sys.foreign_keys fk
JOIN sys.foreign_key_columns fkc ON fk.object_id = fkc.constraint_object_id
Expand Down
84 changes: 84 additions & 0 deletions src/Database/Reflection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\Database;

use Nette\Database\Reflection\Table;


final class Reflection
{
/** @var array<string, Table> */
public readonly array $tables;
private ?string $schema;


public function __construct(
private readonly Driver $driver,
) {
$this->schema = $this->driver->isSupported(Driver::SUPPORT_SCHEMA) ? 'public' : null;
unset($this->tables);
}


/** @return Table[] */
public function getTables(): array
{
return array_values($this->tables);
}


public function getTable(string $name): Table
{
$name = $this->getFullName($name);
return $this->tables[$name] ?? throw new \InvalidArgumentException("Table '$name' not found.");
}


public function hasTable(string $name): bool
{
$name = $this->getFullName($name);
return isset($this->tables[$name]);
}


private function getFullName(string $name): string
{
return $this->schema !== null && !str_contains($name, '.')
? $this->schema . '.' . $name
: $name;
}


/** @internal */
public function getDriver(): Driver
{
return $this->driver;
}


private function initTables(): void
{
$res = [];
foreach ($this->driver->getTables() as $row) {
$res[$row['fullName'] ?? $row['name']] = new Table($this, $row['name'], $row['view'], $row['fullName'] ?? null);
}
$this->tables = $res;
}


public function __get($name): mixed
{
match ($name) {
'tables' => $this->initTables(),
default => throw new \LogicException("Undefined property '$name'."),
};
return $this->$name;
}
}
37 changes: 37 additions & 0 deletions src/Database/Reflection/Column.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\Database\Reflection;


/**
* Column reflection.
*/
final class Column
{
/** @internal */
public function __construct(
public readonly string $name,
public readonly ?Table $table = null,
public readonly string $nativeType = '',
public readonly ?int $size = null,
public readonly bool $nullable = false,
public readonly mixed $default = null,
public readonly bool $autoIncrement = false,
public readonly bool $primary = false,
public readonly array $vendor = [],
) {
}


public function __toString(): string
{
return $this->name;
}
}
34 changes: 34 additions & 0 deletions src/Database/Reflection/ForeignKey.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\Database\Reflection;


/**
* Foreign key reflection.
*/
final class ForeignKey
{
/** @internal */
public function __construct(
public readonly Table $foreignTable,
/** @var Column[] */
public readonly array $localColumns,
/** @var Column[] */
public readonly array $foreignColumns,
public readonly ?string $name = null,
) {
}


public function __toString(): string
{
return (string) $this->name;
}
}
33 changes: 33 additions & 0 deletions src/Database/Reflection/Index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\Database\Reflection;


/**
* Index reflection.
*/
final class Index
{
/** @internal */
public function __construct(
/** @var Column[] */
public readonly array $columns,
public readonly bool $unique = false,
public readonly bool $primary = false,
public readonly ?string $name = null,
) {
}


public function __toString(): string
{
return (string) $this->name;
}
}
114 changes: 114 additions & 0 deletions src/Database/Reflection/Table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/

declare(strict_types=1);

namespace Nette\Database\Reflection;

use Nette\Database\Reflection;


/**
* Table reflection.
*/
final class Table
{
/** @var array<string, Column> */
public readonly array $columns;

/** @var list<Index> */
public readonly array $indexes;
public readonly ?Index $primaryKey;

/** @var list<ForeignKey> */
public readonly array $foreignKeys;


/** @internal */
public function __construct(
private readonly Reflection $reflection,
public readonly string $name,
public readonly bool $view = false,
public readonly ?string $fullName = null,
) {
unset($this->columns, $this->indexes, $this->primaryKey, $this->foreignKeys);
}


public function getColumn(string $name): Column
{
return $this->columns[$name] ?? throw new \InvalidArgumentException("Column '$name' not found in table '$this->name'.");
}


private function initColumns(): void
{
$res = [];
foreach ($this->reflection->getDriver()->getColumns($this->name) as $row) {
$res[$row['name']] = new Column($row['name'], $this, $row['nativetype'], $row['size'], $row['nullable'], $row['default'], $row['autoincrement'], $row['primary'], $row['vendor']);
}
$this->columns = $res;
}


private function initIndexes(): void
{
$this->indexes = array_map(
fn($row) => new Index(
array_map(fn($name) => $this->getColumn($name), $row['columns']),
$row['unique'],
$row['primary'],
is_string($row['name']) ? $row['name'] : null,
),
$this->reflection->getDriver()->getIndexes($this->name),
);
}


private function initPrimaryKey(): void
{
$res = array_filter(
$this->columns,
fn($row) => $row->primary,
);
$this->primaryKey = $res ? new Index(array_values($res), true, true) : null;
}


private function initForeignKeys(): void
{
$tmp = [];
foreach ($this->reflection->getDriver()->getForeignKeys($this->name) as $row) {
$id = $row['name'];
$foreignTable = $this->reflection->getTable($row['table']);
$tmp[$id][0] = $foreignTable;
$tmp[$id][1][] = $this->getColumn($row['local']);
$tmp[$id][2][] = $foreignTable->getColumn($row['foreign']);
$tmp[$id][3] = is_string($id) ? $id : null;
}
$this->foreignKeys = array_map(fn($row) => new ForeignKey(...$row), array_values($tmp));
}


public function __get($name): mixed
{
match ($name) {
'columns' => $this->initColumns(),
'indexes' => $this->initIndexes(),
'primaryKey' => $this->initPrimaryKey(),
'foreignKeys' => $this->initForeignKeys(),
default => throw new \LogicException("Undefined property '$name'."),
};
return $this->$name;
}


public function __toString(): string
{
return $this->name;
}
}
Loading

0 comments on commit 1d9e286

Please sign in to comment.