Tagyii

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.

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.

Yii – Como criar um dropdown

Agora continuaremos a desenvolver alterando a criação da filial, adicionando um dropdown para listar as empresas para seleção.

Primeiro vamos alterar as nossas actions create e update para pegar as empresas cadastradas, que serão utilizadas para preencher o nosso dropdown. Para isso utilizaremos o CDbCriteria, assim criaremos uma regra onde vamos exibir apenas as empresas que ainda não estiverem inativas.

controllers/Filial.php

actionCreate

public function actionCreate()  
{
    $model=new Filial;

    // Uncomment the following line if AJAX validation is needed
    // $this->performAjaxValidation($model);

    if(isset($_POST['Filial']))
    {
        $model->attributes=$_POST['Filial'];
        if($model->save())
            $this->redirect(array('view','id'=>$model->cd_filial));
    }

    $criteria = new CDbCriteria();
    $criteria->addCondition("dt_inativacao = '0000-00-00'");
    $criteria->order = "ds_nome ASC";
    $empresa = CHtml::listData(Empresa::model()->findAll($criteria), 'cd_emp', 'ds_nome');

    $this->render('create',array(
        'model'=>$model,
        'empresa'=>$empresa,
    ));
}

actionUpdate

public function actionUpdate($id)  
{
    $model=$this->loadModel($id);

    // Uncomment the following line if AJAX validation is needed
    // $this->performAjaxValidation($model);

    if(isset($_POST['Filial']))
    {
        $model->attributes=$_POST['Filial'];
        if($model->save())
            $this->redirect(array('view','id'=>$model->cd_filial));
    }

    $criteria = new CDbCriteria();
    $criteria->addCondition("dt_inativacao = '0000-00-00'");
    $criteria->order = "ds_nome ASC";
    $empresa = CHtml::listData(Empresa::model()->findAll($criteria), 'cd_emp', 'ds_nome');

    $this->render('update',array(
        'model'=>$model,
        'empresa'=>$empresa,
    ));
}

Como vocês podem notar, estamos passando mais um parâmetro para o nosso render. Ele é contém as empresas cadastradas no banco de dados que não estão inativas e serão utilizadas para preencher o nosso dropdown.

A variável empresa, agora, também está disponível na view, e devemos fazê-la chegar até o nosso filial/_form.php. Para isso nós modificaremos os arquivos filial/update.php e filial/create.php. Afinal, nas actions create e update do controller, nós estamos passando como parâmetro a variável empresa – que conforma já explicamos contém a lista das empresas sem data de inativação – agora, como é utilizado um renderPartial para o _form, precisamos fazer com que o valor da empresa, seja também passado para essa outra view, para fazermos isso, modificamos apenas o renderPartial.

views/filial

create.php

[...]
<?php echo $this->renderPartial('_form', array('model'=>$model, 'empresa'=>$empresa)); ?>  

update.php

[...]
<?php echo $this->renderPartial('_form', array('model'=>$model, 'empresa'=>$empresa)); ?>  

A última parte dessa alteração, é trocar o textFieldRow, pelo dropDownList, que será feito no arquivo filial/_form.php

Populando nosso dropdown

views/filial
_form.php

<?php //echo $form->textFieldRow($model,'dt_inativacao',array('class'=>'span5')); ?>  
<?php echo $form->labelEx($model,'cd_empresa'); ?>  
<?php echo $form->dropDownList($model,'cd_empresa', $empresa, array('class'=>'span5')); ?>  

Por enquanto é só isso, no próximo post, utilizaremos os relacionamentos criados no post anterior para exibir corretamente o nome da empresa, e para podermos utilizá-la no filtro do CGridview.

Para baixar o projeto como está, clique aqui.

Yii – Indirect modification of overloaded property

Quando eu estava tentando modificar um atributo de uma relation, estava apresentando o erro: Indirect modification of overloaded property.

Esse problema ocorre quando o atributo não é uma propriedade da classe. Por isso, está chamando o método mágico __get(), mas, como se trata de um relacionamento o retorno desse método mágico dá-se por valor e não por atributo, logo, a atribuição direta de um valor para essa propriedade não tem efeito.

Exemplo: Indirect modification of overloaded property.

$count=0;
foreach ($pedido->relationItem as $item)  
{
    $pedido->relationItem[$count]->DATA_ENVIO = $data_envio;
    $pedido->relationItem[$count]->ENVIADO = 1);
    $count++;
}

Você pode resolver esse erro utilizando o método setAttribute:

$count=0;
foreach ($pedido->relationItem as $item)  
{
    $pedido->relationItem[$count]->setAttribute('DATA_ENVIO',$data_envio);
    $pedido->relationItem[$count]->setAttribute('ENVIADO',1);
    $count++;
}

Yii – Como tratar exceções utilizando o Alert do Bootstrap.

No psot de hoje será mostrado como tratar exceções utilizando o alert do Bootstrap, assim, nosso alerta será exibido de uma forma mais amigável para o usuário do sistema. Primeiramente devemos configurar o nosso main/config.php para fazer a rota dos erros para a nossa view de exibição do erro, para isso, nós utilizaremos a propriedade CErrorHandler::errorAction. Caso o seu main.php não possua o array abaixo, é só implementá-lo.

Configurando nosso errorAction

return array(  
    ......
    'components'=>array(
        'errorHandler'=>array(
            'errorAction'=>'site/error',
        ),
    ),
);

Agora que nós configuramos a propriedade errorAction, para o Controller Site, nós precisamos de um action error. Para isso, nós devemos criá-lo manualmente, caso não exista, ou alterá-lo. Afinal, essa action será responsável por exibir os erros disparados ao tratar exceções.

Alterando nosso actionError

public function actionError()  
{
    if($error=Yii::app()->errorHandler->error)
        $this->render('error', $error);
}

Alteraremos a action Contact apenas para exibir uma mensagem de erro. Para podermos testar o nosso código.

public function actionContact()  
{
    throw new CHttpException(400, "Desculpe-nos o transtorno, porém, página solicitada não existe.");
}

Agora vamos alterar a nossa view para utilizar o alert do bootstrap ao tratar exceções. Como só podemos utilizar o TbAlert utilizando setFlash, nós utilizaremos o mesmo html gerado pelo setFlash.

<?php  
/* @var $this SiteController */
/* @var $error array */
$this->pageTitle=Yii::app()->name . ' - Error';
$this->breadcrumbs=array(
    'Error',
);
?>
<div class="alert in alert-block fade alert-error">  
    <a data-dismiss="alert" class="close">×</a>
    <strong><?php echo $code; ?></strong> <?php echo CHtml::encode($message); ?>
</div>  

Agora é só clicar no link Contact, que você irá ver erro exibido como o TbAlert do Yii Twitter Bootstrap.

Você pode baixar os fontes desse exemplo clicando aqui!

Yii – Como pegar o IP do visitante ( ou usuário registrado )

A variável userHostAddress possuem informações do servidor e do visitante, dentre todas as informações contidas nessa variável, também está o ip.

Para obter o endereço IP do usuário dentro do Framework Yii usar o seguinte código:

Como pegar o IP do visitante

echo Yii::app()->request->userHostAddress;  

Você pode pegar o valor dessa variável de qualquer lugar dentro de sua aplicação Yii Framework.

Post feito à partir do comentário do Gustavo.

Yii – Entendendo os relacionamentos (relations)

Vamos considerar a seguinte estrutura abaixo como um exemplo, para que possamos demonstrar a utilização da realtions HASMANY e BELONGSTO.

Relacionamento

A relação BELONGSTO diz que um campo neste modelo aponta para a chave primária em outro modelo, neste caso, o modelo atual possui o domínio de ligação.
A relação HAS
ONE diz que algum outro modelo tem um campo de ligação apontando para a chave principal deste modelo, neste caso, o modelo relacionado possui o campo de ligação.

Abaixo segue o sql para criação do exemplo:

CREATE TABLE `tbl_emp` (  
  `cd_emp` int(9) NOT NULL AUTO_INCREMENT,
  `ds_nome` varchar(20) NOT NULL,
  `nu_cnpj` varchar(15) DEFAULT NULL,
  `dt_inativacao` date DEFAULT NULL,
  PRIMARY KEY (`cd_emp`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8;

CREATE TABLE `tbl_filial` (  
  `cd_filial` int(9) NOT NULL AUTO_INCREMENT,
  `ds_nome` varchar(20) NOT NULL,
  `ds_cnpj` varchar(15) DEFAULT NULL,
  `ds_uf` varchar(2) DEFAULT NULL,
  `ds_cidade` varchar(30) DEFAULT NULL,
  `cd_empresa` int(9) NOT NULL,
  `dt_inativacao` date DEFAULT NULL,
  PRIMARY KEY (`cd_filial`),
  KEY `fb_fil_emp` (`cd_empresa`),
  CONSTRAINT `fk_fil_emp` FOREIGN KEY (`cd_empresa`) REFERENCES `tbl_emp` (`cd_emp`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;

Utilizando essa nossa estrutura, cada filial está obrigatóriamente, vinculada à uma empresa. Sendo assim, uma empresa pode possuir nenhuma, uma ou muitas filiais. Na UML, esse tipo de relação é denominado dependência.

Caso às tabelas estejam modeladas com chaves estrangeiras, como às acima, se você utilizar o Gii, ele automaticamente já vai criar as relations. Caso contrário, você terá que defini-las “programaticamente”. Seguindo a seguinde estrutura:

return array(  
    'nome_do_relacionamento' => array(self::TIPO_DO_RELACIONAMENTO, 'Modelo', 'coluna_da_tabela'),
);

Observação: Lembre-se sempre que o nome do relacionamento não pode ser o nome de uma coluna.

Logo, em nosso exemplo teremos os seguintes relacionamentos, em seus respectivos modelos:

O modelo empresa utilizando o relacionamento HASMANY, para o modelo Filial especificamente na coluna cdempresa.
model/Empresa.php

public function relations()  
{
    return array(
        'filiais' => array(self::HAS_MANY, 'Filial', 'cd_empresa'),
    );
}

O modelo Filial utilizando o relacionamento BELONGSTO, para o modelo Empresa, mas, utilizando a própria coluna cdempresa.
model/Filial.php

public function relations()  
{
    return array(
        'empresa' => array(self::BELONGS_TO, 'Empresa', 'cd_empresa'),
    );
}

Por enquanto é só, no próximo post vou explicar como resgatar esses valores.

Caso você queira baixar o exemplo como está clique aqui, o banco está dentro da pasta data, não se esqueça de alterar as configurações no main.php

Yii – Wform Unexpected T_PAAMAYIM_NEKUDOTAYIM

Se alguém já utilizou a extensão wForm, talvez você se dapare com o erro acima, porém, só se você estiver utilizando uma versão inferior à 5.3 do php.

Utilizando a palavra static no manual do php:

A partir do PHP 5.3.0, é possível referenciar a classe usando uma variável. O valor da variável não pode ser uma palavra-chave (eg self, parent e static).

A razão para o erro é simplesmente porque a sintaxe não é suportada em versões menores do php do que à 5.3.

Você pode alterar a extensão para utilizar ReflectionClass.

<?php  
    class Teste {
        const UM = "Número UM";
        const DOIS = "Número DOIS";
    }

    $obj = new ReflectionClass("Teste");
    echo $obj->getconstant("UM")."\n";
    echo $obj->getconstant("DOIS")."\n";

?>

Saída (output):

Número UM
Número DOIS

Você também poderia escrever um método que retorna o valor correto, sem a necessidade de criar uma instância do objeto, e, por ser genérico, pode ser facilmente copiado e colado para as constantes

<?php  
class TESTE {  
    var $CONSTANTE = 1;
    function CONSTANTE() { $vars = get_class_vars(__CLASS__); return $vars[strToUpper(__FUNCTION__)]; }
}

echo TESTE::CONSTANTE();  
?>

Mas com certeza a alternativa mais rápida e indolor, é – se possível – atualizar o php para 5.3, pelo menos.

Yii – It is not safe to rely on the system’s timezone settings

Quando upamos um sistema novo para um determinado servidor, apareceu o seguinte warning.

getdate() [function.getdate]: It is not safe to rely on the system’s timezone settings. You are required to use the date.timezone setting or the datedefaulttimezoneset() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected ‘America/SaoPaulo’ for ‘BRT/-3.0/no DST’ instead

Esse warning aparece geralmente quando temos em nosso php.ini o error level atribuído como strict.

Para resolver abra o index.php da raiz do seu sistema.

e troque de:

Yii::createWebApplication($config)->run();  

Para:

$app = Yii::createWebApplication($config);
Yii::app()->setTimeZone("America/Sao_Paulo");  
$app->run();

Yii – TDD problema com twitter bootstrap

Eu tenho um projeto Yii usando a extensão mais recente do twitter bootstrap, e ao tentar executar testes unitários, um erro foi exibido:

[adlersd@localhost tests]$ phpunit functional/SiteTest.php
PHP Fatal error:  Uncaught exception 'CException' with message 'CAssetManager.basePath "/usr/bin/assets" is invalid. Please make sure the directory exists and is writable by the Web server process.' in /home/adlersd/www/testes/framework/web/CAssetManager.php:128  
Stack trace:  
#0 /home/adlersd/www/testes/framework/web/CAssetManager.php(113): CAssetManager->setBasePath('/usr/bin/assets')
#1 /home/adlersd/www/testes/framework/web/CAssetManager.php(235): CAssetManager->getBasePath()
#2 /home/adlersd/www/testes/sistema/protected/extensions/bootstrap/components/Bootstrap.php(328): CAssetManager->publish('/home/adlersd/w...', false, -1, false)
#3 /home/adlersd/www/testes/sistema/protected/extensions/bootstrap/components/Bootstrap.php(103): Bootstrap->getAssetsUrl()
#4 /home/adlersd/www/testes/sistema/protected/extensions/bootstrap/components/Bootstrap.php(84): Bootstrap->registerCoreCss()
#5 /home/adlersd/www/testes/framework/base/CModule.php(388): Bootstrap->init()
#6 /home/adlersd/www/testes/framework/base/CModule.php(493): CModule- in /home/adlersd/www/testes/framework/web/CAssetManager.php on line 128

O que precisamos fazer é não carregar o twitter bootstrap quando estivermos rodando via console.

'preload'=>array(  
    'log',
    php_sapi_name() !== 'cli' ?'bootstrap': '',
),

© 2017 Adler Dias

Theme by Anders NorénUp ↑