Domanda

Al momento il mio codice (PHP) contiene troppe query SQL.per esempio...

// not a real example, but you get the idea...
$results = $db->GetResults("SELECT * FROM sometable WHERE iUser=$userid");
if ($results) {
    // Do something
}

Sto valutando l'utilizzo di procedure memorizzate per ridurre questo problema e rendere le cose un po' più robuste, ma ho alcune preoccupazioni.

Ho centinaia di query diverse in uso nel sito web e molte di esse sono abbastanza simili.Come dovrei gestire tutte queste query quando vengono rimosse dal loro contesto (il codice che utilizza i risultati) e inserite in una procedura memorizzata nel database?

È stato utile?

Soluzione

La migliore linea d'azione per te dipenderà da come ti stai avvicinando all'accesso ai dati.Ci sono tre approcci che puoi adottare:

  • Utilizzare procedure memorizzate
  • Mantieni le query nel codice (ma inserisci tutte le query nelle funzioni e correggi tutto per utilizzare PDO per i parametri, come menzionato in precedenza)
  • Utilizza uno strumento ORM

Se vuoi passare il tuo SQL grezzo al motore del database, le procedure memorizzate sarebbero la strada da percorrere se tutto ciò che vuoi fare è estrarre l'SQL grezzo dal tuo codice PHP ma mantenerlo relativamente invariato.Il dibattito sulle procedure memorizzate e sull'SQL grezzo è un po' una guerra santa, ma K.Scott Allen sottolinea un punto eccellente, anche se usa e getta, in un articolo su database di controllo delle versioni:

In secondo luogo, le procedure memorizzate sono cadute in disgrazia ai miei occhi.Vengo dalla scuola di indottrinamento WinDNA secondo la quale le procedure memorizzate dovrebbero essere utilizzate sempre.Oggi considero le procedure memorizzate come un livello API per il database.Questo è utile se hai bisogno di un livello API a livello di database, ma vedo molte applicazioni che incorrono nel sovraccarico di creazione e mantenimento di un livello API aggiuntivo di cui non hanno bisogno.In tali applicazioni le procedure memorizzate rappresentano più un onere che un vantaggio.

Tendo a propendere per non utilizzare le procedure memorizzate.Ho lavorato su progetti in cui il DB ha un'API esposta tramite procedure memorizzate, ma le procedure memorizzate possono imporre alcune limitazioni e tali progetti hanno Tutto, a vari livelli, utilizzavano codice SQL grezzo generato dinamicamente per accedere al DB.

Avere un livello API sul DB offre una migliore definizione delle responsabilità tra il team DB e il team di sviluppo a scapito di parte della flessibilità che avresti se la query fosse mantenuta nel codice, tuttavia è meno probabile che i progetti PHP abbiano dimensioni considerevoli abbastanza squadre per beneficiare di questa delineazione.

Concettualmente, dovresti probabilmente avere la versione del tuo database.In pratica, tuttavia, è molto più probabile che tu abbia solo la versione del tuo codice piuttosto che avere la versione del tuo database.È probabile che tu modifichi le tue query quando apporti modifiche al tuo codice, ma se stai modificando le query nelle procedure memorizzate archiviate nel database, probabilmente non le effettuerai quando archivi il codice e perdi molti dei vantaggi del controllo delle versioni per un'area significativa della tua applicazione.

Indipendentemente dal fatto che tu scelga o meno di non utilizzare le procedure memorizzate, dovresti almeno assicurarti che ogni operazione del database sia memorizzata in una funzione indipendente piuttosto che essere incorporata in ciascuno degli script della tua pagina - essenzialmente un livello API per il tuo DB che viene mantenuto e aggiornato con il tuo codice.Se stai utilizzando procedure memorizzate, ciò significherà effettivamente che hai due livelli API per il tuo DB, uno con il codice e uno con il DB, il che potresti ritenere complichi inutilmente le cose se il tuo progetto non ha team separati.Certamente lo faccio.

Se il problema riguarda la pulizia del codice, ci sono modi per rendere più presentabile il codice con SQL inceppato e la classe UserManager mostrata di seguito è un buon modo per iniziare: la classe contiene solo query relative alla tabella "utente", ogni query ha il proprio metodo nella classe e le query vengono rientrate nelle istruzioni di preparazione e formattate come le formatteresti in una procedura memorizzata.

// UserManager.php:

class UserManager
{
    function getUsers()
    {
        $pdo = new PDO(...);
        $stmt = $pdo->prepare('
            SELECT       u.userId as id,
                         u.userName,
                         g.groupId,
                         g.groupName
            FROM         user u
            INNER JOIN   group g
            ON           u.groupId = g.groupId
            ORDER BY     u.userName, g.groupName
        ');
        // iterate over result and prepare return value
    }

    function getUser($id) {
        // db code here
    }
}

// index.php:
require_once("UserManager.php");
$um = new UserManager;
$users = $um->getUsers();
foreach ($users as $user) echo $user['name'];

Tuttavia, se le tue query sono abbastanza simili ma hai un numero enorme di permutazioni nelle condizioni della query come pagine complicate, ordinamento, filtraggio, ecc., uno strumento di mappatura oggetti/relazionale è probabilmente la strada da percorrere, sebbene il processo di revisione del codice esistente utilizzare lo strumento potrebbe essere piuttosto complicato.

Se decidi di indagare sugli strumenti ORM, dovresti dare un'occhiata Spingere, il componente ActiveRecord di , o il re-papà PHP ORM, Dottrina.Ognuno di questi ti dà la possibilità di creare query a livello di codice sul tuo database con ogni sorta di logica complicata.Doctrine è quello più completo e ti consente di creare modelli del tuo database con cose come Modello di albero impostato nidificato fuori dalla scatola.

In termini di prestazioni, le procedure memorizzate sono le più veloci, ma generalmente non di molto rispetto a SQL grezzo.Gli strumenti ORM possono avere un impatto significativo sulle prestazioni in diversi modi: query inefficienti o ridondanti, enormi I/O di file durante il caricamento delle librerie ORM su ogni richiesta, generazione SQL dinamica su ogni query...tutte queste cose possono avere un impatto, ma l'uso di uno strumento ORM può aumentare drasticamente la potenza a tua disposizione con una quantità di codice molto inferiore rispetto alla creazione del tuo livello DB con query manuali.

Gary Richardson è assolutamente giusto, tuttavia, se intendi continuare a utilizzare SQL nel tuo codice, dovresti sempre utilizzare le istruzioni preparate di PDO per gestire i parametri indipendentemente dal fatto che tu stia utilizzando una query o una procedura memorizzata.La sanificazione degli ingressi viene eseguita per voi da PDO.

// optional
$attrs = array(PDO::ATTR_PERSISTENT => true);

// create the PDO object
$pdo = new PDO("mysql:host=localhost;dbname=test", "user", "pass", $attrs);

// also optional, but it makes PDO raise exceptions instead of 
// PHP errors which are far more useful for debugging
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$stmt = $pdo->prepare('INSERT INTO venue(venueName, regionId) VALUES(:venueName, :regionId)');
$stmt->bindValue(":venueName", "test");
$stmt->bindValue(":regionId", 1);

$stmt->execute();

$lastInsertId = $pdo->lastInsertId();
var_dump($lastInsertId);

Avvertimento:presupponendo che l'ID sia 1, verrà restituito lo script precedente string(1) "1". PDO->lastInsertId() restituisce l'ID come stringa indipendentemente dal fatto che la colonna effettiva sia un numero intero o meno.Questo probabilmente non sarà mai un problema per te poiché PHP esegue automaticamente la conversione delle stringhe in numeri interi.

Verrà prodotto quanto segue bool(true):

// regular equality test
var_dump($lastInsertId == 1); 

ma se hai un codice che prevede che il valore sia un numero intero, ad esempio è_int o PHP "è davvero, veramente, uguale al 100% a" operatore:

var_dump(is_int($lastInsertId));
var_dump($lastInsertId === 1);

potresti incorrere in alcuni problemi.

Modificare: Qualche buona discussione sulle procedure memorizzate Qui

Altri suggerimenti

Innanzitutto, dovresti utilizzare i segnaposto nella tua query invece di interpolare direttamente le variabili.PDO/MySQLi ti consentono di scrivere le tue query come:

SELECT * FROM sometable WHERE iUser = ?

L'API sostituirà in modo sicuro i valori nella query.

Inoltre preferisco avere le mie query nel codice anziché nel database.È molto più semplice lavorare con un RCS quando le query riguardano il tuo codice.

Ho una regola pratica quando lavoro con gli ORM:se lavoro con un'entità alla volta, utilizzerò l'interfaccia.Se sto segnalando/lavorando con record in forma aggregata, in genere scrivo query SQL per farlo.Ciò significa che ci sono pochissime query nel mio codice.

Ho dovuto ripulire un progetto che conteneva molte query (duplicate/simili) piene di vulnerabilità di injection.I primi passi che ho compiuto sono stati l'utilizzo dei segnaposto e l'etichettatura di ogni query con l'oggetto/metodo e la riga di origine in cui è stata creata la query.(Inserisci le costanti PHP METODO E LINEA in una riga di commento SQL)

Sembrava qualcosa del genere:

-- @Line:151 UserClass::getuser():

SELECT * FROM USERS;

La registrazione di tutte le query per un breve periodo mi ha fornito alcuni punti di partenza su quali query unire.(E dove!)

Sposterei tutto l'SQL in un modulo Perl separato (.pm) Molte query potrebbero riutilizzare le stesse funzioni, con parametri leggermente diversi.

Un errore comune per gli sviluppatori è immergersi nelle librerie ORM, nelle query parametrizzate e nelle procedure memorizzate.Quindi lavoriamo per mesi di seguito per rendere il codice "migliore", ma è "migliore" solo in termini di sviluppo.Non stai creando nuove funzionalità!

Utilizza la complessità nel tuo codice solo per soddisfare le esigenze dei clienti.

Usa un pacchetto ORM, qualsiasi pacchetto semidecente te lo consentirà

  1. Ottieni set di risultati semplici
  2. Mantieni il tuo SQL complesso vicino al modello dati

Se hai un SQL molto complesso, anche le visualizzazioni sono utili per renderlo più presentabile a diversi livelli della tua applicazione.

Un tempo ci trovavamo in una situazione simile.Abbiamo interrogato una tabella specifica in vari modi, oltre 50+.

Alla fine abbiamo creato una singola procedura memorizzata Fetch che include un valore di parametro per WhereClause.La WhereClause è stata costruita in un oggetto Provider, abbiamo utilizzato il modello di progettazione Facade, dove potevamo macchia per eventuali attacchi SQL injection.

Quindi, per quanto riguarda la manutenzione, è facile da modificare.Anche SQL Server è piuttosto valido amico e memorizza nella cache i piani di esecuzione delle query dinamiche, quindi le prestazioni complessive sono piuttosto buone.

Dovrai determinare il prestazione inconvenienti basati sul proprio sistema e sulle proprie esigenze, ma tutto sommato, funziona ottimo per noi.

Esistono alcune librerie, come MDB2 in PEAR, che rendono le query un po' più semplici e sicure.

Sfortunatamente, possono essere un po’ prolissi da configurare e a volte è necessario trasmettere loro le stesse informazioni due volte.Ho usato MDB2 in un paio di progetti e tendevo a scriverci sopra una patina sottile, soprattutto per specificare i tipi di campi.In genere creo un oggetto che conosce una particolare tabella e le sue colonne, quindi una funzione di supporto che riempie i tipi di campo per me quando chiamo una funzione di query MDB2.

Ad esempio:

function MakeTableTypes($TableName, $FieldNames)
{
    $Types = array();

    foreach ($FieldNames as $FieldName => $FieldValue)
    {
        $Types[] = $this->Tables[$TableName]['schema'][$FieldName]['type'];
    }

    return $Types;
}

Ovviamente questo oggetto ha una mappa di nomi di tabelle -> schemi di cui è a conoscenza, estrae semplicemente i tipi dei campi specificati e restituisce un array di tipi corrispondenti adatto all'uso con una query MDB2.

MDB2 (e librerie simili) gestiscono quindi la sostituzione dei parametri per te, quindi per le query di aggiornamento/inserimento, devi semplicemente creare un hash/mappa dal nome della colonna al valore e utilizzare le funzioni 'autoExecute' per creare ed eseguire la query pertinente.

Per esempio:

function UpdateArticle($Article)
{
    $Types = $this->MakeTableTypes($table_name, $Article);

    $res = $this->MDB2->extended->autoExecute($table_name,
        $Article,
        MDB2_AUTOQUERY_UPDATE,
        'id = '.$this->MDB2->quote($Article['id'], 'integer'),
        $Types);
}

e MDB2 creerà la query, eseguendo l'escape di tutto correttamente, ecc.

Consiglierei però di misurare le prestazioni con MDB2, poiché inserisce un bel po' di codice che potrebbe causare problemi se non stai utilizzando un acceleratore PHP.

Come ho detto, il sovraccarico di installazione sembra inizialmente scoraggiante, ma una volta terminato, le query possono essere più semplici/più simboliche da scrivere e (soprattutto) modificare.Penso che MDB2 dovrebbe sapere qualcosa in più sul tuo schema, il che semplificherebbe alcune delle chiamate API comunemente utilizzate, ma puoi ridurre il fastidio di ciò incapsulando tu stesso lo schema, come ho menzionato sopra, e fornendo semplici funzioni di accesso che generano il gli array MDB2 devono eseguire queste query.

Ovviamente puoi semplicemente eseguire query SQL semplici come una stringa utilizzando la funzione query() se lo desideri, quindi non sei costretto a passare al "metodo MDB2" completo: puoi provarlo pezzo per pezzo e vedere se riesci odiarlo o no.

Quest'altra domanda contiene anche alcuni link utili...

Utilizza un framework ORM come QCodo: puoi facilmente mappare il tuo database esistente

Cerco di utilizzare funzioni abbastanza generiche e di passare semplicemente le differenze in esse.In questo modo hai solo una funzione per gestire la maggior parte delle SELECT del tuo database.Ovviamente puoi creare un'altra funzione per gestire tutti i tuoi INSERTI.

per esempio.

function getFromDB($table, $wherefield=null, $whereval=null, $orderby=null) {
    if($wherefield != null) { 
        $q = "SELECT * FROM $table WHERE $wherefield = '$whereval'"; 
    } else { 
        $q = "SELECT * FROM $table";
    }
    if($orderby != null) { 
        $q .= " ORDER BY ".$orderby; 
    }

    $result = mysql_query($q)) or die("ERROR: ".mysql_error());
    while($row = mysql_fetch_assoc($result)) {
        $records[] = $row;
    }
    return $records;
}

Questo è appena fuori dalla mia testa, ma hai capito.Per utilizzarlo basta passare alla funzione i parametri necessari:

per esempio.

$blogposts = getFromDB('myblog', 'author', 'Lewis', 'date DESC');

In questo caso $post del blog sarà un array di array che rappresentano ogni riga della tabella.Quindi puoi semplicemente utilizzare foreach o fare riferimento direttamente all'array:

echo $blogposts[0]['title'];
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top