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 29.06.2011, 19:42  
Erfahrener Benutzer
 
Registriert seit: 25.05.2010
Beiträge: 852
PHP-Kenntnisse:
Anfänger
Trainmaster wird schon bald berühmt werden
Standard [Erledigt] Struktur einer Mapper-Klasse

Hallo zusammen,

ich bastle gerade an meinem Projekt weiter und versuche, das Data-Mapper-Pattern zu implementieren. Bei meinen bisherigen Methoden sehe ich kaum Probleme, weil diese meist mit einem/keinem Parameter auskommen. Eben simple Speicher- und Laderoutinen. Jedoch habe ich nun immer komplexere Fälle, z.B.:

PHP-Code:
public function loadOrders(array $ids null$orderBy null, array $limit null) {
    
$db Database::Singleton();
    if (
$ids) {
        foreach(
$ids as &$id) {
            
$id $db->quote($id);
        }
        
$sIds implode(',',$ids);
    }
    
$sql 'SELECT id, order_number, invoice_number, client_id, alt_address,
                    created_at, updated_at, grand_total, grand_total_tax_amount,
                    shipping_costs, shipping_tax_rate, total_quantity, is_canceled,
                    coupon_code, discount_amount
            FROM sales_order'
;
    if (
$ids) {
        
$sql .= ' WHERE id IN ( '.$sIds.' )';        
    }        
    
$pstmt $db->prepare($sql);        
    
$pstmt->execute();
    
$result $pstmt->fetchAll(PDO::FETCH_ASSOC);    
    if (!empty(
$result)) {    
        
$orders = array();
        foreach (
$result as $order) {
            
$orders[] = $this->mapToObject($order);
        }
        return 
$orders;
    }        
    return 
null;

ORDER BY und LIMIT habe ich, wie man sieht, noch nicht in die Methode eingebaut. Irgendwie werde ich das Gefühl nicht los, mich auf dem falschen Pfad zu befinden. Möchte ich bspw. eine weitere WHERE oder GROUP BY Bedingung einbauen, hätte ich schon fünf Parameter. Irgendwie ist das nicht das Wahre.

Im Augenblick überlege ich, ob nicht eine Basis-Mapper-Klasse sinnvoll wäre. Per Vererbung hätte ich Zugriff auf Methoden, die sich um MySQL Bedingungen kümmern. Ein Verwendung würde ich mir wie folgt vorstellen:

PHP-Code:
$aOrders = array();
$oOrderMapper = new Order_Mapper;
$aOrders $oOrderMapper->addWhere('foo'),
                        ->
addOrderBy('bar'),
                        ->
addLimit(array(02))
                        ->
loadOrders(array(6)); 
Was haltet ihr davon? Würdet ihr das Problem anders angehen?
Trainmaster ist offline   Mit Zitat antworten
Sponsor Mitteilung
PHP Code Flüsterer

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

Alt 29.06.2011, 20:43  
Erfahrener Benutzer
 
Registriert seit: 11.04.2011
Beiträge: 260
PHP-Kenntnisse:
Fortgeschritten
lcrash wird schon bald berühmt werden
Standard

Der Mapper sollte Bindeglied zwischen Model und Datenbank-Gateway (oder anderes System) sein, wobei das Model von der Datenbank nichts weiß. Der Query-Generator sollte eine eigene Klasse sein, der die Queries zusammenbaut und der Mapper instanzieren kann um mit dem zurückgegeben String die Datenbank abzufragen.

Ansonsten können solche Queries schon mal lang werden (viele join(), where() etc.) und so lange du (wie schon jetzt) ein Fluent INterface anbietest ist das auch sehr bequem. Das ganze kannst du maximal vereinfachen, wenn diese Methoden auch Arrays annehmen können.
lcrash ist offline   Mit Zitat antworten
Alt 29.06.2011, 20:53  
Moderator
 
Benutzerbild von Chriz
 
Registriert seit: 11.05.2008
Beiträge: 6.269
Chriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer AnblickChriz ist ein wunderbarer Anblick
Standard

Viele haben sich dran versucht und sind gescheitert.

Was ist der Mehrwert, den du dir erhoffst?

Du solltest zunächst mal einen Querygenerator bauen oder am Besten einen existierenden verwenden. Z.B. Zend_Db_Select.
Das wiederum kannst du dann wieder in speziellen DataWrappern kapseln. Die Standardqueries kannst du ja in einer abstrakten Basisklasse definieren (SELECT, WHERE, ORDER BY, LIMIT, ..). Aber du machst es nur noch komplizierter. Überleg dir mal wie kompliziert dann ein einfacher Query mit einem JOIN wird.

Benutz was fertiges wie Doctrine

PS: Database::getSingleton() ..... da kann man nur den Kopf schütteln. Warum uebergibst du der Klasse die Datenbankinstanz nicht und greifst dann per $this->db darauf zu?
__________________
"Nuschel ich?" - "Was?"
Chriz ist offline   Mit Zitat antworten
Alt 29.06.2011, 20:55  
Benutzer
 
Registriert seit: 25.01.2011
Beiträge: 59
PHP-Kenntnisse:
Fortgeschritten
Renner befindet sich auf einem aufstrebenden Ast
Standard

Im Zend Framework wird das genau mit dem Ansatz gemacht, den du dir überlegt hast:
Zend_Db_Select

Wie du siehst, ist das recht umfangreich, was die Anzahl der Methoden angeht. Würde so was auch nicht selber machen. Wär mir zu viel (unnötige) Schreib- und Wartungsarbeit. Zudem ist die Datenbehandlung meist der zentrale Kern einer Anwendung und die sollte halt schon auch sicher und zuverlässig funktionieren.

Edit: Hm Chriz war schneller.
Renner ist offline   Mit Zitat antworten
Alt 29.06.2011, 21:40  
fab
Erfahrener Benutzer
 
Benutzerbild von fab
 
Registriert seit: 28.07.2010
Beiträge: 2.308
PHP-Kenntnisse:
Fortgeschritten
fab ist ein Lichtblickfab ist ein Lichtblickfab ist ein Lichtblickfab ist ein Lichtblickfab ist ein Lichtblick
Standard

Wenn ich so einen Mapper selber implementieren müsste, ohne gleich Doctrine nachzubauen würde ich wohl in der konkreten Klasse direkt die wirklich benötigten Methoden (Queries) zur Verfügung stellen. Das könnte z.B. so aussehen:

PHP-Code:
$orderMapper->loadOrderByCouponCode($couponCode);
$orderMapper->loadOrdersByClient($clientId$offset$limit
So ersparst du dir das hantieren mit vielen oder komplexen Parametern und kannst dich auf deine konkreten Anwendungsfälle konzentrieren anstatt an alle möglichen Eventualitäten zu denken (das ist nahezu unmöglich). Weitere Abstraktion, auch in der Basisklasse oder ggf. einem Query Builder lässt sich dann natürlich immer noch hinzufügen, spätestens sobald du merkst dass es zu Wiederholungen kommt. Die Query-Methoden selbst sollten im Idealfall nur jeweils zwei, drei Zeilen umfassen, ob mit Query-Builder oder ohne.

Ich wette z.B. dass das folgende in fast jeder deiner Methoden analog vorkommt:
PHP-Code:
    $pstmt $db->prepare($sql);        
    
$pstmt->execute();
    
$result $pstmt->fetchAll(PDO::FETCH_ASSOC);    
    if (!empty(
$result)) {    
        
$orders = array();
        foreach (
$result as $order) {
            
$orders[] = $this->mapToObject($order);
        }
        return 
$orders;
    }        
    return 
null
Also: in eine eigene Methode extrahieren.
fab ist offline   Mit Zitat antworten
Alt 29.06.2011, 21:44  
Erfahrener Benutzer
 
Registriert seit: 25.05.2010
Beiträge: 852
PHP-Kenntnisse:
Anfänger
Trainmaster wird schon bald berühmt werden
Standard

Vielen Dank soweit für Eure Meinungen. Jedoch scheine ich etwas missverstanden worden zu sein.

Im Moment geht es mir noch nicht darum, die Queries mithilfe einer Klasse zusammenzubauen. Das wäre des nächste Schritt. Das eigentliche Problem sehe ich in den Strukturierung meiner Mapper-Klasse. Die bereits gezeigte Methode hat eine fixe SELECT/FROM Bedingung und variable andere Bedingungen. Nicht jede Methode ist derart dynamisch. Ein Beispiel für eine fixe Methode ist:

PHP-Code:
public function loadAverageOfGrandTotal() {
    
$sql 'SELECT AVG(grand_total)
            FROM sales_order
            WHERE is_canceled = false;'
;
    
$queryDatabase::Singleton()->query($sql);        
    
$result $query->fetch(PDO::FETCH_COLUMN);    
    if (
$result) {
        return 
$result;
    }        
    return 
null;    

Wie man sieht, kommt die Methode ganz ohne Parameter aus und erfüllt einen bestimmten Zweck. Der Methodenname ist Programm. Ob ich nun in diesem Fall den Query mittels einer Klasse zusammenbaue oder nicht, ist für mich an dieser Stelle (noch) nicht relevant.

Mir geht es vielmehr darum, konfigurierbare Methoden, wie eben loadOrders(), sinnvoll aufzubauen. Es darf nicht möglich sein, SELECT und FROM von loadOrders() zu überschreiben/verändern. Die WHERE, ORDER BY oder LIMIT Bedingungen sollen vielmehr als eine Art Filter für die Methode dienen. Was ich damit auch sagen will, ist, dass selbst wenn ich bspw. Zend_Db_Select einsetzen würde, es an meinem Anliegen nichts ändern würde.


Zitat:
Zitat von Chriz Beitrag anzeigen
PS: Database::getSingleton() ..... da kann man nur den Kopf schütteln.
Das sind noch Relikte aus vergangenen Tagen und wird die nächsten Tage geändert.


@ fab

Du hast mein Problem erfasst. Ich dachte eben, dass ich loadOrders() derart flexibel gestalten kann, dass sich spezifische Methoden erübrigen. Beispielsweise möchte ich Auftrag 1-50 anzeigen, nach der Gesamtwert der Bestellung sortieren und stornierte Bestellungen nicht anzeigen. Das wären allein WHERE, ORDER BY und LIMIT. Es stellt sich nur die Frage, ob ich das in die Parameter packe oder über eigene Methoden/Klasse steure.

Geändert von Trainmaster (29.06.2011 um 22:00 Uhr).
Trainmaster ist offline   Mit Zitat antworten
Alt 29.06.2011, 22:08  
fab
Erfahrener Benutzer
 
Benutzerbild von fab
 
Registriert seit: 28.07.2010
Beiträge: 2.308
PHP-Kenntnisse:
Fortgeschritten
fab ist ein Lichtblickfab ist ein Lichtblickfab ist ein Lichtblickfab ist ein Lichtblickfab ist ein Lichtblick
Standard

Ah, ich sehe du bist eigentlich schon auf der Linie mit spezialisierten Methoden und jetzt geht es um Dinge wie eine Suche mit vielen Parametern? Vielleicht so:

PHP-Code:
public function loadOrdersByFilter(OrderFilter $filter$offset$limit)
{
  
$sql "SELECT ... FROM ... ";
  
$sql .= $this->where($filter);
  
$sql .= $this->limit($offset$limit);
  return 
$this->executeAndFetchAll($sql);

PHP-Code:
class OrderFilter
{
  public 
$id;
  public 
$clientId;
  public 
$createdAt;
  
//...

Verwendungsbeispiel:
PHP-Code:
$filter = new OrderFilter;
$filter->createdAt "BETWEEN $date1 AND $date2";
$orderMapper->loadOrdersByFilter($filter010); 
Ich habe es erstmal bewusst einfach gehalten. OrderFilter ist hier nur ein Value Object welches die einzelnen Bedingungen als SQL enthält, da fällt dir sicher etwas schöneres ein, und die Methoden where() und limit() gehören eigentlich nicht in den Mapper sondern eben in eine Query-Builder-Klasse die wiederum vom Mapper benutzt wird. Ich denke da wirst du am besten Schritt für Schritt drauf hinarbeiten.

Edit: Ich habe mir nochmal weiter über das Filter-Objekt und Sortierungen Gedanken gemacht, ich persönlich finde es wichtig dass außerhalb des Mappers keine datenbankspezifischen Details wie Spaltennamen als bekannt vorausgesetzt werden, daher auch die einzelnen Attribute und nicht etwa ein assoziatives Array. Fürs Sortieren musst du aber irgendwie eine Spalte auswählen können und beim Filtern stößt du an deine Grenzen sobald nicht alles mit AND verknüpft werden soll. Also noch mal ein anderer Ansatz:

PHP-Code:
class OrderFilter
{
  
// spalten
  
const ID 'id';
  const 
CLIENT_ID 'client_id';
  const 
CREATED_AT 'created_at';
  
// ...

  // filter
  
private $conditions = array();
  private 
$sortBy null;
  private 
$sortDir null;

  public function 
setConditions($cond) { ... }
  public function 
setSort($by$dir "ASC") { ... }

Für dein Beispiel, weiter eingegrenzt auf einen bestimmten Kundenkreis um kombinierte Bedingungen zu zeigen:
PHP-Code:
$filter = new OrderFilter;
$filter->setConditions(array(
  
'AND' => array(
    
OrderFilter::IS_CANCELED => "=1",
    
OrderFilter::CLIENT_ID => "IN (13, 42, 128)"
  
)
));
$filter->setSort(OrderFilter::GRAND_TOTAL"DESC"); 
Im Mapper bzw. Query Builder sollte es noch angemessen einfach sein, das zusammenzusetzen und dadurch dass SQL nicht komplett abstrahiert wird bleibst du noch relativ flexibel bei den Bedingungen.

Geändert von fab (29.06.2011 um 22:30 Uhr).
fab ist offline   Mit Zitat antworten
Alt 05.07.2011, 20:13  
moderatives Dielektrikum
 
Benutzerbild von nikosch
 
Registriert seit: 21.05.2008
Beiträge: 35.994
PHP-Kenntnisse:
Fortgeschritten
nikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunft
Standard

Verstehe nicht, warum Du dem Objekt nicht einfach paar Methoden spendierst. Habe letztens was ähnliches gebaut. So sieht das Interface aus:
PHP-Code:
  public function __construct ($sQuery)

  public function 
setLimit ($iLimit 1000 $iLimitOffset 0)

  public function 
getLimit ()

  public function 
getLimitOffset ()

  public function 
addWhere ($sCondition)

  public function 
addOrder ($sField $sDirection 'ASC')

  public function 
addDefaultOrder ($sField $sDirection 'ASC')

  public function 
getOrder ()

  public function 
getDefaultOrder ()

  public function 
getQuery () 
Das leite ich ab und ergänze was noch fehlt oder instanziiere die Klasse.

PS:
Die Where-Conditions sind übrigens AND verknüpft. Wenn ich was anderes brauche, muss ich die Klasse ableiten oder ein komplexe WHERE Bedingung vorher zusammenbauen und dann komplett übergeben.

Es muss nicht immer Doctrine sein.
__________________
--
One pixel is still too big. Please make it smaller. ASAP.

Initiative Mittelstand.
Die wichtigste Gestaltungsregel im Screendesign ist Pi mal Daumen des Arbeitgebers.
--
nikosch ist offline   Mit Zitat antworten
Alt 05.07.2011, 20:20  
moderatives Dielektrikum
 
Benutzerbild von nikosch
 
Registriert seit: 21.05.2008
Beiträge: 35.994
PHP-Kenntnisse:
Fortgeschritten
nikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunftnikosch hat eine strahlende Zukunft
Standard

Zitat:
PS: Database::getSingleton() ..... da kann man nur den Kopf schütteln. Warum uebergibst du der Klasse die Datenbankinstanz nicht und greifst dann per $this->db darauf zu?
Absolut richtig. In loadOrders passiert viel zu viel Foobar. Viel besser wäre doch

loadOrders ($db , $query)

und die Query bauste draußen zusammen. Für wiederkehrende Aufgaben ginge auch bspw. ne Factory (ein Objekt!).
Die eigentliche Daseinsberechtigung für loadOrders ist eigentlich $this->mapToObject, den Rest solltest Du woanders erledigen. Du könntest z.B. auch ein fertiges DB-Request-Handle (Connection auf die Query angesetzt wurde) injezieren. Das Interface des Objekts sichert dann, dass die Anfragemethode (foreach ->fetch()) normiert ist.
__________________
--
One pixel is still too big. Please make it smaller. ASAP.

Initiative Mittelstand.
Die wichtigste Gestaltungsregel im Screendesign ist Pi mal Daumen des Arbeitgebers.
--
nikosch ist offline   Mit Zitat antworten
Alt 05.07.2011, 20:22  
Erfahrener Benutzer
 
Registriert seit: 11.04.2011
Beiträge: 260
PHP-Kenntnisse:
Fortgeschritten
lcrash wird schon bald berühmt werden
Standard

Muss es nicht nein. Aber für ein einfaches whereOr() die Klasse ableiten ist wirklich sehr unbequem.
lcrash 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

Ähnliche Themen
Thema Autor Forum Antworten Letzter Beitrag
Klasse mit Pfadproblem Dieselsepp PHP Einsteiger 6 27.04.2011 13:51
Seite nur im Script aufrufbar BlackScorp PHP Tipps 2010 4 03.11.2010 15:57
[Erledigt] Klasse aus einer anderen Klasse aufrufen Tobby PHP-Fortgeschrittene 7 14.07.2010 20:05
[Erledigt] Nur einer bestimmten Klasse Zugriff andere Klasse erlauben Turamisi Software-Design 2 20.02.2010 20:31
[Erledigt] variable außerhalb einer klasse definieren - "var $variable" in klasse not taurus PHP Tipps 2009 14 05.12.2009 16:00
Klasse für Objekteigenschaft erzeugen? Asipak PHP-Fortgeschrittene 25 30.05.2009 19:45
[Erledigt] Eine klasse einbinden newWorldOrder PHP Tipps 2009 2 23.02.2009 19:32
Methode einer anderen Klasse aufrufen Luka PHP-Fortgeschrittene 15 09.11.2008 14:19
Klasse aus externer Klasse aufrufen kostja PHP Tipps 2008 8 07.08.2008 14:13
Variable aus Klasse herausbekommen GSJLink PHP Tipps 2008 7 16.02.2008 22:25
String-Parser Klasse - was muss rein? Matze PHP Tipps 2007 2 08.04.2007 22:14
mehr als eine Klasse einbinden Alpha Centauri PHP-Fortgeschrittene 4 13.04.2006 20:56
Instanz einer Klasse in einer anderen Klasse verwenden Buhmann PHP-Fortgeschrittene 7 28.10.2005 23:12
[Erledigt] Brauche Hilfe bei meiner ersten Klasse PHP-Fortgeschrittene 9 24.09.2004 17:09
Klasse ändern UniQ PHP Tipps 2004 5 24.08.2004 14:46

Besucher kamen über folgende Suchanfragen bei Google auf diese Seite
mapper klasse, mapper klassen, zend ordermapper, php mapper pattern implementierung, mapperklasse, mapper-klasse, was ist mapper klasse, mapperklasse inner joins, php mapper, was ist eine mapper-class, mapper funktion in class verwenden, php class mapper, srtuktur orders05, php klassen struktur, php methode extrahieren, php pdo data mapper, orders05 struktur, klasse db mapper, eine klasse als struktur verwenden php, mapper-klassen

Alle Zeitangaben in WEZ +2. Es ist jetzt 01:36 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