IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage PHP Discussion :

algorithme de requête


Sujet :

Langage PHP

  1. #21
    Membre du Club
    Homme Profil pro
    Développeur Web
    Inscrit en
    Juillet 2017
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Vienne (Poitou Charente)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2017
    Messages : 337
    Points : 61
    Points
    61
    Par défaut
    enfin pour être plus exact mon serveur local est en PHP 7.3 et le serveur de production est en PHP 7.4
    ....
    sa regexp a l'air très complexe et je suis complètement incapable de la corriger mais si il la rend compatible avec PHP 7.4 je mets la question en résolu
    ....
    mais je pense à quelque chose si j'injecte cette requête dans une requête SQL il faut échapper les guillemets à l'intérieur des chaînes ! pouvez-vous SVP me donner une autre regexp pour faire le travail SVP ?

  2. #22
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Justicier interdimensionnel

    Informations forums :
    Inscription : Mars 2009
    Messages : 2 905
    Points : 6 694
    Points
    6 694
    Par défaut
    Visiblement, avant PHP 7.4.6, (*ACCEPT:bidule)?? avec un quantificateur (ici ??) ne passe pas. Qu'à celà ne tienne, on peut faire autrement. On peut réécrire la sous-pattern p_assertion qui les utilise de cette manière:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    (?<p_assertion>
        (?: \g<lvalue> \s* | \b (*ACCEPT:ERR_UNKNOW_LVALUE) )
        (?: = \s* (?: \g<rvalue> | (*:ERR_MISSING_RVALUE) ) | (*:ERR_MISSING_EQUAL_SIGN) )
    )
    En remplaçant tous les (*ACCEPT:label) par (*:label)(*ACCEPT), ça marche jusqu'à PHP 5.6.0, pour les versions antérieures ça marche aussi sauf que preg_match_all ne renseigne pas le label du marqueur.


    Néanmoins, cette pattern a juste vocation à montrer que c'est faisable et à dégainer certains outils qu'on utilise pas tous les jours, pas forcément qu'on doit le faire. La bonne voie, c'est de tokenizer puis de construire l'arbre syntaxique de l'expression, parce que j'imagine qu'à un moment ou un autre, va bien falloir que tu exécutes ta requête. D'ailleurs je me demande à quoi ressemblent les données que tu vas requêter, sous quelle forme se présentent-elles? Peux-tu poster un exemple?

  3. #23
    Membre du Club
    Homme Profil pro
    Développeur Web
    Inscrit en
    Juillet 2017
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Vienne (Poitou Charente)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2017
    Messages : 337
    Points : 61
    Points
    61
    Par défaut
    ça marche ! un grand merci. En fait cette requête maison est faite pour trouver des plantes dont les caractères (par exemple la famille) correspond à des critères.
    J'ai réfléchi à mon problème d'échappement de guillemets et en fait c'est idiot : il faut échapper directement les guillemets dans les requêtes maison
    Pas besoin d'arbres maintenant, je peux avec des str_replace construire directement ma requête SQL

  4. #24
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Justicier interdimensionnel

    Informations forums :
    Inscription : Mars 2009
    Messages : 2 905
    Points : 6 694
    Points
    6 694
    Par défaut
    Donc le but de tout ceci est en fait de construire la clause WHERE d'une requête SQL?

    J'ai réfléchi à mon problème d'échappement de guillemets et en fait c'est idiot : il faut échapper directement les guillemets dans les requêtes maison
    Pas besoin d'arbres maintenant, je peux avec des str_replace construire directement ma requête SQL
    Mauvaise idée! Construire le squelette de ta requête SQL avec des OR, AND, des parenthèses, des noms de colonnes, ça ok, par contre y concaténer les valeurs (les chaînes) directement, ça non. Même s'il y a des vérifications en amont, se passer de requêtes préparées avec des placeholders à la place des valeurs et les valeurs envoyées à part, c'est se passer d'une couche de sécurité indispensable contre le s injections SQL (pour la simple et bonne raison qu'on pense pas forcément à tout).
    Utiliser str_replace pour remplacer les OU en OR et les ET en AND, et si l'utilisateur a la bonne idée de placer ET ou OU dans une chaîne, tu fais quoi?

  5. #25
    Membre du Club
    Homme Profil pro
    Développeur Web
    Inscrit en
    Juillet 2017
    Messages
    337
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Vienne (Poitou Charente)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juillet 2017
    Messages : 337
    Points : 61
    Points
    61
    Par défaut
    bon ben finalement ce n'est pas résolu ! je vais voir avec mon ami prof mais ce n'est pas garanti ! est-ce qu'une approche par arbre est toujours valable si l'on ajoute le mot-clé NON (la négation logique), car je pense que ça pourrait être utile aussi ?

  6. #26
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Justicier interdimensionnel

    Informations forums :
    Inscription : Mars 2009
    Messages : 2 905
    Points : 6 694
    Points
    6 694
    Par défaut
    Construire l'arbre syntaxique n'est pas nécessaire dans ton cas, car toi ce que tu cherches à faire c'est juste une "traduction" vers le SQL, puis ensuite c'est le sgbdr qui s'occupe de l'exécution de la requête.

    Tu peux procéder en tokenizant ta requête de départ, puis en établissant des règles de succession (en ignorant les espaces) pour chaque type de token, par exemple:
    • une parenthèse ouvrante est suivie soit par une autre parenthèse ouvrante soit par un nom de colonne.
    • un signe égal est suivie par une chaîne
    • une chaîne est suivie soit par la fin, soit par une opérateur OU/ET, soit par une parenthèse fermante
    • etc.


    Ensuite en bouclant sur ta liste de token tu vérifies que ces règles sont vérifiées. Également dans cette même boucle, tu incrémentes ou décrémentes un compteur lorsque tu rencontres une parenthèse: si le compteur est négatif en cours de boucle ou à la fin, c'est qu'il y a une parenthèse fermante en trop, si à la fin le compteur est positif, c'est qu'il en manque au contraire une, s'il est égale à 0, c'est que les parenthèses sont balancées.

    Ça c'est pour la partie vérification, pour la partie construction de la requête SQL, ce n'est pas difficile. Tu crées un tableau associatif qui pour chaque type de token associe son équivalent en SQL. Par exemple, pour le token "parenthèse ouvrante" tu associes '(', pour le token OU tu associes ' OR ', etc. par contre pour le token NOM_DE_COLONNE tu associes la valeur du token, et pour le token chaîne, tu associes ? (le placeholder pour la requête préparée) et tu stockes la valeur dans un tableau que tu passeras à l'éxécution de la requête.

    Je n'ai pas trop le temps de te pondre un exemple, peut-être demain ou ce WE, mais ce serait bien si d'ici là tu fasses tes propres tentatives, fructueuses ou pas.

  7. #27
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 905
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Justicier interdimensionnel

    Informations forums :
    Inscription : Mars 2009
    Messages : 2 905
    Points : 6 694
    Points
    6 694
    Par défaut
    On commence par créer des constantes qui seront utiles pour la suite du code:
    • des constantes pour identifier les différents types de token:
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const TK_WHITESPACES = 0,
            TK_OPENING_PARENTHESIS = 2,  // 0b00000010
            TK_CLOSING_PARENTHESIS = 4,  // 0b00000100
            TK_LOGICAL_CONJUNCTION = 8,  // 0b00001000
            TK_LOGICAL_DISJUNCTION = 16, // 0b00010000
            TK_LOGICAL_NEGATION = 32,    // etc.
            TK_STRING = 64,
            TK_EQUAL = 128,
            TK_NOT_EQUAL = 256,
            TK_COLUMN_NAME = 512,
            TK_END = 1024;
      À l'exception de TK_WHITESPACES, chacune des valeurs est une puissance de 2, soit en représentation binaire, avec un seul bit activé. Chaque type de token a donc son bit propre, propriété qui sera utilisée par la suite pour définir les régles de succession des tokens.
    • des constantes pour les codes d'erreurs:
      • erreurs de tokenization
      • erreurs dites "logiques"

      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const ERR_NONE = 0,
            ERROR = 1;                // 0b00000001
       
      const TOKENIZATION_ERROR = 3,   // 0b00000011
            ERR_UNEXPECTED_CHAR = 7,  // 0b00000111
            ERR_UNCLOSED_STRING = 11, // 0b00001011
            ERR_BAD_UTF8 = 19,        // 0b00010011
            ERR_EMPTY_STRING = 35;    //         TE
       
      const LOGIC_ERROR = 129,                        // 0b10000001
            ERR_UNEXPECTED_TOKEN = 133,               // 0b10000101
            ERR_UNEXPECTED_CLOSING_PARENTHESIS = 137, // 0b10001001
            ERR_MISSING_CLOSING_PARENTHESIS = 145,    // 0b10010001
            ERR_UNKNOW_COLUMN_NAME = 161,             // 0b10100001
            ERR_EMPTY_QUERY = 193;                    //   L      E
      Le premier bit (de droite à gauche) permet d'identifier qu'il y a une erreur, le 2e bit qu'il s'agit d'une erreur de tokenization, le 8e qu'il s'agit d'une erreur de logique.
    • les règles de succession des tokens (pour chaque type de token, quel autre token peut le suivre):
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      const RULES = [
          TK_COLUMN_NAME => TK_EQUAL | TK_NOT_EQUAL,
          TK_EQUAL => TK_STRING,
          TK_NOT_EQUAL => TK_STRING,
          TK_STRING => TK_END | TK_CLOSING_PARENTHESIS | TK_LOGICAL_DISJUNCTION | TK_LOGICAL_CONJUNCTION,
          TK_CLOSING_PARENTHESIS => TK_END | TK_CLOSING_PARENTHESIS | TK_LOGICAL_DISJUNCTION | TK_LOGICAL_CONJUNCTION,
          TK_LOGICAL_DISJUNCTION => TK_LOGICAL_NEGATION | TK_OPENING_PARENTHESIS | TK_COLUMN_NAME,
          TK_LOGICAL_CONJUNCTION => TK_LOGICAL_NEGATION | TK_OPENING_PARENTHESIS | TK_COLUMN_NAME,
          TK_LOGICAL_NEGATION => TK_OPENING_PARENTHESIS | TK_COLUMN_NAME,
          TK_OPENING_PARENTHESIS => TK_OPENING_PARENTHESIS | TK_COLUMN_NAME
      ];
      C'est là que la numérotation des types de token a toute son importance. Prenons la première règle TK_COLUMN_NAME => TK_EQUAL | TK_NOT_EQUAL (un nom de colonne est suivi soit par "égal à" ou soit "différent de"). | est l'opérateur OU sur les bits donc TK_EQUAL | TK_NOT_EQUAL = 128 | 256 = 0b0000_1000_0000 | 0b0001_0000_0000 = 0b0001_1000_0000. Ce qui permet en une seule opération (un ET sur les bits) de savoir si un type de token est compris dans cet ensemble, exemple:
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      TK_OPENING_PARENTHESIS & (TK_EQUAL | TK_NOT_EQUAL) = 0b0000_0000_0010 & 0b0001_1000_0000 = 0b0000_0000_0000 = 0 // pas de bit en commun, ne fait pas partie de l'ensemble
      TK_EQUAL & (TK_EQUAL | TK_NOT_EQUAL) = 0b0000_1000_0000 & 0b0001_1000_0000 = 0b0000_1000_0000 = 128 // un bit en commun, fait partie de l'ensemble.
    • les noms de colonnes autorisés:
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      const COLUMN_NAMES = ['family', 'conditions_of_use', 'taxonomical_group'];
    • des chaînes formatées pour les messages d'erreurs
    • l'équivalent SQL pour chaque type de token:
      Code : Sélectionner tout - Visualiser dans une fenêtre à part
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      const TOKEN2SQL = [
          TK_EQUAL => ' = ',
          TK_NOT_EQUAL => ' <> ',
          TK_CLOSING_PARENTHESIS => ')',
          TK_OPENING_PARENTHESIS => '(',
          TK_LOGICAL_DISJUNCTION => ' OR ',
          TK_LOGICAL_CONJUNCTION => ' AND ',
          TK_LOGICAL_NEGATION => 'NOT ',
          TK_STRING => '?' // placeholder
      ];
      À noter l'absence de certains types de token:
      • TK_WHITESPACE qui sera filtré avant la vérification logique. (dés la fin de la tokenization)
      • TK_COLUMN_NAME puisque c'est la valeur du token qui sera prise dans ce cas.
      • évidemment TK_END qui est un token de pure forme pour indiquer la fin de la liste de tokens.

      Aussi, la valeur pour TK_STRING est ? le placeholder utilisé dans les requêtes préparées.


    Puis on définit 4 fonctions pour la tokenization, la vérification logique, l'affichage des erreurs et la construction de la requête.

    La fonction de tokenization renvoie un tableau avec une ou deux clefs error et tokens (cette dernière n'apparait pas en cas d'erreur). error est associé à un tableau avec le type d'erreur et sa position (en octet) dans la chaîne. tokens contient la liste des tokens, chacun identifié par son type, sa valeur, sa position.
    La pattern en charge du découpage est une simple alternative: token1 | token2 | .... Chaque branche de l'alternative se termine par un marqueur (comme dans la pattern précédente) qui permet soit d'indentifier le type de token (quelle branche a réussi), soit de signaler une erreur.
    Si à une position quelconque de la chaîne, aucun des tokens n'est identifié, la dernière branche se charge d'interrompre la recherche en provoquant l'échec avec (*FAIL) et lorsque le mouvement de bracktracking rencontre (*COMMIT), celui-ci interrompt immédiatement la recherche et "arrête" preg_match_all (pas la peine de chercher plus loin dans la chaîne).

    La fonction de vérification logique, vérifie les règles de succession des tokens, ainsi que les noms de colonnes et si les parenthèses sont correctement balancées (un simple compteur suffit pour ce faire). Elle renvoie soit une erreur, soit true si la requête est correcte.

    La fonction de construction de la requête ne fait rien de particulier si ce n'est d'enlever les doubles quotes qui entourent les tokens TK_STRING de "déséchapper" ceux qu'ils pourraient contenir pour stocker le résultat dans un tableau de paramètres. La fonction renvoie donc la requête préparée et son tableau de paramètres.

    Ce qui nous donne ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    <?php
     
    // Tokens
    const TK_WHITESPACES = 0,
          TK_OPENING_PARENTHESIS = 2,  // 0b00000010
          TK_CLOSING_PARENTHESIS = 4,  // 0b00000100
          TK_LOGICAL_CONJUNCTION = 8,  // 0b00001000
          TK_LOGICAL_DISJUNCTION = 16, // 0b00010000
          TK_LOGICAL_NEGATION = 32,    // etc.
          TK_STRING = 64,
          TK_EQUAL = 128,
          TK_NOT_EQUAL = 256,
          TK_COLUMN_NAME = 512,
          TK_END = 1024;
     
    const ERR_NONE = 0,
          ERROR = 1;                // 0b00000001
     
    const TOKENIZATION_ERROR = 3,   // 0b00000011
          ERR_UNEXPECTED_CHAR = 7,  // 0b00000111
          ERR_UNCLOSED_STRING = 11, // 0b00001011
          ERR_BAD_UTF8 = 19,        // 0b00010011
          ERR_EMPTY_STRING = 35;    //         TE
     
    const LOGIC_ERROR = 129,                        // 0b10000001
          ERR_UNEXPECTED_TOKEN = 133,               // 0b10000101
          ERR_UNEXPECTED_CLOSING_PARENTHESIS = 137, // 0b10001001
          ERR_MISSING_CLOSING_PARENTHESIS = 145,    // 0b10010001
          ERR_UNKNOW_COLUMN_NAME = 161,             // 0b10100001
          ERR_EMPTY_QUERY = 193;                    //   L      E
     
    const RULES = [
        TK_COLUMN_NAME => TK_EQUAL | TK_NOT_EQUAL,
        TK_EQUAL => TK_STRING,
        TK_NOT_EQUAL => TK_STRING,
        TK_STRING => TK_END | TK_CLOSING_PARENTHESIS | TK_LOGICAL_DISJUNCTION | TK_LOGICAL_CONJUNCTION,
        TK_CLOSING_PARENTHESIS => TK_END | TK_CLOSING_PARENTHESIS | TK_LOGICAL_DISJUNCTION | TK_LOGICAL_CONJUNCTION,
        TK_LOGICAL_DISJUNCTION => TK_LOGICAL_NEGATION | TK_OPENING_PARENTHESIS | TK_COLUMN_NAME,
        TK_LOGICAL_CONJUNCTION => TK_LOGICAL_NEGATION | TK_OPENING_PARENTHESIS | TK_COLUMN_NAME,
        TK_LOGICAL_NEGATION => TK_OPENING_PARENTHESIS | TK_COLUMN_NAME,
        TK_OPENING_PARENTHESIS => TK_OPENING_PARENTHESIS | TK_COLUMN_NAME
    ];
     
    const COLUMN_NAMES = ['family', 'conditions_of_use', 'taxonomical_group'];
     
    $errorMessages = [
        ERR_UNEXPECTED_CHAR => 'caractère inattendu à la position %d.',
        ERR_UNCLOSED_STRING => 'chaîne de caractères non fermée à la position %d.',
        ERR_BAD_UTF8 => 'erreur d\'encodage UTF-8 à la position %d.',
        ERR_EMPTY_STRING => 'chaîne vide à la position %d.', // *****
        ERR_UNEXPECTED_TOKEN => 'élément inattendu à la position %d.',
        ERR_UNEXPECTED_CLOSING_PARENTHESIS => 'parenthèse fermante inattendue à la position %d.',
        ERR_MISSING_CLOSING_PARENTHESIS => 'parenthèse fermante manquante à la position %d.',
        ERR_UNKNOW_COLUMN_NAME => 'nom de colonne inconnu à la position %d.',
        ERR_EMPTY_QUERY => 'requête vide à la position %d.'
    ];
     
    const TOKEN2SQL = [
        TK_EQUAL => ' = ',
        TK_NOT_EQUAL => ' <> ',
        TK_CLOSING_PARENTHESIS => ')',
        TK_OPENING_PARENTHESIS => '(',
        TK_LOGICAL_DISJUNCTION => ' OR ',
        TK_LOGICAL_CONJUNCTION => ' AND ',
        TK_LOGICAL_NEGATION => 'NOT ',
        TK_STRING => '?' // placeholder
    ];
     
    function tokenize(string $str) {
        $pattern = <<<'REGEX'
        ~
            \s+ (*:TK_WHITESPACES)
    
          | \(  (*:TK_OPENING_PARENTHESIS)
          | \)  (*:TK_CLOSING_PARENTHESIS)
    
          | \b ET \b    (*:TK_LOGICAL_CONJUNCTION)
          | \b OU \b    (*:TK_LOGICAL_DISJUNCTION)
          | \b NON \b   (*:TK_LOGICAL_NEGATION)
    
          | \B " [^"\\]* (?s: \\ . [^"\\]* )*
            (?: " \B (*:TK_STRING) | " (*:ERR_UNEXPECTED_CHAR) | \z (*:ERR_UNCLOSED_STRING) )
          | \b \w+  (*:TK_COLUMN_NAME)
    
          | =   (*:TK_EQUAL)
          | (?: != | <> )   (*:TK_NOT_EQUAL)
    
          | \z  (*:TK_END)
          | (*COMMIT) (*FAIL) # UNEXPECTED CHARACTER ⮕ tokenization aborted
        ~xu
        REGEX;
     
        $count = preg_match_all($pattern, $str, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE);
     
        if ( preg_last_error() === PREG_BAD_UTF8_ERROR ) {
            preg_match('~(?: # séquences UTF-8 valides
                [\x00-\x7F]
              | [\xC2-\xDF][\x80-\xBF]
              | \xE0[\xA0-\xBF][\x80-\xBF]
              | \xED[\x80-\x9F][\x80-\xBF]
              | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
              | \xF0[\x90-\xBF][\x80-\xBF]{2}
              | \xF4[\x80-\x8F][\x80-\xBF]{2}
              | [\xF1-\xF3][\x80-\xBF]{3}
              )*~x', $str, $m);
     
            return [ 'error' => ['type' => ERR_BAD_UTF8, 'offset' => strlen($m[0])] ];
        }
     
        if ( $count === 0 ) // (*COMMIT) (*FAIL) a été atteint dés la première position
            return [ 'error' => ['type' => ERR_UNEXPECTED_CHAR, 'offset' => 0] ];
     
        $errors = array_filter($matches, fn($m) => constant($m['MARK']) & ERROR);
     
        if ( !empty($errors) ) { // la fin a été atteinte mais il y a des marqueurs d'erreurs parmi les tokens
            $error = array_shift($errors); 
            list($match, $offset) = $error[0];
            return [ 'error' => ['type' => constant($error['MARK']), 'offset' => $offset + strlen($match)] ];
        }
     
        $lastMatch = end($matches);
     
        if ( constant($lastMatch['MARK']) !== TK_END ) { // (*COMMIT) (*FAIL) a été atteint avant la fin
            list($match, $offset) = $lastMatch[0];
            return [ 'error' => ['type' => ERR_UNEXPECTED_CHAR, 'offset' => $offset + strlen($match)] ];
        }
     
        if ( $count === 1 ) // l'unique token est TK_END, la chaîne est vide
            return [ 'error' => ['type' => ERR_EMPTY_STRING, 'offset' => 0] ];
     
        return [
            'error' => ERR_NONE,
            'tokens' => array_map(
                fn($m) => [ 'value' => $m[0][0], 'offset' => $m[0][1], 'type' => constant($m['MARK']) ],
                $matches
            )
        ];
    }
     
    function checkLogic($tokens) {
        $parenthesis = 0;
        $current = current($tokens);
     
        if ( $current['type'] === TK_END )
            return [ 'type' => ERR_EMPTY_QUERY, 'offset' => $current['offset'] ];
     
        do {
            if ( $current['type'] === TK_COLUMN_NAME && !in_array($current['value'], COLUMN_NAMES) )
                return [ 'type' => ERR_UNKNOW_COLUMN_NAME, 'offset' => $current['offset'] ];
     
            if ( $current['type'] === TK_OPENING_PARENTHESIS )
                $parenthesis++;
            elseif ( $current['type'] === TK_CLOSING_PARENTHESIS && --$parenthesis < 0 )
                return [ 'type' => ERR_UNEXPECTED_CLOSING_PARENTHESIS, 'offset' => $current['offset'] ];
     
            $next = next($tokens);
     
            if ( RULES[$current['type']] & $next['type'] ) // le token courant est suivi par un token autorisé
                $current = $next;
            else
                return [ 'type' => ERR_UNEXPECTED_TOKEN, 'offset' => $next['offset'] ];
     
        } while ( $next['type'] !== TK_END );
     
        return $parenthesis ? [ 'type' => ERR_MISSING_CLOSING_PARENTHESIS, 'offset' => $current['offset'] ]
                            : true ;
    }
     
    function displayError($error, $query, $errorMessages) {
        $position = grapheme_strlen(substr($query, 0, $error['offset']));
        $format = "{$errorMessages[$error['type']]}\n%s\n" . str_repeat(' ', $position) . '^';
        printf($format, $position, $query);
    }
     
    function buildQuery($tokens, $prefix = '') {
        $SQLQuery = $prefix;
        $params = [];
     
        foreach ($tokens as $token) {
            switch($token['type']) {
                case TK_COLUMN_NAME:
                    $SQLQuery .= $token['value'];
                    break;
     
                case TK_END: break;
     
                case TK_STRING:
                    $param = substr($token['value'], 1, -1);
                    $params[] = strtr($param, ['\\\\' => '\\\\', '\\"' => '"']);
     
                default:
                    $SQLQuery .= TOKEN2SQL[$token['type']];
            }
        }
        return [$SQLQuery, $params];
    }
     
    $query = "".'family="_\\"bîd'."u\xCC\x8A".'le\\"_" OU (((taxonomical_group="machin")))';
     
    $tokenization = tokenize($query);
    $error = $tokenization['error'];
     
    if ( $error !== ERR_NONE ) {
        displayError($error, $query, $errorMessages);
    } else {
        $tokens = array_filter($tokenization['tokens'], fn($token) => $token['type'] !== TK_WHITESPACES);
     
        if ( true !== $error = checkLogic($tokens) ) {
            displayError($error, $query, $errorMessages);
        } else {
            $prefix = 'SELECT * FROM matable WHERE ';
            list($SQLQuery, $params) = buildQuery($tokens, $prefix);
     
            echo "requête: $query", PHP_EOL,
                 "requête préparée: $SQLQuery", PHP_EOL,
                 'paramètres: ', print_r($params, true);
     
            // $sth = $dbh->prepare($SQLQuery);
            // $sth->execute($params);
        }
    }
    demo

    Ce code fonctionne avec PHP 7.4 minimum, (à cause des fn($x) => "pouet" équivalent de function ($x) { return "pouet"; }) donc installe la chez toi (puisque c'est cette version utilisée sur ton serveur) d'autant plus que la version 7.3 est périmée depuis 3 mois.

Discussions similaires

  1. Algorithme pour une requête
    Par Madfrix dans le forum Requêtes
    Réponses: 2
    Dernier message: 11/12/2012, 12h25
  2. Problème de requête (algorithme ?)
    Par darigaaz dans le forum Requêtes
    Réponses: 3
    Dernier message: 07/04/2010, 14h59
  3. Requête sur "algorithme"
    Par Dr_No dans le forum Requêtes et SQL.
    Réponses: 3
    Dernier message: 25/06/2009, 10h06
  4. Algorithme de randomisation ... ( Hasard ...? )
    Par Anonymous dans le forum Assembleur
    Réponses: 8
    Dernier message: 06/09/2002, 14h25
  5. Algorithme génétique
    Par Stephane.P_(dis Postef) dans le forum Algorithmes et structures de données
    Réponses: 2
    Dernier message: 15/03/2002, 17h14

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo