clqms-be/app/Models/Rule/RuleDefModel.php

281 lines
8.0 KiB
PHP
Raw Normal View History

<?php
namespace App\Models\Rule;
use App\Models\BaseModel;
/**
* RuleDef Model
*
* Rule definitions that can be linked to multiple tests via testrule mapping table.
*/
class RuleDefModel extends BaseModel
{
protected $table = 'ruledef';
protected $primaryKey = 'RuleID';
protected $allowedFields = [
'RuleCode',
'RuleName',
'Description',
'EventCode',
'ConditionExpr',
'ConditionExprCompiled',
'CreateDate',
'StartDate',
'EndDate',
];
protected $useTimestamps = true;
protected $createdField = 'CreateDate';
protected $updatedField = 'StartDate';
protected $useSoftDeletes = true;
protected $deletedField = 'EndDate';
/**
* Fetch active rules for an event scoped by TestSiteID.
*
* Rules are standalone and only apply when explicitly linked to a test
* via the testrule mapping table.
*
* @param string $eventCode The event code to filter by
* @param int|null $testSiteID The test site ID to filter by
* @return array Array of matching rules
*/
public function getActiveByEvent(string $eventCode, ?int $testSiteID = null): array
{
if ($testSiteID === null) {
return [];
}
return $this->select('ruledef.*')
->join('testrule', 'testrule.RuleID = ruledef.RuleID', 'inner')
->where('ruledef.EventCode', $eventCode)
->where('ruledef.EndDate IS NULL')
->where('testrule.TestSiteID', $testSiteID)
->where('testrule.EndDate IS NULL')
->orderBy('ruledef.RuleID', 'ASC')
->findAll();
}
/**
* Get all tests linked to a rule
*
* @param int $ruleID The rule ID
* @return array Array of test site IDs
*/
public function getLinkedTests(int $ruleID): array
{
$db = \Config\Database::connect();
$result = $db->table('testrule')
->where('RuleID', $ruleID)
->where('EndDate IS NULL')
->select('TestSiteID')
->get()
->getResultArray();
return array_column($result, 'TestSiteID');
}
/**
* Link a rule to a test
*
* @param int $ruleID The rule ID
* @param int $testSiteID The test site ID
* @return bool Success status
*/
public function linkTest(int $ruleID, int $testSiteID): bool
{
$db = \Config\Database::connect();
// Check if already linked (and not soft deleted)
$existing = $db->table('testrule')
->where('RuleID', $ruleID)
->where('TestSiteID', $testSiteID)
->where('EndDate IS NULL')
->first();
if ($existing) {
return true; // Already linked
}
// Check if soft deleted - restore it
$softDeleted = $db->table('testrule')
->where('RuleID', $ruleID)
->where('TestSiteID', $testSiteID)
->where('EndDate IS NOT NULL')
->first();
if ($softDeleted) {
return $db->table('testrule')
->where('TestRuleID', $softDeleted['TestRuleID'])
->update(['EndDate' => null]);
}
// Create new link
return $db->table('testrule')->insert([
'RuleID' => $ruleID,
'TestSiteID' => $testSiteID,
'CreateDate' => date('Y-m-d H:i:s'),
]);
}
/**
* Unlink a rule from a test (soft delete)
*
* @param int $ruleID The rule ID
* @param int $testSiteID The test site ID
* @return bool Success status
*/
public function unlinkTest(int $ruleID, int $testSiteID): bool
{
$db = \Config\Database::connect();
return $db->table('testrule')
->where('RuleID', $ruleID)
->where('TestSiteID', $testSiteID)
->where('EndDate IS NULL')
->update(['EndDate' => date('Y-m-d H:i:s')]);
}
/**
* Validate that a DSL expression compiles successfully
*
* @param string $expr The DSL expression to validate
* @return array ['valid' => bool, 'error' => string|null, 'compiled' => array|null]
*/
public function validateExpression(string $expr): array
{
$service = new \App\Services\RuleExpressionService();
try {
$compiled = $service->compile($expr);
return [
'valid' => true,
'error' => null,
'compiled' => $compiled,
];
} catch (\Throwable $e) {
return [
'valid' => false,
'error' => $e->getMessage(),
'compiled' => null,
];
}
}
/**
* Compile a DSL expression to JSON
*
* @param string $expr The DSL expression to compile
* @return array The compiled structure
* @throws \InvalidArgumentException If compilation fails
*/
public function compileExpression(string $expr): array
{
$service = new \App\Services\RuleExpressionService();
return $service->compile($expr);
}
/**
* Check if a rule's expression needs recompilation
* Returns true if the raw expression differs from the compiled version
*
* @param int $ruleID The rule ID to check
* @return bool True if recompilation is needed
*/
public function needsRecompilation(int $ruleID): bool
{
$rule = $this->find($ruleID);
if (!$rule) {
return false;
}
$rawExpr = $rule['ConditionExpr'] ?? '';
$compiledJson = $rule['ConditionExprCompiled'] ?? '';
if (empty($rawExpr)) {
return false;
}
if (empty($compiledJson)) {
return true;
}
// Try to compile current expression and compare with stored version
try {
$compiled = $this->compileExpression($rawExpr);
$stored = json_decode($compiledJson, true);
return json_encode($compiled) !== json_encode($stored);
} catch (\Throwable $e) {
return true;
}
}
/**
* Automatically compile expression if needed and update the rule
*
* @param int $ruleID The rule ID
* @return bool Success status
*/
public function autoCompile(int $ruleID): bool
{
if (!$this->needsRecompilation($ruleID)) {
return true;
}
$rule = $this->find($ruleID);
if (!$rule) {
return false;
}
$rawExpr = $rule['ConditionExpr'] ?? '';
if (empty($rawExpr)) {
return false;
}
try {
$compiled = $this->compileExpression($rawExpr);
return $this->update($ruleID, [
'ConditionExprCompiled' => json_encode($compiled),
]);
} catch (\Throwable $e) {
log_message('error', 'Auto-compile failed for RuleID=' . $ruleID . ': ' . $e->getMessage());
return false;
}
}
/**
* Compile all rules that need recompilation
*
* @return array ['success' => int, 'failed' => int]
*/
public function compileAllPending(): array
{
$rules = $this->where('EndDate IS NULL')->findAll();
$success = 0;
$failed = 0;
foreach ($rules as $rule) {
$ruleID = (int) ($rule['RuleID'] ?? 0);
if ($ruleID === 0) {
continue;
}
if ($this->needsRecompilation($ruleID)) {
if ($this->autoCompile($ruleID)) {
$success++;
} else {
$failed++;
}
}
}
return [
'success' => $success,
'failed' => $failed,
];
}
}