Bonjour,
Je viens reviens deviens vers vous pour une petite question simple et pour laquelle je ne trouve pas de réponse qui me convienne.
Je cherche à éditer un numéro d'estimation unique.
Mon système d'estimation fonctionne de la façon suivante :

Après avoir rempli le formulaire, l'utilisateur à le résumé de son estimation. Il peut alors imprimer un PDF. Lors de cette action, le fichier PDF s'enregistre sur mon serveur.

Je souhaite donc qu'à chaque création et uniquement à chaque création de PDF, un numéro soit attribué à ce dernier, et qu'il s'incrémente.

Mes questions :

Dois-je obligatoirement créer un table pour seulement le numéro du devis ?
Ou est ce qu'avec une boucle cela suffirait-il ?

$filename = 'W'.date('Y').$estim_number;

(en gros, dois-je enregistreer en base $estim_number, ou puis-je l'incrémenter de cette manière 0001, 0002, 0003, ... grâce à une boucle )

Merci du partage de votre expérience...

17 réponses


Huggy
Réponse acceptée

Voici une version avec 3 tentatives

$fp = fopen (WWW_ROOT.'files/online_estimation'.DS. 'number.txt', "r+");
$nb_tentative = 3;
$result = false;
do {
    if (flock($fp, LOCK_EX))    {
    $estim_number = fgets ($fp, 5);
    $estim_number = sprintf("%04s",$estim_number+1);
    fseek ($fp, 0);
    fputs ($fp, $estim_number);
    flock($fp, LOCK_UN);
    $result = true;
} else {
    $nb_tentative--;
    usleep(10000); // 10ms
    }
 } while($nb_tentative >0 && !$result);
fclose ($fp);
$this->set('estim_number', $estim_number);

Bonjour digaburla,
Si j'ai bien compris, si tu n'utilises pas de base de données, tu dois récupérer le dernier numéro créé en faisant un dir du répertoire, trier le résultat, récupérer le plus grand puis faire +1.
Entretemps un autre utilisateur a fait la même chose , certe le risque est faible mais il y a un risque d'avoir plusieur fois le même numéro .
Je pense qu'un champ auto-incrément est plus sûr.
Tu peux aussi utiliser un champ int dans une table mais comme il faut lire la valeur, l'incrémenter et l'écrire sans qu'un autre utilisateur puisse interférer, il faut réaliser une Transaction qui englobe les 3 opérations.

Ou alors si tu ne veux pas d'une base de données, un fichier texte avec le numéro. C'est encore le plus simple :)

Merci de vos réponses,
Huggy, du coup je ne voit pas comment utiliser le champ auto-incrément. Faut il que je dédi un table à cette action ? A laquelle je récupererai son $id à chaque création de PDF ?
Lolotoobo, peux tu dévelloper STP ? Tu parle d'un fichier texte dans le dossier en question ? Du coup ce que soulève Huggy pour les doublons n'est il pas envisageable également avec cette solution ?

Merci encore

Tu peux utiliser une table dédiée, elle contiendra tous les numéros. Si tu rajoutes une date, ça te fera des statistiques.
Sinon je viens de voir que la fonction lastInsertId() retourne aussi l'id même pour un Update
Si tu crées une table DernierNum (numero int) primary key numero
tu fais juste
UPDATE DernierNum SET numero = numero + 1;
et tu récupères le numero avec lastInstertId()

@lolotoobo si le fichier texte n'est pas verrouillé en lecture, 2 utilisateurs peuvent lire la même valeur
Edit : c'est possible de verrouiller avec flock()

Du coup je suis parti sur la solution de lolottobo, ce qui me donne

$fp = fopen (WWW_ROOT.'files/online_estimation'.DS. 'number.txt', "r+");
        if (flock($fp, LOCK_EX)) 
        {
            $estim_number = fgets ($fp, 5);
            $estim_number = sprintf("%04s",$estim_number+1);
            fseek ($fp, 0);
            fputs ($fp, $estim_number);
            fclose ($fp);
            flock($fp, LOCK_UN);
        } 
        else
        {
        echo "une erreur est survenue, merci de retest";
        }

        $this->set('estim_number', $estim_number);

Cela me permet d'avoir un numéro 'W20150004' qui s'incrémente.
Si j'ai bien compris flock(), cela empécherait les doublons.

Qu'en pensez vous ?

PS : Je testerais la table dédiée demain.

ca me parait correct, mais je sortirais le fclose() du if pour le faire quoi qu'il arrive.
Que se passe t-il lorsque le fichier est déjà verrouillé ?, ne faut-il pas faire une autre tentative en laissant passer 10 ms ?
la doc de flock ne précise rien là dessus

Edit : pour tester tu peux utiliser Apache bench (ab), tu balances 50 requêtes simultanées et ton compteur doit tomber juste.

Merci pour ce retour, et pour l'information sur Apache bench.
Pour bien comprendre ton code je le reprends et le commente :

$fp = fopen (WWW_ROOT.'files/online_estimation'.DS. 'number.txt', "r+");  // j'ouvre mon fichier texte
$nb_tentative = 3;  //  je défini cette variable à 3, ce qui me permet de tester pour 3 requêtes simultanées
$result = false; 
do {
    if (flock($fp, LOCK_EX))    { // je vérouille le fichier
    $estim_number = fgets ($fp, 5);  // On lit le fichier
    $estim_number = sprintf("%04s",$estim_number+1); // je défini ce dernier pour qu'il est la forme '0001'
    fseek ($fp, 0); // je replace le curseur
    fputs ($fp, $estim_number); // j'écris par dessus (j'enregistre) 
    flock($fp, LOCK_UN); //je dévérouille
    $result = true;
} else {
    $nb_tentative--; // je décompte les tentative
    usleep(10000); // 10ms (stop l'action durant 10 ms)
    }
 } while($nb_tentative >0 && !$result);
fclose ($fp); //fermeture du fichier
$this->set('estim_number', $estim_number); // je renvoi mon résultat à ma vue

Encore merci

ps : si mes commentaires son corrects, je passerai en résolu.

Pourquoi ne pas utiliser la base de donnée ? C'est plus simple à gérer qu'un fichier.

@lex avec la base de données ça fonctionne aussi parcontre mon idée d'utiliser les lastInsertId ne fonctionne pas.
voici une solution qui utilise une table 'compteur (Description varchar(30), Numero int)
le champ Numéro n'a pas besoin d'index ni d'auto-increment. La description permet de gérer plusieurs compteurs (N° facture, n° Devis ..)
j'utilise une transaction qui va verrouiller la table (testé avec pdo_mysql et innodb)

$newNumber = 0;
try {
        $pdo->beginTransaction();
        $query = "UPDATE compteur SET Numero = Numero + 1 WHERE Description='numFacture'";
        $nb_record = $pdo->exec($query);
        $stmt = $pdo->query("SELECT numero FROM compteur WHERE Description='numFacture'");
        $result = $stmt->fetch(PDO::FETCH_ASSOC);
        $newNumber = $result["Numero"];
        $pdo->commit();
    } catch(PDOException $e)
    {
        $conn->rollBack();
    }
 echo "nouveau n° facture = " . $newNumber;

Plus simplement, tu ajoutes un champ id en clé primaire et auto increment, comme ça tu peux utiliser lastInsertId avec pdo. Du coup, tu économises une requête, tu as juste à faire un insert. Profites-en aussi pour ajouter un ou deux champs qui pourrait te servir au cas ou tu aurais une recherche à faire sur un nom par exemple.

@lex oui l'auto-increment c'est la première solution qui vient à l'esprit mais on a une table qui grossit.
Tu me rejoins dans l'idée qu'on peut utiliser ses informations comme 'trace', 'log' ou 'stats'.

Merci pour ces échanges, ils sont intéressant et constructifs.

@Huggy En effet la table va grossir, peut être qu'il serait bon de faire une petite fonction de nettoyage pour supprimer par exemple les estimations/devis vieux d'un mois et plus et de l'éxécuter de temps en temps soit manuellement soit avec une tâche cron ou autres.

Dans un fichier ça grossira aussi... avec l'inconvénient qu'il sera plus difficile de nettoyer/maintenir de façon "automatique".

L'auto-incrément comme base pour un numéro de facture ou autre est une très mauvaise idée. En effet si tu supprimes ou annules une facture, si tu décide de faire des devis/factures le numéro va bien sûr augmenter mais ne restera pas cohérent.

Merci pour cette remarque, cela dit les documents que j'incrémentes sont de simples estimations en ligne. Elles n'ont donc aucune valeur administrative comme pourrait l'avoir les devis et factures. Cela dis je prend bien note de ta remarque si je dois faire évoluer le système dans ce sens là.
Bon week end à venir