Skip to content

Commit da352c2

Browse files
committed
Support for views #409
1 parent 7986ce4 commit da352c2

File tree

14 files changed

+132
-59
lines changed

14 files changed

+132
-59
lines changed

src/Tqdev/PhpCrudApi/Column/Reflection/ReflectedDatabase.php

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,34 @@
66
class ReflectedDatabase implements \JsonSerializable
77
{
88
private $name;
9-
private $tableNames;
9+
private $tableTypes;
1010

11-
public function __construct(String $name, array $tableNames)
11+
public function __construct(String $name, array $tableTypes)
1212
{
1313
$this->name = $name;
14-
$this->tableNames = [];
15-
foreach ($tableNames as $tableName) {
16-
$this->tableNames[$tableName] = true;
17-
}
14+
$this->tableTypes = $tableTypes;
1815
}
1916

2017
public static function fromReflection(GenericReflection $reflection): ReflectedDatabase
2118
{
2219
$name = $reflection->getDatabaseName();
23-
$tableNames = [];
20+
$tableTypes = [];
2421
foreach ($reflection->getTables() as $table) {
2522
$tableName = $table['TABLE_NAME'];
23+
$tableType = $table['TABLE_TYPE'];
2624
if (in_array($tableName, $reflection->getIgnoredTables())) {
2725
continue;
2826
}
29-
$tableNames[$tableName] = true;
27+
$tableTypes[$tableName] = $tableType;
3028
}
31-
return new ReflectedDatabase($name, array_keys($tableNames));
29+
return new ReflectedDatabase($name, $tableTypes);
3230
}
3331

3432
public static function fromJson( /* object */$json): ReflectedDatabase
3533
{
3634
$name = $json->name;
37-
$tableNames = $json->tables;
38-
return new ReflectedDatabase($name, $tableNames);
35+
$tableTypes = (array) $json->tables;
36+
return new ReflectedDatabase($name, $tableTypes);
3937
}
4038

4139
public function getName(): String
@@ -45,28 +43,33 @@ public function getName(): String
4543

4644
public function hasTable(String $tableName): bool
4745
{
48-
return isset($this->tableNames[$tableName]);
46+
return isset($this->tableTypes[$tableName]);
47+
}
48+
49+
public function getType(String $tableName): String
50+
{
51+
return isset($this->tableTypes[$tableName]) ? $this->tableTypes[$tableName] : '';
4952
}
5053

5154
public function getTableNames(): array
5255
{
53-
return array_keys($this->tableNames);
56+
return array_keys($this->tableTypes);
5457
}
5558

5659
public function removeTable(String $tableName): bool
5760
{
58-
if (!isset($this->tableNames[$tableName])) {
61+
if (!isset($this->tableTypes[$tableName])) {
5962
return false;
6063
}
61-
unset($this->tableNames[$tableName]);
64+
unset($this->tableTypes[$tableName]);
6265
return true;
6366
}
6467

6568
public function serialize()
6669
{
6770
return [
6871
'name' => $this->name,
69-
'tables' => array_keys($this->tableNames),
72+
'tables' => $this->tableTypes,
7073
];
7174
}
7275

src/Tqdev/PhpCrudApi/Column/Reflection/ReflectedTable.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@
66
class ReflectedTable implements \JsonSerializable
77
{
88
private $name;
9+
private $type;
910
private $columns;
1011
private $pk;
1112
private $fks;
1213

13-
public function __construct(String $name, array $columns)
14+
public function __construct(String $name, String $type, array $columns)
1415
{
1516
$this->name = $name;
17+
$this->type = $type;
1618
// set columns
1719
$this->columns = [];
1820
foreach ($columns as $column) {
@@ -37,11 +39,11 @@ public function __construct(String $name, array $columns)
3739
}
3840
}
3941

40-
public static function fromReflection(GenericReflection $reflection, String $name): ReflectedTable
42+
public static function fromReflection(GenericReflection $reflection, String $name, String $type): ReflectedTable
4143
{
4244
// set columns
4345
$columns = [];
44-
foreach ($reflection->getTableColumns($name) as $tableColumn) {
46+
foreach ($reflection->getTableColumns($name, $type) as $tableColumn) {
4547
$column = ReflectedColumn::fromReflection($reflection, $tableColumn);
4648
$columns[$column->getName()] = $column;
4749
}
@@ -59,19 +61,20 @@ public static function fromReflection(GenericReflection $reflection, String $nam
5961
foreach ($fks as $columnName => $table) {
6062
$columns[$columnName]->setFk($table);
6163
}
62-
return new ReflectedTable($name, array_values($columns));
64+
return new ReflectedTable($name, $type, array_values($columns));
6365
}
6466

6567
public static function fromJson( /* object */$json): ReflectedTable
6668
{
6769
$name = $json->name;
70+
$type = $json->type;
6871
$columns = [];
6972
if (isset($json->columns) && is_array($json->columns)) {
7073
foreach ($json->columns as $column) {
7174
$columns[] = ReflectedColumn::fromJson($column);
7275
}
7376
}
74-
return new ReflectedTable($name, $columns);
77+
return new ReflectedTable($name, $type, $columns);
7578
}
7679

7780
public function hasColumn(String $columnName): bool
@@ -94,6 +97,11 @@ public function getName(): String
9497
return $this->name;
9598
}
9699

100+
public function getType(): String
101+
{
102+
return $this->type;
103+
}
104+
97105
public function columnNames(): array
98106
{
99107
return array_keys($this->columns);
@@ -128,6 +136,7 @@ public function serialize()
128136
{
129137
return [
130138
'name' => $this->name,
139+
'type' => $this->type,
131140
'columns' => array_values($this->columns),
132141
];
133142
}

src/Tqdev/PhpCrudApi/Column/ReflectionService.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ private function loadTable(String $tableName, bool $useCache): ReflectedTable
4242
if ($data != '') {
4343
$table = ReflectedTable::fromJson(json_decode(gzuncompress($data)));
4444
} else {
45-
$table = ReflectedTable::fromReflection($this->db->reflection(), $tableName);
45+
$tableType = $this->database->getType($tableName);
46+
$table = ReflectedTable::fromReflection($this->db->reflection(), $tableName, $tableType);
4647
$data = gzcompress(json_encode($table, JSON_UNESCAPED_UNICODE));
4748
$this->cache->set("ReflectedTable($tableName)", $data, $this->ttl);
4849
}
@@ -64,6 +65,11 @@ public function hasTable(String $tableName): bool
6465
return $this->database->hasTable($tableName);
6566
}
6667

68+
public function getType(String $tableName): String
69+
{
70+
return $this->database->getType($tableName);
71+
}
72+
6773
public function getTable(String $tableName): ReflectedTable
6874
{
6975
if (!isset($this->tables[$tableName])) {

src/Tqdev/PhpCrudApi/Controller/RecordController.php

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,14 @@ public function _list(Request $request): Response
3737
public function read(Request $request): Response
3838
{
3939
$table = $request->getPathSegment(2);
40-
$id = $request->getPathSegment(3);
41-
$params = $request->getParams();
4240
if (!$this->service->hasTable($table)) {
4341
return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
4442
}
43+
if ($this->service->getType($table) != 'table') {
44+
return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
45+
}
46+
$id = $request->getPathSegment(3);
47+
$params = $request->getParams();
4548
if (strpos($id, ',') !== false) {
4649
$ids = explode(',', $id);
4750
$result = [];
@@ -61,14 +64,17 @@ public function read(Request $request): Response
6164
public function create(Request $request): Response
6265
{
6366
$table = $request->getPathSegment(2);
67+
if (!$this->service->hasTable($table)) {
68+
return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
69+
}
70+
if ($this->service->getType($table) != 'table') {
71+
return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
72+
}
6473
$record = $request->getBody();
6574
if ($record === null) {
6675
return $this->responder->error(ErrorCode::HTTP_MESSAGE_NOT_READABLE, '');
6776
}
6877
$params = $request->getParams();
69-
if (!$this->service->hasTable($table)) {
70-
return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
71-
}
7278
if (is_array($record)) {
7379
$result = array();
7480
foreach ($record as $r) {
@@ -83,15 +89,18 @@ public function create(Request $request): Response
8389
public function update(Request $request): Response
8490
{
8591
$table = $request->getPathSegment(2);
92+
if (!$this->service->hasTable($table)) {
93+
return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
94+
}
95+
if ($this->service->getType($table) != 'table') {
96+
return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
97+
}
8698
$id = $request->getPathSegment(3);
99+
$params = $request->getParams();
87100
$record = $request->getBody();
88101
if ($record === null) {
89102
return $this->responder->error(ErrorCode::HTTP_MESSAGE_NOT_READABLE, '');
90103
}
91-
$params = $request->getParams();
92-
if (!$this->service->hasTable($table)) {
93-
return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
94-
}
95104
$ids = explode(',', $id);
96105
if (is_array($record)) {
97106
if (count($ids) != count($record)) {
@@ -113,11 +122,14 @@ public function update(Request $request): Response
113122
public function delete(Request $request): Response
114123
{
115124
$table = $request->getPathSegment(2);
116-
$id = $request->getPathSegment(3);
117-
$params = $request->getParams();
118125
if (!$this->service->hasTable($table)) {
119126
return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
120127
}
128+
if ($this->service->getType($table) != 'table') {
129+
return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
130+
}
131+
$id = $request->getPathSegment(3);
132+
$params = $request->getParams();
121133
$ids = explode(',', $id);
122134
if (count($ids) > 1) {
123135
$result = array();
@@ -133,15 +145,18 @@ public function delete(Request $request): Response
133145
public function increment(Request $request): Response
134146
{
135147
$table = $request->getPathSegment(2);
148+
if (!$this->service->hasTable($table)) {
149+
return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
150+
}
151+
if ($this->service->getType($table) != 'table') {
152+
return $this->responder->error(ErrorCode::OPERATION_NOT_SUPPORTED, __FUNCTION__);
153+
}
136154
$id = $request->getPathSegment(3);
137155
$record = $request->getBody();
138156
if ($record === null) {
139157
return $this->responder->error(ErrorCode::HTTP_MESSAGE_NOT_READABLE, '');
140158
}
141159
$params = $request->getParams();
142-
if (!$this->service->hasTable($table)) {
143-
return $this->responder->error(ErrorCode::TABLE_NOT_FOUND, $table);
144-
}
145160
$ids = explode(',', $id);
146161
if (is_array($record)) {
147162
if (count($ids) != count($record)) {

src/Tqdev/PhpCrudApi/Database/GenericReflection.php

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@ public function getIgnoredTables(): array
2020
{
2121
switch ($this->driver) {
2222
case 'mysql':return [];
23-
case 'pgsql':return ['spatial_ref_sys'];
23+
case 'pgsql':return ['spatial_ref_sys', 'raster_columns', 'raster_overviews', 'geography_columns', 'geometry_columns'];
2424
case 'sqlsrv':return [];
2525
}
2626
}
2727

2828
private function getTablesSQL(): String
2929
{
3030
switch ($this->driver) {
31-
case 'mysql':return 'SELECT "TABLE_NAME" FROM "INFORMATION_SCHEMA"."TABLES" WHERE "TABLE_TYPE" IN (\'BASE TABLE\') AND "TABLE_SCHEMA" = ? ORDER BY BINARY "TABLE_NAME"';
32-
case 'pgsql':return 'SELECT c.relname as "TABLE_NAME" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN (\'r\') AND n.nspname <> \'pg_catalog\' AND n.nspname <> \'information_schema\' AND n.nspname !~ \'^pg_toast\' AND pg_catalog.pg_table_is_visible(c.oid) AND \'\' <> ? ORDER BY "TABLE_NAME";';
33-
case 'sqlsrv':return 'SELECT o.name as "TABLE_NAME" FROM sysobjects o WHERE o.xtype = \'U\' ORDER BY "TABLE_NAME"';
31+
case 'mysql':return 'SELECT "TABLE_NAME", "TABLE_TYPE" FROM "INFORMATION_SCHEMA"."TABLES" WHERE "TABLE_TYPE" IN (\'BASE TABLE\' , \'VIEW\') AND "TABLE_SCHEMA" = ? ORDER BY BINARY "TABLE_NAME"';
32+
case 'pgsql':return 'SELECT c.relname as "TABLE_NAME", c.relkind as "TABLE_TYPE" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN (\'r\', \'v\') AND n.nspname <> \'pg_catalog\' AND n.nspname <> \'information_schema\' AND n.nspname !~ \'^pg_toast\' AND pg_catalog.pg_table_is_visible(c.oid) AND \'\' <> ? ORDER BY "TABLE_NAME";';
33+
case 'sqlsrv':return 'SELECT o.name as "TABLE_NAME", o.xtype as "TABLE_TYPE" FROM sysobjects o WHERE o.xtype IN (\'U\', \'V\') ORDER BY "TABLE_NAME"';
3434
}
3535
}
3636

@@ -69,13 +69,36 @@ public function getDatabaseName(): String
6969
public function getTables(): array
7070
{
7171
$sql = $this->getTablesSQL();
72-
return $this->query($sql, [$this->database]);
72+
$results = $this->query($sql, [$this->database]);
73+
foreach ($results as &$result) {
74+
switch ($this->driver) {
75+
case 'mysql':
76+
$map = ['BASE TABLE' => 'table', 'VIEW' => 'view'];
77+
$result['TABLE_TYPE'] = $map[$result['TABLE_TYPE']];
78+
break;
79+
case 'pgsql':
80+
$map = ['r' => 'table', 'v' => 'view'];
81+
$result['TABLE_TYPE'] = $map[$result['TABLE_TYPE']];
82+
break;
83+
case 'sqlsrv':
84+
$map = ['U' => 'table', 'V' => 'view'];
85+
$result['TABLE_TYPE'] = $map[trim($result['TABLE_TYPE'])];
86+
break;
87+
}
88+
}
89+
return $results;
7390
}
7491

75-
public function getTableColumns(String $tableName): array
92+
public function getTableColumns(String $tableName, String $type): array
7693
{
7794
$sql = $this->getTableColumnsSQL();
78-
return $this->query($sql, [$tableName, $this->database]);
95+
$results = $this->query($sql, [$tableName, $this->database]);
96+
if ($type == 'view') {
97+
foreach ($results as &$result) {
98+
$result['IS_NULLABLE'] = false;
99+
}
100+
}
101+
return $results;
79102
}
80103

81104
public function getTablePrimaryKeys(String $tableName): array

src/Tqdev/PhpCrudApi/OpenApi/OpenApiBuilder.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,16 @@ private function isOperationOnColumnAllowed(String $operation, String $tableName
8787
private function setPath(String $tableName) /*: void*/
8888
{
8989
$table = $this->reflection->getTable($tableName);
90+
$type = $table->getType($tableName);
9091
$pk = $table->getPk();
9192
$pkName = $pk ? $pk->getName() : '';
9293
foreach ($this->operations as $operation => $method) {
9394
if (!$pkName && $operation != 'list') {
9495
continue;
9596
}
97+
if ($type != 'table' && $operation != 'list') {
98+
continue;
99+
}
96100
if (!$this->isOperationOnTableAllowed($operation, $tableName)) {
97101
continue;
98102
}

src/Tqdev/PhpCrudApi/Record/ErrorCode.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class ErrorCode
2626
const ACCESS_DENIED = 1012;
2727
const INPUT_VALIDATION_FAILED = 1013;
2828
const OPERATION_FORBIDDEN = 1014;
29+
const OPERATION_NOT_SUPPORTED = 1015;
2930

3031
private $values = [
3132
9999 => ["%s", Response::INTERNAL_SERVER_ERROR],
@@ -44,6 +45,7 @@ class ErrorCode
4445
1012 => ["Access denied for '%s'", Response::FORBIDDEN],
4546
1013 => ["Input validation failed for '%s'", Response::UNPROCESSABLE_ENTITY],
4647
1014 => ["Operation forbidden", Response::FORBIDDEN],
48+
1015 => ["Operation '%s' not supported", Response::METHOD_NOT_ALLOWED],
4749
];
4850

4951
public function __construct(int $code)

src/Tqdev/PhpCrudApi/Record/RecordService.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ public function hasTable(String $table): bool
5050
return $this->reflection->hasTable($table);
5151
}
5252

53+
public function getType(String $table): bool
54+
{
55+
return $this->reflection->getType($table);
56+
}
57+
5358
public function create(String $tableName, /* object */ $record, array $params)
5459
{
5560
$this->sanitizeRecord($tableName, $record, '');

src/Tqdev/PhpCrudApi/Response.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ class Response
77
const UNAUTHORIZED = 401;
88
const FORBIDDEN = 403;
99
const NOT_FOUND = 404;
10+
const METHOD_NOT_ALLOWED = 405;
1011
const CONFLICT = 409;
1112
const UNPROCESSABLE_ENTITY = 422;
1213
const INTERNAL_SERVER_ERROR = 500;
13-
1414
private $status;
1515
private $headers;
1616
private $body;

0 commit comments

Comments
 (0)