Files
pta/app/Core/ContactFormat.php

86 lines
3.6 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Core;
/**
* Contrôle de FORME des coordonnées d'un agent : adresse e-mail et numéro de
* téléphone. Source de vérité UNIQUE côté serveur — create() et update() de
* AgentController s'y réfèrent toutes les deux, et toute nouvelle voie
* d'écriture d'un contact doit faire de même plutôt que recopier une regex.
*
* Une copie de ces règles vit dans assets/js/field-format.js pour le retour
* immédiat à la saisie. C'est l'application habituelle du principe du projet :
* l'interface prévient, le serveur tranche. Modifier une règle ici impose donc
* de reporter la modification dans le module JS — l'inverse n'est jamais vrai.
*
* PORTÉE VOLONTAIREMENT LIMITÉE : ces deux champs sont FACULTATIFS et purement
* informatifs (ils n'ouvrent aucune session, n'envoient aucun message). Le
* contrôle attrape la faute de frappe, il ne prétend pas prouver qu'une boîte
* ou une ligne existe. Une valeur vide reste toujours acceptée.
*/
final class ContactFormat
{
/**
* Adresse e-mail. Regex délibérément plus stricte que la RFC 5322, qui
* autorise des formes que personne ne saisit dans un annuaire d'agents
* (commentaires, guillemets, adresses IP littérales). Elle est DOUBLÉE de
* FILTER_VALIDATE_EMAIL : la regex écarte ce que le filtre tolère (domaine
* sans point, par exemple « agent@mairie »), le filtre écarte ce que la
* regex laisserait passer.
*/
private const EMAIL_PATTERN = '/^[A-Za-z0-9._%+\-]+@[A-Za-z0-9]([A-Za-z0-9\-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9\-]*[A-Za-z0-9])?)*\.[A-Za-z]{2,}$/';
/**
* Numéro français, une fois les séparateurs retirés : 10 chiffres commençant
* par 0, ou la même chose en forme internationale (+33 / 0033). Couvre le
* fixe comme le mobile, métropole et outre-mer.
*/
private const PHONE_FR_PATTERN = '/^(?:\+33|0033|0)[1-9]\d{8}$/';
/**
* Repli international E.164 pour un agent joignable à l'étranger — refuser
* un numéro valide serait pire que d'en accepter un exotique, le champ
* n'étant qu'informatif.
*/
private const PHONE_INTERNATIONAL_PATTERN = '/^\+[1-9]\d{7,14}$/';
/** Séparateurs tolérés à la saisie : espaces (insécable compris), . - / ( ). */
private const PHONE_SEPARATORS = '/[\s.\-\/()\x{00A0}\x{202F}]/u';
/** Vrai si l'adresse e-mail est exploitable. La chaîne vide est refusée : c'est à l'appelant de traiter « non renseigné » avant. */
public static function isEmail(string $value): bool
{
$value = trim($value);
if ($value === '') {
return false;
}
return preg_match(self::EMAIL_PATTERN, $value) === 1
&& filter_var($value, FILTER_VALIDATE_EMAIL) !== false;
}
/** Vrai si le numéro de téléphone est exploitable, séparateurs de saisie ignorés. */
public static function isPhone(string $value): bool
{
$compact = self::compactPhone($value);
if ($compact === '') {
return false;
}
return preg_match(self::PHONE_FR_PATTERN, $compact) === 1
|| preg_match(self::PHONE_INTERNATIONAL_PATTERN, $compact) === 1;
}
/**
* Numéro débarrassé de ses séparateurs, seule forme sur laquelle les regex
* travaillent. « 06 12 34 56 78 », « 06.12.34.56.78 » et « 0612345678 »
* décrivent la même ligne : les distinguer n'apporterait rien.
*/
public static function compactPhone(string $value): string
{
return (string) preg_replace(self::PHONE_SEPARATORS, '', trim($value));
}
}