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 :

traiter des fichiers CSV en POO : quelle conception est mieux ? [POO]


Sujet :

Langage PHP

  1. #1
    Expert confirmé
    Avatar de laurentSc
    Homme Profil pro
    Webmaster débutant perpétuel !
    Inscrit en
    Octobre 2006
    Messages
    10 383
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Webmaster débutant perpétuel !
    Secteur : Industrie

    Informations forums :
    Inscription : Octobre 2006
    Messages : 10 383
    Points : 5 732
    Points
    5 732
    Billets dans le blog
    1
    Par défaut traiter des fichiers CSV en POO : quelle conception est mieux ?
    Bonjour,

    comme mon titre l'indique, mon appli gère des fichiers CSV. En gros, elle les lit, les parse, teste la validité du contenu, écrit leur contenu en BDD (si valide), et met en lecture les informations.

    Avant d'écrire le contenu en BDD, elle teste le contenu pour vérifier sa validité. Il y a donc 2 accès au contenu du fichier : 1- pour tester la validité et 2- pour écrire en BDD.

    Mais problème de conception : au cours du parsage, je vais créer des instances d'objet, que je vais ensuite transmettre aux classes chargées du test de validité et de l'écriture en BDD. Ma question : vaut-il mieux créer une instance par ligne du CSV ou une seule pour le CSV entier ?
    Comme le nombre de lignes est variable, je pencherais plutôt par une seule instance pour le fichier entier, mais du coup, l'instance à transmettre va contenir des tableaux (un par type de données contenue dans le CSV) dont le nombre d'éléments sera le nombre de lignes du CSV. Et ces tableaux, il faudra les parcourir 2 fois : 1 fois pour tester la validité et une 2e fois, si c'est valide, pour écrire en BDD. Ca ne me semble pas économique en performance. Y a-t-il une meilleure conception, SVP ?
    Il vaut mieux viser la perfection et la manquer que viser l'imperfection et l'atteindre. - Bertrand Russell

    Si la discussion est résolue, merci de cliquer sur le bouton

  2. #2
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    Petite précision: Quand une ligne est incorrecte, que doit-il se passer? Est-ce qu'elle est simplement sautée, puis on continue de traiter les autres ou bien est-ce que l'intégralité du fichier csv est abandonné (et donc aucune insertion n'aura lieu en base de données provenant de ce fichier)?
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

  3. #3
    Expert confirmé
    Avatar de laurentSc
    Homme Profil pro
    Webmaster débutant perpétuel !
    Inscrit en
    Octobre 2006
    Messages
    10 383
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Webmaster débutant perpétuel !
    Secteur : Industrie

    Informations forums :
    Inscription : Octobre 2006
    Messages : 10 383
    Points : 5 732
    Points
    5 732
    Billets dans le blog
    1
    Par défaut
    Tu fais bien de poser cette question qui me paraît judicieuse, même si sans y réfléchir, j'avais adopté la 2e option : si une erreur est détectée, aucune écriture en bdd (donc on abandonne le CSV). De toute façon, s'il y a une erreur, l'auteur du CSV devra corriger puis fournir une version corrigée qu'on mettra en bdd. Donc in fine, l'état de la bdd sera identique.
    Il vaut mieux viser la perfection et la manquer que viser l'imperfection et l'atteindre. - Bertrand Russell

    Si la discussion est résolue, merci de cliquer sur le bouton

  4. #4
    Membre actif
    Homme Profil pro
    Webmaster - Développeur/intégrateur web
    Inscrit en
    Septembre 2011
    Messages
    210
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France, Jura (Franche Comté)

    Informations professionnelles :
    Activité : Webmaster - Développeur/intégrateur web
    Secteur : Conseil

    Informations forums :
    Inscription : Septembre 2011
    Messages : 210
    Points : 246
    Points
    246
    Par défaut
    Bonjour,

    Si aucune ligne ne doit être insérée en base dans le cas où il y en au moins une ligne en erreur... alors tu n'a pas le choix, il faut faire les choses en 2 temps.

    Par contre tu peux te contenter de faire qu’un seul INSERT, tu peux par exemple préparer toutes tes lignes "(nom_colonne_1, nom_colonne_2, ... VALUES ('valeur 1', 'valeur 2', ...)" lors du parcours de validation du fichier, puis les regrouper au moment de faire l'INSERT.

    Il y a un exemple ici : https://sql.sh/cours/insert-into
    dans le paragraphe "Insertion de plusieurs lignes à la fois"

    Alors attention par contre au nombre de ligne par Insert, à vérifier mais il me semble que cela peut être limité... du coup il vaudrait mieux partir sur une répartition des lignes sur plusieurs Insert, en espérant qu'il n'y ait pas une des requêtes qui échoue...
    Si vous avez besoin d'une librairie permettant de gérer facilement les fichiers et les dossiers en PHP... ou si vous êtes juste curieux(se) :
    https://github.com/moDevsome/moFilesManager

    N'hésitez pas à me faire un retour

  5. #5
    Expert confirmé
    Avatar de laurentSc
    Homme Profil pro
    Webmaster débutant perpétuel !
    Inscrit en
    Octobre 2006
    Messages
    10 383
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Webmaster débutant perpétuel !
    Secteur : Industrie

    Informations forums :
    Inscription : Octobre 2006
    Messages : 10 383
    Points : 5 732
    Points
    5 732
    Billets dans le blog
    1
    Par défaut
    Merci pour ta réponse.
    D'abord, hier, en répondant à Cosmo, j'ai oublié une précision : même si on détecte une erreur, on continue à balayer les données pour repérer toutes les erreurs dans le CSV. Donc on n'abandonne l'écriture en BDD qu'à la fin du test de validité.

    Ensuite, OK pour un seul INSERT (en fait quelques-uns car pour simplifier mon exposé, j'ai parlé d'une seule table adressant le fichier CSV, mais en réalité, je répartis les informations sur 5 tables). En fait (merci de me dire si je me trompe), un ou quelques INSERTS par ligne du CSV, sachant qu'il y aura un enregistrement en BDD par ligne du CSV.
    Il vaut mieux viser la perfection et la manquer que viser l'imperfection et l'atteindre. - Bertrand Russell

    Si la discussion est résolue, merci de cliquer sur le bouton

  6. #6
    Modérateur
    Avatar de grunk
    Homme Profil pro
    Lead dév - Architecte
    Inscrit en
    Août 2003
    Messages
    6 691
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Côte d'Or (Bourgogne)

    Informations professionnelles :
    Activité : Lead dév - Architecte
    Secteur : Industrie

    Informations forums :
    Inscription : Août 2003
    Messages : 6 691
    Points : 20 222
    Points
    20 222
    Par défaut
    Si tes fichiers sont de taille raisonnable tu peux les parcourir une fois , stocker en mémoire le contenu. Puis faire le test de validité et éventuellement l'insertion sur ce contenu en mémoire.
    Ou encore , parcourir le fichier , tester ligne à ligne et stocker en mémoire ce qui est ok , pour éventuellement ensuite insérer.

    Ce n'est vaalable que pour des fichiers de quelques Ko à quelque Mo , au delà il faudra les parcourir deux fois , pas le choix.

    Pour des gros fichiers on peut imagine ca :
    Tu lis ligne à ligne
    Tu vérifie la validité
    Tu écris la ligne validé dans un nouveau fichier csv.
    Tu donne ce fichier csv à mysql pour import si tout est ok.

    Note que pour ce genre de travail les generateurs sont top car il permettent de garder la mémoire basse
    Pry Framework php5 | N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  7. #7
    Expert confirmé
    Avatar de laurentSc
    Homme Profil pro
    Webmaster débutant perpétuel !
    Inscrit en
    Octobre 2006
    Messages
    10 383
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Webmaster débutant perpétuel !
    Secteur : Industrie

    Informations forums :
    Inscription : Octobre 2006
    Messages : 10 383
    Points : 5 732
    Points
    5 732
    Billets dans le blog
    1
    Par défaut
    Merci pour ta réponse, Grunk.

    Je ne pense pas avoir à traiter des gros fichiers, et heureusement, car j'ai rien compris aux generator...

    Donc, je vais stocker le contenu du CSV dans un tableau.

    Simplement, pour la suite (le test de validité puis éventuellement le stockage en BDD), je suis obligé de parcourir 2 fois le tableau. Pas moyen d'optimiser. On est d'accord ?
    Il vaut mieux viser la perfection et la manquer que viser l'imperfection et l'atteindre. - Bertrand Russell

    Si la discussion est résolue, merci de cliquer sur le bouton

  8. #8
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    C'est bien dommage car l'idée de fournir un générateur est bonne (et en plus c'est comme ça que je vois les choses). Avec ce générateur qui te donnera les lignes une par une, tu pourras les valider au fur et à mesure. Et en ce qui concerne, l'insertion en base de données, tu n'as pas besoin de construire un tableau non plus: il suffit pour cela d'utiliser une transaction et d'effectuer un rollback en cas d'erreur de validation.

    Voici un exemple (à compléter car non fonctionnel):
    • La validation (ça c'est fonctionnel)
      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
      interface iValidator
      {
           public function validate($value):bool;
      }
       
      class FieldValidator implements iValidator
      {
          /* $constraints est un tableau dont les items sont de type callable
              et renvoient true ou false */
          protected $constraints;
       
          public function __construct($constraints) {
              $this->constraints = $constraints;
          }
       
          public function validate($value):bool {
              foreach ($this->constraints as $constraint) {
                  if ( $constraint($value) === false ) {
                      return false;
                  }
              }
              return true;
          }
      }
       
      class RowValidator implements iValidator
      {
          protected $validators;
       
          public function __construct(array $validators) {
              $this->validators = $validators;
          }
       
          public function validate($fields):bool {
              foreach ($fields as $k => $field) {
                  if ( $this->validators[$k]->validate($field) === false ) {
                      return false;
                  }
              }
              return true;
          }        
      }
    • La partie CSV (ça c'est pas complet du tout)
      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
      /**
       * AbstractCSV est une classe abstraite qui ne peut donc pas être instanciée.
       * Un ensemble de données csv n'est pas forcément issu d'un fichier mais peut provenir d'une chaîne
       * ou d'un flux. On se limite donc aux caractèristiques d'un tel ensemble:
       *   - les caractères de séparation, protection, échappement.
       *   - la possibilité d'avoir des colonnes nommées à partir d'une ligne d'entète.
       *   - un nombre de champs fixe.
       * 
       * À noter que la méthode checkFieldsNumber est abstraite ce qui impose aux classes qui héritent de
       * de AbstractCSV de l'implémenter, mais laisse à leur discrètion les détails de cette implémentation.
       * Celle-ci sera différente pour la lecture ou pour l'écriture de données csv, mais n'en sera pas moins
       * nécessaire pour garantir l'intégrité des données dans les deux cas.
       */
       
      abstract class CSV
      {
          protected string $separator = ',';
          protected string $protection = '"';
          protected string $escape = '\\';
       
          protected ?int $fieldsNumber = null;
       
          protected ?array $headers = null;
       
       
          abstract function checkFieldsNumber(int $fieldsNumber):bool;
       
          protected function hasHeaders():bool {
              return (bool) $this->headers;
          }
      }
       
      /**
       * Une interface contient une ou plusieurs des méthodes relatives à un comportement précis.
       * Les classes qui l'implémentent, "signent un contract" avec elle et ont l'obligation
       * d'implémenter ses méthodes (à l'instar d'une méthode abstraite dont elles auraient héritée).
       * Ceci fait, ces classes disposent du comportement en question.
       * 
       * Dans le cas présent: une classe qui implémente iReader, dispose d'une méthode
       * getReader() et peut se comporter comme un Reader. C'est aussi bête que ça.
       * 
       * En sus de blinder le code, l'interface permet une souplesse dans le code, car au lieu de
       * vérifier qu'un objet est une instance d'une des classes ayant le comportement recherché, il
       * suffit de vérifier que l'objet provient d'une classe, quelle qu'elle soit, qui implémente
       * l'interface, et ce, sans pour autant connaitre la classe, via un type hint.
       * On peut résumer la chose ainsi:
       * - Je me fout de savoir si c'est un canard tant que ça fait coin-coin.
       * 
       */
       
      interface iReader
      {
          public function getReader();
      }
       
      interface iFile
      {
          protected function setLocation(string $location):void;
       
          protected function exists():bool;
       
          protected function isReadable():bool;
       
          protected function isWritable():bool;
       
      }
       
      trait FileTrait
      {
          protected ?string $location;
       
          protected function setLocation(string $location):void {
              $this->location = $location;
          }
       
          protected function exists():bool {
              return is_file($this->location);
          }
       
          protected function isReadable():bool {
              return is_readable($this->location);
          }
       
          protected function isWritable():bool {
              return is_writable($this->location);
          }
      }
       
      class CSVFileReader extends CSV implements iReader, iFile
      {
          use FileTrait;
       
          public function __construct($path)
          {
              $this->path = $path;
              $this->open();
          }
       
          public function getReader() {
              while ( false !== $fields = fgetcsv($this->handle) ) {
                  yield $fields;
              }
              $this->close($this->handle);
          }
       
          protected function checkFieldsNumber(int $fieldsNumber):bool {
              return $this->fieldsNumber === $fieldsNumber;
          }
      }
      L'important est que la classe CSVFileReader dispose de la méthode getReader() qui est justement un générateur.
    • Voici un exemple d'utilisation de l'ensemble:
      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
      $rowValidator = new RowValidator([
          new FieldValidator([
              'is_numeric',
              fn($v) => $v > 11
          ]),
          new FieldValidator([
              fn($v) => false !== strpos($v, 'marmotte'), 
              fn($v) => !preg_match('~ \[ [^]]* marmotte [^]]* ] ~x', $v)
          ]) //, ...
      ]);
       
       
      $reader = new CSVFileReader('path/to/my/file.csv');
       
      $genRow = $reader->getReader();
       
      $pdo->beginTransation();
      $query = 'INSERT INTO matable (col1, col2, ... coln) VALUES (?, ?, ... ?)';
      $stmt = $pdo->prepare($query);
       
      $valid = true;
       
      foreach($genRow as $fields) {
          if ( !$rowValidator->validate($fields) ) {
              $valid = false;
              $pdo->rollBack();
              break;
          }
          $stmt->execute($fields);
      }
       
      if ( $valid ) {
          $pdo->commit();
      }
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

  9. #9
    Expert confirmé
    Avatar de laurentSc
    Homme Profil pro
    Webmaster débutant perpétuel !
    Inscrit en
    Octobre 2006
    Messages
    10 383
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Webmaster débutant perpétuel !
    Secteur : Industrie

    Informations forums :
    Inscription : Octobre 2006
    Messages : 10 383
    Points : 5 732
    Points
    5 732
    Billets dans le blog
    1
    Par défaut
    Ouh , là, là...

    je suis à des années-lumière de maîtriser un tel code. La notion d'interface, déjà vue mais jamais utilisée (en fait, si, quand j'utilise le code de rawsrc, mais incapable d'en créer une moi-même), quant à celle de trait, je connais que de nom.
    $constraints est un tableau dont les items sont de type callable
    c'est du chinois.
    il suffit pour cela d'utiliser une transaction et d'effectuer un rollback en cas d'erreur de validation.
    ça aussi.

    Donc ça a l'air intéressant, mais je doute de suivre ce chemin...
    Il vaut mieux viser la perfection et la manquer que viser l'imperfection et l'atteindre. - Bertrand Russell

    Si la discussion est résolue, merci de cliquer sur le bouton

  10. #10
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    Un trait est juste un morceau de code prêt à intégrer dans une classe, je peux écrire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class A {
        public $a;
        public $b;
     
        public function abc() { /******/ }
        public function def() { /******/ }
    }
    ou
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    trait toto {
        public $b;
     
        public function def() { /******/ }
    }
     
    class A {
        use toto;
        public $a;
     
        public function abc() { /******/ }
    }
    Ça revient au même.
    (Le trait, notamment couplé à une interface, permet de pallier à l'absence d'héritage multiple en PHP: on impose la contrainte d'implémenter des méthodes avec l'interface, on les rend disponibles avec le trait).




    Le type callable désigne quelque chose qu'on peut appeler comme un fonction. Exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    $toto = function ($x) { echo $x; }; 
    $toto('lapin'); // lapin
    var_dump(is_callable($toto)); // true
     
    $toto = 'trim';
    var_dump(is_callable($toto)); // true
     
    $toto = 'trom';
    var_dump(is_callable($toto)); // false
    Une transaction est un mécanisme SQL, il n'y a rien de plus simple:
    • on démarre une transaction
    • on effectue des requêtes
    • si on effectue un rollback, la base de données revient à l'état précédant la transaction
    • si on effectue un commit, les requêtes sont appliquées.
    Ça se fait avec trois méthodes PDO (beginTransaction, rollBack et commit). (et en plus, les requêtes effectuées lors d'une transaction, s'effectueront plus rapidement que si elles avaient été appliquées une à une).
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

  11. #11
    Expert confirmé
    Avatar de laurentSc
    Homme Profil pro
    Webmaster débutant perpétuel !
    Inscrit en
    Octobre 2006
    Messages
    10 383
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Webmaster débutant perpétuel !
    Secteur : Industrie

    Informations forums :
    Inscription : Octobre 2006
    Messages : 10 383
    Points : 5 732
    Points
    5 732
    Billets dans le blog
    1
    Par défaut
    Merci pour tes explications qui me donnent envie d'insister (ça a pas l'air si compliqué que ça), alors qu'hier soir, dans ma tête, j'avais déjà abandonné...

    Mais je n'ai pas compris le type callable. J'ai testé ton code
    Code php : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /*
     * Le type callable désigne quelque chose qu'on peut appeler comme un fonction. Exemple:
     */
    $toto = function ($x) { echo $x; };
    $toto('lapin'); // lapin
    var_dump(is_callable($toto)); // true
     
    $toto = 'trim';
    var_dump(is_callable($toto)); // true
     
    $toto = 'trom';
    var_dump(is_callable($toto)); // false
    Donc, d'accord, mais (je n'ai pas trouvé d'explication sur le web), pourquoi 'trim' est callable et pas 'trom' ? En fait, 'trim' ça ressemble pas à un appel de fonction...
    Il vaut mieux viser la perfection et la manquer que viser l'imperfection et l'atteindre. - Bertrand Russell

    Si la discussion est résolue, merci de cliquer sur le bouton

  12. #12
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    Parce que "trim" est le nom d'une fonction qui existe (contrairement à "trom"): echo trim(' x'); // 'x'.

    Sinon la partie poo de mon précédent message n'est pas essentielle par rapport à ta question initiale, c'est juste pour illustrer le fait qu'il y a des moyens d'éviter de produire une classe géante qui fait le café en déportant tout ce qui ne concerne pas directement "le cœur de métier" de la classe.
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

  13. #13
    Expert confirmé
    Avatar de laurentSc
    Homme Profil pro
    Webmaster débutant perpétuel !
    Inscrit en
    Octobre 2006
    Messages
    10 383
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Webmaster débutant perpétuel !
    Secteur : Industrie

    Informations forums :
    Inscription : Octobre 2006
    Messages : 10 383
    Points : 5 732
    Points
    5 732
    Billets dans le blog
    1
    Par défaut
    Merci de m'avoir expliqué pourquoi 'trim' était callable.

    La partie POO n'est peut-être pas essentielle dans mon cas, mais elle me permet de comprendre ce qu'est un trait, et donc de comprendre ton code.

    Le trait, notamment couplé à une interface, permet de pallier à l'absence d'héritage multiple en PHP: on impose la contrainte d'implémenter des méthodes avec l'interface, on les rend disponibles avec le trait
    J'ai essayé de faire un code pour mettre en application cela, mais ça veut pas (y a sûrement des grosses erreurs) :
    Code php : 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
    <?php
    interface I1
    {
        public function test1();
    }
     
    interface I2
    {
        public function test2();
    }
     
    trait T implements I1, I2
    {
        public function test1 ()
        {
            echo "test1";
        }
     
        public function test2 ()
        {
            echo "test2";
        }
    }
     
    class A
    {
        use T;
    //    $this->test1();
    //    $this->test2();
    }

    Parse error: syntax error, unexpected 'implements' (T_IMPLEMENTS), expecting '{' in C:\projets\dvp\test\heritage_multiple.php on line 12
    Pourrais-tu me corriger STP ?
    Il vaut mieux viser la perfection et la manquer que viser l'imperfection et l'atteindre. - Bertrand Russell

    Si la discussion est résolue, merci de cliquer sur le bouton

  14. #14
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    Ce n'est pas le trait que l'on veut forcer à implémenter des méthodes par le biais d'interfaces, c'est la classe! Le trait fournit juste le code. Donc:
    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
    interface I1
    {
        public function test1();
    }
     
    interface I2
    {
        public function test2();
    }
     
    trait T
    {
        public function test1 ()
        {
            echo "test1";
        }
     
        public function test2 ()
        {
            echo "test2";
        }
    }
     
    class A  implements I1, I2
    {
        use T;
    }
    Attention à ne pas utiliser le trait comme quelque chose de purement cosmétique (une sorte de fourre-tout pour que le code de la classe semble plus léger). Il doit regrouper des méthodes qui correspondent à un comportement précis. Il en va de même pour les interfaces. C'est pourquoi dans ton exemple, si I1 et I2 imposent chacune un comportement, il devrait également y avoir respectivement les traits T1 et T2 avec les propriétés et méthodes nécessaires à chacun des comportements.
    Autrement dit, si on pense que I1 et I2 décrivent des comportements différents, il n'y a pas de raison qu'ils soient regroupés dans le même trait (Même si techniquement ça marche, c'est le sens qui doit tout diriger).
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

  15. #15
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    Un exemple concret:
    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
    interface RadioInterface
    {
        public function setStation();
    }
     
    trait RadioTrait
    {
        protected $station;
     
        public function setStation($station) {
            $this->station = $station;
        }
    }
     
     
    interface ReveilInterface
    {
        public function setHeureReveil();
        public function sonne();
    }
     
    trait ReveilTrait
    {
        protected $heureReveil;
     
        public function setHeureReveil($heureReveil) {
            $this->heureReveil = $heureReveil;
        }
     
        public function sonne() {
            // 
        }
    }
     
    // l'intérêt de bien identifier et cerner les comportements est la réutilisabilité de ces
    // couples interface/trait
     
    class PosteDeRadio implements RadioInterface
    {
        use RadioTrait;
    }
     
    // un minuteur n'est pas à proprement parler un réveil, mais il peut se comporter comme tel
    class Minuteur implements ReveilInterface
    {
        use ReveilTrait;
    }
     
    class RadioReveil implements RadioInterface, ReveilInterface
    {
        use RadioTrait, ReveilTrait;
     
        protected $sonnerie = 'bip bip';
     
        public function changeSonnerie() { /***/ }
    }
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

  16. #16
    Expert confirmé
    Avatar de laurentSc
    Homme Profil pro
    Webmaster débutant perpétuel !
    Inscrit en
    Octobre 2006
    Messages
    10 383
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Webmaster débutant perpétuel !
    Secteur : Industrie

    Informations forums :
    Inscription : Octobre 2006
    Messages : 10 383
    Points : 5 732
    Points
    5 732
    Billets dans le blog
    1
    Par défaut
    Merci beaucoup pour tes explications. J'ai néanmoins encore des interrogations.

    D'abord, il semblerait que tu définis un trait pour chaque interface. C'est une "best practice" ?

    Ensuite, la classe RadioReveil qui implémente les interfaces et utilisent les traits, ne fait pas appel aux méthodes qu'elle a "héritées" (en quelque sorte). Pourrais-tu compléter ton exemple avec cela ?

    Enfin, pour revenir au post #8 où tu disais que la partie CSV n'est pas complète, j'ai compris notamment que la classe CSVFileReader devrait fournir le code de toutes les interfaces qu'elle implémente. C'est ça qui fait qu'elle n'est pas complète ?
    Il vaut mieux viser la perfection et la manquer que viser l'imperfection et l'atteindre. - Bertrand Russell

    Si la discussion est résolue, merci de cliquer sur le bouton

  17. #17
    Modérateur
    Avatar de grunk
    Homme Profil pro
    Lead dév - Architecte
    Inscrit en
    Août 2003
    Messages
    6 691
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Côte d'Or (Bourgogne)

    Informations professionnelles :
    Activité : Lead dév - Architecte
    Secteur : Industrie

    Informations forums :
    Inscription : Août 2003
    Messages : 6 691
    Points : 20 222
    Points
    20 222
    Par défaut
    Les traits et les interfaces ne sont pas nécessairement liés.
    Une interface c'est un "contrat" que tu passes avec une classe. Implémenter une interface c'est ce lié à ce "contrat" et donc on est obligé d'implémenter le fonctionnement qu'elle nous impose. Au contraire l'héritage nous propose des fonctionnalité mais il n'ya pas d'obligation.
    Très clairement quand tu développes tous seul dans ton coin les interfaces on un intérêt très limité. Mais quand tu es plusieurs ca permet à quelqu'un qui viendrait ajouter quelque chose à ton code de savoir tout de suite ce qu'il à besoin de faire pour que ca classe fonctionne comme attendu.

    Comme CosmoKnacki le dit le trait c'est un bout de code qui est disponible et utilisable par une ou plusieurs classes.

    Ca répond à plusieurs problématique :
    - La duplication de code => j'ai pas besoin d'écrire 3x la même choise
    - L'héritage fonctionnel => hériter d'une classe juste pour une fonctionnalité qu'elle possède sans réele logique objet. Ex : Ma classe user dérive de pdo parce qu'elle à besoin de se connecter, c'est un héritage fonctionnel et c'est pas bien
    - L'héritage multiple => en php on ne peut pas hériter de 2 classes différentes , même si dans les langages qui le permettent c'est souvent signe d'une mauvaise conception.
    Pry Framework php5 | N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java

  18. #18
    Expert confirmé
    Avatar de laurentSc
    Homme Profil pro
    Webmaster débutant perpétuel !
    Inscrit en
    Octobre 2006
    Messages
    10 383
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 60
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Webmaster débutant perpétuel !
    Secteur : Industrie

    Informations forums :
    Inscription : Octobre 2006
    Messages : 10 383
    Points : 5 732
    Points
    5 732
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par grunk Voir le message
    Les traits et les interfaces ne sont pas nécessairement liés.
    OK, d'ailleurs, j'ai déjà vu du code avec des interfaces mais y avait pas de trait. Par contre, dans l'exemple fourni par CosmoKnacki au post #15, les traits implémentent toutes les méthodes définies dans les interfaces. Je me demandais si on fait souvent ça ?

    Citation Envoyé par grunk Voir le message
    Très clairement quand tu développes tous seul dans ton coin les interfaces on un intérêt très limité.
    Dans l'exemple fourni par CosmoKnacki au post #8, il y a des interfaces. Il semblerait qu'on puisse les supprimer, non ?

    Citation Envoyé par grunk Voir le message
    Ca répond à plusieurs problématique :
    - La duplication de code => j'ai pas besoin d'écrire 3x la même choise
    Avant de découvrir les traits (aujourd'hui), je le faisais avec de l'héritage, mais comme tu le dis juste après, c'est pas bien...
    Citation Envoyé par grunk Voir le message
    - L'héritage fonctionnel => hériter d'une classe juste pour une fonctionnalité qu'elle possède sans réele logique objet. Ex : Ma classe user dérive de pdo parce qu'elle à besoin de se connecter, c'est un héritage fonctionnel et c'est pas bien
    Citation Envoyé par grunk Voir le message
    - L'héritage multiple => en php on ne peut pas hériter de 2 classes différentes ,
    Dans l'exemple concret donné par CosmoKnacki au post #15, la classe RadioReveil "hérite" des traits RadioTrait, ReveilTrait mais il ne dit pas comment on utilise les méthodes apportées par ces traits. Peux-tu le faire, STP ?
    Il vaut mieux viser la perfection et la manquer que viser l'imperfection et l'atteindre. - Bertrand Russell

    Si la discussion est résolue, merci de cliquer sur le bouton

  19. #19
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    la classe RadioReveil qui implémente les interfaces et utilisent les traits, ne fait pas appel aux méthodes qu'elle a "héritées" (en quelque sorte). Pourrais-tu compléter ton exemple avec cela ?
    Pourquoi une classe devrait-elle absolument faire appel à ses propres méthodes? Il n'y a aucune obligation à cela. J'ai complété mon exemple (et retirer quelques bugs) pour que tu vois qu'il n'y a rien de particulier.

    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
    interface RadioInterface
    {
        public function setStation($station);
    }
     
    trait RadioTrait
    {
        protected $station;
     
        public function setStation($station) {
            $this->station = $station;
        }
    }
     
     
    interface ReveilInterface
    {
        public function setHeureReveil($heureReveil);
        public function getHeureReveil();
        public function sonne();
     
    }
     
    trait ReveilTrait
    {
        protected $heureReveil;
     
        public function setHeureReveil($heureReveil) {
            $this->heureReveil = $heureReveil;
        }
     
        public function getHeureReveil() {
            return $this->heureReveil;
        }
     
        public function sonne() {
            echo str_repeat('TutTutTutTut!!     ', 5), PHP_EOL; 
        }
    }
     
    class RadioReveil implements RadioInterface, ReveilInterface
    {
        use RadioTrait, ReveilTrait;
     
        public function afficheHeure() {
            echo date('H:i:s'), PHP_EOL;
        }
    }
     
    $rr = new RadioReveil();
     
    $rr->setHeureReveil('7h00');
    echo $rr->getHeureReveil(), PHP_EOL;
     
    $rr->sonne();
     
    $rr->afficheHeure();
    il semblerait que tu définis un trait pour chaque interface.
    D'un point de vue fonctionnel, un trait ne sert à rien, c'est juste un copier/coller de son contenu vers la classe, ça peut faire une économie de code s'il est partagé dans plusieurs classes, et ça donne un coup de propre dans le corps des classes, mais on peut très bien s'en passer et écrire les méthodes et les propriétés directement dans la classe censée utiliser le trait en question.

    L'intérêt d'associer interface et trait est uniquement guidé par la sémantique, par le sens. On pourrait aussi ne pas les associer et se passer du trait. La démarche de cette association est la suivante:
    1. J'identifie un comportement dont je veux munir une ou plusieurs classes.
    2. Je liste les méthodes nécessaires à ce comportement dans une interface (je lui donne le nom du comportement).
    3. Je groupe les éléments nécessaires à l'implémentation du comportement dans un trait et je le nomme pareillement.

    On est pas dans des histoires de bonnes ou mauvaises pratiques, la sémantique c'est l'essence même de la poo. C'est le premier guide (voire le premier garde-fou) avant même les contraintes liées aux fonctionnalités. C'est pour cette raison qu'on ne va pas faire hériter une supérette d'une vache juste pour qu'elle puisse fournir du lait à ces clients, alors que du point de vue fonctionnel, absolument rien ne t'en empêche (prend tout de même garde à ne traire personne dans le magasin).

    Bien entendu, associer une interface avec un unique trait (donc commun à toutes les classes qui disposeraient de cette interface) n'est pas toujours possible. Mais avant même d'apporter une solution technique à ce problème (comme ne pas utiliser le trait pour certaines classes ou bien le décliner de plusieurs manières, ou simplement le supprimer), on doit se demander si ça ne révèle pas un problème lié à une mauvaise définition des contours du comportement qu'on a voulu isoler.


    Enfin, pour revenir au post #8 où tu disais que la partie CSV n'est pas complète, j'ai compris notamment que la classe CSVFileReader devrait fournir le code de toutes les interfaces qu'elle implémente. C'est ça qui fait qu'elle n'est pas complète ?
    Non pas du tout, toutes les méthodes des interfaces sont bien présentes, les unes définies au sein même de la classe, les autres apportées par le trait FileTrait. Je ne crois pas que ce bout de code soit fonctionnel en l'état, c'est tout.
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

  20. #20
    Expert éminent Avatar de CosmoKnacki
    Homme Profil pro
    Justicier interdimensionnel
    Inscrit en
    Mars 2009
    Messages
    2 858
    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 858
    Points : 6 556
    Points
    6 556
    Par défaut
    Pour en revenir aux interfaces, même en codant seul dans son coin (et pas en équipe), elles constituent un sérieux garde-fou (le premier ennemi est celui de l'intérieur).
    D'autre part, elles permettent surtout de s'attacher aux comportements plutôt qu'à l'origine de l'objet:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    // Là on vérifie tout ce qui est susceptible de sonner en testant si $obj est une instance d'une de ces classes
    if ( $obj instanceof Telephone || $obj instanceof Cloche || $obj instanceof Gong ) {
        $obj->sonne();
    }
     
    // Par contre si ces trois classes implémentent l'interface, disons SonneurInterface, pas besoin de
    // savoir d'où provient l'objet
    if ( $obj instanceof SonneurInterface ) {
       $obj->sonne();
    }
    Cet aspect des interfaces est particulièrement exploité dans les design patterns.
    Brachygobius xanthozonus
    Ctenobrycon Gymnocorymbus

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 3 123 DernièreDernière

Discussions similaires

  1. Réponses: 2
    Dernier message: 13/03/2007, 11h19
  2. Gestion des fichiers CSV
    Par sony351 dans le forum C++Builder
    Réponses: 5
    Dernier message: 02/11/2006, 10h11
  3. importe des fichier csv sous eclipse
    Par nael_n dans le forum PostgreSQL
    Réponses: 1
    Dernier message: 21/08/2006, 13h57
  4. importer des fichier csv sous eclipse
    Par nael_n dans le forum Eclipse Java
    Réponses: 2
    Dernier message: 11/08/2006, 13h00
  5. Réponses: 7
    Dernier message: 15/06/2006, 17h36

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