Files
rspade_system/app/RSpade/SchemaQuality/SchemaQualityChecker.php
root 29c657f7a7 Exclude tests directory from framework publish
Add 100+ automated unit tests from .expect file specifications
Add session system test
Add rsx:constants:regenerate command test
Add rsx:logrotate command test
Add rsx:clean command test
Add rsx:manifest:stats command test
Add model enum system test
Add model mass assignment prevention test
Add rsx:check command test
Add migrate:status command test

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-25 03:59:58 +00:00

194 lines
5.8 KiB
PHP

<?php
namespace App\RSpade\SchemaQuality;
use App\RSpade\SchemaQuality\Rules\Schema_Rule_Abstract;
use Illuminate\Support\Facades\DB;
use App\RSpade\Core\Manifest\Manifest;
class SchemaQualityChecker
{
protected array $violations = [];
protected array $rules = [];
public function __construct()
{
$this->load_rules();
}
/**
* Load all schema rules
*/
protected function load_rules(): void
{
// Directly instantiate known rules
// (Manifest auto-discovery not working for this directory yet)
$rule_classes = [
\App\RSpade\SchemaQuality\Rules\SessionIdForeignKeyRule::class,
\App\RSpade\SchemaQuality\Rules\IdColumnTypeRule::class,
\App\RSpade\SchemaQuality\Rules\BooleanColumnTypeRule::class,
];
foreach ($rule_classes as $rule_class) {
if (!class_exists($rule_class)) {
throw new \RuntimeException(
"Fatal: SchemaQualityChecker expected rule class '{$rule_class}' to exist but it was not found. " .
"This is a framework integrity error - all schema quality rules must exist."
);
}
$this->rules[] = new $rule_class();
}
}
/**
* Run all schema checks
*/
public function check(): array
{
$this->violations = [];
// Get database schema information
$schema = $this->get_database_schema();
// Run each rule
foreach ($this->rules as $rule) {
$rule->clear_violations();
$rule->check($schema);
// Collect violations
foreach ($rule->get_violations() as $violation) {
$this->violations[] = $violation;
}
}
return $this->violations;
}
/**
* Get database schema information
*/
protected function get_database_schema(): array
{
$schema = ['tables' => []];
// Get all tables
$tables = DB::select("SHOW TABLES");
$db_name = DB::getDatabaseName();
$table_key = "Tables_in_{$db_name}";
foreach ($tables as $table) {
$table_name = $table->$table_key;
// Get columns
$columns = DB::select("SHOW COLUMNS FROM `{$table_name}`");
$column_data = [];
foreach ($columns as $column) {
$column_data[] = [
'name' => $column->Field,
'type' => $column->Type,
'nullable' => $column->Null,
'key' => $column->Key,
'default' => $column->Default,
'extra' => $column->Extra,
];
}
// Get foreign keys
$fk_query = "
SELECT
k.COLUMN_NAME as column_name,
k.REFERENCED_TABLE_NAME as referenced_table,
k.REFERENCED_COLUMN_NAME as referenced_column,
k.CONSTRAINT_NAME as constraint_name,
r.DELETE_RULE as delete_rule
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE k
JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS r
ON k.CONSTRAINT_NAME = r.CONSTRAINT_NAME
AND k.CONSTRAINT_SCHEMA = r.CONSTRAINT_SCHEMA
WHERE k.TABLE_SCHEMA = ?
AND k.TABLE_NAME = ?
AND k.REFERENCED_TABLE_NAME IS NOT NULL
";
$foreign_keys = DB::select($fk_query, [$db_name, $table_name]);
$fk_data = [];
foreach ($foreign_keys as $fk) {
$fk_data[] = [
'column' => $fk->column_name,
'referenced_table' => $fk->referenced_table,
'referenced_column' => $fk->referenced_column,
'constraint_name' => $fk->constraint_name,
'delete_rule' => $fk->delete_rule,
];
}
$schema['tables'][$table_name] = [
'columns' => $column_data,
'foreign_keys' => $fk_data,
];
}
return $schema;
}
/**
* Get violations grouped by severity
*/
public function get_violations_by_severity(): array
{
$grouped = [
'critical' => [],
'high' => [],
'medium' => [],
'low' => [],
];
foreach ($this->violations as $violation) {
$grouped[$violation->severity][] = $violation;
}
return $grouped;
}
/**
* Check if there are any violations
*/
public function has_violations(): bool
{
return count($this->violations) > 0;
}
/**
* Get total violation count
*/
public function get_violation_count(): int
{
return count($this->violations);
}
/**
* Format violations for display
*/
public function format_violations(): string
{
if (empty($this->violations)) {
return "✅ No schema violations found.\n";
}
$output = "❌ Found " . count($this->violations) . " schema violation(s):\n\n";
$grouped = $this->get_violations_by_severity();
foreach (['critical', 'high', 'medium', 'low'] as $severity) {
if (!empty($grouped[$severity])) {
$output .= strtoupper($severity) . " VIOLATIONS:\n";
foreach ($grouped[$severity] as $violation) {
$output .= $violation->format_output();
}
$output .= "\n";
}
}
return $output;
}
}