add_violation( $file_path, $fetch_line, "fetch() method must be static", trim($lines[$fetch_line - 1]), "Add 'static' keyword to the fetch() method declaration", 'high' ); } // Rule 2: Check parameters - must be exactly one: $id or int $id if (preg_match('/function\s+fetch\s*\((.*?)\)/', $fetch_signature, $matches)) { $params = trim($matches[1]); // Remove type hints and default values for analysis $param_parts = explode(',', $params); if (count($param_parts) !== 1) { $this->add_violation( $file_path, $fetch_line, "fetch() must take exactly one parameter: \$id", trim($lines[$fetch_line - 1]), "Change fetch() signature to accept only one parameter named \$id", 'high' ); } else { // Check that the parameter is $id (with optional int type hint) $param = trim($param_parts[0]); if (!preg_match('/^(\??int\s+)?\$id$/', $param)) { $this->add_violation( $file_path, $fetch_line, "fetch() parameter must be named \$id (optionally typed as int)", trim($lines[$fetch_line - 1]), "Rename the parameter to \$id", 'high' ); } } } // Rule 3: Check for is_array($id) pattern $fetch_body = implode("\n", $fetch_content); if (preg_match('/\bis_array\s*\(\s*\$id\s*\)/', $fetch_body)) { // Find the line number for ($i = 0; $i < count($fetch_content); $i++) { if (preg_match('/\bis_array\s*\(\s*\$id\s*\)/', $fetch_content[$i])) { $array_check_line = $fetch_line + $i + 1; $this->add_violation( $file_path, $array_check_line, "fetch() must not handle arrays. The framework will split arrays and call fetch() for each ID individually.", trim($lines[$array_check_line - 1]), "Remove is_array(\$id) checks and only handle single IDs", 'high' ); break; } } } // Rule 4: Special handling for Rsx_Model_Abstract base class if ($is_base_model) { // Check that it only throws an exception if (!preg_match('/throw\s+new\s+\\\\?RuntimeException/', $fetch_body)) { $this->add_violation( $file_path, $fetch_line, "Rsx_Model_Abstract's fetch() must throw a RuntimeException to indicate it's abstract", trim($lines[$fetch_line - 1]), "Replace method body with: throw new \\RuntimeException(...)", 'critical' ); } // Check for return statements (not in strings) // Remove string content to avoid false positives $fetch_body_no_strings = preg_replace('/(["\'])(?:[^\\\\]|\\\\.)*?\1/', '', $fetch_body); if (preg_match('/\breturn\b(?!\s*["\'])/', $fetch_body_no_strings)) { // Find the line number for ($i = 0; $i < count($fetch_content); $i++) { $line_no_strings = preg_replace('/(["\'])(?:[^\\\\]|\\\\.)*?\1/', '', $fetch_content[$i]); if (preg_match('/\breturn\b(?!\s*["\'])/', $line_no_strings)) { $return_line = $fetch_line + $i + 1; $this->add_violation( $file_path, $return_line, "Rsx_Model_Abstract's fetch() must not have return statements - it should only throw an exception", trim($lines[$return_line - 1]), "Remove the return statement", 'critical' ); break; } } } } } }