php.de

Zurück   php.de > Webentwicklung > Software-Design

Software-Design Diskussionen auf Profi-Niveau: PHP Lösungen auf konzeptioneller Ebene

Antwort
 
LinkBack Themen-Optionen Thema bewerten
Alt 23.03.2010, 14:42  
Erfahrener Benutzer
 
Registriert seit: 27.06.2009
Beiträge: 498
PHP-Kenntnisse:
Anfänger
cetalian sorgt für eine eindrucksvolle Atmosphärecetalian sorgt für eine eindrucksvolle Atmosphäre
Standard Models/Datenbankabstraktion... wie?

Hallo zusammen

ich habe mal ein seltsames Problem und schiebe es mal unter Einsteiger da es so wenig strukturiert sind. Die Frage schwirrt schon seit Ewigkeiten in meinem Kopf rum und ich versuche sie mal an einem Beispiel zu erklären.

Nehmen wir an, ich arbeite mit einem beliebigen Framework an einem beliebigen Projekt.

Hier habe ich ein Problem wenn ich Objekte haben möchte die Daten enthalten und gleichzeitig weitere Funktionen, sie aber trotzdem Kapseln und von der Datenbank fernhalten will. Was man oft sieht sind reine Datenobjekte nach dem Motto es gibt eine "Entity" Tabelle und dann werden automatisch Models und Objekte erzeugt die die Spalten als public Attribute haben. (Active Record wenn ich nicht irre).

Was ist nun aber wenn ich komplizierte Logik in diesem Objekt haben will? Einige Eigenschaften private ohne getter Methoden die das interne Verhalten des Objekts beeinflussen? Nehmen wir als Beispiel ein komisches Objekt mit einem privaten "typ" Attribut (Löwe/Maus) und einer Methode "saySomething()".

PHP-Code:
class Animal
{
    private 
$type;
    

    public function 
saySomething()
    {
        if (
$this->type == 'Maus') {
            echo 
'piep!';
        } else {
            echo 
'ROAR!';
        }
    }

Dieses Objekt möchte ich nun speichern und wiederherstellen können. Die Tabelle dazu ist "animal" mit einer id und dem typ als varchar. Dazu noch ein paar sinnlose extra Attribute wie Größe oder sowas die ruhig public sein können.

Im Moment löse ich es so:
Ich habe eine Model Klasse die statische Methoden zur Verfügung stellt um Objekte zu bekommen. Zum Beispiel "Model_Animal::getById(2)" gibt das Animal Objekt mit den Daten aus ID 2 zurück. Dafür fragt es die Daten aus einer DB ab und erzeugt das Objekt indem es ihm alle Attribute im Konstruktor übergibt. Dann gibt es im Model eine statische "save" Methode dem das zu speichernde Objekt übergeben wird. Im Objekt gibt es eine "exportData" Funktion die einfach alle Attribute als Array ausliefert (damit man auch private Attribute speichern kann). Das finde ich alles widerlich hässlich, grade die exportData Funktion :/ Nur die Frage, gibt es da eine bessere Methode? Ein "Data" Objekt als Attribut der Animal Klasse das wie eine Verbindung zur Datenbank funktioniert? Irgendwie muss man zum speichern ja wieder an private Attribute kommen.

Mhm, wie man merkt ein unstrukturiertes Problem ohne wirklichen Lösungsansatz von mir. Tut mir leid :/

Gruß
cetalian ist offline   Mit Zitat antworten
Sponsor Mitteilung
PHP Code Flüsterer

Registriert seit: 21.08.2005
Beiträge: 4682
PHP-Kenntnisse:
Fortgeschritten

Alt 23.03.2010, 15:45  
Erfahrener Benutzer
 
Registriert seit: 01.12.2009
Beiträge: 645
PHP-Kenntnisse:
Fortgeschritten
draco88 befindet sich auf einem aufstrebenden Ast
draco88 eine Nachricht über ICQ schicken
Standard

Schau dir mal http://www.php.de/tutorials/54558-tu...l-und-oop.html (Tutorial: PHP/MySQL und OOP) an, da sollte einiges er/ge-klärt werden.
draco88 ist offline   Mit Zitat antworten
Alt 23.03.2010, 17:53  
Erfahrener Benutzer
 
Registriert seit: 27.06.2009
Beiträge: 498
PHP-Kenntnisse:
Anfänger
cetalian sorgt für eine eindrucksvolle Atmosphärecetalian sorgt für eine eindrucksvolle Atmosphäre
Standard

Habs nun nicht überflogen, aber ich denke nicht. Die dort vorgestellten Klassen sind sehr nah an einer Datenbank und haben keine sinnvollen privaten Eigenschaften, keine komplizierte Logik etc. Ich möchte eben keine datenbanknahen Objekte haben sondern logische.
cetalian ist offline   Mit Zitat antworten
Alt 23.03.2010, 18:49  
Erfahrener Benutzer
 
Registriert seit: 29.10.2009
Beiträge: 113
PHP-Kenntnisse:
Fortgeschritten
mYkon befindet sich auf einem aufstrebenden Ast
Standard

Sowas: ORM - php bar ?
mYkon ist offline   Mit Zitat antworten
Alt 23.03.2010, 19:38  
Moderator und Wett-König
 
Benutzerbild von dr.e.
 
Registriert seit: 21.05.2008
Beiträge: 3.629
PHP-Kenntnisse:
Fortgeschritten
dr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblick
dr.e. eine Nachricht über Skype™ schicken
Standard

Ich denke, hier sollte man die Trennung zwischen Business-Schicht und Domänen-Objekten ziehen. MVC wird hier oft gründlich falsch interpretiert. Models, die direkt auf die Datenbank zugreifen sind bullshit und nur aus Faulheit einen DataMapper zu implementieren entstanden. Active Record ist in OO-Applikationen Mist!

Auch wenn es thematisch zu deinem Projekt nicht passt, lies dir mal die Artikel Objektorientiertes Design eines Gästebuchs und Objektorientierte Implementierung eines Gästebuchs durch, dort könntest du vielleicht ein paar Anregungen für die Implementierung ziehen.

Sollte dir das nicht weiterhelfen, können wir gerne nochmal drüber diskutieren. Ich denke, das Thema gehört sogar fast in das Software-Design-Forum. Aber mal sehn, ...
__________________
Viele Grüße,
Dr.E.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Think about software design before you start to write code!
2. Discuss and review it together with experts!
3. Choose good tools (-> Adventure PHP Framework (APF))!
4. Write clean and reusable software only!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dr.e. ist offline   Mit Zitat antworten
Alt 24.03.2010, 11:25  
Erfahrener Benutzer
 
Registriert seit: 27.06.2009
Beiträge: 498
PHP-Kenntnisse:
Anfänger
cetalian sorgt für eine eindrucksvolle Atmosphärecetalian sorgt für eine eindrucksvolle Atmosphäre
Standard

Hey, ich habs nicht vergessen. Danke für die Links, lese ich mir bis heut Abend durch und melde mich dann wieder.

Übrigens nettes neues Design, das alte war doch etwas seltsam .
cetalian ist offline   Mit Zitat antworten
Alt 24.03.2010, 23:38  
Erfahrener Benutzer
 
Registriert seit: 27.06.2009
Beiträge: 498
PHP-Kenntnisse:
Anfänger
cetalian sorgt für eine eindrucksvolle Atmosphärecetalian sorgt für eine eindrucksvolle Atmosphäre
Standard

Ok, mittlerweile habe ich immerhin Zeit gefunden die Seiten einmal zu überfliegen (bin ziemlich krank). Was ich verstanden habe (oder denke verstanden zu haben) ist, dass du quasi zwei Datenquellen nutzt - die Domainenobjekte in der Business Schicht und eine OR-Datenbank in der Datenschicht - und einen DataMapper der zwischen den Domainenobjekten und der Datenbank "dolmetscht" (also Domainenobjekte aus der Datenbank erstellt und sie wieder in die Datenbank speichert).

Dabei sind deine Domainenobjekte jedoch wiederrum nur reine Datenhalter ohne viele Funktionen. (Verstehe ich das richtig? Gibt es auf dieser Ebene keine komplexen Funktionen und nur Datenhalter?) Außerdem schreibst du sie besitzen zu jedem (privaten) Attribut getter und setter Methoden. Der generische OR-Mapper wird einmal konfiguriert und funktioniert dann, sehr praktisch. Trotzdem löst das ja leider nicht mein Problem (wie nicht öffentlich sichtbare private Eigenschaften speichern zum Beispiel). Ist die Möglichkeit einen rein internen Zustand zu haben der aber das Verhalten nach außen beeinflusst so abwegig für ein Objekt?

Mir fällt aber auch kein wirklich gutes Beispiel für mein Problem ein ohne dass man mich nachher noch auslacht wegen meiner aktuellen Lösung. Wobei, wie ist das zum Beispiel mit der ID eines Eintrags? Das ist ja ein komplett sinnfreies Attribut für die Business Logik und nur wichtig in der Datenschicht. Wahrscheinlich steht es auch in dem Tutorial und ich habe es überlesen, aber wo und wie wird die ID mitgeschleppt? Wie kannst du entscheiden ob ein Eintrag den der OR-Mapper speichern soll neu ist oder geupdated wird? Wie würde das GB einen einzelnen Eintrag per ID anzeigen können?
cetalian ist offline   Mit Zitat antworten
Alt 25.03.2010, 12:28  
Moderator und Wett-König
 
Benutzerbild von dr.e.
 
Registriert seit: 21.05.2008
Beiträge: 3.629
PHP-Kenntnisse:
Fortgeschritten
dr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblick
dr.e. eine Nachricht über Skype™ schicken
Standard

Hallo cetalian,

Zitat:
dass du quasi zwei Datenquellen nutzt - die Domainenobjekte in der Business Schicht und eine OR-Datenbank in der Datenschicht
Datenquellen ist die falsche Bezeichnung. Es sind zwei verschiedene Domänen-Objekt-Modelle, die mit Hilfe eines DataMappers der Anwendung zur Verfügung stehen. In komplexeren Anwendungen ist übrigens usus, die Daten anders abzuspeichern als in der Anwendung zu "präsentieren". Das ist im Wesentlichen auch Kern des Domain-Object-Pattern. Im Fall des Gästebuchs ist das sicher auch anders lösbar, ich wollte jedoch genau an diesem Beispiel aufzeigen, wie das in komplexen Applikationen konzeptionell funktionieren kann.

Zitat:
Dabei sind deine Domainenobjekte jedoch wiederrum nur reine Datenhalter ohne viele Funktionen. (Verstehe ich das richtig? Gibt es auf dieser Ebene keine komplexen Funktionen und nur Datenhalter?)
Komplexe Logik hat in Domänen-Objekten nichts verloren! Das muss Teil der Business-Schicht sein, die mit Hilfe der Domänen-Objekte die relevante Geschäfts-Logik abbildet. Sicher kann in einem Domänen-Objekt Benutzer eine Methode getDisplayName() den Namen aus dem Vor- und Nachnamen einer Person zusammensetzen, und falls beide nicht vorhanden sind, die E-Mail-Adresse zurückgeben, das fällt aber nicht unter "komplexe Logik".

Zitat:
Außerdem schreibst du sie besitzen zu jedem (privaten) Attribut getter und setter Methoden.
Das ist quasi JAVA Bean Konvention und macht lediglich die API lesbarer gegen über einer getProperty($name)-Methode.

Zitat:
Der generische OR-Mapper wird einmal konfiguriert und funktioniert dann, sehr praktisch. Trotzdem löst das ja leider nicht mein Problem (wie nicht öffentlich sichtbare private Eigenschaften speichern zum Beispiel). Ist die Möglichkeit einen rein internen Zustand zu haben der aber das Verhalten nach außen beeinflusst so abwegig für ein Objekt?
Der GORM ist für das Verwalten von Objekten der Datenhaltung zuständig. Deshalb ist er per Konvention darauf angewiesen, dass sich das Domänen-Objekt extern repräsentiert. Im GenericDomainObject ist das generisch darüber gelöst, dass es per getProperties() alle definierten Attribute des Objekts - wie von der Anwendung befüllt - ausliest und gemäß des konfigurierten Daten-Modells persistiert.
Sofern ein Objekt private und nach Aussen nicht sichtbare Zustände hält, gehören diese entweder in das Model einer Anwendung (=! Domänen-Objekt) oder muss nicht persistiert werden. Sollte es persistiert werden, muss der Mapper darauf Zugriff haben.

Zitat:
Mir fällt aber auch kein wirklich gutes Beispiel für mein Problem ein ohne dass man mich nachher noch auslacht wegen meiner aktuellen Lösung.
Hier lacht keiner, dafür sorge ich! Es wäre - um das Thema wirklich sinnvoll zu diskutieren - sogar notwendig, dass du deinen Anwendungs-Fall beschreibst. Ohne diesen kann ich dir nur allgemeine Ansätze näherbringen.

Zitat:
Wobei, wie ist das zum Beispiel mit der ID eines Eintrags? Das ist ja ein komplett sinnfreies Attribut für die Business Logik und nur wichtig in der Datenschicht. Wahrscheinlich steht es auch in dem Tutorial und ich habe es überlesen, aber wo und wie wird die ID mitgeschleppt?
Das Attribut wird beispielsweise dann benötigt, wenn du von einer Liste mit Objekten auf eine Detail-Seite verlinken willst. Um hier nicht unnötig ein Mapping zwischen Datenbank-interner Repräsentation und der Repräsentation der Business-Schicht einziehen zu müssen, wird gerne der Einfachheit wegen die DB-Id verwendet. Dagegen spricht IMHO auch nichts, denn es ist nunmal der eindeutigste Identifier eines Objekts.

Zitat:
Wie kannst du entscheiden ob ein Eintrag den der OR-Mapper speichern soll neu ist oder geupdated wird? Wie würde das GB einen einzelnen Eintrag per ID anzeigen können?
Hier behelfe ich mir aus Performance-Gründen mit einem einfachen Trick: ist die ID des Objektes vorhanden, so muss ich ein Update durchführen, ist die ID nicht vorhanden, wird das Objekt angelegt. Simpel, aber es hilft.
__________________
Viele Grüße,
Dr.E.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Think about software design before you start to write code!
2. Discuss and review it together with experts!
3. Choose good tools (-> Adventure PHP Framework (APF))!
4. Write clean and reusable software only!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dr.e. ist offline   Mit Zitat antworten
Alt 26.03.2010, 17:37  
Erfahrener Benutzer
 
Registriert seit: 27.06.2009
Beiträge: 498
PHP-Kenntnisse:
Anfänger
cetalian sorgt für eine eindrucksvolle Atmosphärecetalian sorgt für eine eindrucksvolle Atmosphäre
Standard

Ok, sorry dass ich immer so spät antworte, Zeit ist Geld in den Semesterferien . Gut, bringen wir mal meinen Anwendungsfall hier rein, einen Prototypen für ein Browsergame (hey, lachen verboten! :P).

Es geht um den männlichen Kampf, Mensch gegen Zielscheibe, auf Leben und Tod. Dabei gibt es eine "Fight"-Klasse, die quasi die Welt repräsentieren soll. Eine Entity Klasse die beide Kontrahenten darstellt (per privatem Attribut "type" und "enemy" in zwei Lager geteilt). Fight und Entity sind per "participants" (array) bzw "fight" verbunden.

Nun habe ich einige Models die diese Objekte erstellen, z.b. Model_Entity mit der statischen Methode "getById". Die Entity und Fight Objekte sind größtenteils Datenhalter, es gibt aber auch Logik wie z.b. eine Entity nutzt einen Skill (wird jede Runde vom Fight Objekt aufgerufen).

Meine Probleme damit:
a) Diese doppelte Nutzung stört mich. Es sieht seltsam aus in einem Objekt was eine Lebensform beschreiben soll eine "nutzlose" Datenbankid zu sehen. Sollte ich hier vielleicht eher reine Datenhalterobjekte erstellen (für jede Entity ID eines) und die logischen Entities nur auf diese verweisen lassen?

b) Das erstellen der Objekte ist auch noch semioptimal. Wenn ich eine Entity per ID auslese wird der passende Kampf nicht automatisch erzeugt. Damit habe ich im "besten" Fall ein Problem wenn 2 gleiche Entities die geändert wurden wieder gespeichert werden (klassisch denke ich) und im schlimmsten Fall fatal errors weil irgendeine Nullreferenz aufgerufen wurde.

Ich muss ehrlich sagen ich habe noch ein Problem dein Beispiel mit Domainenobjekten und Datenbank bzw dem ORMapper nachzuvollziehen, bzw auf meinen Anwendungsfall zu passen.

Gruß

P.S. Ich habe hier noch das relative trockene Buch zu Enterprise Design Patterns von Fowler rumliegen, ich denke ich werde mich darüber auch mal ein wenig hermachen
cetalian ist offline   Mit Zitat antworten
Alt 27.03.2010, 10:30  
Moderator und Wett-König
 
Benutzerbild von dr.e.
 
Registriert seit: 21.05.2008
Beiträge: 3.629
PHP-Kenntnisse:
Fortgeschritten
dr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblickdr.e. ist ein Lichtblick
dr.e. eine Nachricht über Skype™ schicken
Standard

Hallo cetalian,

Zitat:
Nun habe ich einige Models die diese Objekte erstellen, z.b. Model_Entity mit der statischen Methode "getById". Die Entity und Fight Objekte sind größtenteils Datenhalter, es gibt aber auch Logik wie z.b. eine Entity nutzt einen Skill (wird jede Runde vom Fight Objekt aufgerufen).
Wo steckt dann die eigentliche Logik? Ich würde "Fight" auf Anhieb als Business-Komponente sehen, die die relevante Logik abbildet. "Entry" ist IMHO besser als "User" oder "Attendant" (erbt von "User") zu bezeichnen. Entität ist ein technischer Begriff und hat damit nichts in einem Domänen-Objekt zu suchen.

Zitat:
a) Diese doppelte Nutzung stört mich. Es sieht seltsam aus in einem Objekt was eine Lebensform beschreiben soll eine "nutzlose" Datenbankid zu sehen. Sollte ich hier vielleicht eher reine Datenhalterobjekte erstellen (für jede Entity ID eines) und die logischen Entities nur auf diese verweisen lassen?
Grundsätzlich musst du mal Business-Logik von Domänen-Objekten trennen, Das scheint mir bei dir nicht konsequent genug stattgefunden zu haben. Anschließend solltest du dir genau überlegen, welche Domänen-Objekte du in der Anwendung wirklich hast. Bei einem Spiel ist das sehr komplex - ich diskutiere mit einigen Leuten im APF-Forum schon länger über Datenmodelle von Online-Spielen - deswegen will das gut überlegt sein.
Zweitens musst du dir im Klaren darüber sein, dass diese "nutzlose" ID ein Performance-Boost ist. Möchtest du diese ID nicht in den Domänen-Objekten mitführen, so brauchst du eine weitere Mapping-Schicht, die dir Domänen-Objekte in Entity-Objekte der Datenschicht übersetzt. Hier brauchst du eine intelligente Registry, die weiß, wleche Daten wie zusammengehören und entsprechendes Handling in der Datenschicht. Das ist nicht nur aufwändiger, sondern auch Fehler-anfälliger. Ich persönlich würde das nicht tun. Denn: die eineindeutige Repräsentation eines Objekts in der Business-Schicht kannst du "auch nur" über GUIDs korrekt lösen und das ist auch nichts anderes als eine ID.

Zitat:
b) Das erstellen der Objekte ist auch noch semioptimal. Wenn ich eine Entity per ID auslese wird der passende Kampf nicht automatisch erzeugt. Damit habe ich im "besten" Fall ein Problem wenn 2 gleiche Entities die geändert wurden wieder gespeichert werden (klassisch denke ich) und im schlimmsten Fall fatal errors weil irgendeine Nullreferenz aufgerufen wurde.
An dieser Stelle musst du die Grundregeln der objektorientierten Entwicklung beachten. Ist ein Objekt (z.B. "Fight") auf die Existenz der Gegner angewiesen, müssen diese zum Konstruktions-Zeitpunkt existieren. Ist das nicht der Fall, fliegt eine Exception und der Kampf kann nicht stattfinden. Die Exception kann unterschiedliche Ausprägungen haben: nur ein Gegner "fehlt" oder alle Gegner "fehlen".
Es darf IMHO auch keine Kopplung zwischen Auslesen eines Objekts und der Erzeugung des Kampfes geben, das wäre ein Fehler in der Modellierung. Für solche Belange würde ich einen GameManager implementieren, der sich um den Ablauf des Spiels und damit auch im den Ablauf eines Kampfes kümmert. Dabei nimmt er per delegate (kann per DI injiziert werden) verschiedene weietre Komponenten (z.B. "FightManager") zu Hilfe. Dieser regelt dann den Kampf und kann sich zur Beschaffung der Daten (wenn du hier die Zuständigkeit umkehren möchtest, was durchaus sinnvoll ist) einer Daten-Komponetne (DataMapper) bedienen. Diese kann man gerne auch per DI bei der Erzeugung des Service injizieren.

Soweit klar?
__________________
Viele Grüße,
Dr.E.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1. Think about software design before you start to write code!
2. Discuss and review it together with experts!
3. Choose good tools (-> Adventure PHP Framework (APF))!
4. Write clean and reusable software only!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dr.e. ist offline   Mit Zitat antworten
Antwort


Themen-Optionen
Thema bewerten
Thema bewerten:

Forumregeln
Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are an
Gehe zu

Besucher kamen über folgende Suchanfragen bei Google auf diese Seite
php datenbank models, mvc pattern der businessschicht, php db setter, php objekt datenbank mapper, model aus datenbank php, \von der datenbank\ mapping php, datenbank in model kapseln, php model aus datenbank erstellen, datenbank abstraktion, php datenbank abstraktion erstellen, was ist datenbankabstraktion?, php alle attribute eines objektes in datenbank, php datenbank abstraktion, logische ebene datenbankabstraktion, pattern datenbankabstraktion java, model user, datenbank umkehren datamapper, php \alle attribute als array\ eines objektes, was ist datenbankabstraktion, php model setter

Alle Zeitangaben in WEZ +1. Es ist jetzt 16:29 Uhr.




Powered by vBulletin® Version 3.7.2 (Deutsch)
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.2.0
Aprilia-Forum, Aquaristik-Forum, Liebeskummer-Forum, Zierfisch-Forum, Geizkragen-Forum