Utiliser le contenu d'une variable comme nom de méthode

Bonjour,

Tout est dans le titre et je comprends que cela puisse sembler bizarre.
J'ai un code PHP dont voici l'extrait simplifié
En gros à partir d'une structure JSON on génère du code SQL
J'ai une classe générateur dans laquelle j'ai ceci

function __construct() {        
    $this->fonctionsSql = new fonctionsSql($this);      
}
.....

$fonction = "INSTR";
$chaine .= $this->fonctionsSql->$fonction($element);

Dans ma classe fonctionSql j'ai une méthode INSTR
En php la méthode en question est bien appelée

Est-ce qu'il est possible de faire la même chose en swift

Réponses

  • Joanna CarterJoanna Carter Membre, Modérateur
    23 juil. modifié #2

    Le mieux que j'ai pu faire avec tes infos, c'est :

    struct FonctionsSQL
    {
      weak var generateur: Generateur?
    
      func instr(param: String) -> String
      {
        return ""
      }
    }
    
    class Generateur
    {
      lazy var fonctionsSql: FonctionsSQL =
      {
        return FonctionsSQL(generateur: self)
      }()
    
      func test()
      {
        let element = "valeur"
    
        let chaine: String = fonctionsSql.instr(param: element)
    
        // …
      }
    }
    
  • yannSyannS Membre

    Merci, mais j'ai peur que cela corresponde au besoin c'est vrai que je n'ai pas donné beaucoup d'infos
    J'ai continué mes recherches d'après ce que j'ai compris il serait possible d'utiliser des selectors
    J'ai testé ceci :

    class Test : NSObject {
        @objc func hello() {
            print("hello")
        }
    }
    
    let objTest = Test()
    let fonction = "hello:"
    objTest.perform(NSSelectorFromString(fonction))
    

    Malheureusement ça plante à la dernière ligne que j'indique @objc ou pas
    ça te semble un voie possible ?

  • Joanna CarterJoanna Carter Membre, Modérateur

    Swift n'est pas une langue interprétée. Bien sûr, avec Objective-C, on peut utiliser les selectors mais, avec Swift, tu ne devrait plus les utiliser ; il y a les autres moyens.

    Pourquoi tu veux trouver une méthode par son nom quand tu peux tout simplement l'appeler directement ?

    class Test
    {
      func hello()
      {
        print("hello")
      }
    }
    
    let objTest = Test()
    
    objTest.hello()
    
  • yannSyannS Membre

    En fait la configuration de la requête SQL à créer est stockée dans une chaîne Json.
    Le générateur analyse les diffèrentes propriétés du Json et prépare les différents éléments.
    Dans les calculs le générateur peut trouver des fonctions SQL particulières.
    Si le générateur trouve une propriété instr par exemple il doit lire d’autres propriétés spécifiques pour créer le code SQL correspondant.
    Dans le code PHP d’origine les méthodes de constitution des fonctions portent le nom des fonctions elles-mêmes, je voulais simplement reprendre le même fonctionnement.

  • Joanna CarterJoanna Carter Membre, Modérateur
    23 juil. modifié #6

    Dans ce cas là, peut-être un dictionnaire avec String pour les clés et un closure pour les valeurs :

    struct FonctionsSQL
    {
      var fonctions = [String : (() -> String)]()
    
      subscript(key: String) -> (() -> String)
      {
        return fonctions[key]
      }
    
      init()
      {
        fonctions["INSTR"] = {
                               return "SQL instr"
                             }
    
         fonctions["FRED"] = {
                               return "SQL fred"
                             }
      }
    }
    
    let fonctions = FonctionsSQL()
    
    let sql = fonctions["INSTR"]
    
  • yannSyannS Membre

    Ok, merci, une dernière question : de cette manière est-ce qu’il serait possible de passer des arguments aux clôtures?

  • Joanna CarterJoanna Carter Membre, Modérateur
    24 juil. modifié #8

    Oui. Mais il faut avoir le même nombre et type d'arguments. Si tu prévois un nombre variable, il faut les mettre dans un seul objet et les passer comme ça.

  • CéroceCéroce Membre, Modérateur

    @yannS a dit :

    J'ai continué mes recherches d'après ce que j'ai compris il serait possible d'utiliser des selectors

    Les selectors sont en fait une fonctionnalité d'Objective-C. On peut effectivement faire hériter une classe Swift de NSObject auquel cas il se comporte comme une classe Objective-C, ce qui s'avère nécessaire pour pouvoir s'interfacer avec tout le code ObjC écrit depuis 30 ans.

    J'ai testé ceci :

    class Test : NSObject {
      @objc func hello() {
          print("hello")
      }
    }
    
    let objTest = Test()
    let fonction = "hello:"
    objTest.perform(NSSelectorFromString(fonction))
    

    Malheureusement ça plante à la dernière ligne que j'indique @objc ou pas

    Oui, c'est normal. Essaie:

    let fonction = "hello"
    

    Parce qu'en ObjC, -[hello] et -[hello:] sont deux méthodes différentes. La seconde prend un argument.

    ça te semble un voie possible ?

    Oui, mais ce sera sans doute plus compliqué que la solution proposée par Joanna — utiliser un dictionnaire [nom: fonction]. De toute façon, le JSON ne peut référencer que des fonctions prévues.

    Si vraiment tu as beaucoup de fonctions, jette un œil à Sourcery pour générer le code.

  • Joanna CarterJoanna Carter Membre, Modérateur
    24 juil. modifié #10

    Pour les paramètres, on peut faire mieux qu'un Array de Any, mais ce n'est qu'un exemple :

    struct FonctionsSQL
    {
      var fonctions = [String : (([Any]) -> String)]()
    
      subscript(key: String) -> (([Any]) -> String)?
      {
        return fonctions[key]
      }
    
      init()
      {
        fonctions["INSTR"] = {
                               params in
    
                               return "SQL instr \(params)"
                             }
    
        fonctions["FRED"] = {
                              params in
    
                              return "SQL fred \(params)"
                            }
      }
    }
    
      {
        let fonctions = FonctionsSQL()
    
        if let instrFonction = fonctions["INSTR"]
        {
          let sql = instrFonction(["Joanna", 123])
    
          print(sql)
        }
      }
    
  • yannSyannS Membre

    Merci pour l'exemple, j'ai légèrement retouché le code, playground n'était d'accord avec tout :smile:
    Le voici

    struct FonctionsSql {
    
        var fonctions = [String : ([Any]) -> String]()
    
        subscript(key: String) -> ([Any]) -> String {
            return fonctions[key]!
        }
    
    
        init(){
            fonctions["INSTR"] = { param in
                return "SQL instr \(param)"
            }
    
            fonctions["MAX"] = { param in
                return "SQL max \(param)"
            }
        }
    }
    
    
    let fonctions = FonctionsSql()
    let sql = fonctions["INSTR"](["ABCD",123])
    

    C'est tout bon, y a plus qu'à ...

  • Joanna CarterJoanna Carter Membre, Modérateur
    24 juil. modifié #12

    Non. Tu ne peux pas écrire

    let fonctions = FonctionsSql()
    let sql = fonctions["INSTR"](["ABCD",123])
    

    … parce que fonctions[key] renvoie une valeur optionnelle. Il ne faut jamais utiliser les !comme ça !!!

    Du coup, il faut écrire ce que je t'ai montré avec l'optionnel pour le retour du subscript, et le if let … pour gérer la possibilité de ne pas trouver une closure pour le clé que tu envoies.

    Tout le code que j'ai écrit a été validé en Xcode et fonctionne nickel et je viens de le tester en Playground ; pas de problème là aussi

  • Joanna CarterJoanna Carter Membre, Modérateur
    24 juil. modifié #13

    Et, pour les params plus sécurisés en termes de types :

    protocol Params { }
    
    struct InstrParams : Params
    {
      let nom: String
    
      let age: Int
    }
    
    struct FredParams : Params
    {
      let montant: Double
    }
    
    struct FonctionsSQL
    {
      var fonctions = [String : ((Params) -> String?)]()
    
      subscript(key: String) -> ((Params) -> String?)?
      {
        return fonctions[key]
      }
    
      init()
      {
        fonctions["INSTR"] = {
                               params in
    
                               guard let params = params as? InstrParams else
                               {
                                 return nil
                               }
    
                               return "SQL instr \(params.nom) \(params.age)"
                             }
    
        fonctions["FRED"] = {
                              params in
    
                              guard let params = params as? FredParams else
                              {
                                return nil
                              }
    
                              return "SQL fred \(params.montant)"
                            }
      }
    }
    
      {
        let fonctions = FonctionsSQL()
    
        if let instrFonction = fonctions["INSTR"]
        {
          let params = InstrParams(nom: "Joanna", age: 21)
    
          let sql = instrFonction(params)
    
          print(sql)
        }
    
        if let fredFonction = fonctions["FRED"]
        {
          let params = FredParams(montant: 12.34)
    
          let sql = fredFonction(params)
    
          print(sql)
        }
    
      }
    

    Mais, attention ! Il ne faut pas changer le ! et les ? :p

  • yannSyannS Membre

    Merci c’est noté

Connectez-vous ou Inscrivez-vous pour répondre.