Como ler aquivos do excel com php

Ler arquivos do Excel com PHP pode ser complicado, mas felizmente há uma grande biblioteca que torna essa tarefa muito mais fácil: PHPExcel. Nesse post vamos ver como utilizar o PHPExcel para converter as planilhas do excel em arrays e usar os dados em PHP.

Instalando o PHPExcel

O primeiro passo para trabalhar com documentos do Excel no PHP será instalar a biblioteca PHPExcel. Eu vou fazer isso utilizando o composer. Se você ainda não está familiarizado com composer, você definitivamente deve ter um olhar para ele e usá-lo para gerenciar as dependências do seu projeto. O arquivo composer.json é extremamente simples:

{
 "require":
   {
      "phpoffice/phpexcel": "dev-develop"
   }
}

Então você precisa para instalar as dependências com o composer, o que pode demorar alguns segundos para completar:

composer install  

Você vai notar que a biblioteca foi baixada e instalada no diretório project_root/vendor/phpoffice/phpexcel juntamente com o diretório project_root/vendor/composer e um arquivo project_root/vendor/autoload.php que foi gerado automaticamente para gerenciar o autoloading das classes para a biblioteca.

Carregando o arquivo no PHPExcel

Há duas maneiras básicas para carregar o arquivo no PHPExcel. Você pode especificar um dos tipos de arquivos suportados manualmente ou você pode deixar a biblioteca determinar o tipo de arquivo automaticamente com base no arquivo que você fornece. Aqui está o código necessário para ambos e uma lista dos tipos de arquivos suportados que você pode escolher se você decidir definir explicitamente o tipo de arquivo:

<?php  
require_once "vendor/autoload.php";

$fileName = "exemplo.xlsx";

/** detecta automaticamente o tipo de arruivo que será carregado */
$excelReader = PHPExcel_IOFactory::createReaderForFile($fileName);

/** Definindo manualmente.
// $inputFileType = 'Excel5';
// $inputFileType = 'Excel2007';
// $inputFileType = 'Excel2003XML';
// $inputFileType = 'OOCalc';
// $inputFileType = 'SYLK';
// $inputFileType = 'Gnumeric';
// $inputFileType = 'CSV';
$excelReader = PHPExcel_IOFactory::createReader($inputFileType);
*/

Definir as opções de leitura

Não vou entrar em muitos detalhes sobre as diferentes opções que você pode usar, porque eles são muito poucos, mas é importante exemplificar as mais comuns e úteis.

//Se não precisarmos de formatação
$excelReader->setReadDataOnly();

//carregar apenas algumas abas
$loadSheets = array('aba1', 'aba2');
$excelReader->setLoadSheetsOnly($loadSheets);

//o comportamente padrão é carregar todas as abas
$excelReader->setLoadAllSheets();

Estes são bastante simples. Certifique-se que você só carregar as abas de que você precisa para usar uma vez que a biblioteca pode usar uma grande quantidade de memória especialmente para arquivos grandes. Além disso, o método setReadDataOnly ajuda a acelerar as coisas um pouco por carregar apenas os dados das células, sem qualquer formatação especial do excel. Você tem que estar ciente de que embora qualquer formatação de data será perdida se você usar essa opção e as datas serão carregados como números, sem formatação.

Carregar e exibir os dados

O passo final é para carregar os dados do Excel para o PHP.

$excelObj = $excelReader->load($fileName);

Isto produzirá um objeto PHPExcel, mas, a fim de modificar e transformar os dados para atender às nossas necessidades o seu melhor para convertê-lo em um array facilmente.

$excelObj->getActiveSheet()->toArray(null, true,true,true);

Tenha em mente que PHPExcel só vai exibir as informações sobre a folha ativa no momento, que é o último a ser carregado. No entanto, você pode alternar manualmente entre as folhas e obter o seu conteúdo ou você pode automatizar o processo e obter todas as folhas como um array:

//Pega os nomes das abas
$worksheetNames = $excelObj->getSheetNames($fileName);
$return = array();
foreach($worksheetNames as $key => $sheetName){  
//define a aba ativa
$excelObj->setActiveSheetIndexByName($sheetName);
//cria um array com o nome da aba como índice
$return[$sheetName] = $excelObj->getActiveSheet()->toArray(null, true,true,true);
}
//exibe o array
var_dump($return);  

Como verificar se os links de um rss estão ativos

Primeiro criamos a classe que vai conter os posts e todos os atributos, em seguida criaremos a classe que manipulará o feed de notícias, para tal utilizaremos o simplexml que interpreta um arquivo XML e o transforma em um objeto.

Esse post foi uma sugestão do Gerson, e, nós vamos utilizar a função daquele mesmo post nesse aqui.

A Classe BlogPost

Nessa classe vamos colocar os atributos de um post no feed rss, que são data, link, título, texto e um atributo disponibilidade para exibir se o post está disponível ou não.

<?php  
class BlogPost  
{
    var $data;
    var $ts;
    var $link;
    var $titulo;
    var $texto;
    var $disponibilidade;
}

A Classe BlogFeed

Agora criaremos uma classe para preencher os objetos da classe BlogPost, que terá um construtor que recebe por parâmetro a url do feed de notícias. Para isso vamos utilizar o simplexmlloadfile.

class BlogFeed  
{
    var $posts = array();

    function __construct($file_or_url)
    {
        $file_or_url = $this->resolverArquivo($file_or_url);
        if (!($x = simplexml_load_file($file_or_url)))
            return;

        foreach ($x->channel->item as $item)
        {
            $post = new BlogPost();
            $post->data             = (string) $item->postsubDate;
            $post->ts               = strtotime($item->pubDate);
            $post->link             = (string) $item->link;
            $post->titulo           = (string) $item->title;
            $post->texto            = (string) $item->description;
            $post->disponibilidade = $this->verificarDisponibilidade($item->link);

            // Create summary as a shortened body and remove images,
            // extraneous line breaks, etc.
            $post->summary = $this->resumirTexto($post->texto);

            $this->posts[] = $post;
        }
    }

    private function resolverArquivo($file_or_url) {
        if (!preg_match('|^https?:|', $file_or_url))
            $feed_uri = $_SERVER['DOCUMENT_ROOT'] .'/shared/xml/'. $file_or_url;
        else
            $feed_uri = $file_or_url;

        return $feed_uri;
    }

    private function resumirTexto($summary) {
        $summary = strip_tags($summary);

        $max_len = 100;
        if (strlen($summary) > $max_len)
            $summary = substr($summary, 0, $max_len) . '...';

        return $summary;
    }

    private function verificarDisponibilidade($url) {
        $ch = @curl_init($url);
        @curl_setopt($ch, CURLOPT_HEADER, TRUE);
        @curl_setopt($ch, CURLOPT_NOBODY, TRUE);
        @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
        @curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        $status = array();
        preg_match('/HTTP\/.* ([0-9]+) .*/', @curl_exec($ch) , $status);
        return ($status[1] == 200) ? "YES" : "NO" ;
    }
}

Como utilizar

Como vocês já devem ter percebido esse post é pra ser uma dica rápida, então para utilizar é simples.

$feed = new BlogFeed("http://blog.adlerdias.eti.br/rss");

foreach ($feed as $posts) {  
    foreach ($posts as $post) {
        echo $post->avaliable . " - " . $post->link . "\n";
    }
}

Juntando Tudo

<?php  
class BlogPost  
{
    var $data;
    var $ts;
    var $link;
    var $titulo;
    var $texto;
    var $disponibilidade;
}

class BlogFeed  
{
    var $posts = array();

    function __construct($file_or_url)
    {
        $file_or_url = $this->resolverArquivo($file_or_url);
        if (!($x = simplexml_load_file($file_or_url)))
            return;

        foreach ($x->channel->item as $item)
        {
            $post = new BlogPost();
            $post->data             = (string) $item->postsubDate;
            $post->ts               = strtotime($item->pubDate);
            $post->link             = (string) $item->link;
            $post->titulo           = (string) $item->title;
            $post->texto            = (string) $item->description;
            $post->disponibilidade = $this->verificarDisponibilidade($item->link);

            // Create summary as a shortened body and remove images,
            // extraneous line breaks, etc.
            $post->summary = $this->resumirTexto($post->texto);

            $this->posts[] = $post;
        }
    }

    private function resolverArquivo($file_or_url) {
        if (!preg_match('|^https?:|', $file_or_url))
            $feed_uri = $_SERVER['DOCUMENT_ROOT'] .'/shared/xml/'. $file_or_url;
        else
            $feed_uri = $file_or_url;

        return $feed_uri;
    }

    private function resumirTexto($summary) {
        $summary = strip_tags($summary);

        $max_len = 100;
        if (strlen($summary) > $max_len)
            $summary = substr($summary, 0, $max_len) . '...';

        return $summary;
    }

    private function verificarDisponibilidade($url) {
        $ch = @curl_init($url);
        @curl_setopt($ch, CURLOPT_HEADER, TRUE);
        @curl_setopt($ch, CURLOPT_NOBODY, TRUE);
        @curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
        @curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        $status = array();
        preg_match('/HTTP\/.* ([0-9]+) .*/', @curl_exec($ch) , $status);
        return ($status[1] == 200) ? "YES" : "NO" ;
    }
}

$feed = new BlogFeed("http://blog.adlerdias.eti.br/rss");

foreach ($feed as $posts) {  
    foreach ($posts as $post) {
        echo $post->disponibilidade . " - " . $post->link . "\n";
    }
}

Caso tenha alguma dúvida é só deixar nos comentários.

File(/usr/bin/php) is not within the allowed path(s)

open_basedir – O que é?

No PHP existe uma diretiva chamada openbasedir, que é uma medida de proteção que previne que usuários abram arquivos ou scripts que estão localizados fora do diretório home com PHP, a não ser que a pasta esteja especificada como excluída. A diretiva openbasedir se ativada, irá garantir que todas as operações em arquivos sejam limitadas à arquivos que estão dentro de uma certa hierarquia de pastas, e assim, evitar que scripts consigam acessar arquivos em uma pasta que não esteja autorizada. Quando um script tenta abrir um arquivo com, por exemplo, fopen() or gzopen(), a localização do arquivo é verificada. Quando o arquivo está fora da árvore de diretórios permitida, o php irá recusar abrir esse arquivo e algum dos erros abaixos poderá acontecer:

[adlersd@insomniac:~/www]
% composer  
PHP Warning:  Phar::mapPhar(): open_basedir restriction in effect. File(/usr/local/bin/composer) is not within the allowed path(s): (/srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/:/home/adlersd/www/) in /usr/local/bin/composer on line 13

Warning: Phar::mapPhar(): open_basedir restriction in effect. File(/usr/local/bin/composer) is not within the allowed path(s): (/srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/:/home/adlersd/www/) in /usr/local/bin/composer on line 13  

ou

[ErrorException]
  is_file(): open_basedir restriction in effect. File(/usr/bin/php) is not within the allowed p  
  ath(s): (/srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/)

ou ainda

[adlersd@insomniac:~/www/sf2]
% composer                                                                255 ↵
PHP Warning:  Phar::mapPhar(): open_basedir restriction in effect. File(/usr/local/bin/composer) is not within the allowed path(s): (/srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/) in /usr/local/bin/composer on line 13

Warning: Phar::mapPhar(): open_basedir restriction in effect. File(/usr/local/bin/composer) is not within the allowed path(s): (/srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/) in /usr/local/bin/composer on line 13  
PHP Warning:  require(phar://composer.phar/bin/composer): failed to open stream: phar error: invalid url or non-existent phar "phar://composer.phar/bin/composer" in /usr/local/bin/composer on line 15

Warning: require(phar://composer.phar/bin/composer): failed to open stream: phar error: invalid url or non-existent phar "phar://composer.phar/bin/composer" in /usr/local/bin/composer on line 15  
PHP Fatal error:  require(): Failed opening required 'phar://composer.phar/bin/composer' (include_path='.:/usr/share/pear') in /usr/local/bin/composer on line 15

Fatal error: require(): Failed opening required 'phar://composer.phar/bin/composer' (include_path='.:/usr/share/pear') in /usr/local/bin/composer on line 15  

Como eu utilizo open_basedir para resolver o meu problema?

Precisamos editar o php.ini, no meu caso, /etc/php/php.ini

[adlersd@insomniac:~/]
% sudo vim /etc/php/php.ini

e modificar a linha abaixo adicionando os paths que estavam nos alertas/erros acima:

open_basedir = /srv/http/:/home/:/tmp/:/usr/share/pear/:/usr/share/webapps/:/usr/bin/:/usr/local/bin/  

Como criar um sistema de autenticação (login) de usuário – parte 1

Nesse post, você vai aprender como criar o seu próprio sistema de autenticação de usuário com o banco de dados, essa é uma dúvida muito comum quando se está começando a lidar com o framework.

Existem uma série de extensões, módulos e sistemas RBAC disponíveis nas extensões Yii e, claro, o sistema RBAC do próprio framework. Porém, muitas vezes eles são destinados a projetos mais complexos, ou ainda, são muito complicados de integrá-los a sua solução quando você ainda está começando a caminhar e entender como o framework funciona.

Esse post é uma sugestão do Luciano.

Há muito tempo atrás eu criei um “projeto” para explicar alguns conceitos no yii. Abaixo seguem os links para você que chegou aqui, agora, não ficar perdido.

  1. Yii – Entendendo os relacionamentos (relations)
  2. Yii – Como criar um dropdown
  3. Yii – Como criar um filtro de um relacionamento
  4. Yii – Como fazer o botão delete do CGridView/TbGridView funcionar usando Ajax

Vamos utilizar essa mesma estrutura, e dar continuidade a esse “projeto”.

O que nós vamos construir

Quando você cria o seu projeto ou aplicação, no yii, automaticamente já está criado um sistema de autenticação de usuário muito simples que pode ser facilmente estendido para fornecer uma autenticação de usuário através do banco de dados. A partir de agora nós vamos construir um sistema de autenticação ou login, com nível de usuário, onde esse mesmo usuário pode ser usuário ou administrador e nós vamos ser capazes de usar esses níveis para limitar o acesso a funções dentro de nossas aplicações. As senhas de usuário serão tratadas de forma diferente, por enquanto basta você saber que não salvaremos as mesmas em texto puro, mas eu falarei sobre ela no seu devido tempo. Os usuários serão obrigados a alterar suas senhas depois de um intervalo de tempo definido.

Entendendo as diferenças entre WebUser e Autenticação

Esta primeira parte será a de entender a diferença entre a autenticação do usuário e do WebUser(identidade do usuário). Informações do usuário são armazenados em uma instância da classe CWebUser e este é criado na inicialização do aplicativo (ou seja: quando o usuário conecta pela primeira vez com o site), independentemente de o usuário estar autenticado (logado) ou não. Por padrão, o usuário é definido como Guest (visitante).
A autenticação é gerenciada por uma classe chamada CUserIdentity e essa classe verifica se o usuário é conhecido e se é um usuário válido. Como esta validação ocorre vai depender de sua aplicação, talvez aconteça no seu banco de dados – que será o nosso caso – ou entrar com facebook/google/github, ou contra um servidor ldap, etc…
O código gerado pelo Gii define a ação de login e modelo LoginForm que gerencia esse processo para nós e une essas duas classes juntas. No login, o sistema cria uma classe UserIdentity, passando os detalhes de login. Estes são validados, no nosso caso, como já citado, em nosso banco de dados.

Então o modelo de login passa o objeto UserIdentity ao objeto CWebUser, que, em seguida, armazena essa informação. Por padrão, a classe CWebUser armazena suas informações em dados da sessão e, portanto, não deve conter informações confidenciais, como senhas, por exemplo.

Nosso model de Usuário

CREATE TABLE `tbl_usuarios` (  
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ds_username` varchar(128) NOT NULL,
  `ds_password` varchar(128) NOT NULL,
  `ds_email` varchar(128) NOT NULL,
  `ds_nome` varchar(128) DEFAULT NULL,
  `nu_paginacao` tinyint(3) NOT NULL DEFAULT '25',
  `cd_empresa` int(9) DEFAULT NULL,
  `cd_filial` int(9) DEFAULT NULL,
  `role` int(1) NOT NULL DEFAULT '0',
  `dt_criacao` datetime DEFAULT NULL,
  `dt_alteracao` datetime DEFAULT NULL,
  `dt_ultima_visita` datetime DEFAULT NULL,
  `status` int(1) NOT NULL DEFAULT '0',
  `dt_expiracao_senha` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ds_username` (`ds_username`),
  UNIQUE KEY `ds_password` (`ds_password`),
  KEY `status` (`status`),
  KEY `superuser` (`role`),
  KEY `fk_usuario_empresa` (`cd_empresa`),
  KEY `fk_usuario_filial` (`cd_filial`),
  CONSTRAINT `fk_usuario_filial` FOREIGN KEY (`cd_filial`) REFERENCES `tbl_filial` (`cd_filial`),
  CONSTRAINT `fk_usuario_empresa` FOREIGN KEY (`cd_empresa`) REFERENCES `tbl_emp` (`cd_emp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Agora você deve utilizar o Gii para gerar o CRUD.

Personalizando o model do Usuário

Agora precisamos atualizar o modelo do usuário da seguinte forma:
1. Incluir constantes para representar nossos papéis.
2. Criptografar a senha
3. Use beforeSave para verificar senhas e atualizar a senha data de validade
4. Adicione quaisquer relações relevantes para a sua aplicação

/**
 * This is the model class for table "tbl_usuarios".
 *
 * The followings are the available columns in table 'tbl_usuarios':
 * @property integer $id
 * @property string $ds_username
 * @property string $ds_password
 * @property string $ds_email
 * @property string $ds_nome
 * @property integer $nu_paginacao
 * @property integer $cd_empresa
 * @property integer $cd_filial
 * @property integer $role
 * @property string $dt_criacao
 * @property string $dt_alteracao
 * @property string $dt_ultima_visita
 * @property integer $status
 * @property string $dt_expiracao_senha
 *
 * The followings are the available model relations:
 * @property TblFilial $cdFilial
 * @property TblEmp $cdEmpresa
 */
class Usuario extends CActiveRecord  
{
    const ROLE_USUARIO      = 3;
    const ROLE_ADMIN        = 1;
    const PASSWORD_EXPIRY   = 90;
    public $passwordSave;
    public $repeatPassword;
    /**
     * @return string the associated database table name
     */
    public function tableName()
    {
        return 'tbl_usuarios';
    }

    /**
     * @return array validation rules for model attributes.
     */
    public function rules()
    {
        // NOTE: you should only define rules for those attributes that
        // will receive user inputs.
        return array(
            //array('ds_password', 'compare', 'compareAttribute'=>'repeatPassword', 'message'=>'Por favor digite as senhas iguais.'),
            array('ds_username, ds_password, ds_email', 'required'),
            array('nu_paginacao, cd_empresa, cd_filial, role, status', 'numerical', 'integerOnly'=>true),
            array('ds_username, ds_password, ds_email, ds_nome', 'length', 'max'=>128),
            array('dt_criacao, dt_alteracao, dt_expiracao_senha', 'safe'),
            // The following rule is used by search().
            // @todo Please remove those attributes that should not be searched.
            array('id, ds_username, ds_password, ds_email, ds_nome, nu_paginacao, cd_empresa, cd_filial, role, dt_criacao, dt_alteracao, dt_ultima_visita, status, dt_expiracao_senha', 'safe', 'on'=>'search'),
        );
    }

    public function beforeSave() {
        parent::beforeSave();
        // Criptografamos a senha se for um novo registro
        if ($this->isNewRecord) {
            $this->ds_password = md5($this->ds_password);
            $this->dt_criacao=new CDbExpression("NOW()");
            $this->dt_expiracao_senha=new CDbExpression("DATE_ADD(NOW(), INTERVAL ".self::PASSWORD_EXPIRY." DAY) ");
        }
        else if (!empty($this->ds_password)&&!empty($this->repeatPassword)&&($this->ds_password===$this->repeatPassword))
        // Se não for um registro novo, salva a senha apenas se as duas senhas casarem.
        {
            $this->ds_password = md5($this->ds_password);
            $this->dt_expiracao_senha=new CDbExpression("DATE_ADD(NOW(), INTERVAL ".self::PASSWORD_EXPIRY." DAY) ");
        }
        return true;
    }

    /**
    * Compare Expiry date and today's date
    * @return type - positive number equals valid user
    */
    public function checkExpiryDate() {
        $expDate=DateTime::createFromFormat('Y-m-d H:i:s',$this->password_expiry_date);
        $today=new DateTime("now");
        fb($today->diff($expDate)->format('%a'),"PASSWORD EXPIRY");
        return ($today->diff($expDate)->format('%a'));
    }

    /**
     * @return array relational rules.
     */
    public function relations()
    {
        // NOTE: you may need to adjust the relation name and the related
        // class name for the relations automatically generated below.
        return array(
            'relationFilial' => array(self::BELONGS_TO, 'Filial', 'cd_filial'),
            'relationEmpresa' => array(self::BELONGS_TO, 'Empresa', 'cd_empresa'),
        );
    }

    /**
     * @return array customized attribute labels (name=>label)
     */
    public function attributeLabels()
    {
        return array(
            'id' => 'id',
            'ds_username' => 'Usuário',
            'ds_password' => 'Senha',
            'repeatPassword' => 'Repita a Senha', 
            'ds_email' => 'Email',
            'ds_nome' => 'Nome',
            'nu_paginacao' => 'Paginacao',
            'cd_empresa' => 'Empresa',
            'cd_filial' => 'Filial',
            'role' => 'Role',
            'dt_criacao' => 'Dt Criacao',
            'dt_alteracao' => 'Dt Alteracao',
            'dt_ultima_visita' => 'Dt Ultima Visita',
            'status' => 'Status',
            'dt_expiracao_senha' => 'Dt Expiracao Senha',
        );
    }

    /**
     * Retrieves a list of models based on the current search/filter conditions.
     *
     * Typical usecase:
     * - Initialize the model fields with values from filter form.
     * - Execute this method to get CActiveDataProvider instance which will filter
     * models according to data in model fields.
     * - Pass data provider to CGridView, CListView or any similar widget.
     *
     * @return CActiveDataProvider the data provider that can return the models
     * based on the search/filter conditions.
     */
    public function search()
    {
        // @todo Please modify the following code to remove attributes that should not be searched.

        $criteria=new CDbCriteria;

        $criteria->compare('id',$this->id);
        $criteria->compare('ds_username',$this->ds_username,true);
        $criteria->compare('ds_password',$this->ds_password,true);
        $criteria->compare('ds_email',$this->ds_email,true);
        $criteria->compare('ds_nome',$this->ds_nome,true);
        $criteria->compare('nu_paginacao',$this->nu_paginacao);
        $criteria->compare('cd_empresa',$this->cd_empresa);
        $criteria->compare('cd_filial',$this->cd_filial);
        $criteria->compare('role',$this->role);
        $criteria->compare('dt_criacao',$this->dt_criacao,true);
        $criteria->compare('dt_alteracao',$this->dt_alteracao,true);
        $criteria->compare('dt_ultima_visita',$this->dt_ultima_visita,true);
        $criteria->compare('status',$this->status);
        $criteria->compare('dt_expiracao_senha',$this->dt_expiracao_senha,true);

        return new CActiveDataProvider($this, array(
            'criteria'=>$criteria,
        ));
    }

    /**
     * Returns the static model of the specified AR class.
     * Please note that you should have this exact method in all your CActiveRecord descendants!
     * @param string $className active record class name.
     * @return Usuarios the static model class
     */
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
}

A classe UserIdentity

Em seguida, vamos alterar a classe UserIdentity original gerado Gii para autenticar o usuário contra a nossa nova tabela do usuário. Isto é encontrado em protected/components/UserIdentity.php e será parecido com este:

/**
 * UserIdentity represents the data needed to identity a user.
 * It contains the authentication method that checks if the provided
 * data can identity the user.
 */
class UserIdentity extends CUserIdentity  
{
    private $_id;
    private $_username;

    public function getName()
    {
        return $this->_username;
    }

    public function getId()
    {
        return $this->_id;
    }

    public function authenticate()
    {
        $user = Usuario::model()->find('LOWER(ds_username)=?', array(strtolower($this->username)));
        if($user === null)
        {
            $this->errorCode= self::ERROR_UNKNOWN_IDENTITY;
        }
        elseif($user->ds_password !== md5($this->password))
        {
            $this->errorCode= self::ERROR_PASSWORD_INVALID;
        }
        else
        {
            $this->_id = $user->id;
            $this->_username = $user->ds_email;
            $user->dt_ultima_visita=new CDbExpression("NOW()");
            $user->save();
            $this->errorCode= self::ERROR_NONE;
        }
        return !$this->errorCode;
    }
}

Personalizando a Classe WebUser

Uma vez que o usuário foi validado, no nosso caso, no banco de dados, podemos carregar os detalhes do usuário para a classe webuser que Yii detém a sessão do usuário. Posto isso, você deve tomar cuidado com as informações que você carrega e armazena na classe webuser, afinal, a sessão pode ser facilmente acessada pelo usuário final. Então, vamos criar um novo componente chamado WebUser em protected/components/

class WebUser extends CWebUser {  
    // Store model to not repeat query.
    private $_model;
    // Return first name.
    // access it by Yii::app()->user->first_name
    function getNome(){
        $user = $this->loadUser(Yii::app()->user->id);
        return $user->ds_nome;
    }
    function getRole(){
        $user = $this->loadUser(Yii::app()->user->id);
        return $user->role;
    }
    function getPaginacao(){
        $user = $this->loadUser(Yii::app()->user->id);
        return $user->nu_paginacao;
    }

    function getDtExpiracao(){
        $user = $this->loadUser(Yii::app()->user->id);
        return $user->checkExpiryDate();
    }

    // verificamos o valor do atributo role com a constante
    // ROLE_ADMIN para sabermos se o usuário é administrador
    function isAdmin(){
        $user = $this->loadUser(Yii::app()->user->id);
        if ($user!==null)
            return intval($user->role) == Users::ROLE_ADMIN;
        else return false;
    }

    // verificamos o valor do atributo role com a constante
    // ROLE_USUARIO para sabermos se o usuário é administrador
    function isUser(){
        $user = $this->loadUser(Yii::app()->user->id);
        if ($user!==null)
            return intval($user->role) == Users::ROLE_USUARIO;
        else return false;
    }

    // carrega o model do usuário
    protected function loadUser($id=null) {
        if($this->_model===null)
        {
            if($id!==null)
                $this->_model=Users::model()->findByPk($id);
        }
        return $this->_model;
    }
}

E então, no principal arquivo de configuração protected/config/main.php) você terá que especificar a classe do componente do usuário, por exemplo:

    [ ... ]
    'components'=>array(
        'user'=>array(
            // enable cookie-based authentication
            'allowAutoLogin'=>true,
            'loginUrl' => array('/site/login'),
            'class'=>'WebUser',
        ),
    [ ... ]

Nossa classe WebUser estende a classe CWebUser que é a classe acessado pelo componente Usuário da nossa aplicação. Assim, quando você referência Yii::app()->user agora irá referenciar a nossa nova classe WebUser. Portanto, tudo o que nós definimos aqui podem ser acessados usando a sintaxe
Yii::app()->user->propriedade ou Yii::app()->user->funcao()
Exemplo: Yii::app()->user->isAdmin()

Definimos um campo de perfil de usuário chamado nu_paginacao, que será utilizado para permitir que os usuários definam quantos itens são exibidos em um GridView. Por motivos de desempenho, então pode ser uma boa idéia definir um limite máximo para não acabar com o desempenho da aplicação.

Personalizando nosso CRUD

Vamos arrumar o formulário protected/views/usuario/_form.php da seguinte forma:

<div class="form">

<?php $form=$this->beginWidget('CActiveForm', array(  
    'id'=>'usuario-form',
    // Please note: When you enable ajax validation, make sure the corresponding
    // controller action is handling ajax validation correctly.
    // There is a call to performAjaxValidation() commented in generated controller code.
    // See class documentation of CActiveForm for details on this.
    'enableAjaxValidation'=>false,
)); ?>

    <p class="note">Fields with <span class="required">*</span> are required.</p>

    <?php echo $form->errorSummary($model); ?>

    <div class="row-fluid">
        <?php echo $form->labelEx($model,'ds_username'); ?>
        <?php echo $form->textField($model,'ds_username',array('size'=>60,'maxlength'=>128)); ?>
        <?php echo $form->error($model,'ds_username'); ?>
    </div>

    <div class="row-fluid">
        <div class="span6">
            <?php echo $form->labelEx($model,'ds_password'); ?>
            <?php echo $form->passwordField($model,'ds_password',array('size'=>60,'maxlength'=>128)); ?>
            <?php echo $form->error($model,'ds_password'); ?>
        </div>
        <div class="span6">
            <?php echo $form->labelEx($model,'repeatPassword'); ?>
            <?php echo $form->passwordField($model,'repeatPassword',array('size'=>60,'maxlength'=>128)); ?>
            <?php echo $form->error($model,'repeatPassword'); ?>
        </div>
    </div>

    <div class="row-fluid">
        <div class="span6">
            <?php echo $form->labelEx($model,'ds_email'); ?>
            <?php echo $form->textField($model,'ds_email',array('size'=>60,'maxlength'=>128)); ?>
            <?php echo $form->error($model,'ds_email'); ?>
        </div>
        <div class="span6">
            <?php echo $form->labelEx($model,'ds_nome'); ?>
            <?php echo $form->textField($model,'ds_nome',array('size'=>60,'maxlength'=>128)); ?>
            <?php echo $form->error($model,'ds_nome'); ?>
        </div>
    </div>

    <div class="row-fluid">
        <?php echo $form->labelEx($model,'nu_paginacao'); ?>
        <?php echo $form->textField($model,'nu_paginacao'); ?>
        <?php echo $form->error($model,'nu_paginacao'); ?>
    </div>

    <div class="row-fluid">
        <div class="span6">
            <?php echo $form->labelEx($model,'cd_empresa'); ?>
            <?php echo $form->textField($model,'cd_empresa'); ?>
            <?php echo $form->error($model,'cd_empresa'); ?>
        </div>
        <div class="span6">
            <?php echo $form->labelEx($model,'cd_filial'); ?>
            <?php echo $form->textField($model,'cd_filial'); ?>
            <?php echo $form->error($model,'cd_filial'); ?>
        </div>
    </div>

    <div class="row-fluid">
        <?php echo $form->labelEx($model,'role'); ?>
        <?php echo $form->textField($model,'role'); ?>
        <?php echo $form->error($model,'role'); ?>
    </div>

    <div class="row buttons">
        <?php echo CHtml::submitButton($model->isNewRecord ? 'Criar' : 'Salvar'); ?>
    </div>

<?php $this->endWidget(); ?>

</div><!-- form -->  

Para logar utilize admin/admin.

Por enquanto é só, vamos continuar com mais detalhes amanhã! Para baixar o projeto como está, clique aqui.

Como utilizar namespaces em php – parte 1

Namespaces já existem em outras liguagens de programação há muito tempo, essa é uma das mudanças mais significativas no PHP 5.3. Desenvolvedores Java e C#, além de outras linguagens, já estão familiarizados com isso, e agora com a utilização de Namapaces a estrutura de aplicações PHP mudará para melhor. Então, o que é um namespace no PHP? Em suma, é um recipiente abstrato que nos permite reutilizar a mesma função, classe e nomes de constantes, mas se aplicam diferentes significados com base em que contexto eles estão inseridos.

Não dá pra tornar a explicação mais fácil?

Imagine um namespace como uma gaveta na qual você pode colocar todos os tipos de coisas: um caderno, uma caneta, um lápis, uma borracha… Estes são os seus pertences. Diretamente debaixo de sua gaveta é de outra pessoa, e ele coloca as mesmas coisas nele. Para evitar o uso de itens de cada um, você decide colocar uma etiqueta nas gavetas para deixar claro o que pertence a quem.
Anteriormente, os desenvolvedores tiveram que usar underscore em suas classes, funções e constantes para separar bases de código. Isso é equivalente à nossa etiqueta.
Essencialmente, um namespace não é nada mais do que um bloco de código hierarquicamente rotulado que contém código PHP normal.

Por que precisamos de Namespaces?

Conforme a sua biblioteca de código vai crescendo, há um aumento do risco de acidentalmente, você acabar redefinindo uma função ou nome de classe que já foi utilizada anteriormente. O problema é agravado quando você tenta adicionar componentes ou plugins de terceiros, por exemplo: o que aconteceria se dois ou mais componentes utilizassem a classe Usuário?
Até agora, a única solução foi alongar os nomes de classe/função de comprimento. Por exemplo, o wordpress utiliza o prefixo ‘wp_'(underscore).
Problemas de colisão de nomes pode ser resolvido com namespaces. Constantes, classes e funções podem ser agrupadas em namespaces diferentes.

Como namespaces são definidos?

Por padrão, todoas as constante, classes e nomes de função são colocados no espaço global – como eram antes do PHP suportar namespace.
O namespace é definido apenas pela palavra-chave namespaceno topo do seu arquivo PHP. Ele deve ser o primeiro comando (com a exceção do declare) e nenhum código não-PHP, HTML, ou de espaço em branco pode preceder essa palavra-chave.

Vamos dar uma olhada em um pequeno trecho de código, e então vamos quebrar e expandir o que está acontecendo em pequenas partes, para que fique mais fácil de entender.

namespace Foo;

function Bar()  
{
    echo __NAMESPACE__;
}

namespace FooFoo;

function Bar()  
{
    echo "Bar";
}

echo \Foo\Bar();  

Saída:

Foo

Existem alguns pontos interessantes aqui:
A palavra-chave namespace
A constante NAMESPACE
A redefinição da função Bar()
A estranha nova sintaxe no comando echo no final do arquivo: a resolução de nomes

A palavra-chave namespace

Namespaces são nomeados arbitrariamente e têm duas formas de sintaxe. Em tempo, em qualquer uma das sintaxes, namespaces não podem ser aninhados, embora, sub-namespaces possam ser definidos, como será explicado mais adiante.

Sintaxe 1

namespace Foo;

// bloco de código

Sintaxe 2

namespace Foo  
{
    // bloco de código
}

A utilização de Namespaces torna mais fácil evitar definições conflitantes e introduzir mais flexibilidade e organização dos seus códigos.
Perceba também que que as chaves em torno do bloco de código são completamente opcionais. Na verdade, aderindo à regra de utilizar um namespace por arquivo e omitindo as chaves você torna seu código muito mais limpo.

A constante __NAMESPACE__

A constante NAMESPACE contém uma seqüência que exibe o namespace atual. Quando chamado a partir do contexto global, uma string vazia é exibida.

<?php  
namespace Blog;  
echo __NAMESPACE__;  
?>

Saída:

Blog

A utilização de namespaces tem benefícios bastante óbvios durante a depuração. Também pode ser usada para gerar dinamicamente um nome de classe completamente qualificado. Por exemplo:

<?php  
namespace Blog;

class MinhaClasse {  
    public function WhoAmI() {
        return __METHOD__;
    }
}

$c = __NAMESPACE__ . '\MinhaClasse';
$m = new $c;
echo $m->WhoAmI();  
?>

Saída:

Blog\MinhaClasse::WhoAmI

PHP – Stream Filters – Comprimir, transformar e re-codificar

Stream Filters

PHP utiliza o conceito de streams, como uma camada de abstração para IO. Leitura e escrita de arquivos pode ser feito com os streams. Sockets, também, podem ser lidos e escritos como streams. FTP e servidores HTTP tem suporte à stream. O Stream Filter nos permite executar operações em dados que estão sendo lidos ou gravados através de um stream.

O Stream nos permite fazer algo assim:

<?php  
    $conteudo = file_get_contents('http://url.com');
?>

O código acima recebe todo o conteúdo da página na URL de destino, e lê como se fosse um arquivo no sistema de arquivos local. Você também pode abrir arquivos compactados ou arquivo como Phar, bzip2, e arquivos GZIP, tendo o conteúdo descompactado em tempo de execução.

Stream Filters fornecem uma camada acima disso: Eles permitem que você abra um stream, e depois ter uma ou mais tarefas (filtros) são executados no stream de como os dados são lidos ou gravados para o stream.

Stream Filters na Prática

O que nós vamos fazer para praticar é ler um arquivo compactado com bzip2, re-codificar o arquivo de ISO-8859-1 para UTF-8, e em seguida transformá-lo em um arquivo onde os dados são capitalizados.

Dividindo nossas tarefas em uma sequência, vamos fazer o seguinte:

  • Abrir uma stream para leitura
  • Abrir uma stream para gravação
  • Descompactar o nosso arquivo
  • Transcodificar de ISO-8859-1 para UTF-8
  • Converter o conteúdo do stream para maiúsculas
  • Gravar o nosso conteúdo tratado em um arquivo de texto simples

Com o Stream Filter, isto é realizado através da criação de um par de streams, e depois atribuímos filtros para cada stream. Quando copiar os dados de um stream para outro, os filtros serão executados internamente.

Vamos começar com um arquivo teste.txt.bz2, que é um arquivo de texto ISO-8859-1 comprimido em bzip2 cujo conteúdo são os valores utilizados no post anterior separados por ponto e vírgula. E nós vamos gerar um arquivo chamado teste_fixed.txt.

Agora que tudo já foi explicado, vamos executar nossas tarefas passo a passo:

Nosso primeiro passo é criar nosso file handler, que servirá para manipularmos os arquivos, criaremos um com o arquivo de entrada e outro com o arquivo de saída.

<?php  
    $entrada = fopen('teste.txt.bz2', 'r');
    $saida   = fopen('teste_fixed.txt', 'wb');
?>

Apesar de, provavelmente, não acrescentar nada, o código acima, acaba com nossas duas primeiras tarefas foram executadas. Temos um file handler para entrada que é o arquivo que vamos ler e outro para saída, que será o arquivo que receberá o conteúdo já tratado.

Nosso último passo será descompactar e transcodificar o conteúdo do arquivo, de ISO-8859-1 para UTF-8. Agora é que vamos fazer jus ao título do post.

<?php  
    $entrada = fopen('teste.txt.bz2', 'r');
    $saida   = fopen('teste_fixed.txt', 'w');

    // Primeiro adicionamos um filtro de decodificação.
    stream_filter_prepend($entrada, 'bzip2.decompress', STREAM_FILTER_READ);

    // Trocamos o charset de  ISO-8859-1 para UTF-8.
    stream_filter_append($saida, 'convert.iconv.ISO-8859-1/UTF-8', STREAM_FILTER_WRITE);

    // Passamos todo o texto para maiúscula.
    stream_filter_append($saida, 'string.toupper', STREAM_FILTER_WRITE);

    // Agora copiamos para o arquivo de saída. Todos os filtros serão aplicados aqui.
    stream_copy_to_stream($entrada, $saida);

    // fechamos os handlers.
    fclose($entrada);
    fclose($saida);
?>

Basicamente, o que o código utilizando stream filter acima faz é:

  • Nós abrimos dois handlers, uma para ao arquivo de entrada e um para o arquivo de saída.
  • Atribuímos um stream_filter bzip2.decompress em modo de leitura, que vai descomprimir o fluxo de entrada como se lê.
  • Utilizamos um stream filter com iconv para transcodificar de ISO-8859-1 para UTF-8.
  • Utilizamos outro stream filter com string.toupper para transformar os dados para maiúsculas quando aplicável.
  • Depois copiar o fluxo de entrada ($entrada) para o fluxo de saída ($saida) num único passo.
  • Finalmente, fechamos os handlers..

É importante observar que nenhum dos filtros são realmente aplicados até que as stream são processadas. Por isso, somente quando streamcopyto_stream() é executado de que os quatro filtros são aplicados.

Este método é muito mais eficiente do que executar as mesmas operações em loop porque a cópia é feita a um nível inferior, em que os dados não tem de ser passado para dentro e para fora do espaço do utilizador. Assim, além de ser mais fácil de implementar, também é mais rápido e a utilização de memória é menor.

PHP – Como ordenar arrays de arrays

O PHP fornece uma gama de funções para que você possa ordenar arrays com base na chave ou valor de um array associativo. Porém, você pode ter um pouco de dificuldade de ordenar quando seus dados estão em um array de arrays associativos e, apartir daí, você precisa ordenar seu array de acordo com um ou mais atributos do mesmo.

O array

Os exemplos que se seguem têm por base os seguintes dados como uma matriz associativa. O array dados contém seis arrays, cada um dos quais tem um valor de “nome”, “sobrenome” e de “idade”.

<?php  
    $data = array(
        array( 'nome' => "Adler", 'sobrenome' => "Dias", 'idade' => 28 ),
        array( 'nome' => "Paulo", 'sobrenome' => "Ferraz", 'idade' => 24 ),
        array( 'nome' => "Andre", 'sobrenome' => "Rosset", 'idade' => 21 ),
        array( 'nome' => "Rafael", 'sobrenome' => "Quaresma", 'idade' => 22 ),
        array( 'nome' => "Rafael", 'sobrenome' => "Oliveira", 'idade' => 29 ),
        array( 'nome' => "Achiles", 'sobrenome' => "Bianchi", 'idade' => 30 )
   );
?>

Este conjunto de dados pode ser representado em formato de tabela como:

nome sobrenome idade
[0] Adler Dias 28
[1] Paulo Ferraz 24
[2] Andre Rosset 21
[3] Rafael Quaresma 22
[4] Rafael Oliveira 29
[5] Achiles Bianchi 30

Agora acredito que você já tenha conseguido visualizar esse array de array’s. Então, só nos resta começar a ordenar esse array de arrays.

Ordenar array com base em um único atributo

Ordenar arrays associativos com base em único campo é algo extremamente simples, desde, você saiba de antemão, qual o campo que você deseja ordenar.

Por exemplo, para ordenar por sobrenome você pode usar a seguinte função:

<?php  
    function compararSobrenome($a, $b)
    {
        return strnatcmp($a['sobrenome'], $b['sobrenome']);
    }
    // ordenar por sobrenome
     uasort($data, 'compararSobrenome');
?>

No código acima foi utilizada a função uasort para que fosse possível manter a associação do índice ao ordenar o array, casso não fosse necessário manter o índice, você pode utilizar a função usort.

A saída é a seguinte:

nome sobrenome idade
[5] Achiles Bianchi 30
[0] Adler Dias 28
[1] Paulo Ferraz 24
[4] Rafael Oliveira 29
[3] Rafael Quaresma 22
[2] Andre Rosset 21

A essa altura você deve ser capaz de modificar a função compararSobrenome para ordenar por nome (veja adiante), por idade, ou qualquer outro campo que você tenha a necessidade de ordenar em seu array associativo. A função strnatcmp é particularmente útil, uma vez que ela pode ser utilizada tanto aos números quanto com strings.

Ordenar array com base em múltiplos atributos

Se você quiser ordenar por nome e sobrenome, então, você pode pensar que apenas aplicando dois tipos em seqüência lhe daria o resultado desejado, como no exemplo abaixo:

<?php  
        // utilize o mesmo array do exemplo de cima.
        function CompararNome($a, $b)
    {
        return strnatcmp($a['nome'], $b['nome']);
    }
    // ordenar por nome
    function compararSobrenome($a, $b)
    {
        return strnatcmp($a['sobrenome'], $b['sobrenome']);
    }
    // ordenar por sobrenome
    uasort($data, 'CompararNome');
    uasort($data, 'compararSobrenome');
?>

Isso significa que, depois de ordenar pelo nome, em seguida, o resultado da ordenação, pelo sobrenome não necessariamente mantêm a ordem correta dos nomes.

Então, não é possível ordenar por duas colunas? Apenas precisaremos de uma nova função:

<?php  
    function comparaNomeSobrenome($a, $b)
    {
        $retval = strnatcmp($a['sobrenome'], $b['sobrenome']);
        if(!$retval)
            return strnatcmp($a['nome'], $b['nome']);
        return $retval;
    }
    // sort alphabetically by firstname and lastname
    usort($data, 'comparaNomeSobrenome');
?>

Agora a nossa saída seria a seguinte:
A saída é a seguinte:

nome sobrenome idade
[5] Achiles Bianchi 30
[0] Adler Dias 28
[2] Andre Rosset 21
[1] Paulo Ferraz 24
[4] Rafael Oliveira 29
[3] Rafael Quaresma 22

Esta função funciona porque primeiro fazemos a comparação dos nomes, e, se e somente se, eles são idênticos vai comparar os sobrenomes, assim conseguimos ordenar arrays da maneira que desejamos, ou seja, primeiro nome e depois sobrenome.

Yii – Como fazer o botão delete do CGridView/TbGridView funcionar usando Ajax

No post de hoje você vai entender como você pode modificar a função de delete do CGridView para funcionar com ajax, exibindo a mensagem de erro, ou, de sucesso de uma maneira mais amigável. Para isso nós primeiro vamos alterar o nosso método delete dos nossos controllers Empresa e Filial. Essa parte não têm segredo, nós utilizamos try/catch para garantirmos a manipulação de erros, e, verificamos se a chamada foi ajax, ou não.

Uma parte muito importante do desenvolvimento de software é a usabilidade, e, se preocupar como o usuário receberá alertas é também muito importante.

Vamos continuar o desenvolvimento do nosso “projetinho” com cadastro de Empresa e Filial.

  1. Yii – Entendendo os relacionamentos (relations)
  2. Yii – Como criar um dropdown
  3. Yii – Como criar um filtro de um relacionamento

Alterando o actionDelete do controller da Empresa

controller/EmpresaController.php

  
public function actionDelete($id)  
{
    try
    {
        $this->loadModel($id)->delete();
        if(!isset($_GET['ajax']))
            Yii::app()->user->setFlash('success','Empresa removida com sucesso.');
        else
            echo '
×Sucesso! Empresa removida com sucesso.
'; } catch(CDbException $e) { if(!isset($_GET['ajax'])) Yii::app()->user->setFlash('error','Não foi possível remover essa Empresa.'); else echo '
×Erro! Não foi possível remover essa Empresa.
'; } // if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser if(!isset($_GET['ajax'])) $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin')); }

controller/FilialController.php

  
public function actionDelete($id)  
{
    try
    {
        $this->loadModel($id)->delete();
        if(!isset($_GET['ajax']))
            Yii::app()->user->setFlash('success','Filial removida com sucesso.');
        else
            echo '
×Sucesso! Filial removida com sucesso.
'; } catch(CDbException $e) { if(!isset($_GET['ajax'])) Yii::app()->user->setFlash('error','Não foi possível remover essa Filial.'); else echo '
×Erro! Não foi possível remover essa Filial.
'; } // if AJAX request (triggered by deletion via admin grid view), we should not redirect the browser if(!isset($_GET['ajax'])) $this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin')); }

Agora que você já tratou as requisições via POST e via AJAX, nós devemos alterar a nossa view admin.php, dessa forma, quando o retorno vier, ele será exibido corretamente. Aproveitando já vamos exibir os tooltips e a confirmação da opção de remover traduzidos. Quando tratamos o retorno exibimos a mensagem de erro ou sucesso, esperamos 5 segundos, então fechamos o alerta.

views/empresa/admin.php

  
breadcrumbs=array(
    'Empresa'=>array('index'),
    'Gerenciar',
);
$this->menu=array(
    array('label'=>'Listar Empresa','url'=>array('index')),
    array('label'=>'Criar Empresa','url'=>array('create')),
);
?>

Gerenciar Empresa

user->hasFlash('success')):?>
× Sucesso! user->getFlash('success'); ?>
user->hasFlash('error')):?>
× Erro! user->getFlash('error'); ?>
widget('bootstrap.widgets.TbGridView',array( 'id'=>'empresa-grid', 'dataProvider'=>$model->search(), 'filter'=>$model, 'columns'=>array( 'cd_emp', 'ds_nome', 'nu_cnpj', 'dt_inativacao', array( 'header' => 'Ações', 'class'=>'bootstrap.widgets.TbButtonColumn', 'template'=>'{view}{update}{delete}', 'buttons' => array( 'view' => array( 'label'=>'Exibir', ), 'update' => array( 'label'=>'Editar', ), 'delete' => array( 'label'=>'Remover', ), ), 'afterDelete'=> 'function(link,success,data) { if(success) { $("#statusMsg").html(data); setTimeout(function() { $(".alert").delay(1000).alert("close") }, 5000); } }', 'deleteConfirmation'=>"js:'Tem certeza que deseja remover a empresa: '+$(this).parent().parent().children(':nth-child(2)').text()+'?'", ), ), )); ?>

views/filial/admin.php

  
breadcrumbs=array(
    'Filial'=>array('index'),
    'Gerenciar',
);

$this->menu=array(
    array('label'=>'Listar Filial','url'=>array('index')),
    array('label'=>'Criar Filial','url'=>array('create')),
);
?>

Gerenciar Filial

user->hasFlash('success')):?>
× Sucesso! user->getFlash('success'); ?>
user->hasFlash('error')):?>
× Erro! user->getFlash('error'); ?>
widget('bootstrap.widgets.TbGridView',array( 'id'=>'filial-grid', 'dataProvider'=>$model->search(), 'filter'=>$model, 'columns'=>array( 'cd_filial', 'ds_nome', 'ds_cnpj', 'ds_uf', 'ds_cidade', array( 'name' => 'cd_empresa', 'value' => '$data->relationEmpresa->ds_nome', ), 'dt_inativacao', array( 'header' => 'Ações', 'class'=>'bootstrap.widgets.TbButtonColumn', 'template'=>'{view}{update}{delete}', 'buttons' => array( 'view' => array( 'label'=>'Exibir', ), 'update' => array( 'label'=>'Editar', ), 'delete' => array( 'label'=>'Remover', ), ), 'afterDelete'=> 'function(link,success,data) { if(success) { $("#statusMsg").html(data); setTimeout(function() { $(".alert").delay(1000).alert("close") }, 5000); } }', 'deleteConfirmation'=>"js:'Tem certeza que deseja remover a empresa: '+$(this).parent().parent().children(':nth-child(2)').text()+'?'", ), ), )); ?>

Para baixar o projeto como está, clique aqui.

No próximo post da série, vamos fazer um botão adicional e faremos ativar/desativar a filial.

PHP – Como fazer consultas utilizando o PDO

Dando continuidade ao post de configuração do PDO que pode ser encontrado aqui.

Primeiro vamos criar uma tabela com algumas informações:

  
CREATE TABLE album (  
  id int(11) NOT NULL auto_increment,
  artist varchar(100) NOT NULL,
  title varchar(100) NOT NULL,
  PRIMARY KEY (id)
);
INSERT INTO album (artist, title)  
    VALUES  ('The  Military  Wives',  'In  My  Dreams');
INSERT INTO album (artist, title)  
    VALUES  ('Adele',  '21');
INSERT INTO album (artist, title)  
    VALUES  ('Bruce  Springsteen',  'Wrecking Ball (Deluxe)');
INSERT INTO album (artist, title)  
    VALUES  ('Lana  Del  Rey',  'Born  To  Die');
INSERT INTO album (artist, title)  
    VALUES  ('Gotye',  'Making  Mirrors');

Agora que você já terminou de criar sua tabela, nós podemos começar a falar de consultas utilizando o pdo.

Tá, mas como eu faço um select utilizando PDO?

Depois de ter criado e adicionado alguns dados em uma tabela do nosso banco de dados, você pode usar uma consulta SELECT, com o método query() do PDO para obter esses dados.
O método query() retorna um conjunto de resultados com os dados retornados pelo MySQL, ou FALSE em caso de erro.

  
exec("SET CHARACTER SET utf8");      // Sets encoding UTF-8

    // Define and perform the SQL SELECT query
    $sql = "SELECT * FROM `album` WHERE `id` IN (4, 5)";
    $resultado = $conn->query($sql);

    // verificamos se a nossa consulta foi executada com sucesso
    if($resultado !== false)
    {
        // Vamos imprimir os nossos resultados
        foreach($resultado as $row) {
            echo $row['id']. ' - '. $row['artist'] . ' - ' . $row['title'] . '
'; } } // Desconectamos do banco $conn = null; } catch(PDOException $e) { // imprimimos a nossa excecao echo $e->getMessage(); } ?>

caso você esteja utilizando a mesma tabela do início do post você verá na tela o seguinte resultado:

4 – Lana Del Rey – Born To Die
5 – Gotye – Making Mirrors

Claro que você também pode utilizar o while para percorrer o resultado, mas, para isso você precisa utilizar o método fetch().

  
exec("SET CHARACTER SET utf8");      // Sets encoding UTF-8

    // Define and perform the SQL SELECT query
    $sql = "SELECT * FROM `album` WHERE `id` IN (4, 5)";
    $resultado = $conn->query($sql);

    // Vamos imprimir os nossos resultados
    while($row = $resultado->fetch()) {
        echo $row['id']. ' - '. $row['artist'] . ' - ' . $row['title'] . '
'; } // Desconecta $conn = null; } catch(PDOException $e) { // imprimimos a nossa excecao echo $e->getMessage(); } ?>

Mas qual é a desse método fetch()?

Ele retorna cada linha do conjunto de resultados, uma após a outra, ou se já não há mais linhas ele nos retorna FALSO. Este método contém várias constantes que determinam o modo para retornar as linhas: Array, Objeto, e etc… Este argumento é utilizado com a seguinte sintaxe:

  
fetch(PDO::FETCH_MODE)  

Agora para você poder visualizar as diferenças entre alguns dos modificadores do tipo de retorno do método fetch(), eu vou exemplificar alguns:

FETCHASSOC
Retorna um array indexado pelo nome da coluna como retornado no conjunto de resultados (similar ao mysql
fetch_assoc).

  
exec("SET CHARACTER SET utf8");      // Sets encoding UTF-8

        // Define and perform the SQL SELECT query
        $sql = "SELECT * FROM `album`";
        $resultado = $conn->query($sql);

        // Vamos imprimir os nossos resultados
        while($row = $resultado->fetch(PDO::FETCH_ASSOC)) {
            echo $row['id']. ' - '. $row['artist'] . ' - ' . $row['title'] . '
'; } // Desconecta $conn = null; } catch(PDOException $e) { // imprimimos a nossa excecao echo $e->getMessage(); } ?>

o resultado do código acima será exibido conforme abaixo:

1 – The Military Wives – In My Dreams
2 – Adele – 21
3 – Bruce Springsteen – Wrecking Ball (Deluxe)
4 – Lana Del Rey – Born To Die
5 – Gotye – Making Mirrors

FETCH_OBJ
Retorna um objeto anônimo com nomes de propriedades que correspondem aos nomes das colunas retornadas no conjunto de resultados.

  
exec("SET CHARACTER SET utf8");      // Sets encoding UTF-8

        // Define and perform the SQL SELECT query
        $sql = "SELECT * FROM `album`";
        $resultado = $conn->query($sql);

        // Vamos imprimir os nossos resultados
        while($row = $resultado->fetch(PDO::FETCH_OBJ)) {
            echo $row->id. ' - '. $row->artist . ' - ' . $row->title . '
'; } // Desconecta $conn = null; } catch(PDOException $e) { // imprimimos a nossa excecao echo $e->getMessage(); } ?>

o resultado do código acima será exibido conforme o codigo anterior, porém, dessa vez como o código acima nos mostra, nós acessamos os valores como um objeto e não mais como um array:

1 – The Military Wives – In My Dreams
2 – Adele – 21
3 – Bruce Springsteen – Wrecking Ball (Deluxe)
4 – Lana Del Rey – Born To Die
5 – Gotye – Making Mirrors

Por enquanto é só, até a próxima.

Yii – Como criar um filtro de um relacionamento

Hoje explicarei como fazer o filtro do CGridView funcionar utilizando o relacionamento implementado no post Yii – Entendendo os relacionamentos (relations) e no segundo post Yii – Como criar um dropdown, você pode continuar utilizando o mesmo fonte, caso, ainda não tenha feito, você pode fazer clicando aqui.

Onde utilizaremos o Relacionamento do Yii

Quando você usa o gii para gerar as views, por padrão, todos os campos exibidos são ligados ao modelo que você está vendo. Entretanto, muitas vezes, você não quer ver o valor de uma chave estrangeira, mas sim o valor que está associado(relacionado) a esse atributo(coluna). Todas as nossas views exibem o id da empresa no lugar do nome da empresa, logo primeiro faremos o básico, que é alterar o index e a view para que seja exibido corretamente o nome da empresa.

filial/_view

    <b><?php echo CHtml::encode($data->getAttributeLabel('cd_empresa')); ?>:</b>
    <?php echo CHtml::encode($data->relationEmpresa->ds_nome); ?>

filial/view

'attributes'=>array(  
    [...]
    'relationEmpresa.ds_nome',

Em nosso exemplo, o nosso CGridView exibe o valor de nossa chave estrangeira – ou do nosso relacionamento – para empresa, porém, nós queremos que seja exibido o nome da empresa em questão. Isso é muito fácil de corrigir, alterando apenas algumas partes do código gerado pelo gii.

Como vocês puderam perceber, em nosso exemplo, quando nos acessamos filial/admin não aparece o nome da empresa, mas sim seu id.

A primeira parte desse post trata exatamente disso, vamos utilizar o nosso relacionamento para exibir o nome da empresa, invés de mostrar o id da mesma. Para que possamos fazer isso vamos alterar a nossa view filial/admin:

filial/admin

'columns'=>array(  
    [...]
    // aqui devemos trocar a linha 'cd_empresa' pelo array abaixo
    array(
        'name' => 'cd_empresa',
        'value' => '$data->relationEmpresa->ds_nome',
    ),

O problema, no entanto, mesmo que estejamos exibindo agora o nome da empresa o filtro continua apenas funcionando pelo id do mesmo. Primeiro, você precisa mudar a forma como a comparação é feita no método search().

Essa alteração do método search() visa alterar a comparação do idempresa para o nome da empresa. Como você pode perceber agora é utilizado usando o “ponto” de notação, como em “state.name”. Esta é a sintaxe para consultar uma tabela com o alias de “relationEmpresa” para o valor de sua coluna “dsnome”.

models/Filial.php

search

    [...]
    $criteria->with=array('relationEmpresa');

    //$criteria->compare('cd_empresa',$this->cd_empresa);
    $criteria->compare('relationEmpresa.ds_nome',$this->cd_empresa, true);
    $criteria->together=true;
    [...]

A segunda parte da alteração diz respeito ao nosso método rules(). Você tem que declarar que a coluna “ds_nome” da tabela Empresa pode ser pesquisada(safe).

models/Filial.php

rules

'attributes'=>array(  
    [...]
    array('cd_filial, ds_nome, ds_cnpj, ds_uf, ds_cidade, relationEmpresa.ds_nome, dt_inativacao,', 'safe', 'on'=>'search'),

Para a nossa busca funcionar, isso é tudo que precisa ser feito.

Para baixar o projeto como está, clique aqui.