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 Bewertung: Bewertung: 1 Stimmen, 5,00 durchschnittlich.
Alt 18.03.2009, 18:19  
Erfahrener Benutzer
 
Registriert seit: 06.09.2008
Beiträge: 189
#Avedo befindet sich auf einem aufstrebenden Ast
Standard Persistence Framework

Hallo zusammen!

Ich beschäftige mich nun seit längerem mit verschiedenen Architectual Pattern, die von Martin Fowler, David Rice, Matthew Foemmel, Edward Hieatt, Robert Mee, Randy Stafford in ihrer gemeinsamen Veröffentlichung Patterns of Enterprise Application Architecture vorgestellt werden.Von besonderem Interesse sind dabei für mich Pattern gewesen, die dem Programmierer helfen Domain Objekte zu überwachen, dauerhaft zu speichern, sie identifizieren zu können usw. Mit anderen Worten ich möchte ein eigenes kleines Persistence Framework schreiben.

Viele werden jetzt fragen wieso? Es gibt ja eigentlich schon recht gute Lösungen, die zudem sogar kostenlos sind, auf dem Markt. Das stimmt zwar, man denke an Propel oder doctrine, jedoch muss ich sagen, dass die meisten Lösungen entweder viel zu umfangreich sind, meiner Meinung nach unnütze Tools implementieren oder wichtige Punkte beim Object Relational Mapping vergessen. Deshalb habe ich angefangen die wichtigsten Pattern selbst zu implementieren und bin damit auch bisher sehr gut vorangekommen.

Da ich sicherstellen möchte, dass Domain Objekte nur dann geladen, erstellt, gelöscht oder geändert werden, wenn dies nötig bzw. möglich ist, habe ich eine Klasse geschrieben, die das Unit of Work Pattern implementiert. Dieses von Martin Fowler vorgestellte Pattern beschreibt wie eine Klasse dafür Sorge tragen kann, dass Veränderungen an Objekten, neue Objekte, nicht neue aber unveränderte Objekte und zu löschende Objekte erfasst und unterschieden werden können. So kann sichergestellt werden, dass alle Veränderungen an der Objektstruktur auf die Datenbank übertragen werden können.

Die verschiedenen Objekte können also überwacht werden, jedoch muss ich momentan noch für jedes Objekt eine Klasse schreiben, was manchmal nicht nötig ist. Deshalb habe ich eine abstrakte Implementierung eines Domain Objekts vorgenommen, die neben einfachen Setter- und Getter-Methoden das Identy Map Pattern implementiert. Das Identy Map Pattern stellt sicher, dass jedes Objekt eindeutig identifiziert werden kann. Dank dieser Klasse ist es nun also möglich einfache Objekte, zum beispiel für eine Ausgabe zu laden, die neben den Standard-Methoden nichts bereitstellen.

Ich kann nun also einfache Objekte laden und diese überwachen. Jedoch muss ich auf der einen Seite die Struktur der Objekte speichern und auf der anderen Seite die Struktur der Datenbank erfassen. Das Metadata Mapping Pattern zeigt, wie man eben dieses Problem lösen kann. Auch dieses Pattern habe ich (in abgewandelter Form) in drei Klassen implementiert.

Wieso nun aber dieser Thread? Ich habe nun noch zwei Klassen bzw. Klassenkonstrukte zu implementieren, wovon mir eins bisher einige Schwierigkeiten bereitet. Es Fehlen ein Klassenkonstrukt, dass das QueryObject Pattern und eine Klasse, die den eigentlichen Mapper implementiert.

Bevor ich den Mapper implementieren kann muss ich natürlich erst das QueryObject Pattern implementieren, da dies mir ermöglicht die Datenbank-Request an Bedingungen (where-Klauseln) und andere Einschränkungen und Spezifizierungen zu binden. Des weiteren soll dieses Klassenkonstrukt es mir ermöglichen Teile der Queries automatisch zu generieren. Bisher habe ich folgende überlegungen angestellt. Es soll die folgenden Klassen geben:
  • QueryObject
  • Criteria
  • Join
Ein Objekt der Klasse Criteria soll eine einzelne Where-Klausel repräsentieren und ein Objekt der Klasse Join ein einzelnes Join Statement. Die Klasse QueryObjekt soll Instanzen der beiden Klassen erzeugen und verwalten können. Da das allerdings noch lange nicht ausreichen würde implementiert die Klasse QueryObjekt diverse weitere Methoden, die das Hinzufügen von Sortierungsvorschriften, Gruppierungsvorschriften und Modifikatoren ermöglichen. Soweit so gut jedoch weiß ich nicht ob mir das bisher so wirklich gelungen ist. Die Klassen habe ich in der bisherigen Form unten angehängt. Die Klassen Join und Criteria sind eigentlich soeit fertig über eine Rückmeldung würde ich mich dennoch freuen. Besondere Schwierigkeiten habe ich bei der Klasse QueryObjekt. Ich weiß nicht ob es ok ist solche Dinge wie eine Methode addOrder() oder addLimit() direkt in die Klasse aufzunehmen und ob ich die Konstanten nicht hätte außerhalb der Klasse definieren sollen. Ob die Methoden reichen oder ob ich etwas ganz elementares vergessen habe. Ich hoffe also in dieser Sache auf eure Unterstützung. Was würde euch noch Fehlen? Ist der Aufbau so ok? etc...

Ich würde mich sehr über Anregungen und Hilfestellungen freuen. Fragen und Anmerkungen rund um das Thema Object Relational Mapping sind natürlich auch erwünscht.

MfG, Andy

Criteria
PHP-Code:
/**
 * Class Criteria
 * 
 * An object of the Criteria class represents an 
 * a condition that is assigned to the sql 
 * statement. The generateSql()-Method returns 
 * the strinng representation of an objekt so that
 * the full sql statement can be build  in a parent 
 * objekt.
 * 
 * @package Criteria
 * @version 0.2
 * @date 2/3/2009
 * @author Andreas Wilhelm <Andreas2209@web.de>
 * @copyright Andreas Wilhelm
 * @see http://avedo.net
 */  
class Criteria
{
    
/**
     * @var String $sqlOperator The sql comparison operator
     * @access private
     */
    
private $sqlOperator null;
    
    
/**
     * @var String $field The field the comparison is focused on
     * @access private
     */
    
private $field null;
    
    
/**
     * @var Mixed $value The value of the comparison
     * @access private
     */
    
private $value null;
    
    
/**
     * Assigns the important instance properties
     *
     * @access public
     * @param String $field The field the comparison is focused on
     * @param Mixed $value The value of the comparison
     * @param String $sql The sql comparison operator
     * @return void
     */
    
public __construct(String $fieldMixed $valueString $sql)
    {
        
$this->field $field;
        
$this->value $value;
        
$this->sqlOperator $sql;
    }
    
    
/**
     * Returns the sql representation of this objekt
     *
     * @access public
     * @param DataMap $dataMap The DataMap objekt
     * @return void
     */
    
public generateSql(DataMap $dataMap)
    {
        
// get the column name
        
$column $dataMap->getColumnForField($this->field);
        
        
// create sql statement
        
$sql $column ' ' $this->sqlOperator ' ' $this->value;
    
        return 
$sql;
    }

Join.php
PHP-Code:
/**
 * Class Join
 * 
 * The Join class enables the handling 
 * of a join sql statement in an abstract 
 * class. So it is like the Criterion class 
 * but there is a little bit more complexity.
 * 
 * @package Join
 * @version 0.1
 * @date 12/3/2009
 * @author Andreas Wilhelm <Andreas2209@web.de>
 * @copyright Andreas Wilhelm
 * @link http://avedo.net
 */
class Join
{
    
/**
     * @var String $left The left column of the join dot seperated from the table
     * @access private
     */
    
private $left null;

    
/**
     * @var String $right The right column of the join dot seperated from the table
     * @access private
     */
    
private $right null;

    
/**
     * @var String $right The join type
     * @access private
     */
    
private $type null;

    
/**
     * Assigns the important instance properties
     *
     * @access public
     * @param String $left The left column of the join dot seperated from the table
     * @param String $right The right column of the join dot seperated from the table
     * @param String $type The join type
     * @return void
     */
    
public function __construct($left$right$type)
    {
        
$this->left $left;
        
$this->right $right;
        
$this->type $type;
    }

    
/**
     * Returns the join type
     *
     * @access public
     * @return String
     */
    
public function getType()
    {
        return 
$this->joinType;
    }

    
/**
     * Returns the left column of the join dot seperated from the table
     *
     * @access public
     * @return String
     */
    
public function getLeft()
    {
        return 
$this->left;
    }

    
/**
     * Returns the left join column
     *
     * @access public
     * @return String
     */
    
public function getLeftColumn()
    {
        return 
substr($this->leftstrpos($this->left'.') + 1);
    }

    
/**
     * Returns the left join table
     *
     * @access public
     * @return String
     */
    
public function getLeftTable()
    {
        return 
substr($this->left0strpos($this->left'.'));
    }

    
/**
     * Returns the right column of the join dot seperated from the table
     *
     * @access public
     * @return String
     */
    
public function getRight()
    {
        return 
$this->right;
    }

    
/**
     * Returns the right join column
     *
     * @access public
     * @return String
     */
    
public function getRightColumn()
    {
        return 
substr($this->rightstrpos($this->right'.') + 1);
    }

    
/**
     * Returns the right join table
     *
     * @access public
     * @return String
     */
    
public function getRightTable()
    {
        return 
substr($this->right0strpos($this->right'.'));
    }

    
/**
     * Returns a string representation of the join statement
     *
     * @access public
     * @return String
     */
    
public function generateSql()
    {
        
// add tables to join statement
        
$join $this->getLeftTable() . $this->getType() . $this->getRightTable();
        
        
// add join columns dot seperated from the join tables
        
$join .= ' ON ' $this->getLeft() . ' = ' $this->getRight();
        
        return 
$join;
    }

QueryObject
PHP-Code:
/**
 * Class QueryObject
 * 
 * The QueryObject class enables the handling of sql statements. 
 * You can add different comparisons and order or where 
 * clauses to a single sql statement. Furthermore 
 * you are able to add joins and limits to your 
 * statement. Finally you can use this class to build
 * different string representations of an instance of this class 
 * to generate diffenrent sql statements.
 * 
 * @package Criteria
 * @version 0.1
 * @date 2/3/2009
 * @author Andreas Wilhelm <Andreas2209@web.de>
 * @copyright Andreas Wilhelm
 * @see http://avedo.net
 */  
class QueryObject
{
    const 
EQUAL '=';
    const 
NOT_EQUAL '!=';
    const 
GREATER_THAN '>';
    const 
LESS_THAN '<';
    const 
GREATER_EQUAL '>=';
    const 
LESS_EQUAL '<=';
    const 
LIKE ' LIKE ';
    const 
NOT_LIKE ' NOT LIKE ';
    const 
DISTINCT 'DISTINCT ';
    const 
IN ' IN ';
    const 
NOT_IN ' NOT IN ';
    const 
ASC 'ASC';
    const 
DESC 'DESC';
    const 
IS_NULL ' IS NULL ';
    const 
IS_NOT_NULL ' IS NOT NULL ';
    const 
CURRENT_TIMESTAMP 'CURRENT_TIMESTAMP';
    const 
CURRENT_DATE 'CURRENT_DATE';
    const 
CURRENT_TIME 'CURRENT_TIME';
    const 
INNER_JOIN 'INNER JOIN';
    const 
LEFT_JOIN 'LEFT JOIN';
    const 
RIGHT_JOIN 'RIGHT JOIN';

    private 
$uow null;
    private 
$domainClass null;
    private 
$modifiers = array();
    private 
$joins = array();
    private 
$criteria = array();
    private 
$limit = array();
    private 
$properties = array();
    private 
$orders = array();

    public 
execute(UnitOfWork $uow)
    {
        
$this->uow $uow;
        return 
$uow->getMapper($domainClass)->findObjectsWhere($this->generateWhereClause());
    }

    public function 
addProperty($column)
    {
        
// check if entry already exists
        
if( !in_array($column$this->properties) )
        {
            
// add column which should be selected
            
$this->properties[] = $column;
        }
    }

    public function 
addJoin($left$right$type)
    {
        
// create join object
        
$join = new Join($left$right$type);

        
// create hash code
        
$hash md5(serialize($join));

        
// add join to join array
        
$this->joins[$hash] = $join;
    }
    
    public 
addCriteria($field$value$operator)
    {
        
// create object
        
$obj = new Criteria($field$value$operator);

        
// create hash code
        
$hash md5(serialize($obj));

        
// overwrite old Criteria object
        
$this->criteria[$hash] = $obj;
    }

    public function 
addOrder($column$dir self::ASC)
    {
        
// add entry to the order clause
        
$this->orders[$column] = $column ' ' $dir;
    }
    
    public function 
addGroupBy($column)
    {
        
// add entry to the groupby clause
        
$this->groupBy[$column] = $column;
    }

    public function 
addLimit($startOrCount$count null)
    {
        
// no offset defined
        
if( $count === null )
        {
            
// assign limit
             
$this->limit['count'] = $startOrCount;
        }

        else
        {
            
// assign limit and offset
            
$this->limit['start'] = $startOrCount;
            
$this->limit['count'] = $count;
        }
    }

    private 
String generateWhereClause()
    {
        
$result null;
        
        foreach( 
$this->criteria as $criterion )
        {
            if( 
$result != null )
            {
                
$result .= ' AND ';
            }

            
$result .= $criterion->generateSql($this->uow->getMapper($this->domainClass)->getDataMap());
        }

        return 
$result;
   }

__________________
I'm so tired of slitting the throats of people calling me a violent psychopath.

Geändert von #Avedo (19.03.2009 um 00:12 Uhr).
#Avedo ist offline   Mit Zitat antworten
Sponsor Mitteilung
PHP Code Flüsterer

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

Alt 18.03.2009, 18:56  
phpdev
Gast
 
Beiträge: n/a
Standard

Das ist ja ein Zufall, gerade gestern habe ich mir darüber Gedanken gemacht. Dazu habe ich mir erstmal folgendes angesehen:
Chapterª34.ªAddon: YDDatabaseQuery, using objects to create SQL queries

Vielleicht könntest du auch mal ein Beispiel geben, wie du diese Query mit deinen Klassen generieren würdest. Weil ich mir auch noch nicht so ganz im klaren bin, was der große Vorteil daran ist.
Code:
SELECT `id`, `name`, `g`.`id` AS `gid`, `g`.`name` AS `group_name`,
`a`.`id` AS `aid`, `a`.`name` AS `attach_name` FROM `user` AS `u` LEFT JOIN `group` AS `g`
ON ( u.group_id = g.id ) INNER JOIN `attach` AS `a` ON ( g.attach_id = a.id OR ( a.size > 150
AND a.status = 3 ) ) WHERE g.id = 144 ORDER BY `a`.`name` LIMIT 50
Was mir bei dir so auf die schnelle aufgefallen ist:
Bei dir werden alle WHERE-Bedingungen AND-verknüpft, und JOINs nur über =.
Eigentlich könnte man ON/WHERE (eine Klasse) gleichbehandeln?

Achso, ein Vorteil wäre das automatische Escapen von Bezeichnern bzw. quoten von Werten, wo machst du das?
Und wie escapet man folgende Konstrukte: SUM( `value` ) die Lösung aus dem YDF gefällt mir garnicht.
  Mit Zitat antworten
Alt 18.03.2009, 19:04  
Erfahrener Benutzer
 
Benutzerbild von Phoscur
 
Registriert seit: 01.12.2008
Beiträge: 450
PHP-Kenntnisse:
Fortgeschritten
Phoscur wird schon bald berühmt werdenPhoscur wird schon bald berühmt werden
Standard

Ich kenne jetzt nicht deine ganzen Patterns, aber bei dem Kontakt den wir bereits hatten, haben wir ja festgestellt, dass wir auf etwas ähnliches zuarbeiten.

Mich hat dieser ganze Criteria Kram abgeschreckt bei Propel. Ich habe mir da selbst was ausgedacht:

Ich wollte anfangs einen "Mapper" (boah Namesgebung.. furchtbar, hab bestimmt vier mal gewechselt) der Queries speichert, Arrays ausgibt und selbige Arrays auch wieder annimmt und Änderungen updatet (Änderungen auf Basis von array_diff war die Anfangsidee).
Dieses Stück ist relativ selbstständig und heißt bei mir AutoUpdater. Ich benutze das quasi als "QueryTemplateRegistry", in der verwendete Queries festliegen. Ich hänge dann noch den LOCK Level für die Transaktion an (Transaktionen hatten mir bei Propel auch gefehlt, weiß nimmer).

Um daraus dann Objekte zu machen wird der Autoupdater dekoriert, mit einem Service der eine Factory verwendet um die Objekte zu basteln, welche dann bei Schließung oder Zerstörung ihr Eigenschaftenarray zurückmelden.

Das sollte nur eine Idee sein, wir können das auch gerne diskutieren.

Momentan kümmere ich mich noch um die Relationen, bisher funktioniert es nur hierarchisch von oben nach unten.
__________________
Phoscur ist offline   Mit Zitat antworten
Alt 18.03.2009, 20:26  
Erfahrener Benutzer
 
Registriert seit: 06.09.2008
Beiträge: 189
#Avedo befindet sich auf einem aufstrebenden Ast
Standard

@phpdev
Ich muss leider gestehen, dass ich deinen Beitrag quasi nicht verstanden habe. Ein Beispiel, wie ich das Query mit meinen Klassen generieren würde kann ich dir momentan noch nicht geben, da ich ja noch nichteinmal die QueryObject Klasse fertig habe. Wo ist der Vorteil bei einem QueryObject? Es gibt keinen. Ein QueryObject ist ohne eine dazugehörige Mapper Klasse vollkommen sinnlos. Setzt man jedoch auf einen DataMapper, wie ihn Martin Fowler vorstellt, So ist es sinnvoll nicht für alle möglichen Abfragen eine eigene Methode zu implementieren, sondern eine abstrakte Möglichkeit zu geben eine Anfrage an eine Datenbank an verschiedene Kriterien zu knüpfen. Was möchtest du mir mit deinem schönen Select Statement sagen? Und ja du hast Recht ich muss mir noch etwas einfallen lassen um die verschiedenen Criteria Objekte auch durch eine OR-Konjunktion zu verbinden. Und es wäre auch eine Möglichkeit die Join Kriterien in einem Criteria Objekt festzuhalten.

@Phlegma
Ein Mapperder fertige Queries speichert und schlicht Arrays zurückgibt halte ich für nicht sehr sinnvoll. Dann könnte ich entweder einfach auf die hervorragenden Eigenschaften von MySQLi zurückgreifen und dies mit einer Konfigurationsdatei kombinieren, die zuvor erstellte Queries bereitstellt.
Deine Lösung für die Verwaltung von Transaktionen würde mich allerdings schon interessieren. Was meinst du mit "Objekte basteln"? Hört sich irgendwie nach etwas schrägem an.

MfG, Andy

//EDIT:

Habe mir mal YDDatabaseQuery angesehen. Grauenhaft! Da schreibe ich meine Queries lieber direkt selbst. Da gefällt mir die Umsetzung bei Propel schon deutlich besser. So wie es dort gelöst ist muss ich ja doch wieder die komplette Datenbankstruktur kennen und wissen, wie ich die Datenbank ansprechen muss.
__________________
I'm so tired of slitting the throats of people calling me a violent psychopath.

Geändert von #Avedo (18.03.2009 um 20:31 Uhr).
#Avedo ist offline   Mit Zitat antworten
Alt 18.03.2009, 20:48  
Erfahrener Benutzer
 
Benutzerbild von Phoscur
 
Registriert seit: 01.12.2008
Beiträge: 450
PHP-Kenntnisse:
Fortgeschritten
Phoscur wird schon bald berühmt werdenPhoscur wird schon bald berühmt werden
Standard

Zitat:
Zitat von #Avedo Beitrag anzeigen

@Phlegma
Ein Mapperder fertige Queries speichert und schlicht Arrays zurückgibt halte ich für nicht sehr sinnvoll. Dann könnte ich entweder einfach auf die hervorragenden Eigenschaften von MySQLi zurückgreifen und dies mit einer Konfigurationsdatei kombinieren, die zuvor erstellte Queries bereitstellt.
Mh, muss nicht jeder mögen. Praktischerweise ist der Teil austauschbar, also wenn man das braucht könnte man den Criteria Kram auch dranbauen. Vielleicht spalte ich sogar noch den Teil für das Update ab, denn das ist immer gleich.
Zitat:
Deine Lösung für die Verwaltung von Transaktionen würde mich allerdings schon interessieren.
Nehmen wir an du hast InnoDB Tabellen. Jetzt setzt du autocommit auf aus und beginnst eine neue Transaktion. Das übliche Select, PHP Verarbeitung, Update. Update hab ich bereits erklärt (evtl. verändertes Array in den Autoupdater schmeißen). Select baue ich mir spezifisch, vielleicht kapsele ich sogar Objektspezifische Businesslogik mit hinein, das sehe ich noch. Auf jeden Fall habe ich für jedes Objekt einen eigenen AutoUpdater oder Requester (so hab ich das früher genannt, weil er die Queries abschickt), der vom einfachsten AutoUpdater abgeleitet wird. Wenn man nichts hinzuzufügen hat, nimmt man halt den einfachen AutoUpdater, der zum Beispiel bereits eine Methode wie getByID($id[s]) vorgibt.
Zu LOCK. Das wird wie bereits gesagt an die Query angehängt, je nach eingestelltem WriteLock mit ' FOR UPDATE' oder ' LOCK IN SHARE MODE' im Falle von ImDB.
Sollte man eine andere Datenbank verwenden wollen, muss dafür ein neuer Basis AutoUpdater geschrieben werden.

Zitat:
Was meinst du mit "Objekte basteln"? Hört sich irgendwie nach etwas schrägem an.
Zuerst war nur vorgesehen, im Service einfach Objekte einer Art oder der Art eigenschaften[klasse] herzustellen. Als ich dann bemerkte, dass das zu unflexibel war, habe ich eine Factory eingebunden, die bei mir auch die Rollen des Verschachtelns übernehmen kann. So habe ich eine ParentFactory und eine OwnedFactory, die zusammen das Erstellen eines Objekts mit Unterobjekten erlauben. Wobei selbst bei mehreren ParentObjekten nur zwei Queries ausgeführt werden.

Wenn ich hier schon so viel über mein System erzähle wünsche auch ich mir Kritik, selbst wenn ich nicht Threadersteller bin. (z.B @Doc)
__________________
Phoscur ist offline   Mit Zitat antworten
Alt 18.03.2009, 21:37  
Moderator und Wett-König
 
Benutzerbild von dr.e.
 
Registriert seit: 21.05.2008
Beiträge: 3.633
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 Phlegma, hallo Andy,

Zitat:
Wenn ich hier schon so viel über mein System erzähle wünsche auch ich mir Kritik, selbst wenn ich nicht Threadersteller bin. (z.B @Doc)
Das ist nicht so einfach, denn Andy und ich haben unter forum.adventure-php-framework.org [de] &bull; Thema anzeigen - DataMapper Implementierung bereits eine sehr ausführliche Diskussion über die Implementierung einer Persistenz-Schicht geführt. Ich habe mit dem GenericORMapper im APF einen Ansatz gewählt, der jedoch etwas von Andy's differiert. Beide sind sicher richtig, es ist nur im Endeffekt wichtig, dass diese auch möglichst abstrakt nutzbar sind. "Abstrakt" meint hier, dass ich mich möglichst wenig um Datenbank-Themen kümmern muss und mir das bereits abgenommen wird.

@Andy: Ich denke, dass du bei deinem aktuellen Ansatz deshalb etwas stecken bleibst, weil du zuerst die Helper- und dann die Core-Komponenten implementiert hast. Was ist dein Join-Objekt wert, wenn du keinen DataMapper hast, der Objekte mappt. Weiterhin hast du noch nicht berücksichtigt, wie deine Objekte von der Peristenz-Schicht zur Verfügung gestellt werden. Du erinnerst dich? GenericDomainObject vs. konkrete Domänen-Objekt. Ich würde mir diese Fragen zuerst stellen und dann Helper und Gimmicks schreiben. Du wirst sehn, das ist einfacher.

Noch eine kleine Anmerkung:
PHP-Code:
$hash md5(serialize($obj)); 
Sowas würde ich nie machen, denn das kostet ordentlich Performance - und gerade in der Datenschicht. Schreib lieber

PHP-Code:
$obj = new Criteria($field$value$operator);
$hash md5($field.$value.$operator);
$this->criteria[$hash] = $obj
Das ist effizienter und schneller. Wenn du noch mehr optimieren wolltest, kannst du md5() auch weglassen.

Just my 2 cent!
Dr.E.
__________________
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!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Geändert von dr.e. (18.03.2009 um 21:38 Uhr). Grund: Code-Formatierung bearbeitet!
dr.e. ist offline   Mit Zitat antworten
Alt 18.03.2009, 22:25  
Erfahrener Benutzer
 
Registriert seit: 06.09.2008
Beiträge: 189
#Avedo befindet sich auf einem aufstrebenden Ast
Standard

Hallo Christian!

Schön dass du dich hier auch in die Diskussion einklinkst. In Bezug auf das Problem DomainObject vs. konkrete Domänen-Objekt habe ich bereits eine Lösung gefunden. In den Klassen, die sich um das Metadata Mapping kümmern, wird nicht nur die Struktur der Datenbank und derer Tabellen abgebildet sondern auch die der Domain Objekte. Es reichen ja manchmal einfache Domain Objekte zum beispiel wenn ein Artikel gespeichert werden soll. Wenn man allerdings ein Auto aus einer Datenbank läd möchte man nicht nur die Farbe und die Marke wissen, sondern man möchte damit auch fahren, Radio hören oder es putzen. Dazu braucht man aber Methoden, die generisches Domain Objekt nicht implementiert. Man also von Fall zu Fall unterscheiden können und diese Unterscheidung möchte ich mit Hilfe von Reflections umsetzen.

Ich werde mich aber nun erstmal an die eigentliche Mapper Klasse setzen und dann die einzelnen Helper schreiben. Das ist eine gute Idee. Danke!

MfG, Andy
__________________
I'm so tired of slitting the throats of people calling me a violent psychopath.
#Avedo ist offline   Mit Zitat antworten
Alt 19.03.2009, 16:06  
Erfahrener Benutzer
 
Registriert seit: 06.09.2008
Beiträge: 189
#Avedo befindet sich auf einem aufstrebenden Ast
Standard

Guten Abend!
Habe heute den ganzen Tag an der Fertigstellung der Mapper Klasse gearbeitet. Und ja es ist tatsächlich einfacher erst die Kernklassen zu schreiben und dann die Helper-Klassen zu implementieren, denn ich weiß nun, welche Methoden diese zur Verfügung stellen sollen und welche Parameter diese Erwarten müssen. Sie ist mit sicherheit noch nicht ganz Fehlerfrei jedoch würde ich mich sehr freuen, wenn trotzdem schonmal jemand einen Blick darauf werfen könnte.
MfG, Andy

PHP-Code:
/**
 * Class Mapper
 * 
 * The Mapper class enables the moving of data between objects and a database 
 * while keeping them independent of each other and the mapper itself. Therefore a 
 * UnitOfWork object takes care about all changes at the Domain Model and the mapping 
 * of this changes onto the database. This ensures a good performace of the Mapper. 
 * The Mapper class implements different methods to load records from the database as 
 * a domain object. In doing so it differentiates between a generic and a specific 
 * domain object. Sometimes it will suffice to load an object that only implements 
 * some getter and setter methods, but sometimes there is more functionality required. 
 * Because an basic select statement is not enough for a more komplex system, each 
 * method that enables the loading of domain objects by specifing a Criteria object 
 * that should be used to load the required object. To use this Mapper class you only 
 * have to create an xml document that holds the structure of the database and if 
 * needed of the domain objects. How to create such a document you will discover if 
 * you refer to the documentation on http://avedo.net (comming soon). 
 * Finally you are able to delete or save single DomainObjects to the database and if 
 * you would like to ensure that all changed, deleted or created objects are updated, 
 * removed or inserted into the database, just use the commit() method.
 * 
 * @package Mapper
 * @version 0.1
 * @date 18/3/2009
 * @author Andreas Wilhelm <Andreas2209@web.de>
 * @copyright Andreas Wilhelm
 * @link http://avedo.net
 */  
class Mapper
{    
    
/**
     * @var MySQLi $mysqli The MySQLi object that is used to access the database
     * @access private
     */
    
private $mysqli null;
    
    
/**
     * @var UnitOfWork $uow The UnitOfWork object that handles the domain objects
     * @access private
     */
    
private $uow null;
    
    
/**
     * @var DataMap $dataMap The DataMap object that holds all information of a single database
     * @access private
     */
    
private $dataMap null;
    
    
/**
     * @var String $table The mapping table
     * @access private
     */
    
private $table null;
    
    
/**
     * Initializes the UnitOfWork object that controls the DomainObjects, 
     * assigns the MySQLi object that is used to access the database 
     * and the DataMap object that holds all information of the mapping 
     * table and its relaited tables.
     *
     * @access public
     * @param MySQLi $mysqli The MySQLi object that is used to access the database
     * @param String $schema Path to the xml database description
     * @return void
     */
    
public function __construct($table$mysqli$schema)
    {
        
// assign database object
        
$this->mysqli $mysqli
    
        
// create a new UnitOfWork object
        
$this->uow UnitOfWork::newCurrent();
        
        
// get DataMap object
        
$this->dataMap $this->loadSchema($schema);

        
// assign the mapping table
        
$this->table $table;
    }
    
    
/**
     * Loads all domain objects affected by the given query and 
     * returns them in an array.
     *
     * @access public
     * @param String $sql The sql statement that should be used
     * @return Array
     */
    
public function fetchBySql($sql)
    {
        
// send query
        
$result $mysqli->query($sql);
        
        
// check for result
        
if( !$result )
        {
            throw new 
Exception('MySQL error while performing query');
        }
        
        
// initiate result array
        
$records = array();
        
        
// save record objects to array
        
while( $record $result->fetch_assoc() )
        {
            
// create DomainObject
            
$records[] = $this->load($record);
        }

        
// free result set
        
$result->close();
            
        return 
$records;
    }

    
/**
     * Loads all domain objects affected by the given QueryObject 
     * object and returns them in an array.
     *
     * @access public
     * @param QueryObject $query The QueryObject object that should be used
     * @return Array
     */
    
public function fetchByQueryObject(QueryObject $query)
    {
        
// create select statement
        
$select $query->generateSelect();

        
// fetch result by sql statement
        
return $this->fetchBySql($select);
    }

    
/**
     * Instances a specific domain object by a given ResultSet, 
     * registers it at the UnitOfWork object and returns it.
     *
     * @access private
     * @param ResultSet $rs The ResultSet the domain object should instanced by
     * @return DomainObject
     */
    
private function load(ResultSet $rs)
    {
        
// get primary key
        
$key $rs["ID"];
        
        
// check if object was already loaded
        
if( $this->uow->isLoaded($key) )
        {
            
// return loaded object
            
return $this->uow->getObject($key);
        }
        
        
// get table map
        
$tableMap $this->dataMap->getTableMap($this->table);
        
        
// initiate data array
        
$data = array();
        
        
// replace column names with field names
        
foreach( $rs as $column => $value )
        {
            
// get field name
            
$field $tableMap->getFieldByColumn($column);
            
            
// add data to data array
            
$data[$field] = $value;
        }
        
        
// get name of the DomainClass
        
$domainClass $tableMap->getDomainClass();
        
        
// instance reflection class
        
$reflect = new ReflectionClass($domainClass);

        
// create an instance of the DomainClass
        
$obj $reflect->newInstance($data);
        
        
// assign primary key
        
$obj->setID($key);
        
        
// register object in unit of work
        
$this->uow->registerClean($result);
        
        return 
$result;
    }

    
/**
     * Stores a given domain object in the database.
     *
     * @access public
     * @param DomainObject $obj The object that should be stored
     * @return void
     */
    
public function insert(DomainObject $obj)
    {
        
// check if the domain object must be save
        
if( $obj->getID() != DomainObject::PLACEHOLDER_ID )
        {
            throw new 
Exception('Domain object is already stored');
        }

        
// create insert statement
        
$insert QueryObject::generateInsert($obj);

        
// send query
        
if( !$this->mysqli->query($insert) ) 
        {
            throw new 
Exception('Failed to insert domain object');
        }

        
// mark the domain object as clean
        
$this->uow->registerClean($obj);

        
// return the object id
        
return $this->mysqli->insert_id;
    }

    
/**
     * Updates a given domain object.
     *
     * @access public
     * @param DomainObject $obj The object that should be updated
     * @return void
     */
    
public function update(DomainObject $objQueryObject $query null )
    {
        
// get primary key
        
$key $obj->getID();
        
        
// check if object really have to be updated
        
if( !$this->uow->isDirty($key) )
        {
            return 
null;
        }

        
// create update statement
        
$update QueryObject::generateUpdate($obj$query);

        
// send sql update statement
        
if( !$this->mysqli->query($update) )
        {
            throw new 
Exception('Failed to update record');
        }

        
// mark the domain object as clean
        
$this->uow->registerClean($obj);
    }

    
/**
     * Removes a given domain object from the database.
     *
     * @access public
     * @param DomainObject $obj The object that should be removed
     * @return void
     */
    
public function delete(DomainObject $obj)
    {
        
// get primary key
        
$key $obj->getID();
        
        
// check if object really have to be updated
        
if( !$this->uow->isDeleted($obj) )
        {
            return 
null;
        }

        
// create delete statement
        
$update QueryObject::generateDelete($obj$query);

        
// send sql update statement
        
if( !$this->mysqli->query(delete) )
        {
            throw new 
Exception('Failed to delete record');
        }

        
// remove object from the UnitOfWork
        
unset($this->uow->$key);
    }

    
/**
     * Stores or updates a given domain object.
     *
     * @access public
     * @param DomainObject $obj The domain object
     * @return void
     */
    
public function save(DomainObject $obj)
    {
        
// check if object is new
        
if( $this->uow->isNew($obj) )
        {
            return 
$this->insert($obj);
        }

        
// check if object is dirty
        
elseif( $this->uow->isDirty($obj) )
        {
            return 
$this->update($obj);
        }

        return 
null;
    }

    
/**
     * Stores, removes or updates all loaded and new created objects 
     * using the UnitOfWork object.
     *
     * @access public
     * @return void
     */
    
public function commit()
    {
        
// remove all deleted objects
        
foreach( $this->uow->getDeleted() as $obj)
        {
            
$this->delete($obj);
        }

        
// save all new created domain objects
        
foreach( $this->uow->getNew() as $obj )
        {
            
$this->insert($obj);
        }

        
// update all dirty objects
        
foreach( $this->uow->getDirty() as $obj )
        {
            
$this->update($obj);
        }
    }
    
    
/**
     * Creates the DataMap object that holds
     * all information of the database
     *
     * @access private
     * @param String $schema The path to the xml description of the database schema
     * @return void
     */
    
private function loadSchema($schema)
    {
        
// check if file exists
        
if( file_exists($schema) )
        {
            
// parse xml description of the database schema
            
$database simplexml_load_file($schema'SimpleXMLElement'LIBXML_NSCLEAN);
            
            
// get name of the database
            
$name = (string) $database['name'];
            
            
// create new DataMap object
            
$this->dataMap = new DataMap($name);
            
            
// add tables
            
foreach( $database->table as $table )
            {
                
// get the name of the table
                
$tableName = (string) $table['name'];
                
                
// add table to the DataMap object
                
$tableMap $this->dataMap->addTable($tableName);
                
                
// get name of the DomainClass
                
if( !isset($table['domainClass']) )
                {
                    
$domainClass 'DomainObject';
                }
                
                else
                {
                    
$domainClass = (string) $table['domainClass'];
                }
                
                
// get name of the DomainClass
                
if( !isset($table['prefix']) )
                {
                    
$prefix $tableMap->removeUnderScores($tableName);
                }
                
                else
                {
                    
$prefix = (string) $table['prefix'];
                }
                
                
// set table data
                
$tableMap->setDomainClass($domainClass);
                
$tableMap->setPrefix($prefix);
                
                
// add columns
                
foreach( $table->column as $column )
                {
                    
// get the name of the column
                    
$columnName = (string) $column['name'];
                    
                    
// get the name of the DomainObject field
                    
if( !empty($column['field']) )
                    {
                        
// get field name
                        
$fieldName = (string) $column['field'];
                    }
                    
                    else
                    {
                        
// create field name
                        
$fieldName $columnName;
                    }
                    
                    
// get the type of the column
                    
$type = (string) $column['type'];
                    
                    
// check if column could be null
                    
$isNotNull = (bool) $column['required'];
                    
                    
// get the size of the column
                    
$size = (int) $column['size'];
                    
                    
// check if column is a primary key
                    
if( isset($column['primaryKey']) && ($column['primaryKey'] == 'true') )
                    {
                        
$pk = (bool) $column['primaryKey'];
                    }
                    
                    else
                    {
                        
$pk null;
                    }
                    
                    
// check if column is a foreign key
                    
if( isset($table->foreignKey) && ($columnName == $table->foreignKey->reference['local']) )
                    {
                        
// get foreign key table
                        
$fkTable = (string) $table->foreignKey['foreignTable'];
                            
                        
// get foreign key column
                        
$fkColumn = (string) $table->foreignKey->reference['foreign'];
                    }
                    
                    else
                    {
                        
$fkTable null;
                        
$fkColumn null;
                    }
                    
                    
// add a new Columnmap object to the TableData object
                    
$tableMap->addColumn($columnName$fieldName$type$isNotNull$size$pk$fkTable$fkColumn);
                }
            }
        }
        
        else
        {
            throw new 
Exception('Could not found file ' $schema);
        }
    }

__________________
I'm so tired of slitting the throats of people calling me a violent psychopath.
#Avedo ist offline   Mit Zitat antworten
Alt 19.03.2009, 20:59  
Moderator und Wett-König
 
Benutzerbild von dr.e.
 
Registriert seit: 21.05.2008
Beiträge: 3.633
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

Meine Punkte:
  • new ReflectionClass($domainClass) ist IMHO ein Performance-Killer
  • update() und save() sollten nicht unterschieden werden, da sie das gleiche tun.
  • Es fehlt mir das Gefühl für die Konfiguration und die Anwendung
  • Sind Beziehungen mit verschiedenen Typen umgesetzte?
__________________
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!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Geändert von dr.e. (19.03.2009 um 20:59 Uhr). Grund: Farbe geändert
dr.e. ist offline   Mit Zitat antworten
Alt 20.03.2009, 10:26  
Erfahrener Benutzer
 
Registriert seit: 06.09.2008
Beiträge: 189
#Avedo befindet sich auf einem aufstrebenden Ast
Standard

Zitat:
Zitat von dr.e. Beitrag anzeigen
new ReflectionClass($domainClass) ist IMHO ein Performance-Killer
Eine Alternative könnte so aussehen:
PHP-Code:
// get name of the DomainClass
$domainClass $tableMap->getDomainClass();

// create an instance of the DomainClass
$obj = new $class($data); 
Wäre das eine bessere Lösung?

Zitat:
Zitat von dr.e. Beitrag anzeigen
update() und save() sollten nicht unterschieden werden, da sie das gleiche tun.
Das stimmt nicht ganz. update() speichert die Änderungen, die an einem Domain Objekt vorgenommen wurden in der Datenbank. insert() speichert ein neu erstelltes Domain Objekt in der Datenbank und save() entscheidet ob ein neuer Eintrag in der Datenbank erstellt oder ein alter geändert werden muss. Ist beides nicht von Nöten (das Objekt wurde weder neu erstellt noch geändert) wird einfach null zurückgegeben.

Zitat:
Zitat von dr.e. Beitrag anzeigen
Es fehlt mir das Gefühl für die Konfiguration und die Anwendung

Was meinst du damit?


Zitat:
Zitat von dr.e. Beitrag anzeigen
Sind Beziehungen mit verschiedenen Typen umgesetzt?
Meinst du damit ob die unterschiede zwischen den Datentypen in der Datenbank und den PHP Datentypen berücksichtigt werden? Nein noch nicht, aber ich werde diese Änderung auch noch vornehmen.

Ich habe leider noch ein kleines Problem. Ich überlege gerade, wie ich Änderungen an Domain Objekten speichern soll, die sich auf mehrere Tabellen in der Datenbank beziehen (wurden mittels Joins geladen). Wie soll ich das erkennen und wie kann ich es später umsetzen?

MfG, Andy
__________________
I'm so tired of slitting the throats of people calling me a violent psychopath.
#Avedo 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
PHP Application Framework root66 PHP-Fortgeschrittene 6 13.02.2009 22:45
Suche Schüler o.ä. mit Zend Framework Erfahrung -> 200 Euro mrmojo Beitragsarchiv 3 10.01.2009 15:38
CMS mit Framework KeKs0r PHP-Fortgeschrittene 11 27.12.2008 23:03
Zend Studio for Eclipse und Zend Framework KeKs0r PHP-Fortgeschrittene 5 15.12.2008 15:10
Release 1.8 (RC1) des Adventure PHP Framework verfügbar! dr.e. Beitragsarchiv 4 11.12.2008 09:44
Bestehende Seite in Framework einbinden MaWe4585 PHP-Fortgeschrittene 1 16.10.2008 08:20
Suche nach Framework für schnelles programmieren BLACK PHP Tipps 2008 4 13.08.2008 04:54
Release 1.7 beta des Adventure PHP Framework verfügbar! dr.e. Beitragsarchiv 2 30.07.2008 17:50
BrickOO :: PHP Framework Hroudtwolf Scriptbörse 14 06.05.2008 12:04
Zend Framework Melchior PHP-Fortgeschrittene 29 13.03.2008 21:12
framework struktur notyyy PHP Tipps 2008 3 06.11.2007 18:19
Framework aus Haupt und Unterverzeichnis nutzen? Anotherone PHP-Fortgeschrittene 8 24.07.2007 15:05
Framework gesucht Trash 8 06.06.2006 14:04
[Erledigt] Framework empfehlen? PHP-Fortgeschrittene 1 01.06.2004 07:56

Besucher kamen über folgende Suchanfragen bei Google auf diese Seite
php persistence, php persistence framework, php persistenz, php persitence, persistence framework, persistence php, persistenz framework php, persistence framework php, php object persistence, data mapper pattern, persistenz php, php persistenz framework, php persistance, php persistenzframework, php persistenzschicht, persistenz framework, php persistance framework, data mapper pattern php, php persitence framework, persistence frameworks

Alle Zeitangaben in WEZ +1. Es ist jetzt 11:24 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