Des accessoires fous à @Dave Redfern sur Slack qui a signalé mon problème. Lors du passage d'un tableau indexé non nul, il est interprété comme un objet.
dump(json_encode([
0 => "ROLE_SITE_DIRECTOR", 2 => "ROLE_TRANSLATOR", 1 => "ROLE_DATA_ENTRY",
]));
dump(json_encode(array_values([
0 => "ROLE_SITE_DIRECTOR", 2 => "ROLE_TRANSLATOR", 1 => "ROLE_DATA_ENTRY",
])));
la sortie sera :
"{"0":"ROLE_SITE_DIRECTOR","2":"ROLE_TRANSLATOR","1":"ROLE_DATA_ENTRY"}"
"["ROLE_SITE_DIRECTOR","ROLE_TRANSLATOR","ROLE_DATA_ENTRY"]"
La solution est donc simple, je suis mon passeur :
$this->roles = array_values($roles);
À l'avenir, il est également préférable d'utiliser les relations de la base de données avec les rôles. La recherche par données JSON n'est pas amusante, mais dans une jointure est une pratique courante.