Data Access Objects (DAO) ========================= Data Access Objects (DAO, Objetos de Acesso a Dados), fornecem uma API genérica para acessar dados em diferentes tipos de bancos de dados. Como resultado, pode-se alterar o sistema de banco de dados sem haver a necessidade de alterar o código que utiliza DAO para fazer o acesso. O DAO do Yii é feito utilizando a extensão [PHP Data Objects (PDO)](http://php.net/manual/en/book.pdo.php), que fornece um acesso de dados unificado para muitos SGBD populares, tais como MySQL e PostgreSQL. Por esse motivo, para utilizar o DAO no Yii, a extensão PDO deve estar instalada, bem como o driver PDO específico para o banco de dados utilizado (por exemplo, `PDO_MYSQL`). O DAO no Yii, consiste, basicamente, das quatro classes seguinte: - [CDbConnection]: representa uma conexão com o banco de dados. - [CDbCommand]: representa uma instrução SQL a ser executada no banco de dados. - [CDbDataReader]: representa um conjunto de registros retornados, navegável apenas para frente. - [CDbTransaction]: representa uma transação (transaction) no banco de dados. A seguir, apresentaremos a utilização do DAO no Yii em diferente cenários. Estabelecendo uma Conexão com o Banco de Dados ---------------------------------------------- Para estabelecer uma conexão com o banco de dados, criamos uma instância de [CDbConnection] e a ativamos. É necessário o nome da fonte de dados (data source name, DSN) para conectar-se ao banco. Também podem ser necessários o nome de usuário e senha para o acesso. Uma exceção será disparada caso ocorra um erro ao estabelecer a conexão (por exemplo, um DSN incorreto, ou usuário/senha inválidos). ~~~ [php] $connection=new CDbConnection($dsn,$username,$password); // estabelece a conexão. Você pode utilizar um try... catch tratar exceções $connection->active=true; ...... $connection->active=false; // fecha a conexão ~~~ O formato do DSN depende do driver PDO em uso. Geralmente, o DSN é formado pelo nome do driver PDO, seguido por ":", seguido pela sintaxe de conexão específica do driver. Veja [PDO documentation](http://www.php.net/manual/en/pdo.construct.php) para mais informações. Abaixo temos uma lista dos formatos de DSN mais utilizados: - SQLite: `sqlite:/path/to/dbfile` - MySQL: `mysql:host=localhost;dbname=testdb` - PostgreSQL: `pgsql:host=localhost;port=5432;dbname=testdb` - SQL Server: `mssql:host=localhost;dbname=testdb` - Oracle: `oci:dbname=//localhost:1521/testdb` Como a classe [CDbConnection] estende a classe [CApplicationComponent], podemos utiliza-la como um [componente da aplicação](/doc/guide/basics.application#application-component). Para isso, configure um componente chamado `db` (ou qualquer outro nome) na [configuração da aplicação](/doc/guide/basics.application#application-configuration), como no código a seguir: ~~~ [php] array( ...... 'components'=>array( ...... 'db'=>array( 'class'=>'CDbConnection', 'connectionString'=>'mysql:host=localhost;dbname=testdb', 'username'=>'root', 'password'=>'password', 'emulatePrepare'=>true, // necessário em algumas instalações do MySQL ), ), ) ~~~ Podemos então acessar a conexão com o banco via `Yii::app()->db`, que é ativada automaticamente, a não ser que se configure a propriedade [CDbConnection::autoConnect] para false. Dessa maneira, uma única conexão com o banco de dados pode ser compartilhada entre diversas partes de seu código. Executando Instruções SQL ------------------------- Uma vez que a conexão com o banco de dados tenha sido estabelecida, comandos SQL podem ser executados utilizando-se a classe [CDbCommand]. Você pode criar uma instância de [CDbCommand] utilizando o método [CDbConnection::createCommand()], com a instrução SQL desejada: ~~~ [php] $connection=Yii::app()->db; // Aqui, estamos assumind que você configurou um conexão com o banco de dados // Caso contrario, você deverá cria-la explicitamente: // $connection=new CDbConnection($dsn,$username,$password); $command=$connection->createCommand($sql); // se necessário, a instrução SQL pode ser alterada da seguinte maneira: // $command->text=$newSQL; ~~~ Uma instrução SQL pode ser executada via [CDbCommand] de duas maneiras: - [execute()|CDbCommand::execute]: executa uma instrução SQL que não retorna resultados, tal como `INSERT`, `UPDATE` e `DELETE`. Em caso de sucesso, retorna a quantidade de registros afetados pela consulta. - [query()|CDbCommand::query]: executa uma instrução que retorna registros, tal como `SELECT`. Em caso de sucesso, retorna uma instância de [CDbDataReader], que pode ser utilizada para acessar os registros encontrados. Por conveniência, um conjunto de métodos `queryXXX()` também está implementado, e retornam diretamente o resultado da consulta. Uma exceção será disparada caso ocorram erros durante a execução de instruções SQL. ~~~ [php] $rowCount=$command->execute(); // executa uma consulta que não retorna resultados $dataReader=$command->query(); // executa uma consulta SQL $rows=$command->queryAll(); // consulta e retorna todos os resultados encontrados $row=$command->queryRow(); // consulta e retorna apenas o primeiro registro do resultado $column=$command->queryColumn(); // consulta e retorna a primeira coluna do resultado $value=$command->queryScalar(); // consulta e retorna o primeiro campo do primeiro registro ~~~ Obtendo Resultados de Consultas ------------------------------- Depois que o método [CDbCommand::query()] gerar uma instância de [CDbDataReader], você pode recuperar os registros do resultado através do método [CDbDataReader::read()], repetidamente. Você também pode utilizar um [CDbDataReader] dentro de um loop `foreach`, para recuperar os registros, um a um: ~~~ [php] $dataReader=$command->query(); // executando read() repetidamente, até que ele retorne false while(($row=$dataReader->read())!==false) { ... } // utilizando foreach para "navegar" por cada registro encontrado foreach($dataReader as $row) { ... } // recuperando todos os registros de uma vez, em um vetor $rows=$dataReader->readAll(); ~~~ > Note|Nota: Diferente do método [query()|CDbCommand::query], todos os métodos `queryXXX()`, retornam os dados diretamente. Por exemplo, o método [queryRow()|CDbCommand::queryRow] retorna um vetor representando o primeiro registro do resultado da consulta. Utilizando Transações (Transactions) ------------------------------------ Quando uma aplicação executa algumas consultas, seja lendo ou gravando informações no banco de dados, é importante garantir que todas as consultas tenham sido executadas. Nesse caso, uma transação, representada por uma instância de [CDbTransaction], pode ser iniciada: - Inicie a transação. - Execute as consultas, uma a uma. Todas as atualizações no banco de dados não são visíveis aos outros. - Encerre a transação. Nesse momento, as atualizações tornam-se visíveis, caso a transação tenha encerrado com sucesso. - Se uma das consultas falhar, toda a transação é desfeita. O fluxo acima pode ser implementado como no código a seguir: ~~~ [php] $transaction=$connection->beginTransaction(); try { $connection->createCommand($sql1)->execute(); $connection->createCommand($sql2)->execute(); //.... outras execuções de comandos SQL $transaction->commit(); } catch(Exception $e) // uma exceção é disparada caso uma das consultas falhe { $transaction->rollBack(); } ~~~ Vinculando (Binding) Parâmetros ------------------------------- Para evitar ataques de [SQL injection](http://en.wikipedia.org/wiki/SQL_injection) e aumentar a performance ao executar instruções SQL repetidamente, você pode "preparar" um comando SQL com marcadores opcionais que serão substituídos pelos valores reais, durante o processo de vinculação de parâmetros. Os marcadores de parâmetros podem ser nomeados (representados por tokens únicos) ou anônimos (representados por interrogações). Execute o método [CDbCommand::bindParam()] ou [CDbCommand::bindValue()] para substituir esses marcadores pelos parâmetros reais. Eles não precisam estar entre aspas: o próprio driver do banco de dados faz isso para você. A vinculação de parâmetros deve ser realizada antes da instrução SQL ser executada. ~~~ [php] // uma consulta com dois marcadores ":username" e ":email" $sql="INSERT INTO tbl_users(username, email) VALUES(:username,:email)"; $command=$connection->createCommand($sql); // substitui o marcador ":username" com o valor atual de $username $command->bindParam(":username",$username,PDO::PARAM_STR); // substitui o marcador ":email" com o valor atual de $email $command->bindParam(":email",$email,PDO::PARAM_STR); $command->execute(); // insere um novo registro com um novo conjunto de parâmetros $command->bindParam(":username",$username2,PDO::PARAM_STR); $command->bindParam(":email",$email2,PDO::PARAM_STR); $command->execute(); ~~~ Os métodos [bindParam()|CDbCommand::bindParam] e [bindValue()|CDbCommand::bindValue] são similares. A única diferença entre eles é que o primeiro vincula um parâmetro utilizando uma referência para a variável enquanto o outro utilizar um valor. Para parâmetros que representem uma grande quantidade de dados em memória, o primeiro é mais recomendado, devido a uma melhor performance. Para mais detalhes sobre a vinculação de parâmetros, veja a [documentação do PHP](http://www.php.net/manual/en/pdostatement.bindparam.php). Vinculando Colunas ------------------ Ao recuperar os resultados de uma consulta, você também pode vincular colunas à variáveis, de forma que elas sejam automaticamente preenchidas cada vez que um novo registro é recuperado: ~~~ [php] $sql="SELECT username, email FROM tbl_users"; $dataReader=$connection->createCommand($sql)->query(); // vincula a 1ª coluna (username) à variável $username $dataReader->bindColumn(1,$username); // vincula a 2ª coluna (email) à variável $email $dataReader->bindColumn(2,$email); while($dataReader->read()!==false) { // $username e $email contém o nome de usuário e a senha do registro atual } ~~~ Utilizando Prefixos de Tabelas ------------------------------ A partir da versão 1.1.0, o Yii framework possui suporte integrado para a utilização de prefixos em nomes de tabela. Um prefixo é uma string que será anexada ao início dos nomes das tabelas do banco de dados conectado. Normalmente, eles são utilizados em ambientes de hospedagem compartilhada, onde múltiplas aplicações utilizam um único banco de dados e é necessário diferenciar as tabelas de cada aplicação. Por exemplo, uma aplicação pode utilizar o prefixo `tbl_`, enquanto outra utiliza `yii_`. Para utiliza-los, você deverá configurar a propriedade [CDbConnection::tablePrefix] com o prefixo desejado. Feito isso, em suas consultas SQL você deverá utilizar `{{NomeDaTabela}}`, onde `NomeDaTabela` é o nome da tabela sem o prefixo. Por exemplo, se um banco de dados contém uma tabela chamada `tbl_user` e `tbl_` é o prefixo configurado, então você pode utilizar o seguinte código para realizar consultas nessa tabela: ~~~ [php] $sql='SELECT * FROM {{user}}'; $users=$connection->createCommand($sql)->queryAll(); ~~~