File manager - Edit - /home/webapp69.cm.in.th/u69319090028/Shop/app/Helpers/Validator.php
Back
<?php /** * Universal Form & API Input Validation Engine * Comprehensive Rules: Type, Format, Length, Range, Allowed Values, Existence, Thai ID Checksum, Database Checks */ require_once __DIR__ . '/../../database/Database.php'; require_once __DIR__ . '/../Core/Exceptions/ValidationException.php'; class Validator { private array $data; private array $rules; private array $errors = []; private array $validatedData = []; public function __construct(array $data, array $rules) { $this->data = $data; $this->rules = $rules; } public static function make(array $data, array $rules): self { return new self($data, $rules); } /** * Validate and throw ValidationException on failure, or return validated data */ public static function validateOrFail(array $data, array $rules): array { $validator = new self($data, $rules); if (!$validator->validate()) { throw new ValidationException($validator->errors()); } return $validator->validated(); } /** * Sanitize input by only keeping allowed fields and stripping harmful tags */ public static function sanitize(array $data, array $allowedFields = []): array { $cleaned = []; $targetData = !empty($allowedFields) ? array_intersect_key($data, array_flip($allowedFields)) : $data; foreach ($targetData as $key => $val) { if (is_string($val)) { $cleaned[$key] = trim(strip_tags($val)); } elseif (is_array($val)) { $cleaned[$key] = self::sanitize($val); } else { $cleaned[$key] = $val; } } return $cleaned; } public function validate(): bool { $this->errors = []; $this->validatedData = []; foreach ($this->rules as $field => $fieldRules) { $value = $this->data[$field] ?? null; $ruleList = is_string($fieldRules) ? explode('|', $fieldRules) : $fieldRules; foreach ($ruleList as $rule) { $params = []; if (strpos($rule, ':') !== false) { [$ruleName, $paramStr] = explode(':', $rule, 2); $params = explode(',', $paramStr); } else { $ruleName = $rule; } $this->applyRule($field, $value, $ruleName, $params); } if (!isset($this->errors[$field])) { $this->validatedData[$field] = $value; } } return empty($this->errors); } private function applyRule(string $field, $value, string $rule, array $params): void { // If field is optional and empty, skip further rules unless rule is required if ($rule !== 'required' && ($value === null || $value === '' || (is_array($value) && empty($value)))) { return; } switch ($rule) { // ── Required & Nullability ─────────────────────────────── case 'required': if ($value === null || (is_string($value) && trim($value) === '') || (is_array($value) && empty($value))) { $this->addError($field, "กรุณากรอกข้อมูล {$field}"); } break; case 'nullable': // Handled implicitly by the early return break; // ── Data Types ─────────────────────────────────────────── case 'numeric': if (!is_numeric($value)) { $this->addError($field, "ข้อมูล {$field} ต้องเป็นตัวเลข"); } break; case 'integer': case 'int': if (filter_var($value, FILTER_VALIDATE_INT) === false) { $this->addError($field, "ข้อมูล {$field} ต้องเป็นจำนวนเต็ม"); } break; case 'float': case 'decimal': if (filter_var($value, FILTER_VALIDATE_FLOAT) === false) { $this->addError($field, "ข้อมูล {$field} ต้องเป็นตัวเลขทศนิยม"); } break; case 'boolean': case 'bool': if (!in_array($value, [true, false, 1, 0, '1', '0', 'true', 'false'], true)) { $this->addError($field, "ข้อมูล {$field} ต้องเป็นค่าจริงหรือเท็จ (boolean)"); } break; case 'array': if (!is_array($value)) { $this->addError($field, "ข้อมูล {$field} ต้องเป็นชุดข้อมูล (array)"); } break; case 'string': if (!is_string($value)) { $this->addError($field, "ข้อมูล {$field} ต้องเป็นข้อความ"); } break; case 'json': if (!is_string($value) || json_decode($value) === null && json_last_error() !== JSON_ERROR_NONE) { $this->addError($field, "ข้อมูล {$field} ต้องอยู่ในรูปแบบ JSON ที่ถูกต้อง"); } break; // ── Formats & Regex ────────────────────────────────────── case 'email': if (!filter_var($value, FILTER_VALIDATE_EMAIL)) { $this->addError($field, "รูปแบบอีเมลไม่ถูกต้อง"); } break; case 'phone': // Thai 10-digit mobile/tel: 08x, 09x, 06x, 02x $cleanPhone = preg_replace('/[^0-9]/', '', (string)$value); if (!preg_match('/^0[0-9]{9}$/', $cleanPhone)) { $this->addError($field, "เบอร์โทรศัพท์ต้องเป็นตัวเลข 10 หลักและขึ้นต้นด้วย 0"); } break; case 'thai_id': // Thai 13-digit National ID Checksum Algorithm (Mod 11) $cleanId = preg_replace('/[^0-9]/', '', (string)$value); if (strlen($cleanId) !== 13) { $this->addError($field, "เลขบัตรประชาชนต้องเป็นตัวเลข 13 หลัก"); } else { $sum = 0; for ($i = 0; $i < 12; $i++) { $sum += ((int)$cleanId[$i]) * (13 - $i); } $check = (11 - ($sum % 11)) % 10; if ($check !== (int)$cleanId[12]) { $this->addError($field, "เลขบัตรประชาชนไม่ถูกต้องตามรูปแบบทางการ"); } } break; case 'url': if (!filter_var($value, FILTER_VALIDATE_URL)) { $this->addError($field, "รูปแบบ URL ไม่ถูกต้อง"); } break; case 'slug': if (!preg_match('/^[a-z0-9]+(?:-[a-z0-9]+)*$/', (string)$value)) { $this->addError($field, "สลัก (slug) ต้องประกอบด้วยตัวอักษรพิมพ์เล็ก ตัวเลข และเครื่องหมาย - เท่านั้น"); } break; case 'postal_code': if (!preg_match('/^[0-9]{5}$/', (string)$value)) { $this->addError($field, "รหัสไปรษณีย์ต้องเป็นตัวเลข 5 หลัก"); } break; case 'username': if (!preg_match('/^[a-zA-Z0-9_]{3,30}$/', (string)$value)) { $this->addError($field, "ชื่อผู้ใช้ต้องเป็นตัวอักษร ตัวเลข หรือ _ ความยาว 3-30 ตัวอักษร"); } break; case 'alpha': if (!preg_match('/^[\p{L}]+$/u', (string)$value)) { $this->addError($field, "ข้อมูล {$field} ต้องเป็นตัวอักษรเท่านั้น"); } break; case 'alpha_num': if (!preg_match('/^[\p{L}0-9]+$/u', (string)$value)) { $this->addError($field, "ข้อมูล {$field} ต้องเป็นตัวอักษรหรือตัวเลขเท่านั้น"); } break; case 'alpha_dash': if (!preg_match('/^[\p{L}0-9_-]+$/u', (string)$value)) { $this->addError($field, "ข้อมูล {$field} ต้องเป็นตัวอักษร ตัวเลข _ หรือ - เท่านั้น"); } break; case 'regex': $pattern = $params[0] ?? '//'; if (!preg_match($pattern, (string)$value)) { $this->addError($field, "ข้อมูล {$field} ไม่ตรงตามรูปแบบที่กำหนด"); } break; // ── Ranges & Comparisons ───────────────────────────────── case 'min': $min = (float)($params[0] ?? 0); if (is_numeric($value)) { if ((float)$value < $min) { $this->addError($field, "ค่าต้องไม่น้อยกว่า {$min}"); } } elseif (is_string($value)) { if (mb_strlen($value) < (int)$min) { $this->addError($field, "ความยาวต้องไม่น้อยกว่า {$min} ตัวอักษร"); } } elseif (is_array($value)) { if (count($value) < (int)$min) { $this->addError($field, "ต้องมีรายการอย่างน้อย {$min} รายการ"); } } break; case 'max': $max = (float)($params[0] ?? 255); if (is_numeric($value)) { if ((float)$value > $max) { $this->addError($field, "ค่าต้องไม่เกิน {$max}"); } } elseif (is_string($value)) { if (mb_strlen($value) > (int)$max) { $this->addError($field, "ความยาวต้องไม่เกิน {$max} ตัวอักษร"); } } elseif (is_array($value)) { if (count($value) > (int)$max) { $this->addError($field, "ต้องมีรายการไม่เกิน {$max} รายการ"); } } break; case 'between': $min = (float)($params[0] ?? 0); $max = (float)($params[1] ?? 0); if (is_numeric($value)) { if ((float)$value < $min || (float)$value > $max) { $this->addError($field, "ค่าต้องอยู่ระหว่าง {$min} ถึง {$max}"); } } else { $len = mb_strlen((string)$value); if ($len < (int)$min || $len > (int)$max) { $this->addError($field, "ความยาวต้องอยู่ระหว่าง {$min} ถึง {$max} ตัวอักษร"); } } break; case 'gt': $target = (float)($params[0] ?? 0); if ((float)$value <= $target) { $this->addError($field, "ค่าต้องมากกว่า {$target}"); } break; case 'gte': $target = (float)($params[0] ?? 0); if ((float)$value < $target) { $this->addError($field, "ค่าต้องมากกว่าหรือเท่ากับ {$target}"); } break; case 'lt': $target = (float)($params[0] ?? 0); if ((float)$value >= $target) { $this->addError($field, "ค่าต้องน้อยกว่า {$target}"); } break; case 'lte': $target = (float)($params[0] ?? 0); if ((float)$value > $target) { $this->addError($field, "ค่าต้องน้อยกว่าหรือเท่ากับ {$target}"); } break; // ── Whitelisting & In/Not In ────────────────────────────── case 'in': if (!in_array((string)$value, $params, true)) { $this->addError($field, "ค่า {$field} ต้องเป็นหนึ่งใน: " . implode(', ', $params)); } break; case 'not_in': if (in_array((string)$value, $params, true)) { $this->addError($field, "ค่า {$field} ไม่อยู่ในรายการที่อนุญาต"); } break; // ── Database Uniqueness & Existence ─────────────────────── case 'unique': // format: unique:table,column[,ignore_id,ignore_column] $table = $params[0] ?? 'users'; $column = $params[1] ?? $field; $ignoreId = $params[2] ?? null; $ignoreCol = $params[3] ?? 'id'; $db = Database::getInstance(); $sql = "SELECT COUNT(*) FROM `{$table}` WHERE `{$column}` = :val"; $bindings = [':val' => $value]; if ($ignoreId !== null && $ignoreId !== '') { $sql .= " AND `{$ignoreCol}` != :ignore_id"; $bindings[':ignore_id'] = $ignoreId; } $stmt = $db->prepare($sql); $stmt->execute($bindings); if ((int)$stmt->fetchColumn() > 0) { $this->addError($field, "ข้อมูล {$field} นี้ถูกใช้งานในระบบแล้ว"); } break; case 'exists': // format: exists:table,column $table = $params[0] ?? 'users'; $column = $params[1] ?? 'id'; $db = Database::getInstance(); $sql = "SELECT COUNT(*) FROM `{$table}` WHERE `{$column}` = :val"; $stmt = $db->prepare($sql); $stmt->execute([':val' => $value]); if ((int)$stmt->fetchColumn() === 0) { $this->addError($field, "ไม่พบข้อมูลที่อ้างอิงในระบบ"); } break; // ── Confirmation & Equality ────────────────────────────── case 'same': case 'matches': $targetField = $params[0] ?? ''; $targetVal = $this->data[$targetField] ?? null; if ($value !== $targetVal) { $this->addError($field, "ข้อมูล {$field} ไม่ตรงกับ {$targetField}"); } break; case 'confirmed': $confirmField = $field . '_confirmation'; $confirmVal = $this->data[$confirmField] ?? null; if ($value !== $confirmVal) { $this->addError($field, "การยืนยันรหัสผ่านไม่ตรงกัน"); } break; case 'different': $targetField = $params[0] ?? ''; $targetVal = $this->data[$targetField] ?? null; if ($value === $targetVal) { $this->addError($field, "ข้อมูล {$field} ต้องแตกต่างจาก {$targetField}"); } break; case 'accepted': if (!in_array($value, [1, '1', true, 'true', 'on', 'yes'], true)) { $this->addError($field, "คุณต้องกดยอมรับเงื่อนไขก่อนดำเนินการ"); } break; // ── Date Rules ─────────────────────────────────────────── case 'date': if (strtotime((string)$value) === false) { $this->addError($field, "วันที่ไม่ถูกต้อง"); } break; case 'date_format': $format = $params[0] ?? 'Y-m-d'; $d = DateTime::createFromFormat($format, (string)$value); if (!$d || $d->format($format) !== (string)$value) { $this->addError($field, "รูปแบบวันที่ต้องเป็น {$format}"); } break; case 'after': $targetDate = strtotime($params[0] ?? 'now'); $valDate = strtotime((string)$value); if ($valDate === false || $valDate <= $targetDate) { $this->addError($field, "วันที่ต้องอยู่หลังจาก " . date('Y-m-d', $targetDate)); } break; case 'before': $targetDate = strtotime($params[0] ?? 'now'); $valDate = strtotime((string)$value); if ($valDate === false || $valDate >= $targetDate) { $this->addError($field, "วันที่ต้องอยู่ก่อน " . date('Y-m-d', $targetDate)); } break; } } private function addError(string $field, string $message): void { if (!isset($this->errors[$field])) { $this->errors[$field] = $message; } } public function errors(): array { return $this->errors; } public function firstError(): ?string { return reset($this->errors) ?: null; } public function validated(): array { return $this->validatedData; } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.24 |
proxy
|
phpinfo
|
Settings