Skip to content

Commit fc0eda6

Browse files
author
Chris Zuber
authored
Merge pull request #28 from superuserdev/feature/27
Add missing tables and check db tables vs. PHP classes
2 parents 102c647 + 90688ac commit fc0eda6

16 files changed

Lines changed: 374 additions & 189 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/npm-debug.log
55
*.min*
66
*.map
7-
/creds.json
7+
/.tests/creds.json
88

99
#################
1010
## Eclipse

.lint.php

Lines changed: 0 additions & 45 deletions
This file was deleted.

.tests/consts.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
namespace SuperUserDev\SchemaServer\Tests\Consts;
3+
const CREDS = __DIR__ . DIRECTORY_SEPARATOR . 'creds.json';
4+
const MIN_PHP_VERSION = '7.1';
5+
const DB_TEST = true;
6+
const EXTS = ['php'];
7+
const IGNORED_DIRS = ['.git', '.tests'];
8+
const TZONE = 'America/Los_Angeles';
9+
const CLI_SAPI = ['cli', 'cli-server'];

.tests/funcs.php

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
<?php
2+
namespace SuperUserDev\SchemaServer\Tests\Funcs;
3+
4+
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'consts.php');
5+
6+
use const \SuperUserDev\SchemaServer\Tests\Consts\{
7+
MIN_PHP_VERSION,
8+
CREDS,
9+
DB_TEST,
10+
EXTS,
11+
IGNORED_DIRS,
12+
TZONE,
13+
CLI_SAPI
14+
};
15+
16+
use \PDO;
17+
use \SuperUserDev\SchemaServer\Thing;
18+
use \DirectoryIterator;
19+
use \Throwable;
20+
use \Exception;
21+
use \ErrorException;
22+
use \InvalidArgumentException;
23+
24+
function exception_handler(Throwable $e): Void
25+
{
26+
$code = $e->getCode();
27+
if (is_int($code) and $code > 299 and $code < 600) {
28+
http_response_code($code);
29+
} else {
30+
http_response_code(500);
31+
}
32+
echo json_encode([
33+
'message' => $e->getMessage(),
34+
'line' => $e->getLine(),
35+
'file' => $e->getFile(),
36+
'trace' => $e->getTrace(),
37+
], JSON_PRETTY_PRINT);
38+
}
39+
40+
function error_handler(
41+
Int $errno,
42+
String $errstr,
43+
String $errfile,
44+
Int $errline
45+
): Void
46+
{
47+
throw new ErrorException($errstr, $errno, $errno, $errfile, $errline);
48+
}
49+
50+
function lint_dir(String $dir = __DIR__): Bool
51+
{
52+
$paths = new DirectoryIterator($dir);
53+
$valid = true;
54+
foreach ($paths as $path) {
55+
if (
56+
$path->isDir()
57+
and ! $path->isDot()
58+
and ! in_array($path, IGNORED_DIRS)
59+
and ! call_user_func(__FUNCTION__, $path->getPathname())
60+
) {
61+
$valid = false;
62+
break;
63+
} elseif (
64+
$path->isFile()
65+
and in_array($path->getExtension(), EXTS)
66+
) {
67+
if (! lint_file($path->getPathname())) {
68+
$valid = false;
69+
break;
70+
} elseif (dirname($path->getPathname()) === dirname(__DIR__) and ! valid_class($path)) {
71+
throw new Exception("{$path->getBaseName()} does not contain a valid class");
72+
}
73+
}
74+
}
75+
return $valid;
76+
}
77+
78+
function lint_file(String $path): Bool
79+
{
80+
$path = escapeshellarg($path);
81+
$msg = exec("php -l {$path}", $arr, $return_var);
82+
if ($return_var !== 0) {
83+
throw new Exception($msg);
84+
} else {
85+
return true;
86+
}
87+
}
88+
89+
function valid_class(DirectoryIterator $path, Array $tables = []): Bool
90+
{
91+
$ext = ".{$path->getExtension()}";
92+
$fname = $path->getBaseName($ext);
93+
$class = "\\SuperUserDev\\SchemaServer\\{$fname}";
94+
return class_exists($class);
95+
}
96+
97+
function gravatar(String $email, Int $size = 80): String
98+
{
99+
return sprintf(
100+
'https://gravatar.com/avatar/%s?s=%d',
101+
md5($email),
102+
$size
103+
);
104+
}
105+
106+
function init(): Void
107+
{
108+
if (version_compare(PHP_VERSION, MIN_PHP_VERSION, '<')) {
109+
throw new Exception(sprintf('PHP version %s or greater is required', MIN_PHP_VERSION));
110+
} elseif (in_array(PHP_SAPI, ['cli'])) {
111+
set_include_path(dirname(__DIR__, 3) . PATH_SEPARATOR . get_include_path());
112+
spl_autoload_register('spl_autoload');
113+
spl_autoload_extensions('.php');
114+
date_default_timezone_set(TZONE);
115+
set_exception_handler(__NAMESPACE__ . '\\exception_handler');
116+
set_error_handler(__NAMESPACE__ . '\\error_handler', E_ALL);
117+
} else {
118+
throw new Exception('Request is only available through CLI', 400);
119+
}
120+
}
121+
122+
function get_db_tables(PDO $pdo, $schema = 'public'): Array
123+
{
124+
$stm = $pdo->prepare('SELECT "table_name" AS "name"
125+
FROM information_schema.tables
126+
WHERE table_schema = :schema;');
127+
128+
$stm->bindValue(':schema', $schema);
129+
$stm->execute();
130+
return array_map(function(Array $result): String
131+
{
132+
return strtolower($result['name']);
133+
}, $stm->fetchAll());
134+
}
135+
136+
function get_missing_classes(Array $tables): Array
137+
{
138+
$base = dirname(__DIR__);
139+
return array_filter($tables, function(String $table) use ($base): Bool
140+
{
141+
return ! file_exists($base . DIRECTORY_SEPARATOR . $table . '.php');
142+
});
143+
}
144+
145+
function get_missing_tables(Array $tables, $dir = __DIR__): Array
146+
{
147+
$paths = new DirectoryIterator($dir);
148+
$missing = [];
149+
foreach ($paths as $path) {
150+
if (
151+
$path->isFile()
152+
and in_array($path->getExtension(), EXTS)
153+
and ! in_array($path->getBaseName(".{$path->getExtension()}"), $tables)
154+
) {
155+
array_push($missing, $path->getBaseName());
156+
}
157+
}
158+
return $missing;
159+
}
160+
161+
function connect(String $creds_file = CREDS): PDO
162+
{
163+
static $pdo = null;
164+
if (! isset($pdo)) {
165+
if (file_exists($creds_file)) {
166+
$creds = $creds = json_decode(file_get_contents(CREDS));
167+
$pdo = Thing::connect($creds->user, $creds->pass ?? '', $creds->dbname ?? $creds->user);
168+
} else {
169+
$pdo = Thing::connect('postgres', '', 'schema');
170+
}
171+
}
172+
return $pdo;
173+
}

.tests/lint.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace SuperUserDev\SchemaServer\Tests;
4+
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'funcs.php');
5+
6+
use const \SuperUserDev\SchemaServer\Tests\Consts\{
7+
CREDS,
8+
DB_TEST
9+
};
10+
11+
use function \SuperUserDev\SchemaServer\Tests\Funcs\{
12+
lint_dir,
13+
init,
14+
connect,
15+
get_db_tables,
16+
get_missing_classes,
17+
get_missing_tables
18+
};
19+
20+
use \SuperUserDev\SchemaServer\{
21+
Thing
22+
};
23+
use \PDO;
24+
use \DirectoryIterator;
25+
use \Throwable;
26+
use \Exception;
27+
use \ErrorException;
28+
29+
30+
init();
31+
32+
lint_dir(dirname(__DIR__));
33+
34+
$tables = get_db_tables(connect());
35+
$missing_tables = get_missing_tables($tables, dirname(__DIR__));
36+
37+
if (! empty($missing_tables)) {
38+
throw new Exception(sprintf('Missing database tables for [%s]', join(', ', $missing_tables)));
39+
}
40+
unset($missing_tables);
41+
$missing_files = get_missing_classes($tables);
42+
unset($tables);
43+
44+
if (! empty($missing_files)) {
45+
throw new Exception(sprintf('Missing PHP classes for [%s]', join(', ', $missing_files)));
46+
}

0 commit comments

Comments
 (0)