2009-07-22 11 views
25

Quando si programma in PHP, cerco sempre di creare "modelli" (classi) significativi che corrispondono alle tabelle nel database. Incontro spesso il seguente problema:Pulizia della struttura OO rispetto alle prestazioni SQL

Supponendo di aver creato un database con due tabelle: authors e blogs, che hanno entrambi un modello corrispondente nella mia applicazione.

Diciamo che voglio stampare tutti i blog con informazioni sull'autore, avrei dovuto fare qualcosa di simile:

<?php 
foreach ($app->getBlogs() as $blog) { 
    echo "<h1>" . $blog->title . "</h1>"; 
    echo "Written by" . $blog->getAuthor()->name . "</p>"; 
    // ... et cetera 
} 
?> 

Il problema è che l'applicazione sarà ora il fuoco 1 query SQL ottenere tutti gli articoli del blog e [numero di articoli del blog] query per ottenere le informazioni per ogni autore. Avendo SQL semplice usato avrei potuto recuperare queste informazioni utilizzando una semplice query:

SELECT * FROM blogs 
JOIN authors ON authors.id = blogs.author 

Qual è il modo migliore di affrontare tali questioni: lo sviluppo di un'applicazione orientata agli oggetti senza eseguire troppe query SQL inutili.

risposta

3

IMO, penso che dovresti scrivere solo un'altra classe che incapsulerà quello che hai. Ha sempre senso che un blog abbia un autore? Ogni autore ha un blog? Un autore può avere più blog? Pensa a questi problemi, quindi progetta una classe che incapsulerà questo. Ricorda, i tipici schemi di database non sono OO ... sono relazionali. Sì, sono vicini, ma ci sono sottili differenze.

Quindi, se un autore può avere più blog, è possibile avere una chiave per una classe multivalore di qualche tipo (con la chiave basata sull'ID autore) e si può inizializzare o caricare questa classe con una chiamata SQL. Solo alcune cose a cui pensare.

1

Qualunque soluzione tu usi, ORM o no, dovrebbe essere in grado di emettere una singola selezione in questo caso, e dovrebbe anche essere in grado di selezionare solo le colonne necessarie. Quindi da quel join dovrebbe essere in grado di popolare gli oggetti autori con corrispondenti elenchi di blog per autore. Dover pubblicare più SQL è uno spreco.

0

Questo è il classico problema di ORM. Molte molte scuole di pensiero. Non sono sicuro delle specifiche del php, ma ci sono diverse strategie per risolvere questa "mancata corrispondenza dell'impedenza". Google orm.

2

Cose come questa sono esattamente ciò che la creazione del proprio livello di dati dovrebbe risolvere per voi. Nel tuo modello per i tuoi blog, dovrebbe esserci una funzione come getBlogList() che restituirà i titoli del blog e il nome dell'autore in un'unica query.

+0

Una sorta di soluzione che ho pensato a me stesso ma il punto è: non so in anticipo se l'autore-oggetto è mai necessario, $ blog-> getAuthor() potrebbe non essere mai chiamato ma avrei recuperato l'informazione comunque con la soluzione fornita. – Thijs

+0

Quello che stai chiedendo è impossibile, allora. In pratica stai chiedendo un codice che in qualche modo sappia in anticipo cosa utilizzerai. Devi hardcode te stesso; devi scegliere il caricamento pigro, che è efficiente se vuoi solo pochi autori ma non tutti, o il caricamento in batch, come descritto sopra, che è efficiente se hai bisogno di molti Autori. Non puoi avere entrambi. – ryeguy

+0

Lo strumento ORM che usiamo qui in Inntec avrebbe generato classi per (a) tirando giù un singolo Autore e un altro per (b) tirando giù una raccolta di Autori. Quindi potrei facilmente dire "prendi tutti gli autori che hanno la lettera" m "nel nome" o quasi qualsiasi altra cosa. Tuttavia, sia (a) che (b) tirano giù l'intero Autore, quindi tutti i campi attraversano il filo. Se volevi un solo campo per un caso speciale o se avevi bisogno di un join insolito, dovresti scriverlo da solo. Che va bene Io uso il codice gen'd il 90% delle volte. –

0

un modo per fare ciò è creare una vista con il join in esso e visualizzare i risultati della visualizzazione in un'altra classe che contiene dati per blog e autore.

1

Propel è un esempio di un ORM PHP che può far fronte a questo. Sono sicuro che Doctrine deve essere in grado di farlo, anche se non l'ho mai guardato.

Perché reinventare la ruota?

3

A meno che non si sappia che le inefficienti operazioni di SQL non avranno alcun impatto reale, come il numero di iterazioni ridondanti o le righe interessate sarà sempre piccola (ad esempio, iterando un'operazione sul numero di figli di una famiglia, che, a meno di rarissimi casi come i Duggars, si può contare su meno di 10), ho sempre privilegiato l'efficienza della query relazionale sulla bellezza del codice OO.

Sebbene il brutto codice OO possa rendere la manutenzione un problema, l'accesso inefficiente ai dati può mettere in ginocchio un sistema, di solito quando si è in vacanza o si cerca di dormire. E la maggior parte delle volte, è possibile trovare un buon compromesso che rende le operazioni SQL più efficienti con un'interfaccia ragionevolmente "obiettiva". Potrebbe costare un po 'più tempo quando si tratta di rifattorizzare o aggiungere funzionalità se il tuo modello di oggetto non è bello, ma sta costando tempo ai tuoi clienti ogni volta che spingono quel pulsante (o denaro in termini di hardware più grande per eseguire il app - mai un buon metodo di ottimizzazione), e le ore di lavoro trascorse con l'app dovrebbero superare di gran lunga le ore lavorative impiegate per svilupparlo (ci si aspetterebbe).

Per quanto riguarda la necessità di un'interfaccia (costringendovi a capire tutti i possibili modelli di consumo), ho affrontato questo problema eseguendo tutte le mie modifiche dei dati tramite stored procedure, ma consentendo l'accesso ai dati direttamente. contro le tabelle & visualizzazioni dando a tutti gli utenti solo i privilegi di selezione. Questa è una posizione semi-controverso, poiché molte persone vorrebbero bloccare tutte le operazioni di accesso ai dati da parte dei consumatori a valle nel nome di garantire che tutti gli sql eseguiti siano conformi ai loro standard. Ma i nuovi modi di guardare i dati sono sempre in arrivo, e se devi aggiungere un nuovo stored proc, aggiornare le tue librerie di classi core e aggiornare il codice cliente ogni volta che qualcuno vuole implementare una nuova funzionalità, la distribuzione e la qualifica possono crescere essere un peso reale - molto più che dover affrontare un modello di oggetto che non si adatta ad un ideale religioso. Ed è molto più semplice implementare un processo di ispezione del codice che verifichi che le nuove dichiarazioni selezionate scritte dai consumatori a valle siano kosher.

0

Onestamente, basta creare un metodo sul tuo blog classe chiamata getBlogsWithAuthors() e quindi eseguire

SELECT * 
FROM blogs 
JOIN authors 
     ON authors.id = blogs.author 

So che può sembrare come un dolore di scrivere cose come questa per ogni classe del modello, ma non c'è davvero nessun altro modo. Si potrebbe fare un po 'più dinamica, però:

//this is a method of a model class. 
//Assume $this->table is the table name of the model (ie, Blog) 
public function getWith($joinTable, $pivot1, $pivot2) 
{ 
    $sql="SELECT * 
      FROM {$this->table} 
      JOIN $joinTable 
        ON $pivot1 = $pivot2"; 

    return executeQuery($sql);  
} 

$blog=new Blog(); 
$result=$blog->getWith('authors', 'authors.id', 'blogs.author'); 
[play with results here] 
+0

Per essere onesti, questa soluzione mi sembra un po 'hacky. Il metodo getWith() non è molto descrittivo e non conforma l'incapsulamento promosso dalla progettazione orientata agli oggetti perché altri oggetti devono conoscere il funzionamento interno della classe Blog (nomi di colonne, join candidati). – Thijs

+0

Bene, l'idea era che questo potesse essere nella classe del modello base e che potesse essere adattato senza alcun hardcoding a nessun modello. Se vuoi qualcosa di più elegante, perché lo stai codificando manualmente? Vai per la propulsione o la dottrina. Stai solo reinventando la ruota altrimenti. – ryeguy

+0

Penso che RyeGuy abbia ragione su questo. ;) SE la sua soluzione è hacker, è perché è troppo minimalista. Non mi piace passare gli elementi del database come testo. Ma qual è il prossimo passo? Creare una classe separata per ogni entità con più strumenti (enumerazioni, ecc.) Per le colonne.Ma questo è dispendioso in termini di tempo e fragile (pensa alle modifiche del database, ecc.), Quindi che cosa succederà? La generazione del codice risolve una tonnellata. E se c'è la generazione del codice, potremmo fare molto di più. Ed è così che è nato il mio strumento ORM. È iniziato letteralmente in ASP Classic, utilizzando MS Access per generare classi. :) –

3

Sono un grande sostenitore ORM, e qui è il mio peso-in:

Va bene per il commercio una quantità inperceptible delle prestazioni delle applicazioni per una tonnellata delle prestazioni dello sviluppatore. I server sono estremamente potenti in questi giorni e quel ferro extra ci dà una nuova flessibilità.

Detto questo, se fai qualcosa di stupido che annienta l'esperienza dell'utente mettendo in ginocchio il server, non va più bene. Se avessi un milione di autori nel tuo esempio, trascinarli tutti insieme a tutti i loro campi e scorrere le righe sarebbe imprudente. Se avessi solo 20 autori, non è un grosso problema.

Nel caso di enormi set di dati e costose operazioni batch, anche come un ORM, devo ottimizzare e scrivere sprocs speciali o istruzioni SQL solo per quel caso. E devo stare attento a non scrivere il mio codice in modo tale che io martello il database sarebbe meglio usare un modello di cache in cui estraggo un grande set di dati e poi lavoro fuori da quello.

Questo è un grande dibattito in corso, ma per me è solo una questione di capire che non è possibile risolvere ogni problema con un singolo strumento.

+0

Grazie per il tuo commento. Ci sarà mai un compromesso tra prestazioni e design pulito? Non c'è un modo per scrivere sia il codice orientato agli oggetti riutilizzabile e pulito ed eseguire query efficienti selezionando i dati giusti? – Thijs

+0

thijs: Penso che tu possa avere la tua torta e mangiarla anche tu. Se devi scrivere una sola istruzione sql, fai del tuo meglio per incapsulare quel codice di accesso ai dati in una classe da qualche parte usando un modello che è coerente in tutta la tua applicazione. Non deve essere orribile. ;) Ho progetti in cui esiste una classe chiamata "sprocs" che contiene il codice per accedere a tutte le mie stored procedure. Quindi posso fare qualcosa del tipo: myData = Sprocs.AuthorNames, ed è molto coerente. Molti strumenti ORM generano automaticamente quel tipo di cose per te, il che è bello. –

+0

Come sai di avere 20 autori quando gli utenti possono aggiungerne altri in qualsiasi momento? Metti i vincoli CHECK sul numero di righe nella tabella o su cosa? Questo tipo di pianificazione delle capacità è un disastro in attesa di accadere. – wqw

1

Ho usato alcuni altri attacchi .. Esempio:

<?php 
$blogs = $app->getBlogs(); 
$blogs->getAuthor(); 
foreach ($blogs as $blog) { 
    echo "<h1>" . $blog->title . "</h1>"; 
    echo "Written by" . $blog->getAuthor()->name . "</p>"; 
    // ... et cetera 
} 
?> 

-> getAuthor() chiamata sul blog $ interroga il DB solo una volta, e l'utilizzo di oggetto speciale per array, la chiamata getAuthor() viene chiamato su ciascuno (ma, in qualche modo è ottimizzato per essere eseguito solo come una query).

0

È sempre possibile utilizzare memcached come livello intermedio. Ogni query dovrebbe essere basata esclusivamente sulla RAM, il che significa che puoi eseguire quanti ne vuoi.

1

Hai già risposto alla domanda:

aver usato SQL semplice avrei potuto recuperare queste informazioni utilizzando una query semplice

Avete una scelta tra SQL che recupera solo blog post e SQL che recupera i post del blog e autori. Allo stesso modo si ha una scelta tra un codice PHP che recupera solo post di blog o codice PHP che recupera i post del blog e gli autori. Devi fare una scelta sul tuo codice PHP, così come devi fare una scelta sul tuo SQL.

Ci sono molti esempi sopra che dimostrano come ciò funzionerebbe nella pratica. Anche la raccomandazione di usare Doctrine o Propel è buona.

1

Considera la separazione di comando/query come descritto da Greg Young e Martin Fowler. Il tuo modello di query può avere Blog e autore de-normalizzati in una singola tabella ottimizzata per il recupero di DTO per il tuo livello di presentazione.

Greg Young ha un'ottima presentazione su CQS su InfoQ.

Problemi correlati