En PHP, vérifier si une chaîne ou un tableau contient une valeur paraît simple, mais c’est un détail qui déclenche vite des bugs concrets en production: validation de formulaire trop permissive, filtre qui laisse passer une mauvaise donnée, ou comparaison qui échoue à cause d’un type inattendu. La bonne méthode dépend surtout de ce que vous testez: une sous-chaîne dans un texte, une valeur dans un tableau ou une clé dans un tableau associatif. Je vais aller droit au but, avec les fonctions à utiliser, les pièges à éviter et les réflexes que je garde sur des projets web réels.
Les bons réflexes dépendent du type de donnée à tester
- Chaîne de caractères : `str_contains()` est le choix le plus lisible sur PHP 8.
- Recherche de position : `strpos()` ou `mb_strpos()` si vous avez besoin de l’index exact.
- Valeur dans un tableau : `in_array(..., true)` évite les faux positifs dus aux conversions de type.
- Clé dans un tableau : `array_key_exists()` est plus fiable que `isset()` quand la valeur peut être `null`.
- Texte multibyte ou insensible à la casse : `mb_stripos()` est souvent le choix le plus sûr.
Choisir la bonne fonction selon le type de donnée
Je commence toujours par cette distinction, parce qu’elle évite une grande partie des confusions. En PHP, une chaîne, une liste de valeurs et un tableau associatif ne se testent pas de la même manière, et vouloir appliquer la mauvaise fonction au mauvais support mène presque toujours à un bug silencieux.
| Besoin réel | Fonction conseillée | Ce qu’elle renvoie | Quand je l’utilise |
|---|---|---|---|
| Vérifier qu’un texte contient un fragment | str_contains() |
bool |
Quand je veux juste savoir si la sous-chaîne est là |
| Obtenir la position d’un fragment dans un texte |
strpos() ou mb_strpos()
|
int|false |
Quand l’index de départ compte, pas seulement la présence |
| Recherche insensible à la casse dans un texte |
stripos() ou mb_stripos()
|
int|false |
Pour du texte utilisateur, des titres ou des chaînes multibytes |
| Vérifier qu’une valeur existe dans un tableau | in_array() |
bool |
Quand je compare une valeur à une liste autorisée |
| Vérifier qu’une clé existe dans un tableau associatif | array_key_exists() |
bool |
Quand la présence de la clé compte, même si la valeur est null
|
Cette carte mentale suffit à éviter les mélanges les plus fréquents. Une fois qu’elle est claire, la recherche dans une chaîne devient très simple à lire, et c’est là que les différences entre les fonctions commencent vraiment à compter.
Vérifier qu’un texte contient une sous-chaîne
Pour une chaîne, str_contains() est la solution la plus propre si votre projet tourne sur PHP 8 ou plus. Elle répond à une seule question: le fragment est-il présent ou non ? C’est précisément ce qu’on veut dans la majorité des contrôles métier, parce qu’un booléen est plus clair qu’une position numérique dont il faut ensuite interpréter le résultat.
Je l’utilise souvent pour des règles simples: vérifier un chemin, détecter un suffixe, repérer un mot-clé dans un titre, ou bloquer une chaîne suspecte dans une entrée utilisateur. Le point important, c’est que la comparaison est sensible à la casse. Si vous cherchez php, la présence de PHP ne donnera pas le même résultat.
Il y a aussi une subtilité que je garde en tête: une chaîne vide est considérée comme présente. Ce comportement est logique techniquement, mais il mérite d’être surveillé si votre variable peut être vide ou venir d’une source externe. Dans ce cas, je valide d’abord l’entrée, puis je teste la présence réelle du fragment.
Quand la position compte encore
Si vous avez besoin de savoir où commence la sous-chaîne, strpos() reste utile. Mais il faut être rigoureux: si le fragment est trouvé au début, la fonction renvoie 0, et 0 est faux en contexte booléen. C’est l’erreur classique qui fait croire qu’une chaîne ne contient rien alors qu’elle commence justement par la valeur cherchée.
Quand je travaille sur du texte qui peut contenir des caractères accentués ou des alphabets non latins, je préfère une variante multibyte comme mb_strpos() ou mb_stripos(). En pratique, pour du contenu éditorial, des noms de produits ou des champs utilisateur, cette prudence évite des résultats incohérents liés à l’encodage. C’est la transition naturelle vers la gestion des chaînes insensibles à la casse.
Gérer la casse et l’UTF-8 sans surprises
Pour une recherche insensible à la casse, mb_stripos() me paraît plus robuste que d’improviser une normalisation approximative. Sur des textes français, la question n’est pas seulement la majuscule ou la minuscule, mais aussi la manière dont les caractères sont encodés. Si votre application manipule du UTF-8, je préfère rester explicite plutôt que de compter sur un comportement implicite.
Je ne considère pas cette approche comme un luxe technique. Sur une interface web, c’est souvent ce qui fait la différence entre une recherche fiable et un filtrage qui rate des variantes légitimes. Une fois la logique des chaînes maîtrisée, le vrai sujet devient souvent la recherche dans les tableaux.
Vérifier qu’une valeur existe dans un tableau
Pour un tableau de valeurs, in_array() est la fonction à connaître. Là encore, la version la plus sûre est celle avec le troisième paramètre à true, donc en comparaison stricte. Sans cela, PHP peut convertir des valeurs de manière souple, et cette souplesse est rarement ce qu’on veut dans du code applicatif.
Je conseille presque toujours la version stricte dès qu’on valide une permission, un type de contenu, un identifiant ou une option de configuration. Le gain n’est pas seulement de la précision: c’est aussi de la lisibilité. On comprend immédiatement que la valeur et le type doivent correspondre.
| Mode | Comportement | Usage que je recommande |
|---|---|---|
false |
Comparaison souple, avec conversions possibles | Cas hérités, données très contrôlées, migration temporaire |
true |
Comparaison stricte sur la valeur et le type | Code applicatif courant, API, permissions, formulaires |
Si vous devez récupérer la position de la valeur dans le tableau, array_search() peut compléter le tableau, mais je garde la même discipline: comparaison stricte et test avec !== false. Là aussi, un index 0 n’est pas un échec, c’est un résultat valide. Cette nuance mène directement à la question des clés de tableau.
Savoir si une clé existe vraiment
Quand je travaille avec un tableau associatif, je ne cherche pas toujours une valeur: parfois je veux juste savoir si une clé est présente. Dans ce cas, array_key_exists() est plus fiable que isset() si la valeur peut être null. C’est une différence subtile, mais elle change le comportement dans des payloads JSON, des formulaires partiellement remplis ou des objets hydratés en tableau.
null];
var_dump(isset($user['email'])); // false
var_dump(array_key_exists('email', $user)); // trueJe choisis isset() seulement si je veux vérifier qu’une clé existe et qu’elle n’est pas nulle. Dès que null a une valeur métier, ou que je veux distinguer “absent” de “présent mais vide”, je bascule sur array_key_exists(). Cette distinction évite pas mal de comportements bizarres dans les API et les back-offices.

Les pièges qui provoquent les faux positifs
La plupart des erreurs que je vois viennent d’un mélange entre présence, position et comparaison souple. Le code semble court, mais il raconte souvent la mauvaise chose au moteur PHP. Quand je relis une condition, je vérifie toujours si elle teste vraiment ce qu’elle prétend tester.
-
strpos()utilisé comme un booléen : si la valeur cherchée est au début, le résultat vaut0, donc la condition peut échouer à tort. Je corrige avec!== false. -
in_array()sans mode strict : les conversions de type peuvent faire passer une valeur qui ne devrait pas matcher. En production, je mets presque toujourstruesur le troisième argument. -
isset()sur une valeur nulle : la clé existe, mais le test renvoie faux. Si la différence entre absent et null compte, je passe àarray_key_exists(). -
Recherche insensible à la casse mal gérée : sur du texte multibyte, je préfère
mb_stripos()à une normalisation improvisée. -
Tableaux imbriqués :
in_array()ne cherche pas en profondeur. Si les données sont nested, je fais une boucle explicite ou je transforme la structure avant de tester.
Le piège de array_search() mérite aussi une mention à part. La fonction renvoie la clé trouvée, pas un simple vrai/faux. Si la valeur est à la position 0 et que vous testez la réponse avec un simple if ($key), vous perdez le résultat. Je préfère écrire le test de manière explicite, parce que c’est plus lisible et beaucoup moins fragile.
Des cas concrets que j’utilise sur un projet web
Dans un projet web, la bonne fonction dépend presque toujours du métier. Sur un site vitrine, dans un back-office ou dans une API, je m’intéresse rarement à la théorie pure: je veux surtout savoir quelle vérification évite le bug sans compliquer la lecture du code. Voici les cas que je croise le plus souvent.
Valider une liste autorisée
Quand je dois accepter seulement certains types de contenu, certaines routes ou certains rôles, je pars sur un tableau blanc et in_array(..., true). C’est simple, lisible et difficile à mal interpréter par la suite.
Ici, le mode strict évite les correspondances involontaires avec des valeurs proches mais différentes. C’est exactement le genre de contrôle que je veux quand la donnée vient d’une requête HTTP.
Bloquer ou détecter un fragment dans une chaîne
Pour vérifier une route, un slug ou un titre, j’utilise str_contains() quand la présence suffit. Si la casse ou l’encodage peuvent varier, je passe à une fonction multibyte et je garde un œil sur le comportement exact attendu.
Ce test est typique des front-controllers, des filtres de catégories ou des règles de redirection. Je le trouve plus lisible qu’une série de conditions bricolées autour d’une position numérique.
Lire aussi : Dictionnaire JavaScript - Object, Map ou WeakMap ? Le bon choix
Contrôler un payload JSON ou un formulaire
Quand je reçois un tableau décodé depuis du JSON, la question n’est pas seulement “la clé existe-t-elle ?”, mais aussi “la clé est-elle présente même si sa valeur est vide ou nulle ?”. C’est là que array_key_exists() garde tout son intérêt.
Dans ce genre de cas, je préfère une règle explicite plutôt qu’un raccourci qui mélange absence, null et chaîne vide. C’est plus robuste à long terme, surtout quand le schéma du payload évolue.
Ce que je garde en tête avant de valider en production
Si je devais résumer ma règle de travail, elle serait simple: chaîne, valeur et clé ne se testent pas avec la même fonction. Une fois cette séparation nette, la plupart des validations deviennent triviales à relire et beaucoup moins sujettes aux faux positifs.
-
Pour une chaîne, je privilégie
str_contains()oustrpos()selon que j’ai besoin d’un booléen ou d’une position. -
Pour une valeur de tableau, j’utilise
in_array(..., true)presque systématiquement. -
Pour une clé,
array_key_exists()est le bon réflexe sinulla un sens. -
Pour du texte multibyte, je pense tout de suite à
mb_strpos()oumb_stripos(). - Pour les cas ambigus, je préfère écrire un test explicite plutôt qu’une condition compacte mais fragile.
Sur un projet PHP moderne, cette discipline évite une bonne partie des erreurs de validation, surtout dans les formulaires, les API et les règles d’accès. Quand la logique de présence est claire dès le départ, le code reste plus simple à maintenir, et c’est souvent là que se joue la qualité réelle d’une base web.