feat: P4 backend kvalita - SELECT * fix, overdue konsolidace, Validator

- SELECT * nahrazen explicitnimi sloupci ve 22 PHP souborech (69+ vyskytu)
- users-handlers.php: password_hash explicitne vyloucen z dotazu
- Overdue detekce presunuta do invoices.php routeru (1x pred dispatch misto 3x v handlerech)
- Validator.php: validacni helper s pravidly required, string, int, email, in, numeric
- PaginationHelper: PHPStan typy opraveny

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 18:42:42 +01:00
parent df506dfea4
commit 758be819c3
25 changed files with 513 additions and 102 deletions

139
api/includes/Validator.php Normal file
View File

@@ -0,0 +1,139 @@
<?php
/**
* Validacni helper pro API vstupy.
*
* Pouziti:
* $v = new Validator($input);
* $v->required('name')->string('name', 1, 255);
* $v->required('email')->email('email');
* $v->int('amount', 0, 1000000);
* $v->in('status', ['active', 'inactive']);
* if ($v->fails()) errorResponse($v->firstError());
*/
declare(strict_types=1);
class Validator
{
/** @var array<string, mixed> */
private array $data;
/** @var array<string, string> */
private array $errors = [];
/** @param array<string, mixed> $data */
public function __construct(array $data)
{
$this->data = $data;
}
public function required(string $field, string $label = ''): self
{
$value = $this->data[$field] ?? null;
if ($value === null || $value === '') {
$this->errors[$field] = ($label ?: $field) . ' je povinné pole';
}
return $this;
}
public function string(string $field, int $min = 0, int $max = 0, string $label = ''): self
{
$value = $this->data[$field] ?? null;
if ($value === null || $value === '') {
return $this;
}
if (!is_string($value)) {
$this->errors[$field] = ($label ?: $field) . ' musí být text';
return $this;
}
$len = mb_strlen($value);
if ($min > 0 && $len < $min) {
$this->errors[$field] = ($label ?: $field) . " musí mít alespoň {$min} znaků";
} elseif ($max > 0 && $len > $max) {
$this->errors[$field] = ($label ?: $field) . " nesmí překročit {$max} znaků";
}
return $this;
}
public function int(string $field, ?int $min = null, ?int $max = null, string $label = ''): self
{
$value = $this->data[$field] ?? null;
if ($value === null || $value === '') {
return $this;
}
if (!is_numeric($value)) {
$this->errors[$field] = ($label ?: $field) . ' musí být číslo';
return $this;
}
$intVal = (int) $value;
if ($min !== null && $intVal < $min) {
$this->errors[$field] = ($label ?: $field) . " musí být alespoň {$min}";
} elseif ($max !== null && $intVal > $max) {
$this->errors[$field] = ($label ?: $field) . " nesmí překročit {$max}";
}
return $this;
}
public function email(string $field, string $label = ''): self
{
$value = $this->data[$field] ?? null;
if ($value === null || $value === '') {
return $this;
}
if (!is_string($value) || !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$this->errors[$field] = ($label ?: $field) . ' musí být platný e-mail';
}
return $this;
}
/**
* @param list<string> $allowed
*/
public function in(string $field, array $allowed, string $label = ''): self
{
$value = $this->data[$field] ?? null;
if ($value === null || $value === '') {
return $this;
}
if (!in_array($value, $allowed, true)) {
$this->errors[$field] = ($label ?: $field) . ' má neplatnou hodnotu';
}
return $this;
}
public function numeric(string $field, ?float $min = null, ?float $max = null, string $label = ''): self
{
$value = $this->data[$field] ?? null;
if ($value === null || $value === '') {
return $this;
}
if (!is_numeric($value)) {
$this->errors[$field] = ($label ?: $field) . ' musí být číslo';
return $this;
}
$numVal = (float) $value;
if ($min !== null && $numVal < $min) {
$this->errors[$field] = ($label ?: $field) . " musí být alespoň {$min}";
} elseif ($max !== null && $numVal > $max) {
$this->errors[$field] = ($label ?: $field) . " nesmí překročit {$max}";
}
return $this;
}
public function fails(): bool
{
return count($this->errors) > 0;
}
public function firstError(): string
{
return reset($this->errors) ?: '';
}
/** @return array<string, string> */
public function errors(): array
{
return $this->errors;
}
}