4

J'ai une webapp écrite en PHP en utilisant un backend de base de données MySQL. Cette question pourrait tout aussi bien s'appliquer à n'importe quelle langue et application essayant d'utiliser une base de données SQL et une conception MVC OOP.Quelles sont les meilleures pratiques pour empêcher le fluage SQL?

Comment garder votre code SQL restreint au modèle?

Il y a une histoire plus longue spécifique à mon cas derrière la question. Comme mentionné précédemment, je travaille sur un site web PHP/MySQL/AJAX. Je l'ai conçu en utilisant les principes de conception OOP et MVC - avec un modèle, vue et contrôleur. J'ai réussi à garder les éléments de la vue - tels que le balisage et le style - entièrement restreints à la vue et à les rendre facilement réutilisables. Je pensais que j'avais fait la même chose avec le code SQL. Mais au fur et à mesure que le travail progresse, il devient évident que le modèle a besoin d'être sérieusement refactorisé.

La méthode que j'avais trouvée pour conserver le SQL dans le modèle consistait à encapsuler chaque requête SQL dans son propre objet de requête. Et puis, quand j'ai eu besoin d'appeler du SQL dans la vue ou le contrôleur, j'accédais à la requête via une usine. Aucun code SQL n'existe dans le contrôleur ou la vue.

Mais cela est devenu extrêmement fastidieux. Je ne pense pas que je gagne quelque chose en faisant cela et je passe beaucoup trop de temps à créer des requêtes avec des noms comme "SelectIdsFromTableWhereUser". L'usine pour les requêtes approche des milliers de lignes. Un peu de recherche dans Eclipse a révélé que la grande majorité de ces requêtes sont utilisées dans un ou deux endroits et jamais plus. Pas bon.

Je sais que dans un bon MVC vous voulez garder le SQL complètement séparé du contrôleur ou de la vue. Mais à ce stade, il me semble qu'il aurait été préférable de simplement placer le SQL là où il était nécessaire dans le code et ne pas essayer d'enterrer le code de base de données dans le modèle. Ces requêtes ne sont utilisées qu'une seule fois, pourquoi s'embêter à les encapsuler?

Est-il si important de séparer le SQL du contrôleur ou de la vue? Qu'est-ce qui est gagné en faisant cela? Que perd-on en lui permettant de se propager? Comment pouvez-vous résoudre ce problème?

Modifier Par demande, voici un peu plus de détails sur mon modèle.

Il y a deux parties. La partie Tables et la partie Requêtes. La partie Tables contient les objets de domaine - principalement conçus comme des wrappers autour d'objets de classe qui sont des analogues exacts des tables de la base de données. Par exemple, il peut y avoir une table de base de données Foo qui a les champs id, name et type. Il y aura un objet Table (class FooTable) qui a un tableau avec les champs 'id', 'name' et 'type'. Il ressemblerait à ceci:

class FooTable extends MySQLTable { 
    private $id; 
    private $data; 
    private $statements; 

    public function __construct($id, $data=NULL, $populate=false) { 
     // Initialize the table with prepared statements to populate, update and insert. Also, 
     // initialize it with any data passed in from the $data object. 
    } 

    public function get($field) {} 
    public function set($field, $value) {} 
    public function populate() {} 
    public function update() {} 
    public function insert() {} 
} 

S'il y a une table de base de données fooBar qui a un à plusieurs rapport (un Foo beaucoup Bars) avec des champs id, fooID et bar alors il y aura un objet Tableau FooBar (class FooBarTable) qui ressemblera à peu près au même que ci-dessus FooTable.

Le FooTable et plusieurs objets FooBarTable seront tous deux contenus dans l'objet Foo. Attribuez un ID à la fabrique d'objets Foo à un tableau Foo et il se remplit avec les données Foo et tous ses Bar s et leurs données.

Les objets de requête sont utilisés pour extraire les ID Foo dans l'ordre dans lequel ils sont nécessaires. Donc, si je veux que les objets Foo soient classés par date, par vote ou par nom, j'ai besoin d'un objet de requête différent pour le faire. Ou si je veux sélectionner tous les objets Foo qui ont un Bar dans une certaine gamme. J'ai besoin d'un objet de requête.

La plupart du temps, j'utilise les objets Table (les wrappers, pas les tables de base) pour interagir avec la base de données. Mais quand il s'agit de sélectionner les objets de la table, c'est là que les requêtes entrent.

Lors de la conception originale, je ne pensais pas qu'il y aurait trop de requêtes et je pensais qu'ils allaient être réutilisés. Comme il peut y avoir plusieurs endroits où je voudrais que les Foo s dans l'ordre de la date. Mais cela n'a pas fonctionné de cette façon. Il y en a beaucoup plus que prévu et la plupart d'entre eux sont à un coup, utilisés une fois dans une vue ou un commandement, et plus jamais. Je pensais aussi que les requêtes pouvaient encapsuler un SQL assez complexe et qu'il serait bon de les avoir comme objets de sorte que je serais toujours sûr de leur donner les données dont ils avaient besoin et que ce serait un environnement relativement sain pour tester la requête SQL elle-même . Mais encore une fois, cela n'a pas fonctionné de cette façon. La plupart d'entre eux contiennent un langage SQL assez simple.

+0

Je pense que vous avez déjà répondu à votre propre question. Vous avez suivi ce qui semblait être une «meilleure pratique» raisonnable, seulement pour constater que cette pratique est devenue pénible pour un avantage marginal. C'est bien d'être "correct", mais il vaut mieux être "efficace". –

+0

@Robert J'ai compris, mais je me demandais si la façon dont je suivais la «meilleure pratique» était ce qui me causait des problèmes. Existe-t-il d'autres moyens de limiter l'accès au modèle au modèle que d'encapsuler des requêtes qui seraient meilleures pour de nombreuses requêtes à usage unique? –

+1

@Robert Je pense qu'il est un peu prématuré de jeter l'abstraction au vent juste parce que Daniel ne fait pas de bonnes abstractions. – timdev

Répondre

2

Commençons par la dernière question: ce qui est gagné, c'est la séparation des préoccupations ou en anglais "Garder les choses ensemble qui vont ensemble".Un mot clé ici est appartenance, qui est un mot plutôt subjectif. Dans les premiers jours de PHP, beaucoup de gens ont trouvé que tout ce qui est sur une seule page "appartient" ensemble. Comme PHP était un acronyme pour "Personal Home Page", son objectif de conception était de petits sites avec quelques pages, et ce sentiment d'appartenance prend tout son sens. Quand les choses se développent, le sentiment d'appartenance change. Lorsque nous obtenons un modèle de données complexe qui doit être cohérent et qui doit évoluer avec le temps, alors les opérations sur ce "modèle" commencent à "appartenir" ensemble parce qu'il devient trop difficile de déterrer les opérations et les requêtes SQL partout le lieu. Donc, pour garder le contrôle, nous avons besoin de toutes les opérations du modèle sur la même page. En pratique, j'aime tracer une ligne dans le sable entre l'interface utilisateur et le modèle et y définir une API qui encapsule les objectifs du mini-utilisateur (l'utilisateur veut voir les promotions -> la méthode getPromotions est nécessaire, l'utilisateur veut ajouter quelque chose au panier: la méthode addToCart est nécessaire, etc ...). J'aime dessiner la ligne ici, car elle capture les désirs de l'utilisateur, la responsabilité de l'interface utilisateur est d'amener l'utilisateur à s'y rendre de manière simple et efficace, la responsabilité de la couche service/modèle/dépôt est de réaliser ce désir.

Si fait correctement, il est également sur un niveau 1 étape enlevée de l'utilisateur. Beaucoup de développeurs confondent cependant ce que l'utilisateur veut avec les fonctionnalités qui doivent être implémentées. Ensuite, vous obtenez des méthodes de service très inefficaces comme saveProject (Un utilisateur ne veut pas que le projet soit enregistré, elle veut juste qu'il soit là la prochaine fois qu'il se connecte. Il est pris pour acquis, et n'a donc pas sa place dans le service API). Cette API basée sur l'implémentation conduit à des couches de méthodes wrapper presque vides.

Des choses comme les référentiels, etc., sont un moyen de structurer cette couche de service. Cette approche API orientée utilisateur a également tendance à nettoyer les vues (le contenu des éléments d'affichage peut être récupéré avec une poignée d'appels de service) et les contrôleurs (une action consiste alors en une désinfection normale et typiquement un appel de méthode unique).

3

Pourquoi ne pas utiliser repositories? Il me semble que ce serait un moyen simple et agréable d'encapsuler le SQL. Votre approche actuelle semble inutilement compliquée.

Le didacticiel NerdDinner has a good example d'utilisation du référentiel dans un contexte MVC; même si ce n'est pas en PHP, j'espère que cela vous donnera une idée de la façon dont ce modèle fonctionne.

+0

+1 pour suggérer quelque chose de concret. Il semble que OP doive creuser dans les modèles de conception en général, et déterminer quelle approche est susceptible de fonctionner pour son problème. – timdev

3

Il est impossible de donner de bons conseils sans savoir quel genre de choses vous faites, mais il est clair que quelque chose est probablement très faux. D'après ce que vous avez dit, il semble que vous ayez raison de penser que votre modèle a besoin d'un refactoring majeur. En fait, il semble que cela nécessite une refonte sérieuse (ce qui signifie que l'API utilisée par vos contrôleurs et les vues pour y accéder changera).

Quelques réflexions:

Vous dites:

La façon dont je l'avais trouvé pour garder le SQL dans le modèle était d'encapsuler chaque seule requête SQL dans son propre objet de requête . Et puis quand je devais appeler certains SQL dans la vue ou le contrôleur I accéderaient à la requête par l'intermédiaire d'une usine . Aucun code SQL n'existe dans le contrôleur ou le View.Controller ou la vue.

Cela me fait penser que vous manquez le point. Votre modèle devrait être plus qu'un tas de code standard pour que SQL ne s'affiche pas (gasp!) Dans certains contrôleurs. Votre modèle doit être un modèle de votre objet de domaine - ce que vous avez décrit est juste un proxy inefficace pour SQL.

Il pourrait être utile d'afficher des exemples d'utilisation de votre modèle. C'est-à-dire, éditez votre question pour inclure quelques exemples de la façon dont vos contrôleurs et vues utilisent vos modèles.

+0

Il y a deux parties à mon modèle. Il y a une section qui modélise les objets du domaine. Les requêtes sont utilisées pour obtenir des listes d'identifiants de la base de données qui servent ensuite à instancier des objets de domaine individuels. –

+0

Ok, modifié la question originale avec plus de détails sur le modèle. –