0 follower

Data Access Objects (DAO)

DAO (Obiecte accesare date) pune la dispozitie un API generic pentru accesul datelor stocate in diverse DBMS (Sisteme de Management Baze de Date). Ca urmare, DBMS-ul poate fi schimbat oricand cu altul, fara sa fie nevoie sa schimbam codul nostru in care folosim DAO sa accesam datele.

Yii DAO este construit pe baza PDO (obiecte date PHP) care este o extensie PHP ce pune la dispozitie accesul unificat la date stocate in diverse DBMS cunoscute, precum MySQL si PostgreSQL. De aceea, pentru a folosi Yii DAO, trebuie instalate extensia PDO si driverul PDO de baze de date specific (ex. PDO_MYSQL).

Yii DAO este format in principal din urmatoarele patru clase:

In cele ce urmeaza, explicam folosirea Yii DAO in diverse scenarii.

1. Stabilirea conexiunii la baza de date

Pentru a stabili o conexiune, cream o instanta CDbConnection si o activam. Avem nevoie de un DSN (nume pentru sursa de date) pentru a specifica informatiile necesare pentru conectarea la baza de date. Pot fi necesare si un nume si o parola pentru stabilirea conexiunii. Va fi generata o exceptie in cazul in care apare o eroare la stabilirea conexiunii (ex. DSN gresit sau nume/parola gresite).

$connection=new CDbConnection($dsn,$username,$password);
// stabilirea conexiunii. Putem incerca try-catch pentru a identifica exceptii posibile
$connection->active=true;
......
$connection->active=false;  // inchidere conexiune

Formatul DSN-ului depinde de driverul PDO folosit. In general, DSN este format din numele driver-ului PDO, urmat de semnul :, urmat de sintaxa conexiunii specifice driver-ului. Pentru informatii complete, trebuie vazuta documentatia PDO. Mai jos este o lista de format-uri obisnuite pentru DSN:

  • SQLite: sqlite:/path/to/dbfile
  • MySQL: mysql:host=localhost;dbname=testdb
  • PostgreSQL: pgsql:host=localhost;port=5432;dbname=testdb

Pentru ca CDbConnection este derivata din clasa CApplicationComponent, o putem folosi de asemenea pe postul de componenta aplicatie. Pentru a face acest lucru, configuram componenta aplicatie db (sau alta componenta aplicatie, daca se doreste) din configurarea aplicatiei dupa cum urmeaza:

array(
    ......
    'components'=>array(
        ......
        'db'=>array(
            'class'=>'CDbConnection',
            'connectionString'=>'mysql:host=localhost;dbname=testdb',
            'username'=>'root',
            'password'=>'password',
        ),
    ),
)

Putem dupa aceea sa accesam conexiunea DB prin Yii::app()->db, care este activata automat (putem interzice acest comportament prin setarea cu false a proprietatii CDbConnection::autoConnect. Folosind aceasta metoda conexiunea DB poate fi utilizata oriunde in cod.

2. Executarea instructiunilor SQL

O data ce este stabilita o conexiune DB, instructiunile SQL pot fi executate folosind CDbCommand. Cream o instanta CDbCommand prin apelarea CDbConnection::createCommand() cu instructiunea SQL specificata:

$command=$connection->createCommand($sql);
// daca este necesar, instructiunea SQL poate fi actualizata asa:
// $command->text=$newSQL;

O instructiune SQL este executata prin CDbCommand in unul din urmatoarele doua moduri:

  • execute(): executa o instructiune SQL, precum INSERT, UPDATE si DELETE. Daca are succes, returneaza numarul de randuri afectate.

  • query(): executa o instructiune SQL care returneaza randuri de date, precum SELECT. Daca are succes, returneaza o instanta CDbDataReader pe care putem sa o parcurgem pentru a folosi randurile de date rezultate. Pentru usurinta, este implementat de asemenea un set de metode queryXXX() care returneaza direct rezultatele cererii.

Va fi generata o exceptie daca apare vreo eroare in timpul executiei instructiunilor SQL.

$rowCount=$command->execute();   // executa SQL
$dataReader=$command->query();   // executa o cerere SQL
$rows=$command->queryAll();      // o cerere care returneaza toate randurile rezultate
$row=$command->queryRow();       // o cerere care returneaza primul rand dintre rezultate
$column=$command->queryColumn(); // o cerere care returneaza prima coloana din rezultate
$value=$command->queryScalar();  // o cerere care returneaza primul camp din primul rand

3. Extragerea rezultatelor cererii

Dupa ce CDbCommand::query() genereaza instanta CDbDataReader, putem extrage randurile cu datele rezultate prin apelarea repetata a CDbDataReader::read(). Putem de asemenea folosi CDbDataReader intr-un foreach pentru a extrage fiecare rand in parte.

$dataReader=$command->query();
// apelam repetat read() pana cand returneaza false
while(($row=$dataReader->read())!==false) { ... }
// folosim foreach pentru a trece prin fiecare rand de date
foreach($dataReader as $row) { ... }
// extragem toate randurile o data intr-un singur array
$rows=$dataReader->readAll();

Nota: Spre deosebire de query(), toate metodele queryXXX() returneaza date direct. De exemplu, queryRow() returneaza un array care reprezinta primul rand din rezultatele cererii.

4. Folosirea tranzactiilor

Cand o aplicatie executa cateva cereri, operatii de citire sau/si scriere in baza de date, este important sa ne asiguram ca baza de date sa contina toate schimbarile facute de aceste operatii. In aceste cazuri, poate fi initiata o tranzactie, reprezentata prin instanta CDbTransaction din Yii:

  • Incepem tranzactia.
  • Executam cererile una cate una. Orice actualizare nu este vizibila in exteriorul bazei de date.
  • Executam tranzactia. Actualizarile devin vizibile daca tranzactia a avut succes.
  • Daca o cerere esueaza, intreaga tranzactie este derulata inapoi.

Fluxul de lucru de mai sus poate fi implementat folosind urmatorul cod:

$transaction=$connection->beginTransaction();
try
{
    $connection->createCommand($sql1)->execute();
    $connection->createCommand($sql2)->execute();
    //.... alte executii SQL
    $transaction->commit();
}
catch(Exception $e) // daca o cerere esueaza, este generata o exceptie
{
    $transaction->rollBack();
}

5. Conectarea de parametri (Binding)

Pentru a evita [atacurile SQL injection] (http://en.wikipedia.org/wiki/SQL_injection) si pentru a imbunatati performanta executiilor cererilor SQL folosite des, putem "prepara" o instructiune SQL cu placeholder-e de parametri optionali care vor fi inlocuiti cu parametrii reali in timpul procesului de conectare de parametri.

Placeholder-ele de parametri pot avea nume (token-uri unice) sau pot fi anonime (prin semne de intrebare). Apelam CDbCommand::bindParam() sau CDbCommand::bindValue() pentru a inlocui aceste placeholder-e cu parametrii reali. Conectarea parametrilor trebuie facuta inainte ca instructiunea SQL sa fie executata.

// o cerere SQL cu 2 placeholdere ":username" si ":email"
$sql="INSERT INTO users(username, email) VALUES(:username,:email)";
$command=$connection->createCommand($sql);
 
// inlocuim placeholder-ul ":username" cu valoarea reala username
$command->bindParam(":username",$username,PDO::PARAM_STR);
 
// inlocuim placeholder-ul ":email" cu valoarea reala email
$command->bindParam(":email",$email,PDO::PARAM_STR);
$command->execute();
 
// inseram un alt rand cu un nou set de parametri
$command->bindParam(":username",$username2,PDO::PARAM_STR);
$command->bindParam(":email",$email2,PDO::PARAM_STR);
$command->execute();

Metodele bindParam() si bindValue() sunt foarte asemanatoare. Singura diferenta este ca bindParam() conecteaza un parametru cu o referinta a unei variabile PHP, in timp ce bindValue() conecteaza un parametru cu o valoare. Pentru parametri care reprezinta blocuri mai de date memorate, este de preferat sa folosim bindParam() din considerente de perfomanta.

Pentru mai multe detalii despre conectarea de parametrii, trebuie vazuta sectiunea din documentatia PHP.

6. Conectarea coloanelor (Binding)

Cand extragem rezultatele cererii, putem sa conectam si coloane la variabile PHP pentru a fi populate automat cu ultimele date, de fiecare data cand se extrage un rand nou.

$sql="SELECT username, email FROM users";
$dataReader=$connection->createCommand($sql)->query();
// conectam prima coloana (username) cu variabila $username
$dataReader->bindColumn(1,$username);
// conectam a doua coloana (email) cu variabila $email
$dataReader->bindColumn(2,$email);
while($dataReader->read()!==false)
{
    // $username si $email contin username si email din randul curent
}