Les requêtes de base de données doivent-elles être extraites de la page elle-même?

10

Lors de l'écriture de la génération de pages en PHP, je me retrouve souvent à écrire un ensemble de fichiers jonché de requêtes de base de données. Par exemple, je pourrais avoir une requête pour récupérer des données sur un poste directement à partir de la base de données pour les afficher sur une page, comme ceci:

$statement = $db->prepare('SELECT * FROM posts WHERE id=:id');
$statement->bindValue(':id', $id, PDO::PARAM_INT);
$statement->execute();
$post = $statement->fetch(PDO::FETCH_ASSOC);
$content = $post['content']
// do something with the content

Ces requêtes rapides et ponctuelles sont généralement petites, mais je me retrouve parfois avec de grandes parties de code d'interaction de base de données qui commencent à sembler assez désordonnées.

Dans certains cas, j'ai résolu ce problème en créant une simple bibliothèque de fonctions pour gérer mes requêtes db liées à un post, raccourcissant ce bloc de code en un simple:

$content = post_get_content($id);

Et c'est super. Ou du moins, c'est jusqu'à ce que je doive faire autre chose. Peut-être que je dois afficher les cinq articles les plus récents dans une liste. Eh bien, je pourrais toujours ajouter une autre fonction:

$recent_posts = post_get_recent(5);
foreach ($recent_posts as $post) { ... }

Mais cela finit par utiliser une SELECT *requête, dont je n'ai généralement pas vraiment besoin de toute façon, mais qui est souvent trop compliquée pour être raisonnablement abstraite. Je me retrouve finalement avec soit une bibliothèque massive de fonctions d'interaction de base de données pour chaque cas d'utilisation unique, soit une série de requêtes compliquées à l'intérieur du code de chaque page. Et même une fois que j'ai construit ces bibliothèques, je devrai faire une petite jointure que je n'avais pas utilisée auparavant, et j'ai soudainement besoin d'écrire une autre fonction hautement spécialisée pour faire le travail.

Bien sûr, je pourrais utiliser les fonctions pour les cas d'utilisation généraux et les requêtes pour des interactions spécifiques, mais dès que je commence à écrire des requêtes brutes, je commence à revenir en accès direct pour tout. Soit ça, soit je deviens paresseux et je vais commencer à faire des choses dans les boucles PHP qui devraient vraiment être faites directement dans les requêtes MySQL, de toute façon.

Je voudrais demander à ceux qui sont plus expérimentés dans l'écriture d'applications Internet: le boost de maintenabilité vaut-il les lignes de code supplémentaires et les éventuelles inefficacités que les abstractions peuvent introduire? Ou est-ce que l'utilisation de chaînes de requête directes est une méthode acceptable pour gérer les interactions avec la base de données?

Alexis King
la source
Peut-être que vous pouvez utiliser des procédures stockées pour «envelopper» des fichiers en désordre select- si vous n'aurez qu'à appeler de telles procédures avec certains paramètres dont vous avez besoin
k102

Réponses:

7

Lorsque vous avez trop de fonctions de requête spécialisées, vous pouvez essayer de les diviser en bits composables. Par exemple

$posts = posts()->joinWithComments()->orderBy("post.post_date")->first(5);

Il existe également une hiérarchie des niveaux d'abstraction que vous pourriez trouver utile de garder à l'esprit. Vous avez

  1. API mysql
  2. vos fonctions mysql, telles que select ("select * from posts where foo = bar"); ou peut-être plus composableselect("posts")->where("foo = bar")->first(5)
  3. des fonctions spécifiques à votre domaine d'application, par exemple posts()->joinWithComments()
  4. des fonctions spécifiques à une page particulière, telles que commentsToBeReviewed($currentUser)

Il est payant en termes de facilité de maintenance de respecter cet ordre d'abstractions. Les scripts de pages doivent utiliser uniquement des fonctions de niveau 4, les fonctions de niveau 4 doivent être écrites en termes de fonctions de niveau 3, etc. Il est vrai que cela prend un peu plus de temps à l'avance, mais cela aidera à maintenir vos coûts de maintenance constants dans le temps (par opposition à "oh mon Dieu, ils veulent un autre changement !!!")

xpmatteo
la source
2
+1 Cette syntaxe crée essentiellement votre propre ORM. Si vous êtes vraiment inquiet à propos de la complexité de l'accès aux bases de données et que vous ne voulez pas passer beaucoup de temps à bricoler les détails, je vous suggère d'utiliser un framework web mature (par exemple CodeIgniter ) qui a déjà compris cela. Ou essayez au moins de l'utiliser pour voir quel type de sucre syntaxique il vous donne, similaire à ce que xpmatteo a démontré.
Hartley Brody
5

La séparation des préoccupations est un principe qui mérite d'être lu, voir l'article Wikipedia à ce sujet.

http://en.wikipedia.org/wiki/Separation_of_concerns

Un autre principe intéressant à lire est le couplage:

http://en.wikipedia.org/wiki/Coupling_(computer_science )

Vous avez deux préoccupations distinctes, l'une est le marshaling des données de la base de données et la seconde est le rendu de ces données. Dans une application vraiment simple, il n'y a probablement pas de quoi s'inquiéter, vous avez étroitement couplé votre couche d'accès à la base de données et de gestion avec votre couche de rendu mais pour les petites applications ce n'est pas grave. Le problème est que les applications Web ont tendance à évoluer et si vous souhaitez augmenter l'échelle d'une application Web, de quelque manière que ce soit, c'est-à-dire les performances ou les fonctionnalités, vous rencontrez des problèmes.

Disons que vous générez une page Web de commentaires générés par les utilisateurs. Vient ensuite le patron aux cheveux pointus et vous demande de commencer à prendre en charge les applications natives, par exemple iPhone / Android, etc. Nous avons besoin d'une sortie JSON, vous devez maintenant extraire le code de rendu qui générait du HTML. Lorsque vous avez fait cela, vous avez maintenant une bibliothèque d'accès aux données avec deux moteurs de rendu et tout va bien, vous avez évolué de manière fonctionnelle. Vous avez peut-être même réussi à garder tout séparé, c'est-à-dire la logique métier du rendu.

Le patron arrive et vous dit qu'il a un client qui souhaite afficher les publications sur son site Web, qu'il a besoin de XML et qu'il a besoin d'environ 5000 demandes par seconde de pointe. Maintenant, vous devez générer XML / JSON / HTML. Vous pouvez à nouveau séparer votre rendu, comme précédemment. Cependant, vous devez maintenant ajouter 100 serveurs pour obtenir confortablement les performances dont vous avez besoin. Maintenant, votre base de données est touchée par 100 serveurs avec peut-être des dizaines de connexions par serveur, chacune étant directement exposée à trois applications différentes avec des exigences différentes et des requêtes différentes, etc. L'accès à la base de données sur chaque machine frontale est un risque pour la sécurité et une augmentation un mais je ne vais pas y aller. Maintenant, vous devez évoluer pour les performances, chaque application a des exigences de mise en cache différentes, c'est-à-dire des préoccupations différentes. Vous pouvez essayer de gérer cela dans une couche étroitement couplée, c'est-à-dire votre accès à la base de données / logique métier / couche de rendu. Les préoccupations de chaque couche commencent maintenant à se gêner les unes les autres, c'est-à-dire que les exigences de mise en cache des données de la base de données pourraient être très différentes de la couche de rendu, la logique que vous avez dans la couche métier est susceptible de se fondre dans le SQL, c'est-à-dire reculer ou cela pourrait saigner dans la couche de rendu, c'est l'un des plus gros problèmes que j'ai vu avec tout dans une couche, c'est comme verser du béton armé dans votre application et pas dans le bon sens.

Il existe des moyens standard d'aborder ces types de problèmes, à savoir la mise en cache HTTP des services Web (squid / yts, etc.). Mise en cache au niveau de l'application au sein des services Web eux-mêmes avec quelque chose comme memcached / redis. Vous rencontrerez également des problèmes lorsque vous commencerez à faire évoluer votre base de données, c'est-à-dire plusieurs hôtes de lecture et un maître, ou des données partagées entre les hôtes. Vous ne voulez pas que 100 hôtes gèrent diverses connexions à votre base de données qui diffèrent en fonction des demandes d'écriture ou de lecture ou dans une base de données fragmentée si un utilisateur «usera» hache dans «[table / base de données] foo» pour toutes les demandes d'écriture.

La séparation des préoccupations est votre ami, choisir quand et où le faire est une décision architecturale et un peu d'art. Évitez de coupler étroitement tout ce qui évoluera pour avoir des exigences très différentes. Il y a un tas d'autres raisons pour garder les choses séparées, c'est-à-dire que cela simplifie les tests, le déploiement des modifications, la sécurité, la réutilisation, la flexibilité, etc.

Harry
la source
Je comprends d'où vous venez et je ne suis pas en désaccord avec tout ce que vous avez dit, mais c'est une petite préoccupation en ce moment. Le projet en question est personnel, et la plupart de mes problèmes avec mon modèle actuel proviennent de l'instinct de mon programmeur pour éviter un couplage serré, mais je suis vraiment un débutant dans le développement complexe côté serveur, donc la fin de cela est allée un peu au dessus de ma tête. Pourtant, +1 pour ce qui me semble être un bon conseil, même si je ne le suis pas entièrement pour ce projet.
Alexis King
Si ce que vous faites va rester petit alors je le garderais aussi simple que possible. Un bon principe à suivre ici est YAGNI .
Harry
1

Je suppose que lorsque vous dites "la page elle-même", vous voulez dire le fichier source PHP qui génère dynamiquement du HTML.

Ne pas interroger la base de données et générer du HTML dans le même fichier source.

Le fichier source dans lequel vous interrogez la base de données n'est pas une "page", même s'il s'agit d'un fichier source PHP.

Dans le fichier source PHP où vous créez dynamiquement le code HTML, vous passez simplement des appels aux fonctions définies dans le fichier source PHP où la base de données est accessible.

Tulains Córdova
la source
0

Le modèle que j'utilise pour la plupart des projets de taille moyenne est le suivant:

  • Toutes les requêtes SQL sont mises à part le code côté serveur, dans un emplacement séparé.

    Travailler avec C #, cela signifie utiliser des classes partielles, c'est-à-dire placer les requêtes dans un fichier séparé, étant donné que ces requêtes seront accessibles à partir d'une seule classe (voir le code ci-dessous).

  • Ces requêtes SQL sont des constantes . Ceci est important, car il empêche la tentation de créer des requêtes SQL à la volée (augmentant ainsi le risque d'injection SQL et en même temps rendant plus difficile la révision des requêtes plus tard).

Approche actuelle en C #

Exemple:

// Demo.cs
public partial class Demo : DataRepository
{
    public IEnumerable<Stuff> LoadStuff(int categoryId)
    {
        return this
            .Query(Queries.LoadStuff)
            .With(new { CategoryId = categoryId })
            .ReadRows<Stuff>();
    }

    // Other methods go here.
}

public partial class Demo
{
    private static class Queries
    {
        public const string LoadStuff = @"
select top 100 [StuffId], [SomeText]
    from [Schema].[Table]
    where [CategoryId] = @CategoryId
    order by [CreationUtcTime]";

        // Other queries go here.
    }
}

Cette approche a l'avantage d'avoir les requêtes dans un fichier séparé. Cela permet à un administrateur de base de données d'examiner et de modifier / optimiser les requêtes et de valider le fichier modifié pour le contrôle de code source sans entrer en conflit avec les validations effectuées par les développeurs.

Un avantage connexe est que le contrôle de code source peut être configuré de manière à limiter l'accès de DBA uniquement aux fichiers contenant les requêtes et à refuser l'accès au reste du code.

Est-il possible de faire en PHP?

PHP manque à la fois de classes partielles et de classes internes, de sorte qu'il ne peut pas être implémenté en PHP.

Vous pouvez créer un fichier séparé avec une classe statique distincte ( DemoQueries) contenant les constantes, étant donné que la classe sera accessible de partout. De plus, afin d'éviter de polluer la portée globale, vous pouvez placer toutes les classes de requête dans un espace de noms dédié. Cela créera une syntaxe plutôt verbeuse, mais je doute que vous puissiez éviter la verbosité:

namespace Data {
    public class Demo inherit DataRepository {
        public function LoadStuff($categoryId) {
            $query = \Queries\Demo::$LoadStuff;
            // Do the stuff with the query.
        }

        // Other methods go here.
    }
}

namespace Queries {
    public static class Demo {
        public const $LoadStuff = '...';

        // Other queries go here.
    }
}
Arseni Mourzenko
la source