Skip to content

Commit 3abfaf5

Browse files
committed
initial import
1 parent 86a5689 commit 3abfaf5

16 files changed

Lines changed: 2573 additions & 1 deletion

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2026 Joe Huss
3+
Copyright (c) 2025 InterServer — Joe Huss <detain@interserver.net>
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 684 additions & 0 deletions
Large diffs are not rendered by default.

composer.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name": "detain/dbrel-data-php",
3+
"description": "PHP backend data provider for database relationship visualization. Collects customer data across multiple databases and computes relationship matches for consumption by dbrel-viz.",
4+
"type": "library",
5+
"license": "MIT",
6+
"keywords": [
7+
"database",
8+
"visualization",
9+
"relationships",
10+
"schema",
11+
"mysql",
12+
"erd",
13+
"dataviz"
14+
],
15+
"authors": [
16+
{
17+
"name": "Joe Huss",
18+
"email": "detain@interserver.net"
19+
}
20+
],
21+
"homepage": "https://github.com/detain/dbrel-data-php",
22+
"support": {
23+
"issues": "https://github.com/detain/dbrel-data-php/issues",
24+
"source": "https://github.com/detain/dbrel-data-php"
25+
},
26+
"require": {
27+
"php": ">=7.4",
28+
"ext-json": "*",
29+
"ext-mysqli": "*"
30+
},
31+
"require-dev": {
32+
"phpunit/phpunit": "^9.6"
33+
},
34+
"autoload": {
35+
"psr-4": {
36+
"DbRel\\Data\\": "src/"
37+
}
38+
},
39+
"autoload-dev": {
40+
"psr-4": {
41+
"DbRel\\Data\\Tests\\": "tests/"
42+
}
43+
},
44+
"scripts": {
45+
"test": "phpunit",
46+
"test:coverage": "phpunit --coverage-html coverage"
47+
},
48+
"config": {
49+
"sort-packages": true
50+
},
51+
"minimum-stability": "stable"
52+
}

examples/basic-usage.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
/**
3+
* Minimal example: use dbrel-data-php to build a visualization payload.
4+
*
5+
* This example uses PDO directly. In production, wrap your DB layer
6+
* with an implementation of DbRel\Data\DbInterface.
7+
*/
8+
9+
require_once __DIR__ . '/../src/DbInterface.php';
10+
require_once __DIR__ . '/../src/RelationshipSchema.php';
11+
require_once __DIR__ . '/../src/RelationshipMatcher.php';
12+
require_once __DIR__ . '/../src/DataCollector.php';
13+
require_once __DIR__ . '/../src/DataProvider.php';
14+
15+
use DbRel\Data\RelationshipSchema;
16+
use DbRel\Data\DataCollector;
17+
use DbRel\Data\DataProvider;
18+
19+
// 1. Load your relationship schema
20+
$schema = new RelationshipSchema(__DIR__ . '/sample_schema.json');
21+
22+
// 2. Collect data. For this example, we skip DB queries and addTable() directly.
23+
$collector = new DataCollector();
24+
$collector->addTable('my', 'accounts', [
25+
['account_id' => 123, 'account_email' => 'user@example.com'],
26+
], ['account_id', 'account_email']);
27+
28+
$collector->addTable('my', 'vps', [
29+
['vps_id' => 1, 'vps_custid' => 123, 'vps_hostname' => 'server1.example.com'],
30+
['vps_id' => 2, 'vps_custid' => 123, 'vps_hostname' => 'server2.example.com'],
31+
], ['vps_id', 'vps_custid', 'vps_hostname']);
32+
33+
// 3. Build the response payload
34+
$provider = new DataProvider($schema);
35+
$response = $provider->build($collector, [
36+
'custid' => 123,
37+
'primaryKeys' => [
38+
'accounts' => 'account_id',
39+
'vps' => 'vps_id',
40+
],
41+
'prefixes' => [
42+
'accounts' => 'account_',
43+
'vps' => 'vps_',
44+
],
45+
'hiddenFields' => ['password'],
46+
]);
47+
48+
// 4. Return as JSON
49+
header('Content-Type: application/json');
50+
echo json_encode($response, JSON_PRETTY_PRINT);

phpunit.xml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
colors="true"
6+
verbose="true"
7+
cacheResultFile=".phpunit.result.cache"
8+
convertErrorsToExceptions="true"
9+
convertNoticesToExceptions="true"
10+
convertWarningsToExceptions="true"
11+
beStrictAboutTestsThatDoNotTestAnything="false">
12+
<testsuites>
13+
<testsuite name="DbRel Data PHP">
14+
<directory suffix="Test.php">tests</directory>
15+
</testsuite>
16+
</testsuites>
17+
<coverage>
18+
<include>
19+
<directory suffix=".php">src</directory>
20+
</include>
21+
</coverage>
22+
</phpunit>

src/DataCollector.php

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
/**
3+
* Collects rows from database tables into a normalized structure.
4+
*
5+
* Thin wrapper around a DbInterface that accumulates query results keyed by "db.table"
6+
* in the format expected by the visualization.
7+
*
8+
* @package DbRel\Data
9+
*/
10+
11+
namespace DbRel\Data;
12+
13+
class DataCollector
14+
{
15+
/** @var array [ 'db.table' => ['rows' => [...], 'total' => N, 'columns' => [...], 'truncated' => bool] ] */
16+
private $tables = [];
17+
18+
/**
19+
* Collect rows from a query into $tables.
20+
*
21+
* @param DbInterface $db The database handle to execute the query on
22+
* @param string $dbName The logical database name (e.g. 'my', 'kayako_v4', 'pdns')
23+
* @param string $table Table name
24+
* @param string $sql The SELECT query
25+
* @param int $limit Max rows to include in output
26+
*/
27+
public function collect(DbInterface $db, $dbName, $table, $sql, $limit = 50)
28+
{
29+
$key = $dbName . '.' . $table;
30+
$db->query($sql, __LINE__, __FILE__);
31+
$total = $db->num_rows();
32+
if ($total == 0) {
33+
return;
34+
}
35+
$rows = [];
36+
$columns = [];
37+
$count = 0;
38+
while ($db->next_record(1)) {
39+
$record = property_exists($db, 'Record') ? $db->Record : null;
40+
if ($record === null && method_exists($db, 'getRecord')) {
41+
$record = $db->getRecord();
42+
}
43+
if (!is_array($record)) continue;
44+
if (empty($columns)) {
45+
$columns = array_keys($record);
46+
}
47+
if ($count < $limit) {
48+
$rows[] = $record;
49+
}
50+
$count++;
51+
}
52+
$this->tables[$key] = [
53+
'rows' => $rows,
54+
'total' => $total,
55+
'columns' => $columns,
56+
'truncated' => $total > $limit,
57+
];
58+
}
59+
60+
/**
61+
* Manually add a table entry (useful for pivot tables like accounts_ext).
62+
*
63+
* @param string $dbName
64+
* @param string $table
65+
* @param array $rows
66+
* @param array $columns
67+
* @param int|null $total
68+
*/
69+
public function addTable($dbName, $table, array $rows, array $columns, $total = null)
70+
{
71+
$key = $dbName . '.' . $table;
72+
$this->tables[$key] = [
73+
'rows' => $rows,
74+
'total' => $total !== null ? $total : count($rows),
75+
'columns' => $columns,
76+
'truncated' => false,
77+
];
78+
}
79+
80+
/**
81+
* Append rows to an existing table (or create it if missing).
82+
*
83+
* @param string $dbName
84+
* @param string $table
85+
* @param array $rows
86+
*/
87+
public function appendRows($dbName, $table, array $rows)
88+
{
89+
$key = $dbName . '.' . $table;
90+
if (!isset($this->tables[$key])) {
91+
$columns = !empty($rows) ? array_keys($rows[0]) : [];
92+
$this->addTable($dbName, $table, $rows, $columns);
93+
} else {
94+
foreach ($rows as $row) {
95+
$this->tables[$key]['rows'][] = $row;
96+
$this->tables[$key]['total']++;
97+
}
98+
}
99+
}
100+
101+
/** @return array All collected tables */
102+
public function getTables()
103+
{
104+
return $this->tables;
105+
}
106+
107+
/**
108+
* Check if a given "db.table" key has been collected.
109+
* @param string $key
110+
* @return bool
111+
*/
112+
public function has($key)
113+
{
114+
return isset($this->tables[$key]);
115+
}
116+
117+
/**
118+
* Get rows for a collected table.
119+
* @param string $key "db.table" key
120+
* @return array
121+
*/
122+
public function getRows($key)
123+
{
124+
return isset($this->tables[$key]) ? $this->tables[$key]['rows'] : [];
125+
}
126+
127+
/** Total row count across all tables */
128+
public function getTotalRows()
129+
{
130+
$n = 0;
131+
foreach ($this->tables as $t) {
132+
$n += $t['total'];
133+
}
134+
return $n;
135+
}
136+
}

0 commit comments

Comments
 (0)