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

AngularJs – Como começar se já sei jQuery

1. Não projete a sua página, para depois alterá-la com manipulações DOM

Em jQuery, você cria uma página, e então você começa a torná-la dinâmica. Isso ocorre porque o jQuery foi projetado para o acréscimo e tem crescido incrivelmente partir dessa premissa simples.

Por outro lado, em AngularJS, você deve começar a partir do zero, com sua arquitetura em mente. Em vez de começar por pensar “eu tenho esse pedaço do DOM e eu quero fazê-lo fazer X”, você tem que começar com o que você quer realizar, em seguida, ir sobre o projeto de sua aplicação, e, finalmente, ir sobre o projeto de seu ponto de vista.

2. Não aumente jQuery com AngularJS

Da mesma forma, não comece com a idéia de que o jQuery faz X, Y e Z, por isso vou adicionar AngularJS em cima disso para os models e controllers. Isso é muito tentador quando você está apenas começando, e é por isso que sempre é recomendo que os novos desenvolvedores AngularJS não utilizem jQuery em tudo, pelo menos até que se acostumarem a fazer as coisas da “Jeito Angular”.

É muito mais comum do que se pensa ver desenvolvedores criando soluções elaboradas com plugins jQuery de 150 ou 200 linhas de código que, depois, colá-las em AngularJS com uma coleção de callbacks e $applys que são confusas e complicadas, mas, eles eventualmente vão conseguir fazer isso funcionar! O problema é que na maioria dos casos esse plugin jQuery poderia ser reescrito em AngularJS em uma fração do código, onde de repente tudo se torna compreensível e simples.

A questão de fundo é esta: quando você está projetando uma solução, primeiro “pense em AngularJS”, se você não pode pensar em uma solução, faça uma pesquisa no google, vá em busca de ajuda na comunidade, se depois de tudo isso, você não conseguir encontrar uma solução, então sinta-se livre para utilizar o jQuery. Mas não deixe que jQuery tornar-se uma muleta ou você nunca vai dominar AngularJS.

3. Sempre pense em termos de arquitetura

Em primeiro lugar saber que as aplicações de uma única página são aplicações. Eles não são páginas da web. Então, precisamos pensar como um desenvolvedor do lado do servidor(server-side), além de pensar como um desenvolvedor do lado do cliente(client-side). Temos que pensar em como dividir a nossa aplicação, em, componentes testáveis, extensíveis e individuais.

Então, como você faz isso? Como você “pensa em AngularJS”? Aqui estão alguns princípios gerais, em contraste com jQuery.

3.1 O ponto de vista é o “official record

Em jQuery, nós programaticamente alteramos nossa view. Poderíamos ter um menu dropdown definido como um ul assim:

<ul class="main-menu">  
    <li class="active">
        <a href="#">Home</a>
    </li>
    <li>
        <a href="#">Menu 1</a>
        <ul>
            <li><a href="#">Submenu 1</a></li>
            <li><a href="#">Submenu 2</a></li>
            <li><a href="#">Submenu 3</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Menu 2</a>
    </li>
</ul>  

Em jQuery, em nossa lógica de aplicação, gostaríamos de ativá-lo com algo como:

$('.main-menu').dropdownMenu();

Quando nós basta olhar para o ponto de vista, não é imediatamente óbvio que há alguma funcionalidade aqui. Para pequenas aplicações, isso é bom. Mas para aplicações não-triviais, as coisas rapidamente se confuso e difícil de manter.

Em AngularJS, porém, a vista é o official record de funcionalidade view-based. Nossa declaração ul ficaria assim:

<ul class="main-menu" dropdown-menu>  
    ...
</ul>  

Estes dois fazem a mesma coisa, mas na versão AngularJS alguém olhando para o model sabe o que é deverá acontecer. Sempre que um novo membro da equipe de desenvolvimento vem a bordo, ela pode olhar para isso e, em seguida, saber que existe uma diretiva chamado DropDownMenu operando nela, ela não precisa intuir a resposta certa ou vasculhar todo o código. A visão nos disse o que era suposto acontecer. Muito mais limpo.

Desenvolvedores novos em AngularJS muitas vezes perguntam algo como: como faço para encontrar todos os links de um tipo específico e adicionar uma diretiva sobre eles. O desenvolvedor é sempre espantado quando têm sua pergunta respondida: Você não… Mas a razão pela qual você não deve fazer isso é que isto é como meia jQuery, meio-AngularJS, e não é bom. O problema aqui é que o desenvolvedor está tentando “fazer jQuery” no contexto da AngularJS. Isso nunca vai funcionar bem. A vista é o official record. Fora de uma diretiva (mais sobre isso abaixo), você nunca, nunca, nunca muda o DOM. E as diretrizes são aplicadas na visão, por isso a intenção é clara.

Lembre-se: não se projetar, e, em seguida, marcar. Você deve arquiteto e design.

3.2 Data binding

Esta é, de longe, uma das características mais impressionantes do AngularJS e diminui muito a necessidade de fazer o tipo de manipulações DOM que foram mencionadas na seção anterior. AngularJS irá atualizar automaticamente a sua visão, logo, você não precisa fazer isso! Em jQuery, nós devemos esperar a resposta de um evento e, em seguida atualizar o conteúdo. Algo como:

$.ajax({
  url: '/myEndpoint.json',
  success: function ( data, status ) {
    $('ul#log').append('<li>Data Received!</li>');
  }
});

Para a view teríamos algo parecido com:

<ul class="messages" id="log">  
</ul>  

Além das interesses de mistura, também temos os mesmos problemas de significar a intenção que foram mencionadas anteriormente. Mas o mais importante, tivemos a referência e atualizar um nó DOM manualmente. E se quisermos eliminar uma entrada de log, temos de código contra o DOM para isso também. Como é que podemos testar a lógica além do DOM? E se nós queremos mudar a apresentação?

Este pouco confuso e um pouco frágil. Mas, em AngularJS, podemos fazer isso dessa maneira:

$http( '/myEndpoint.json' ).then( function ( response ) {
    $scope.log.push( { msg: 'Data Received!' } );
});

Para a view teríamos algo parecido com:

<ul class="messages">  
    <li ng-repeat="entry in log">{{ entry.msg }}</li>
</ul>  

Mas, também poderíamos ter a view dessa maneira:

<div class="messages">  
    <div class="alert" ng-repeat="entry in log">
        {{ entry.msg }}
    </div>
</div>  

E agora, em vez de utilizarmos uma lista não ordenada, estamos usando caixas de alerta do Twitter Bootstrap. E nunca se fez necessária qualquer alteração no código do controlador! Mas o mais importante, não importa onde ou como o registro é atualizado, a visão vai mudar também. Automaticamente.

Embora não tenha exibido o mesmo aqui, a ligação de dados é bidirecional. Então, essas mensagens de log também podem ser editadas na exibição apenas fazendo isso:

<input ng-model="entry.msg" />  

3.3 Camada modelo distinto

Em jQuery, o DOM é uma espécie de model. Mas, em AngularJS, temos uma camada de modelo separada que nós podemos controlar de qualquer forma que for necessária, de forma completamente independente do ponto de vista. Isso ajuda para a ligação de dados acima, mantém a separação de interesses, e apresenta muito maior testabilidade.

3.4 Separação de interesses

Você deve manter as seus interesses em separado. Sua visão funciona como o official record do que é deve acontecer (pelo menos, na maioria das vezes), o model representa os dados, você tem uma camada de serviço para executar tarefas reutilizáveis; você faz manipulação DOM e acrescenta a sua view com as diretivas, e você junta tudo isso com os controladores.

A única coisa que se faz necessário acrescentar pertence à testabilidade, que será discutida em outro trecho mais abaixo.

3.5 Injeção de Dependência

Para nos ajudar com separação de interesses é a injeção de dependência(DI). Se você vem de uma linguagem server-side (de Java à PHP), você provavelmente está familiarizado com este conceito já, mas se você é um cara do lado do cliente vindo de jQuery, este conceito pode parecer bobo. Mas não é.

A partir de uma perspectiva ampla, DI significa que você pode declarar componentes muito livremente e, em seguida, a partir de qualquer outro componente, basta pedir uma instância dele e ele vai será concedido. Você não tem que saber sobre a ordem de carregamento, ou locais de arquivo, ou qualquer coisa assim. O poder não pode ser imediatamente visível, um exemplo comum seria teste.

Vamos dizer que em sua aplicação, precisamos de um serviço que implementa o armazenamento do lado do servidor por meio de uma API REST e, dependendo do estado do aplicativo, armazenamento local também. Ao executar testes em nossos controladores, não quero ter que comunicar com o servidor – estamos testando o controlador, depois de tudo. Nós podemos apenas adicionar um serviço de simulação do mesmo nome do nosso componente original, e, o injector irá garantir que o nosso controlador recebe um falso automaticamente – o nosso controlador não precisa e não saberá a diferença.

4 Desenvolvimento dirigido a testes (TDD) – Sempre

É impossível negar que isso é realmente parte da seção 3, na arquitetura, mas é tão importante que é melhor ser colocado em sua própria seção.

Fora de todos os muitos plugins jQuery que você já viu, utilizou, ou escreveu, quantos deles tinham um conjunto de testes que o acompanha? Não muitos, porque jQuery não é muito favorável a isso. Mas AngularJS é.

Em jQuery, a única maneira de testar é muitas vezes para criar o componente de forma independente com uma página de amostra / demo contra o qual nossos testes pode executar a manipulação de DOM. Então nós temos que desenvolver um componente separadamente e depois integrá-lo em nossa aplicação. Assim, grande parte do tempo, ao desenvolver com jQuery, optamos por uma maneira de desenvolvimento iterativa, em vez de dirigida a testes(tdd).

Mas porque temos separação de interesses, podemos fazer o desenvolvimento orientado a testes de forma iterativa em AngularJS! Por exemplo, vamos dizer que queremos uma diretiva super-simples para indicar nosso menu que nossa rota atual. Podemos declarar o que queremos em nossa view:

<a href="#" when-active>Hello</a>  

Agora você pode escrever o seu teste:

it( 'should add "active" when the route changes', inject(function() {  
    var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope );

    $location.path('/not-matching');
    expect( elm.hasClass('active') ).toBeFalsey();

    $location.path( '/hello' );
    expect( elm.hasClass('active') ).toBeTruthy();
}));

Rodando o teste podemos confirmar que ele falha. Então agora podemos escrever a nossa diretiva:

.directive( 'whenActive', function ( $location ) {
    return {
        scope: true,
        link: function ( scope, element, attrs ) {
            scope.$on( '$routeChangeSuccess', function () {
                if ( $location.path() == element.attr( 'href' ) ) {
                    element.addClass( 'active' );
                }
                else {
                    element.removeClass( 'active' );
                }
            });
        }
    };
});

Agora o teste passa e nosso menu executa conforme solicitado. Nosso desenvolvimento é iterativo e dirigido a testes.

5. Conceitualmente, as diretivas não são “pacotes” jQuery

Você vai ouvir muitas vezes “só fazem manipulação DOM em uma diretiva”. Isso é uma necessidade. Então, trate com o devido respeito!

Algumas diretivas apenas decoram o que já está na view (pense em ngClass) e, portanto, às vezes, fazer manipulação DOM imediatamente e, em seguida, são basicamente feito. Mas, se a diretiva é como um “widget” e tem um model, ele também deve respeitar a separação de interesses. Ou seja, o model também deve permanecer em grande parte independente da sua implementação nos links e controllers.

AngularJS vem com um conjunto completo de ferramentas para fazer isto muito facilmente, com ngClass podemos atualizar dinamicamente a classe; ngBind permite a ligação de dados bidirecional; ngShow e ngHide programaticamente mostrar ou ocultar um elemento, e muitos mais – inclusive os que escrevemos nós mesmos. Em outras palavras, nós podemos fazer todos os tipos de grandiosidade sem manipulação DOM. Quanto menos manipulação DOM, será mais fácil testar as suas diretivas, será mais fácil estilizá-las, será mais fácil de modificá-las no futuro, e mais re-utilizável e distribuível eles são.

É fácil perceber que muitos desenvolvedores novos em AngularJS utilizando diretivas como o lugar para jogar um monte de jQuery. Em outras palavras, eles pensam “já que eu não posso fazer manipulação DOM no controlador, eu vou levar esse código colocá-lo numa diretiva”. Enquanto que, certamente, é muito melhor, muitas vezes é ainda errado.

Pense no logger da seção 3. Mesmo que você coloque isso em uma diretiva, você ainda pode fazê-lo do “jeito Angular”. Ele ainda não requer qualquer manipulação DOM! Há muitas ocasiões em que a manipulação DOM é necessária, mas é muito mais raro do que você pensa! Antes de fazer a manipulação DOM em qualquer lugar em seu aplicativo, pergunte-se se você realmente precisa. Pode haver uma maneira melhor.

Aqui está um exemplo que mostra o padrão que possivelmente será encontrado com mais freqüência. Queremos um botão alternável. Em tempo, este exemplo é um pouco artificial para representar os casos mais complicados, que são resolvidos da mesma maneira.

.directive( 'myDirective', function () {
    return {
        template: '<a class="btn">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            var on = false;

            $(element).click( function () {
                if ( on ) {
                    $(element).removeClass( 'active' );
                }
                else {
                    $(element).addClass( 'active' );
                }

                on = !on;
            });
        }
    };
});

Existem algumas coisas de errado com isso. Primeiro, jQuery nunca foi necessário. Não há nada que fiz aqui que precisava jQuery! Segundo, mesmo que já utilizássemos jQuery na nossa página, não há nenhuma razão para usá-lo aqui, podemos simplesmente utilizar o angular.element e nosso componente continuará funcionando mesmo em um projeto que não tem jQuery. Em terceiro lugar, mesmo assumindo jQuery foi necessário para a presente directiva para o trabalho, jqLite(angular.element) sempre utilizará jQuery se ele foi carregado! Então, não precisamos usar o $ – podemos apenas usar angular.element. O quarto lugar, está intimamente relacionado com a terceiro, é que os elementos jqLite não necessitam estar envolvidos com $ – o elemento que é passado para a função de ligação já constituiria um elemento jQuery! E quinto, que já foi mencionado em seções anteriores, porque estamos misturando coisas do modelo em nossa lógica?

Esta diretiva poderia ser reescrita de maneira mais simples, assim:

.directive( 'myDirective', function () {
    return {
        scope: true,
        template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            scope.on = false;

            scope.toggle = function () {
                scope.on = !$scope.on;
            };
        }
    };
});

Mais uma vez, tudo que for modelo deve ficar no modelo, de modo que você (ou seus usuários) pode facilmente trocá-lo por um que atenda a qualquer estilo necessário, e a lógica nunca teve de ser tocado. Reutilização.

E ainda há todos os outros benefícios, como o teste – é fácil! Não importa o que está no modelo, API interna da diretiva nunca é tocada, dessa maneira a refatoração é fácil. Você pode alterar o modelo, tanto quanto você quiser, sem tocar na directiva. E não importa o que você mude, seus testes ainda passar.

Então, se diretivas não são apenas coleções de funções jQuery, quais são eles? Diretivas são extensões de HTML. Se o HTML não fizer algo que você precisa fazer, você escreve uma diretiva para fazer isso por você e, em seguida, passa a utilizá-la como se fosse parte de HTML.

Colocando isso de outra maneira, se AngularJS não fazer algo, pense em como a equipe iria realizá-lo para combinar perfeitamente com ngClick, ngClass…

escreva um algoritmo que calcule a tensão elétrica

Esse post veio do comentário do Marcos, que propôs um algoritmo para calcular a tensão elétrica.

Ele pediu esse algoritmo para calcular a tensão elétrica, em nosso exemplo o usuário vai entrar com dois parâmetros:

  1. resistência elétrica
  2. corrente elétricas

Depois de digitar esses parâmetros, o programa vai guardá-los em duas variáveis do tipo float. Uma vez com os valores guardados em suas respectivas variáveis, vamos efetuar o cálculo.

Mas como eu calculo a tensão elétrica?

Para fazermos esse algoritmo precisamos saber a fórmula da tensão elétrica. É perfeitamente possível calcular a tensão elétrica de um circuito tendo as grandezas de corrente e resistência, a fórmula é:

V = I.R Onde:
V = tensão elétrica
I = corrente elétrica
R = resistência elétrica

<pre lang="c" line="1">  
#include <stdio.h>

int main(int argc, char **argv[]){  
    float corrente, resistencia;
    printf("Digite o valor da corrente: ");
    scanf("%f", &corrente);
    printf("Digite o valor da resistencia: ");
    scanf("%f", &resistencia);
    printf("A tensão elétrica para uma corrente de %.2f e uma resistencia de %.2f é %.2f",corrente,resistencia,corrente*resistencia);
    return 0;
}

Em Python:

#!/usr/bin/env python

def main():  
    corrente = input("Digite o valor da corrente: ")
    resistencia = input("Digite o valor da resistencia: ")
    print "O valor da tensão é: " , corrente*resistencia

if __name__ == "__main__":  
    main()

qualquer dúvida ou se você quiser propor um algoritmo é só deixar um comentário!

abraço,
até…

Como procurar uma coluna em todo banco de dados

Algumas vezes é preciso buscar em todo o banco de dados, por uma – ou várias – coluna(s). Quando você precisar procurar por um nome de uma coluna que poderia estar em qualquer tabela em um banco de dados saiba que existe uma maneira rápida e fácil de encontrar essa informação, e a sintaxe é:

Como procurar por uma coluna

SELECT DISTINCT TABLE_NAME  
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE COLUMN_NAME = 'colunaNomeA'
        AND TABLE_SCHEMA='SeuBancoDeDados';

Como procurar por uma coluna por parte do nome

SELECT DISTINCT TABLE_NAME  
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE COLUMN_NAME LIKE 'coluna%'
        AND TABLE_SCHEMA='SeuBancoDeDados';

Como procurar por várias colunas

SELECT DISTINCT TABLE_NAME  
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE COLUMN_NAME IN ('colunaNomeA','ColunaNomeB')
        AND TABLE_SCHEMA='SeuBancoDeDados';

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.

Algoritmo que calcule a média aritmética, harmônica, geométrica

No post de hoje você vai ser capaz de desenvolver um algoritmo que leia 3 valores: a, b, c e calcule e escreva a média aritmética, harmônica, geométrica correspondente, esse algoritimo foi pedido por comentário no final do ano passado, mas eu não tive tempo hábil para fazê-lo, depois acabou caindo no esquecimento e só olhei novamente os comentário quando eu migrei o blog para o novo domínio.

As definições eu vou pegar empresado às definições da wikipedia, assim, é mais rápido para concluir o post, afinal, o que importa é o algoritimo mesmo.

Média harmônica

Em Matemática, a média harmônica é um dos vários métodos de calcular uma média.
A média harmônica dos números reais positivos x1,…,xn é definida como sendo o número de membros dividido pela soma do inverso dos membros… continua aqui.

Média aritmética

A média aritmética simples é a mais utilizada no nosso dia-a-dia. É obtida dividindo-se a soma das observações pelo número delas… continua aqui.

Média geométrica

A Média geométrica é definida como o produto de todos os membros do conjunto elevado ao inverso do número de membros… continua aqui.

C, calcule e escreva a média aritmética, harmônica, geométrica:

/*
 * Escrever um algoritmo que leia 3 valores: a, b, c e calcule e escreva a média aritmética, 
 * harmônica, geométrica correspondente.
 */
#include <stdio.h>
#include <math.h>

int main(int argc, char **argv[]){  
    float numero1, numero2, numero3;
    printf("Digite o primeiro numero: ");
    scanf("%f", &numero1);
    printf("Digite o segundo numero: ");
    scanf("%f", &numero2);
    printf("Digite o terceiro numero: ");
    scanf("%f", &numero3);
    printf("Média aritmética: %.2f\n", ((numero1+numero2+numero3)/3));
    printf("Média harmônica: %.2f\n", (3/((1/numero1)+(1/numero2)+(1/numero3))));
    printf("Média geométrica: %.2f\n", pow(numero1*numero2*numero3, 1.0/3.0));
    return 0;
}

Java, calcule e escreva a média aritmética, harmônica, geométrica:

/*
 * Escrever um algoritmo que leia 3 valores: a, b, c e calcule e escreva a média aritmética,
 * harmônica, geométrica correspondente.
 */
import java.io.* ;  
class Pedido01 {  
    public static void main(String args[])
    {
        Console console = System.console();

        String numero1 = null;
        String numero2 = null;
        String numero3 = null;

        numero1 = console.readLine("Digite o primeiro numero: ");
        numero2 = console.readLine("Digite o segundo numero: ");
        numero3 = console.readLine("Digite o terceiro numero: ");

        System.out.println("Média aritmética: " + ((Float.parseFloat(numero1)+Float.parseFloat(numero2)+Float.parseFloat(numero3))/3));
        System.out.println("Média harmônica: " + (3/((1/Float.parseFloat(numero1))+(1/Float.parseFloat(numero2))+(1/Float.parseFloat(numero3)))));
        System.out.println("Média geométrica: " + Math.pow(Float.parseFloat(numero1)*Float.parseFloat(numero2)*Float.parseFloat(numero3), 1.0/3.0));
    }
}

Como instalar o PHPUnit no Ubuntu 12.10

Seguindo o post de hoje você vai ser capaz de o instalar o PHPUnit no Ubuntu 12.10. Antes de mais nada, você precisa garantir que o seu sistema está atualizado.

Como eu atualizo meu sistema?

Para isso basta você executar no terminal o seguinte comando:

$ sudo apt-get update
$ sudo apt-get upgrade

Agora, para que você possa instalar o PHPUnit no Ubuntu 12.10, devemos atualizar e adicionar alguns canais no pear.

O que diabos é PEAR?

Para explicar de maneira rápida e consistente, além de economizar algum tempo, resolvi roubar a definição da Wikipedia mesmo.

PEAR (PHP Extension and Aplication Repository) é uma plataforma e um sistema de distribuição para a codificação de componentes em PHP. O projecto foi fundado em 1999 por Stig S. Bakken para promover a reutilização de código.

Entendi, mas, como eu adiciono esses tais canais no pear?

Para fazer isso, basta que você execute os comando abaixo no terminal.

$ sudo pear channel-update pear.php.net
$ sudo pear channel-discover pear.phpunit.de
$ sudo pear channel-discover components.ex.no
$ sudo pear channel-discover symfony-project.com
$ sudo pear channel-discover pear.symfony.com

Apenas para informações adicionais dos canais acima:

pear.php.net

É o repositório oficial do PHP.

pear.phpunit.de

É o repositório oficial do PHPUnit.

components.ex.no

Segundo o próprio site, Com eZ Components, os desenvolvedores não tem que reinventar a roda, em vez disso eles podem concentrar-se em resolver as necessidades específicas do cliente. Por favor, encontrar mais informações sobre por que você deve usar o eZ

pear.symfony.com

É o repositório oficial do Symfony.

Como atualizar os pacotes do PEAR?

Agora você precisa atualizar todos os pacotes que você já possui intalado em seu pear.

$ sudo pear upgrade -all

Uma vez terminada a atualização de todos os pacotes, você já pode instalar o PHPUnit.

Como instalar o PHPUnit?

$ sudo pear install --alldeps phpunit/PHPUnit

Pronto, agora que você já instalou o PHPUnit é só você começar a se divertir com TDD.

Opa, espera um pouco. Como eu faço pra saber que o PHPUnit está funconando?

É uma dúvida bastante razoável, e para testar tão simples quanto tudo até agora. É só você executar o próximo comando em seu terminal:

$ phpunit --version

No seu terminal vai aparecer uma linha como a seguinte:

PHPUnit 3.7.10 by Sebastian Bergmann.

Como criar virtual host no ubuntu 12.10

Para o post de hoje eu assumo que você sabe instalar, ou, já tem instalado uma configuração LAMP em sua máquina Ubuntu, logo, eu vou descrever os passos para criar virtual host no ubuntu 12.10 (Quantal Quetzal), você pode criar uma para cada site que você está develvlvendo ou estudando.

Você vai ensinar a instalar o apache com php?

Não. Eu presumo também que você tenha o módulo mod_rewrite do Apache ativado. Mas, para ativar esse módulo o processo é bastante simples, é só você executar a linha abaixo no seu console:

$ sudo a2enmod rewrite

Mas, por onde eu começo?

Para continuar o processo de criação de nossos virtual host’s, você deve criar algumas pastas em seu Document root, que é a raiz do seu servidor web, no Ubuntu 12.10, ele está localizado em /var/www. Nesse guia vamos criar dois virtual host’s neste tutorial que estamos criando três locais: dev.teste, dev.producao. Você pode, facilmente, criar esses diretórios através do terminal digitando:

$ sudo mkdir /var/www/teste
$ sudo mkdir /var/www/producao

Como eu faço pra saber se o diretório já está funcioando?

Você pode criar um arquivo index.html em cada uma dessas pastas apenas para fins de teste. Utilizando os comandos:

$ sudo echo "<h1>Ambiente de teste!</h1>" > /var/www/teste/index.html
$ sudo echo "<h1>Ambiente de teste!</h1>" > /var/www/producao/index.html

Agora, se você abrir o seu navegador com as URL’s http://localhost/teste ou etc http://localhost/producao você será capaz de ver as respectivas páginas HTML que criamos ainda pouco. Mas, meu objetivo aqui é que você seja capaz de carregar esses endereços em seu navegador utilizando URL’s diferentes, como por exemplo: http://dev.teste/ ou http://dev.producao.

Então, agora vamos criar os virtual host’s?

Exatamente, então, vamos colocar a mão na massa. É realmente um processo muito simples.

  1. Adicione seus sites no arquivo /etc/hosts

Você precisa abri-lo como root com o editor de texto de sua escolha. Eu vou usar nano, apresar de preferir o vim:

$ sudo nano /etc/hosts

Você vai possuir pelo menos uma linha em seu arquivo hosts, o meu está assim:

127.0.0.1    localhost  
127.0.1.1    insomniac

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet  
ff00::0 ip6-mcastprefix  
ff02::1 ip6-allnodes  
ff02::2 ip6-allrouters  

Repare na primeira linha, ela configura o seu endereço localhost para o seu endereço de IP local. Você precisa criar outra linha, para os nosos dois virtual host’s. Logo, você deve deixar o seu arquivo hosts desse jeito:

127.0.0.1    localhost  
127.0.1.1    insomniac  
127.0.0.1     dev.teste dev.producao

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet  
ff00::0 ip6-mcastprefix  
ff02::1 ip6-allnodes  
ff02::2 ip6-allrouters  

Basicamente isso diz que quando você acessar a url dev.teste, por exemplo, ele vai resolver localmente, utilizando o seu servidor web local.

  1. Hora de configurar o Apache
    Agora você precisa alterar as configurações do seu apache, para que o Apache saiba o que fazer para os seus novos sites. Se você listar o conteúdo da pasta /etc/apache2/sites-available você verá dois arquivos lá: default e default-ssl. Você precisa adicionar os arquivos correspondentes aos virtual host’s que você deseja criar aqui, com seus respectivos conteúdos. Logo, a partir do terminal:
$ sudo nano /etc/apache2/sites-available/dev.teste

Neste novo arquivo, digite o código a seguir, lembrando-se de substituir todas as referências a dev.teste com o nome do seu próprio virtual host.

    <VirtualHost *:80>
        DocumentRoot /var/www/teste
        ServerName dev.teste
    </VirtualHost>

Esse conjunto de diretrizes configura o nosso virtual host:
Abrimos a tag VirtualHost e isso diz ao apache para ficar escutando na porta 80.
A diretiva DocumentRoot diz apache onde os arquivos do seu site estão armazenados.
ServerName é o nome do seu virtual host, ou seja, a url que irá utilizar para navegar no site.

Você pode adicionar apelidos (alias), mas este é o mais simples. Existem outras diretivas que você pode adicionar a este arquivo, tais como, informações de log de ​​erro, e-mail do administrador e etc…

Agora que tudo já foi explicado, crie um arquivo para o virtual host dev.producao.

  1. Habilitando os virtual hosts e reiniciando o Apache

Agora que você criou o arquivo, é necessário habilitá-los no Apache utilizando o comando a2ensite. No terminal, digite:

$ sudo a2ensite dev.teste

Você deverá receber uma mensagem de que o virtual host foi habilitado, e um aviso para reiniciar o apache. Se você tem mais de um virtual host para habilitar, você pode continuar utilizando o a2ensite para habilitá-lo(s), antes de reiniciar o apache. Quando estiver tudo pronto, você pode reiniciar o apache para que as alterações comecem a funcionar. Para reiniciar o apache, no terminal, digite:

$ sudo /etc/init.d/apache2 reload