2009-04-07 25 views
1

J'ai un problème d'œuf de poule. Je voudrais aussi implémenter un système PHP de manière OOP, dans lequel deux classes joueraient des rôles importants: Database et Log. Mon idée était de construire la connexion par la classe Database, qui aurait des méthodes publiques, par exemple. runQuery (sUpdateQuery), doInsert (sInsert), etc. La classe Log écrirait les logs par des méthodes communes, tout comme logMessage (message), logDatabaseQuery (sQuery) TO THE DATABASE. Le problème vient maintenant.Problème de poulet à l'œuf OOP

1: Je l'intérieur des méthodes de la classe de la base de données comme pour pouvoir utiliser la logDatabaseQuery de classe Log (sQuery)

2: Ce ne serait pas encore un grand défi si je ne voudrais pas utiliser le Méthode doInsert (sInsert) de la classe de base de données dans la méthode logDatabaseQuery. Je voudrais le garder simple - et juste utiliser une instance de l'objet de connexion de base de données, et du loggeras bien, si c'est possible.

Pour beaucoup de gens, le modèle Singleton serait la première idée à choisir, mais je voudrais certainement utiliser une solution différente.

Donc, il y aurait deux classes qui utiliserait chaque-autres méthodes:

Base de données doInsert logDatabaseQuery

Connexion logDatabaseQuery doInsert

Je voudrais garder les méthodes Log séparément (dans la classe Log), puisque plus tard, il aurait d'autres méthodes pour se connecter non seulement à la base de données, mais aussi aux fichiers ou aux e-mails.

Toutes les idées, comment cela devrait/pourrait être fait de la manière la plus agréable, OOP friendly?

Je pensais à une classe abstraite parent commun, ou sur l'utilisation des interfaces aussi bien, mais finalement ne pouvait pas comprendre la bonne façon :(

Ce que je voudrais savoir est une suggestion pour une hiérarchie de classes correcte

+0

Les gars, je vous remercie pour toutes vos réponses. Maintenant je peux voir clairement - ce dont j'avais un peu peur, que mon idée était fausse. Maintenant, je peux comprendre, que je dois séparer l'accès à la base de données au moins en deux classes. – simply4it

Répondre

4

Vous avez combiné trop de choses ensemble.

La base de données ne peut pas vraiment dépendre d'un enregistreur qui dépend de la base de données. Ce n'est pas un bon design.

Ce que vous avez réellement sont deux types d'accès à la base de données.

L'accès de bas niveau fait du SQL "brut".

L'enregistreur peut dépendre de cette classe de niveau inférieur. Il n'a pas - lui-même - de SQL brut. Cela dépend d'une classe de niveau inférieur.

L'accès de haut niveau effectue des requêtes d'application, il utilise l'accès de bas niveau et l'enregistreur.

+0

Mettre du SQL "brut" dans votre classe d'enregistreur est une excellente façon de faire un gâchis que vous devrez nettoyer plus tard. Transmettez-le simplement à un objet de base de données et transmettez-lui l'objet de base de données. –

+0

Il n'est pas nécessaire que le DB de niveau inférieur ne fasse que du SQL brut. si vous voulez, faire une couche supplémentaire: 1: SQL brute, 2: POO DB (utilise 1), enregistreur (utilisations 2), 3: connecté DB (utilise 2 et enregistreur) – Javier

0

Créer une surcharge de la méthode d'insertion qui permet d'inserts sans vous connecter, et votre utilisation de classe de journalisation qui Sinon, votre conception est récursive par définition:.. Consigner tous les inserts DB en effectuant un insert DB

0

ajouter une option paramètre à doInsert $ callerIsLogger = false, puis à chaque fois vous devez faireInsert() à partir de votre enregistreur, fournir le deuxième paramètre true, et votre doInsert pourrait vérifier cette situation et ne pas appeler l'enregistreur quand il est appelé par le logger. Quelles hiérarchies? La méthodologie KISS est en pleine ébullition :)

0

Ceci est un bon candidat pour le Mediator Pattern. Vous auriez un objet sur lequel l'enregistreur et la base de données appellent à la fois diverses méthodes, et cet objet médiateur conserverait des références à l'enregistreur et à la base de données et gérerait la communication pour vous.

1

Créez une classe Logger qui implémente une interface ILogger. Une nouvelle instance de la classe Logger reçoit un objet qui implémente l'interface ILoggingProvider utilisée pour la sortie des messages de journal.

Créez une classe Database qui implémente l'interface ILoggingProvider. Une nouvelle instance de la base de données reçoit un objet qui implémente l'interface ILogger utilisée pour consigner les messages.

public interface ILogger 
{ 
    void Debug(String message) 
} 

public interface ILoggingProvider 
{ 
    void Log(String message) 
} 

public class Logger : ILogger 
{ 
    private ILoggingProvider LoggingProvider { get; set; } 

    public Logger(ILoggingProvider loggingProvider) 
    { 
     this.LoggingProvider = loggingProvider; 
    } 

    public void Debug(String message) 
    { 
     this.LoggingProvider.Log(message); 
    } 
} 

public class Database : ILoggingProvider 
{ 
    private ILogger Logger { get; set; } 

    public Database(ILogger logger) 
    { 
     this.Logger = logger; 
    } 

    public void DoStuffWithTheDatabase() 
    { 
     // Do stuff with the database 
     this.Logger.Debug("Did stuff with the database."); 
    } 

    public void Log(String message) 
    { 
     // Store message to database - be carefull not to generate new 
     // log messages here; you can only use the subset of the Database 
     // methods that do not themselve generate log messages 
    } 
} 
+0

Cela ne vous laisse-t-il pas avec un problème de récursivité? –

+0

C'est exactement ce que S.Lott a souligné. Vous ne pouvez pas utiliser la journalisation partout dans la classe Database. Il serait donc judicieux de séparer ces parties explicitement. –

4

Il semble que vous avez deux bases de données différents accès - connecté (cas normal) et non exploitées (pour les fonctions de journalisation). Divisez la classe de base de données en deux, une version de niveau supérieur pour les demandes consignées et une version de niveau inférieur pour les demandes non enregistrées. Implémentez la classe de base de données de niveau supérieur en utilisant des références aux classes de base de données de journalisation et de niveau inférieur.

+0

Vous pouvez écrire la classe de base de données afin qu'il puisse enregistrer les activités si un objet enregistreur est disponible, et que ce ne sera pas enregistrer les activités si un objet enregistreur n'est pas disponible. L'écriture de deux classes de base de données n'est pas nécessaire. –

+0

Bien sûr, vous pourriez le faire de cette façon. Mais la question demandait une solution orientée objet qui réutilisait une instance d'un objet de base de données, et je pense que c'est la façon la plus propre de le faire. –

0

Vous pouvez ajouter une autre classe que la base de données et le consignateur utilisent pour éviter d'entrer dans une boucle de journalisation infinie (et éviter également la dépendance circulaire).

// Used only by Logger and DatabaseProxy 
class Database { 
    function doInsert($sql) { 
     // Do the actual insert. 
    } 
} 

class Logger { 
    function log($sql) { 
    $msg_sql = ... 
    Database.doInsert($msg_sql); 
} 

// Used by all classes 
class DatabaseProxy { 
    function doInsert($sql) { 
     Logger.log($sql); 
     Database.doInsert($sql); 
    } 
} 

Vous pouvez l'habiller un peu en ayant à la fois la base de données et DatabaseProxy mettre en œuvre une interface commune aussi bien, et utiliser une usine pour fournir l'instance appropriée.

3

Si vous voulez donner à l'objet de base de données l'accès à un enregistreur, alors vous lui passez l'enregistreur. Si vous voulez donner à votre enregistreur l'accès à un objet de base de données, alors vous lui passez l'objet de base de données.

Vérifiez si ces objets existent dans la classe avant d'utiliser les fonctions qui leur sont fournies.

Puisque PHP passe ces objets par référence par défaut, vous pouvez le faire avec un seul enregistreur et une classe de base de données pour plusieurs objets.

Je vous recommande également de faire toute votre écriture de base de données en un seul point dans l'enregistreur. Rangez tout simplement dans un tableau temporaire et exécutez votre code DB lorsque vous déconstruisez. Sinon, vous allez créer une tonne de surcharge si vous écrivez dans la base de données tout au long de votre code.

class Database { 
    private $_logger = 0; //contains your logger 
    /* The rest of your class goes here */ 

    //Add the logger to your class so it may access it. 
    public function setLogger($logger) { 
     $this->_logger = $logger; 
    } 

    //To check if we have a logger, we don't want to use one if we lack one. 
    public function hasLogger() { 
     if($this->_logger) return true; 
     else return false; 
    } 
}
class Logger { 
    private $_database = 0; //variable to hold your database object 
    /* The rest of your class goes here */ 

    public function setDatabase($database) { //Same crap basically 
     $this->_database = $database; 
    } 

    public function hasDatabase() { 
     if($this->_database) return true; 
     else return false; 
    } 

    public function doSomething { //This is new though 
     if($this->hasDatabase()) { //Shows how you use hasDatabase() 
      $this->_database->someDatabaseFunction(); 
     } 
     else { 
      //Whatever you'd do without a database connection 
     } 
    } 
}
$database = new Database; 
$logger = new Logger; 

$database->setLogger($logger); 
$logger->setDatabase($database);
0

à mon humble avis, la classe de base de données ne doit pas être en interaction avec l'enregistreur. Je serais enclin à appeler le logger de la même partie du code qui appelle la classe de base de données. Ensuite, l'enregistreur peut utiliser la classe de base de données pour faire ses insertions.

3

Séparer la base de données de l'enregistreur. Je voudrais concevoir l'application pour se connecter à partir du niveau intermédiaire et prendre la décision sur le type d'enregistreur à utiliser à runtime pas compiler le temps. C'est à dire. utilisez une méthode factory pour déterminer si vous souhaitez vous connecter à db/xml/whatever.

Si la couche de données a besoin de se connecter (par ex.signale un problème), lancez une exception, attrapez-la dans le niveau intermédiaire, puis décidez comment la gérer ou la transmettre à une classe de diagnostic et décidez-en. D'une manière ou d'une autre, je garderais la DAL aussi "aveugle/muette" que possible et je ne la ferais pas prendre de décisions sur ce qui est et ce qui n'est pas un événement logique.

Une classe parente commune n'est pas une bonne idée. Un enregistreur n'est pas une base de données [r]. Et une base de données n'est pas non plus un enregistreur. Ce n'est pas tellement un problème de poule et d'oeuf que c'est un problème de vache et de cochon. Ce sont deux animaux différents. La base de données n'a aucune raison d'être au courant de l'enregistreur. Je pense que vous êtes en train de forcer l'abstraction. Tout ce que je vois est qu'un enregistreur a une base de données .... si c'est un enregistreur de db qui est.

vous conduisez la couche de données à partir du niveau intermédiaire de toute façon, en incluant des exceptions et des événements, je ne peux pas voir où vous perdriez la fidélité à des événements liés à la consignation db.

public interface ILoggingProvider 
    { 
     void Log(String message) 
    } 

    public static class Logger 
    { 
     public static ILoggingProvider GetLoggingProvider() 
     { 
      //factory to return your logger type 
     } 

     public void Log(String message) 
     { 
      Logger.GetLoggingProvider().Log(message); 
     } 
    } 

    public class DBLogger : ILoggingProvider { 

     void Log(string message) { 
     Database db = new Database(); 
     db.doInsert(someLoggingProc); 
    } 
    } 

    public class Database 
    { 
     runQuery(sUpdateQuery) 
     doInsert(sInsert) 
    } 

... 


public class Customer{ 

public void SaveCustomer(Customer c) 
{ 
    try { 
    // Build your query 
    Database db = new Database(); 
    db.runQuery(theQuery); 
    } catch (SqlException ex) { 
    Logger.Log(ex.message); 
    } 
} 
} 
0

vous ressemble en ajoutant beaucoup de complication à un problème simple pour le simple plaisir de le rendre orienté objet ...

vous demander cela, qu'est-ce que vous gagnez vraiment en prenant cette approche?

+0

autant que je déteste « OOP pour le bien de OOP "(ou tout style de design juste parce que), dans ce cas précis, ce n'est pas un mauvais design, et bien fait peut être beaucoup plus facile à maintenir que le code séquentiel non-OOP (y at-il un nom?non, «impératif» n'est pas) – Javier

+0

«procédural», je pense est le mot –

+0

je ne suis pas d'accord, il a besoin d'un seul module d'interface pour une seule base de données et un seul module de journal. chaque fois que le terme singleton entre en jeu, vous avez admis qu'une approche POO n'a pas réussi à s'adapter au problème. s'il avait plusieurs modules de type log ou plusieurs back ends de base de données, alors OOP tout le chemin –

0

Vous pouvez faire une deuxième arg pour votre fonction Database::insert():

function insert($sql, $logThis = true) { ... } 

Et il est évident que lorsque l'enregistreur appelle, faire le second argument faux.

Sinon, il suffit de vérifier la pile de fonction pour voir si la fonction d'insertion a été appelée à partir de la classe de journalisation.

function insert($sql) { 
    $callStack = debug_backtrace(); 
    if (!isset($callStack[1]) || $callStack[1]['class'] !== "Logger") { 
     Logger::logSQL($sql); 
    } 
    // ... 
} 
0

Comment je le ferais:

public interface ILogger 
{ 
    void LogWarning(string message); 
    void LogMessage(string message); 
} 

public interface ILoggingProvider 
{ 
    void LogEntry(string type, string message); 
} 

public interface IDbProvider 
{ 
    void DoInsert(string insertQuery, params object[] parameters); 
} 

public class Logger: ILogger 
{ 
    private ILoggingProvider _provider = ProviderFactory.GetLogProvider(); 

    public void LogWarning(string message) 
    { 
     _provider.LogEntry("warn", message); 
    } 

    public void LogMessage(string message) 
    { 
     _provider.LogEntry("message", message); 
    } 

    public void LogEntry(string type, string message) 
    { 
     _provider.LogEntry(type, message); 
    } 
} 

public class Database : IDbProvider 
{ 
    private Logger _logger = new Logger(); 
    public void DoInsert(string insertQuery, params object [] parameters) 
    { 
     _logger.LogEntry("query", insertQuery); 
    } 
} 

public class DbLogProvider : ILoggingProvider 
{ 
    private IDbProvider _dbProvider = ProviderFactory.GetDbProvider(); 

    public void LogEntry(string type, string message) 
    { 
     _dbProvider.DoInsert("insert into log(type,message) select @type,@message",type,message); 
    } 

} 

public static class ProviderFactory 
{ 
    public static IDbProvider GetDbProvider() 
    { 
     return new Database(); 
    } 


    internal static ILoggingProvider GetLogProvider() 
    { 
     return new DbLogProvider(); 
    } 
} 

Bien que je ne serais probablement faire aussi rechercher un type dans un fichier de configuration plutôt que de les coder en dur dans la classe ProviderFactory. L'hypothèse ici est que votre code ne se soucie pas de la façon dont il est connecté, c'est l'administrateur, et que toute la journalisation se ferait d'une manière lors de l'exécution (ce qui serait ma préférence de toute façon). Évidemment, vous pouvez étendre cela pour faire un choix quant à la cible du journal lors de la création de l'enregistreur et créer le fournisseur approprié.

+0

Même problème de récurrence. J'ai presque répondu à un commentaire ci-dessus. Oui, vous devez séparer en un DbProvider sans journalisation qui ne fait que l'accès à la base de données. –

Questions connexes