} */ public function validate(mixed $password): array { $errors = []; if (!is_string($password)) { return ['valid' => false, 'errors' => ['password must be a string']]; } if (mb_strlen($password) < self::MINIMUM_LENGTH) { $errors[] = 'minimum length'; } if (preg_match('/\p{Lu}/u', $password) !== 1) { $errors[] = 'uppercase letter'; } if (preg_match('/\p{Ll}/u', $password) !== 1) { $errors[] = 'lowercase letter'; } if (preg_match('/\p{N}/u', $password) !== 1) { $errors[] = 'number'; } if (preg_match('/[^\p{L}\p{N}\s]/u', $password) !== 1) { $errors[] = 'symbol'; } if ($this->isCommonPlaceholder($password)) { $errors[] = 'common placeholder'; } return ['valid' => $errors === [], 'errors' => $errors]; } /** @return array{valid: bool, errors: list} */ public function validateInitial(mixed $password): array { return $this->validate($password); } /** @return array{valid: bool, errors: list} */ public function validateReset(mixed $password): array { return $this->validate($password); } public function isValid(mixed $password): bool { return $this->validate($password)['valid']; } private function isCommonPlaceholder(string $password): bool { $canonical = strtolower($password); $canonical = strtr($canonical, ['@' => 'a', '$' => 's', '0' => 'o']); $canonical = preg_replace('/[^a-z0-9]/', '', $canonical) ?? ''; return preg_match( '/^(?:password|changeme|welcome|admin|administrator|temporary|temppassword|qwerty|letmein)[0-9]*$/', $canonical, ) === 1; } }