recherche

Maison  >  Questions et réponses  >  le corps du texte

La méthode require renvoie un entier lorsque ce n'est pas possible en PHP

<p>J'ai le code suivant qui enregistre du code php dans un fichier, puis le charge et l'exécute à nouveau et parfois la méthode require renvoie un int, pourquoi cela se produit-il ? </p> <h1>demo.php</h1> <pre class="brush:php;toolbar:false;"><?php $f = fonction() utiliser($a){ $cachePath = '/tmp/t.php'; $code = '<?php'; $code .= "nn"; $code .= 'retour ' . var_export([], vrai) ';'; file_put_contents($cachePath, $code, LOCK_EX); if (file_exists($cachePath)) { // Parfois, la ligne suivante renvoie un entier, pourquoi ? $result = require($cachePath); si (!is_array($result)) { var_dump($result, $cachePath, file_get_contents($cachePath)); quitter("ok"); } var_dump ($ résultat); } } ; pour($i=0;$i<1000000;$i++) { $f(); }</pré> <h1>Comment reproduire ? </h1> <p>Utilisez deux processus php pour exécuter le code ci-dessus</p> <pre class="brush:php;toolbar:false;">php demo.php</pre></p>
P粉883223328P粉883223328458 Il y a quelques jours591

répondre à tous(2)je répondrai

  • P粉351138462

    P粉3511384622023-09-03 09:03:54

    Voici la documentation de require 的标准行为,与 include , le comportement est le même entre les deux :

    Comme vous pouvez le voir, lorsque la valeur de retour n'est pas écrasée, un entier (1) est renvoyé sur le chemin heureux.

    Cela a du sens pour votre exemple, jusqu'à présent le fichier existe (donc pas d'erreur fatale), mais comme le fichier vient d'être créé, il se peut qu'il soit simplement tronqué, c'est-à-dire qu'il est vide.

    Ainsi, la valeur de retour n'est pas écrasée et vous pouvez voir le int(1).

    Une autre explication est naturellement que vous avez écrasé avec des nombres entiers, ce qui est également possible puisque plusieurs processus peuvent écrire dans le même fichier, mais avec la façon dont vous avez écrit votre exemple, c'est moins probable. Je le mentionne seulement parce que c'est une autre explication valable.

    contient si présent

    Exemple comment suspendre une condition de concurrence critique lorsque vous recherchez $result au lieu de (uniquement) lorsque le fichier existe :

    if (($result = @include($cachePath)) &&
        is_array($result)    
    ) {
       # $result is array, which is required
       # ...
    }
    

    L'idée derrière cela est que nous faisons très peu de gestion des erreurs, comme vérifier si le fichier existe, sinon il ne peut pas être inclus (include() émet simplement un avertissement et le transmet avec $result = false), et ensuite si $ le résultat est chargé, il le fait. S'applique aux tests is_array().

    C'est ce que nous avons pour les erreurs, mais nous savons ce que nous recherchons, c'est-à-dire que $result est un tableau.

    C'est ce qu'on appelle souvent une transaction ou une opération de transaction.

    Dans ce nouvel exemple, nous n'entrons même pas le if-body lorsque le tableau $result est vide, c'est-à-dire qu'il ne contient aucune donnée.

    Au niveau du traitement du programme, qui pourrait nous intéresser, la présence ou l'absence d'un fichier, être vide ou non, ou même être mal écrit sont autant de conditions d'erreur qui doivent être "mangées" et invalider le $result.

    L'erreur de définition n'existe pas.

    Gestion des erreurs d'analyse (pour Include-If-Exists)

    Depuis PHP 7.0, nous pouvons utiliser include() et si malheureusement le fichier include renvoyé est à moitié écrit, nous verrons une erreur d'analyse PHP qui peut être attrapée :

    # start the transaction
    $result = null;
    assert(
        is_string($cachePath) &&           # pathnames are strings,
        '' !== $cachePath &&               # never empty,
        false === strpos($cachePath, "rrreee") # and must not contain null-bytes
    );
    try {
        if (file_exists($cachePath)) {
            $result = include($cachePath);
        }
        # invalidate $result in case include() did technically work.
        if (!$result || !is_array($result) {
            $result = null;
        }
    } catch (Throwable $t) {
        # catch all errors and exceptions,
        # the fall-through is intended to invalidate $result.
        $result = null;
    } finally {
        # $result is not null, but a non-empty array if it worked.
        # $result is null, if it could not be acquired.
    }
    

    Veuillez vous référer à PHP try-catch-finally pour savoir comment lancer des exceptions/travail de gestion des exceptions en détail, assert() est utilisé pour enregistrer la signification du paramètre d'entrée $cachePath dans l'exemple.

    Le deuxième exemple n'utilise pas l'opération de suppression "@", la raison est que si vous l'utilisez comme l'exemple précédent, et que le fichier à inclure contiendra une véritable erreur fatale, l'erreur fatale sera réduite au silence. De nos jours, dans PHP moderne, ce n'est plus un gros problème, mais utiliser file_exists() + include() - bien qu'il existe une condition de concurrence due au temps de vérification par rapport au temps d'utilisation - est sûr (seulement un avertissement) pour les fichiers inexistants. et les erreurs fatales ne sont pas cachées.

    Comme vous l'avez peut-être vu, plus vous connaissez de détails, plus il est difficile d'écrire un code aussi évolutif que possible. Nous ne devons pas nous perdre dans la gestion des erreurs elle-même, mais nous concentrer sur les résultats et définir que ces erreurs n'existent pas.

    Cela dit, include() provoque toujours le chargement des données en mémoire, file_exists() n'est utilisé que pour "supprimer" les avertissements, nous savons que, néanmoins, include() peut émettre des avertissements et renvoyer un entier au lieu d'un tableau .


    Maintenant, puisque la programmation est difficile : vous pouvez alors l'enrouler en boucle et dire de réessayer trois fois. Pourquoi ne pas utiliser une boucle for pour compter et protéger les tentatives numériques ?

    répondre
    0
  • P粉550323338

    P粉5503233382023-09-03 00:09:17

    Ce problème ne peut pas être reproduit si le script n'a toujours qu'un seul exécuteur.

    Si vous parlez d'exécuter ce script en parallèle, le problème est que l'écriture du fichier en mode exclusif ne vous protège pas de la lecture du fichier ultérieurement pendant le processus d'écriture.

    Le processus peut écrire dans le fichier (et posséder le verrou), mais require ne pas honorer le verrou (les verrous du système de fichiers sont consultatifs et non appliqués).

    La bonne solution est donc :

    <?php
    
    $f = function()  use($a){
        $cachePath = '/tmp/t.php';
    
        /* Open the file for writing only. If the file does not exist, it is created.
           If it exists, it is neither truncated (as opposed to 'w'), nor the call to this function fails (as is the case with 'x').
           The file pointer is positioned on the beginning of the file. 
           This may be useful if it's desired to get an advisory lock (see flock()) before attempting to modify the file, as using 'w' could truncate the file before the lock was obtained (if truncation is desired, ftruncate() can be used after the lock is requested). */
        $fp = fopen($cachePath, "c");
    
        $code = '<?php';
        $code .= "\n\n";
        $code .= 'return ' . var_export([], true) . ';';
     
        // acquire exclusive lock, waits until lock is acquired
        flock($fp, LOCK_EX);
        // clear the file
        ftruncate($fp, 0);
        // write the contents
        fwrite($fp, $code);
    
        //wrong (see my answer as to why)
        //file_put_contents($cachePath, $code, LOCK_EX);
    
        //not needed
        //if (file_exists($cachePath)) {
            // Lock is held during require
            $result = require($cachePath);
            if (!is_array($result)) {
                var_dump($result, $cachePath, file_get_contents($cachePath));
                exit("ok");
            }
            var_dump($result);
        //}
        // closing the file implicitly releases the lock
        fclose($fp);
    };
    
    
    for($i=0;$i<1000000;$i++) {
        $f();
    }
    

    Notez que le verrou n'est pas libéré ni réacquis après l'écriture, car un autre processus peut attendre pour écraser le fichier.

    Ne pas le publier pour garantir que le même morceau de code écrit est également required.

    Cependant, tout cela était discutable dès le départ.

    Pourquoi dois-je écrire dans un fichier pour qu'il puisse être renvoyé plus tard require ?

    répondre
    0
  • Annulerrépondre