PHP + TDD + “Generics”

16 11 2007

(Observação: Poucas coisas são tão terríveis quanto se formatar um post nesse wordpress. Todos devem ficar assim…)

“Test-Driven Development (TDD) is a software development technique consisting of short iterations where new test cases covering the desired improvement or new functionality are written first, then the production code necessary to pass the tests is implemented, and finally the software is refactored to accommodate changes. The availability of tests before actual development ensures rapid feedback after any change. Practitioners emphasize that test-driven development is a method of designing software, not merely a method of testing.”

Acredito que já conheçam TDD. O que ??? Não conhece ??? Ok, ótima hora para conhecer:

Wikipedia – TDD
Introduction to Test Driven Design (TDD)
testdriven.com
improveit – tdd
phpunit

Não será eu quem vai dizer os benefícios de se utilizar TDD, até porque não é o objetivo deste post. Vamos ver de forma bem rápida como podemos criar uma classe que tem o comportamente semelhante a generics em java. E, novamente, não será eu que vai dizer o que são generics.

1 – Os testes
Vejamos um simples teste, com os comentários:

TestGenericList.php

<?php

include 'g.class.php';
include 'invalidgenerictypeexception.class.php';
include 'genericlist.class.php';

// Classe que servira de modelo e teste para o que
// vamos desenvolver
class TestGenericList extends PHPUnit_Framework_TestCase {

    /**
    * Testa o tamanho da nossa lista
    */
    public function testSize()
    {
        // instanciamos a GenericList
        // o parametro que vamos passar indica
        // o tipo de lista que teremos, neste
        // caso uma lista de Integer (int)
        // List l = new List<Integer>()
        $list = new GenericList(G::Integer);
        $list->add(1); // adicionamos um item...
        $list->add(2); // ... e mais um ...
        $list->add(3); // ... e mais um, totalizando tres
        
        // testamos se o tamanho da nossa lista
        // realmente eh igual a 3
        $this->assertEquals($list->size(), 3);
    }

    /*
        Note que ao final do teste acima, ja temos pelo menos
        tres metodos e duas propriedades definidas:
        __construct($param):
            O construtor da classe, vai receber pelo menos um
            parametro, indicando o tipo de lista. $param
            devera ser salvo para uso futuro, entao teremos
            uma propriedade na classe para isso
        add($param):
            Adiciona um item na lista. Assim como o parametro
            passado no construtor, deveremos salvar este parametro
            para uso futuro. Entao teremos mais uma propriedade na 
            classe
        size():
            Retorna o tamanho da lista. Provavelmente vai retornar
            a quantidade de itens adicionados em add($param), que
            estao armazenados em uma propriedade da classe, que
            certamente sera um array. Pensando em sizeof() ?
    */

    /**
    * Testa uma lista de Integers se aceita adicao
    * de somente novos integers
    */
    public function testAddSameTypeInteger()
    {
        // criamos uma lista de integers
        $list = new GenericList(G::Integer);
        // adicionamos um integer
        $list->add(1);
        // se tudo deu certo, teremos um integer
        // na lista e ela vai ter um tamanho = 1
        $this->assertEquals($list->size(), 1);
    }
    
    /**
    * Testa uma lista de Strings se aceita adicao
    * de somente novas Strings
    */
    public function testAddSameTypeString()
    {
        // criamos uma lista de Strings
        $list = new GenericList(G::String);
        // adicionamos uma string
        $list->add('foo');
        // se tudo deu certo, teremos uma string
        // na lista e ela vai ter um tamanho = 1
        $this->assertEquals($list->size(), 1);
    }

    /**
    * Testa uma lista de Doubles se aceita adicao
    * de somente novas Doubles
    */
    public function testAddSameTypeDouble()
    {
        // criamos uma lista de doubles
        $list = new GenericList(G::Double);
        // adicionamos um double
        $list->add(10.1);
        // e se tudo deu certo, teremos um double
        // na lista e ela vai ter um tamanho = 1
        $this->assertEquals($list->size(), 1);
    }
    
    /**
    * Testa uma lista de Objetos se aceita adicao
    * de somente novas Objetos DO MESMO TIPO. Ou seja
    * Uma lista de "new Pessoa()" deve aceitar adicao
    * de somente "new Pessoa()"
    */
    public function testAddSameTypeObject()
    {
        // criamos um objeto que servira de
        // modelo para a lista...
        $obj = new StdClass();
        
        // ...e um objeto que sera adiciona na lista.
        // Note que ambos sao instancias de StdClass
        $obj2 = new StdClass();
        
        // criamos uma lista de objetos do tipo
        // stdclass
        $list = new GenericList($obj);
        // e adicionamos um objeto a esta lista
        $list->add($obj2);
        // se tudo deu certo, teremos um objeto
        // na lista e ela vai ter um tamanho = 1
        $this->assertEquals($list->size(), 1);

        // novamente criamos uma lista de objetos do tipo
        // stdclass, mas desta vez ao inves de passar um objeto
        // da classe, passamos o tipo de objeto que a lista
        // ira conter
        $list = new GenericList(G::type('stdclass')); // same as above, but using class name
        // e adicionamos um objeto do tipo especificado a lista
        $list->add($obj2);
        // novamente, se tudo deu certo, teremos uma
        // lista de tamanho = 1, e testamos isso
        $this->assertEquals($list->size(), 1);
    }
    
    /**
    * Aqui iremos forcar um erro no teste
    * Vamos criar uma lista de Integer e tentar
    * adicionar outros tipos. Em todos os casos
    * diferentes do tipo original, devera ocorrer
    * um erro
    */
    public function testAddDifferentTypeInteger()
    {
        // criamos a lista de integer
        $list = new GenericList(G::Integer);

        // nos casos em que vamos utilizar algo diferente
        // de integer, utilizamos bloco try/catch. O
        // comportamento da lista sera de gerar uma
        // exception no caso de adicao de um tipo diferente

        try {
            // tentamos adicionar uma string na lista de
            // integer
            $list->add('foo');
            
            // nao deveria chegar neste ponto, visto que
            // na linha acima deveria ter sido gerada. Entao
            // dizemos ao phpunit que este teste falhou
            $this->fail('Generic Integer accepted a string');
        } catch (InvalidGenericTypeException $e) {}

        try {
            $list->add(2.1);
            $this->fail('Generic Integer accepted a double');
        } catch (InvalidGenericTypeException $e) {}

        try {
            $list->add(new StdClass());
            $this->fail('Generic Integer accepted a object');
        } catch (InvalidGenericTypeException $e) {}
    }
    
    /**
    * Aqui iremos forcar um erro no teste
    * Vamos criar uma lista de Strings e tentar
    * adicionar outros tipos. Em todos os casos
    * diferentes do tipo original, devera ocorrer
    * um erro
    */
    public function testAddDifferentTypeString()
    {
        // criamos a lista de strings
        $list = new GenericList(G::String);

        // e tentamos adicionar outros tipos, do mesmo
        // modo que fazemos no teste anterior
        
        try {
            $list->add(1);
            $this->fail('Generic String accepted a integer');
        } catch (InvalidGenericTypeException $e) {}

        try {
            $list->add(1.0);
            $this->fail('Generic String accepted a double');
        } catch (InvalidGenericTypeException $e) {}

        try {
            $list->add(new StdClass());
            $this->fail('Generic String accepted a object');
        } catch (InvalidGenericTypeException $e) {}
    }
    
    /**
    * Aqui tambem iremos forcar um erro no teste
    * Vamos criar uma lista de Double e tentar
    * adicionar outros tipos. Em todos os casos
    * diferentes do tipo original, devera ocorrer
    * um erro
    */
    public function testAddDifferentTypeDouble()
    {
        // criamos a lista de Double
        $list = new GenericList(G::Double);

        // e depois eh aquilo...

        try {
            $list->add(1);
            $this->fail('Generic Double accepted a integer');
        } catch (InvalidGenericTypeException $e) {}

        try {
            $list->add('foo');
            $this->fail('Generic Double accepted a string');
        } catch (InvalidGenericTypeException $e) {}

        try {
            $list->add(new StdClass());
            $this->fail('Generic Double accepted a object');
        } catch (InvalidGenericTypeException $e) {}
    }
    
    /**
    * Novamente o erro.
    * Vamos criar uma lista de Objetos e tentar
    * adicionar outros tipos. Em todos os casos
    * diferentes do tipo original, devera ocorrer
    * um erro
    */
    public function testAddDifferentTypeObject()
    {
        $obj = new StdClass();
        // criamos uma lista de objetos StdClass
        $list = new GenericList($obj);

        try {
            // nao podemos adicionar Integer
            $list->add(1);
            $this->fail('Generic Object accepted a integer');
        } catch (InvalidGenericTypeException $e) {}

        try {
            // e nem String
            $list->add('foo');
            $this->fail('Generic Object accepted a string');
        } catch (InvalidGenericTypeException $e) {}

        try {
            // nem double
            $list->add(2.5);
            $this->fail('Generic Object accepted a double');
        } catch (InvalidGenericTypeException $e) {}

        try {
            // e nem outro tipo de objeto, neste caso uma
            // Exception
            $list->add( new Exception());
            $this->fail('Generic Object accepted a Exception');
        } catch (InvalidGenericTypeException $e) {}

        // mesma ideia, mas utilizando uma lista criada
        // com o nome da classe
        $obj = new StdClass();
        $list = new GenericList(G::type('stdclass'));

        try {
            $list->add(1);
            $this->fail('Generic Object accepted a integer');
        } catch (InvalidGenericTypeException $e) {}

    }
    
    /**
    * Neste teste vamos verificar se a nossa lista
    * aceita um objeto de uma classe diferente da
    * classe definida como o tipo da lista, mas sendo
    * uma classe "extendida":
    *
    * class Car{}
    * class Ferrari extends Car {}
    *
    * criamos uma lista de Car e tentaremos adicionar
    * uma Ferrari
    */
    public function testAddParentClassObject()
    {
        // criamos 2 classes em runtime
        // php nao aceita criacao de nested classes
        eval('class Car {}');
        eval('class Ferrari extends Car {}');
        
        $obj = new Car();
        // criamos uma lista de Car, da mesma forma que criavamos
        // anteriormente. Entao se tentarmos adicionar uma Ferrari
        // na lista, devera ser gerada uma exception
        $list = new GenericList($obj); // creates a Car's list

        try {
            // adicionar uma Ferrari em uma lista de Car
            // deve gerar uma exception
            $list->add(new Ferrari()); // must throw a exception
            $this->fail('Generic Object accepted a parent class object without G::extend');
        } catch (InvalidGenericTypeException $e) { }

        $obj = new Car();

        // agora vamos definir que a nossa lista pode
        // receber alem de Car, classes que herdem Car
        // Collection c = new Collection<? extends Car>()
        $list = new GenericList(G::extend($obj));
        // Ferrari herda Car, entao esperamos que funcione
        $list->add(new Ferrari()); 


        // fazemos a mesma coisa, mas utilizando o nome da classe
        // ao inves de um objeto
        
        $obj = new Car();
        $list = new GenericList(G::type('car')); // creates a Car's list

        try {
            $list->add(new Ferrari()); // must throw a exception
            $this->fail('Generic Object accepted a parent class object without G::extend');
        } catch (InvalidGenericTypeException $e) { }

        $obj = new Car();
        $list = new GenericList(G::extend($obj));
        $list->add(new Ferrari()); // should work
    }
}

Após terminarmos de escrever o teste, temos bem definido uma grande parte da classe que vamos implementar. Já temos pelo menos 3 métodos públicos definidos:

__construct($param):
O construtor da classe, vai receber pelo menos um
parâmetro, indicando o tipo de lista. $param
deverá ser salvo para uso futuro, então teremos
uma propriedade na classe para isso

add($param):
Adiciona um item na lista. Assim como o parâmetro
passado no construtor, deveremos salvar este parâmetro
para uso futuro. Então teremos mais uma propriedade na
classe

size():
Retorna o tamanho da lista. Provavelmente vai retornar
a quantidade de itens adicionados em add($param), que
estao armazenados em uma propriedade da classe, que
certamente sera um array. Pensando em sizeof() ?

E também pelo menos 2 propriedades.
Observando o teste notamos também mais duas classes: G, que contem os tipos disponíveis e dois métodos, e também uma classe InvalidGenericTypeException, que representa as exceptions geradas por GenericList.

2 – Implementação: g.class.php
A Classe G (de Generic), contém tres constantes – Integer, String e Double – e dois métodos estáticos – extend e type.
O método type será utilizado para informar para lista o tipo de valor que ela vai conter, sem que ela precise descobrir por si. Já o método extend será utilizado para informar a lista que o tipo a ser utilizado pode ser também subtipos. Os dois métodos vão retornar um array que será trabalhado pela lista. Vamos a implementação:

g.class.php

<?php
/**
* Classe que contem a definicao dos tipos disponiveis
*/
class G {
    /**
    * Constantes com os tipos
    */
    const Integer = 'integer';
    const String  = 'string';
    const Double  = 'double';

    public static function extend($obj)
    {
        if (!is_object($obj)) {
            throw new InvalidGenericTypeException('G::parentsOf must be called with a valid object');
        }
        return array('extend' => strtolower(get_class($obj)));
    }
    
    public static function type($val)
    {
        return array('type' => strtolower($val));
    }
}

2 – Implementação: invalidgenerictypeexception.class.php
Uma exception simples, desnecessária explicação

invalidgenerictypeexception.class.php

<?php
class InvalidGenericTypeException extends Exception {
    public function __construct($message)
    {
        parent::__construct($message);
    }
}

4 – Implementação: genericlist.class.php
Aqui temos, finalmente, a implementação da lista em si. Note que esta implementação chega a ser bem mais simples que o teste. Veja o código e acompanhe pelos comentários:

genericlist.class.php

<?php
/**
* Implementacao da lista
*
* Tem apenas proposito educacional, poderia implementar
* algum iterator, ter metodos para remover ou retornar
* um item, buscar por um item, etc.
*/
class GenericList {
    
    /**
    * Tipo de valores da lista
    */
    private $type = null;
    
    /**
    * Valores da lista
    */
    private $values = array();
    
    /**
    * Informa se o tipo deve levar em consideracao
    * tambem os subtipos
    */
    private $useExtend = false;
    
    /**
    * Construtor da classe
    *
    * Baseado no parametro recebido, inicializa as propriedades
    * $this->type e $this->useExtend
    */
    public function __construct($type)
    {
        // se o parametro for um objeto, o tipo
        // da lista sera definido para o nome da
        // classe utilizada para criacao do objeto,
        // em minusculo
        if (is_object($type)) {
            $this->type = strtolower(get_class($type));
        } 
        // se nao for um objeto, pode ser um array,
        // G::String, G::Double ou G::Integer
        else {
            // caso seja um array...
            if (is_array($type)) {
                // ... verifica se existe a chave
                // extend. Caso exista, significa que
                // o tipo foi definido por meio de
                // G::extend
                if (array_key_exists('extend', $type)) {
                    // define que estara levando em consideracao
                    // os subtipos do tipo
                    $this->useExtend = true;
                    // define o tipo
                    $this->type = $type['extend'];
                // caso exista a chave 'type', significa que o tipo
                // da lista foi definida com g::type. Apenas
                // pega o valor de type e seta como padrao para
                // a lista
                } else if(array_key_exists('type', $type)) {
                    $this->type = $type['type'];
                }
                // nao deveria chegar aqui
                else {
                    throw new InvalidGenericTypeException('Undefined type');
                }
            }
            // se nao for array, o tipo foi passado diretamente como
            // parametro. Apenas utiliza-o
            else {
                $this->type = $type;
            }
        }
    }
    
    /**
    * Adiciona um valor a lista
    */
    public function add($val)
    {
        // verifica se o tipo sendo adicionado eh um objeto
        // e se o tipo do objeto eh diferente do tipo definido
        // para a lista
        if (is_object($val) && strtolower(get_class($val)) != $this->type) {
            // caso o tipo seja diferente, verifica se a lista foi criada
            // com a opcao de se utilizar subtipos do tipo (extend)
            if ($this->useExtend != true || !($val instanceof $this->type)) {
                // caso nao tenha, gera uma exception
                throw new InvalidGenericTypeException(sprintf('Invalid object type: %s, expected: %s', strtolower(get_class($val)), $this->type));
            }
        }
        
        // caso o tipo sendo adicionado nao seja um objeto
        // e seja diferente do tipo definido para a lista,
        // gera uma exception (tambem)
        if (!is_object($val) && gettype($val) != $this->type) {
            throw new InvalidGenericTypeException(sprintf('Invalid type: %s, expected: %s', get_class($val), $this->type));
        }
        
        // se chegou ate aqui, nao ha erros
        $this->values[] = $val;
        
        // retorna o valor sendo adicionado a lista
        return $val;
    }
    
    /**
    * Retorna o tamanho da lista
    */
    public function size()
    {
        // exatamente como imaginou no teste,
        // um simples sizeof
        return sizeof($this->values);
    }
}

5 – Testando
Depois de termos as implementações, já podemos testar:

$ phpunit TestGenericList.php
PHPUnit 3.0.6 by Sebastian Bergmann.

……….

Time: 00:00

OK (10 tests)

E tudo ok. Não é comum passar 100% logo na primeira implementação, mas tudo bem.

6 – Considerações
Algumas coisas que podemos notar:

– Antes mesmo de escrever a implementação da classe GenericList já tinhamos “pronto” nos testes o comportamento que desejavamos
– Todos os testes FALHAVAM antes da implementação da classe. E falhavam de duas formas: Primeiro porque não havia a classe e nem os métodos, então havia a falha de classe não encontrada. Segundo, por mais que tivessemos a classe e os métodos, mas os métodos não tivessem sido implementados, os testes iriam falhar. Este é o comportamento correto de um teste. É comum quando se começa a escrever testes (unitários) fazer com que eles passem antes mesmo de falhar.
– Escreva um teste para falhar e não para passar
– Em PHP não poderemos (não no 5, 6 e nem tão cedo, acredito) utilizar de forma “elegante”, como se faz em outras linguagens, a GenericList como parâmetro de um método, mas podemos emular isso:
java: public void showNomes(GenericList<Pessoa> pessoas)
c#: public void ShowNomes(GenericList<Pessoa> pessoas)
php: um leve “hack”
public function showNomes(GenericList $pessoas)
{
$pessoas->getType() == ‘pessoa’ || eval(‘throw new InvalidGenericTypeException(“Expected GenericList”);’);
}
– Como escrevi ali antes, sabiamos o comportamento que desejamos para a nossa classe. Há um outro método de design que dá mais ênfase nisso. Veja:
behavior-driven.org
Behavior driven development

É isso ai. Mais, em breve.

Arquivos:
g.class.php
genericlist.class.php
invalidgenerictypeexception.class.php
TestGenericList.php





PHP / refactoring: Como ? (Parte 2)

25 05 2007

Pois é, eu não sabia que teria parte 2, mas este é para ser uma continuação do post PHP / refactoring: Como ?.

Vejamos agora como modificar o nome de uma função e as chamadas para a mesma. O exemplo anterior com algumas modificações será utilizado:


<?php
// arquivo sample2.php

$foo   ‘foo’;
$hello “Ola $foo\n”;

// funcao foo() no comentario nao sera modificada

function foo()
{
    
// variavel $foo nao sera modificada
    
$foo “dentro de foo()\n”;
    echo 
$foo;
}

// exibimos $hello
echo $hello;

// observe que foo() e foo () sao diferentes: 
// foo() equivale a:
//      T_STRING        (foo)
//      (               (abre parenteses)
//
// enquanto foo () equivale a:
//      T_STRING        (foo)
//      T_WHITESPACE    (‘ ‘)
//      (               (abre parenteses)
//
// e por esse motivo deve ser tratado como diferente
foo ( );
?>

E a função com exemplo de uso:


<?php
// arquivo change_function_name.php

/**
 * Funcao que ira modificar o nome de uma funcao
 *
 * @param  array  $tokens  Array com os tokens a serem analisados
 * @param  string $oldName Nome da funcao que sera modificada
 * @param  string $newName Novo nome da funcao
 * @return string Codigo-fonte referente aos tokens fornecidos, com a 
 * funcao devidamente modificada
 */
function changeFunctionName($tokens$oldName$newName)
{
    
// variavel que ira conter o codigo-fonte a ser retornado
    
$ret ;

    // contador, marcara o indice do token em uso
    
$tokenIndex = –1;
    
    
// como $tokens e’ um array, utilizamos foreach para
    // passar por todos os seus itens
    
foreach ($tokens as $token) {
        
$tokenIndex++;
        
// quando se obtem os tokens de um arquivo, eles vem em um 
        // array contendo o numero do token e o valor ou em uma string,
        // contendo apenas o valor. Entao verificamos qual o caso.
        
if (!is_array($token)) {
            
$tokenName  false;
            
$tokenValue $token;
        } else {
            
$tokenName  token_name($token[0]);
            
$tokenValue $token[1];
        }

        // verificamos se o token atual pode ser considerado uma funcao
        
if ($tokenName == ‘T_STRING’) {
            
// obtemos os dois proximos tokens
            
$nextToken  $tokens[$tokenIndex 1];
            
$nnextToken $tokens[$tokenIndex 2];

            // aqui temos o seguinte:
            // se o proximo token for igual a “(” E se o valor do token atual for igual
            // ao nome da funcao que queremos modificar…
            
$flag  = ($nextToken[0] == ‘(‘ && $tokenValue == $oldName);
            
            
// … OU o proximo token for um espaco E o token apos o proximo for igual a “(“
            // E o valor do token atual for igual ao nome da funcao que queremos modificar…
            
$flag |= (@token_name($nextToken[0]) == ‘T_WHITESPACE’ && $nnextToken[0] == ‘(‘ && $tokenValue == $oldName);

            // … entao modificamos o valor do token,
            
if ($flag) {
                
$tokenValue $newName;
                
$ret .= $tokenValue;
                continue;
            }
        }
        
        
// se chegou ate aqui, significa que nao e’ um token que nos interessa, apenas pegamos
        // o valor do token e o adicionamos ao final do fonte a ser retornado
        
$ret .= $tokenValue;
    }
    
    
// retornamos o fonte
    
return $ret;
}

// exemplo de uso da funcao

// obtemos o conteudo do arquivo que iremos modificar
$arquivo file_get_contents(‘sample2.php’);

// e utilizamos a funcao token_get_all para obter todos
// os tokens do arquivo. Observe que tudo no script e’
// tratado como sendo um token
$tokens  token_get_all($arquivo);

// modificamos foo() para bar()
$bla changeFunctionName($tokens‘foo’‘bar’);

echo $bla;

?>

Executando:

$ php change_function_name.php
<?php
// arquivo sample2.php

$foo = ‘foo’;
$hello = "Ola $foo\n";

// funcao foo() no comentario nao sera modificada

function bar()
{
// variavel $foo nao sera modificada
$foo = "dentro de foo()\n";
echo $foo;
}

// exibimos $hello
echo $hello;

// observe que foo() e foo () sao diferentes:
// foo() equivale a:
// T_STRING (foo)
// ( (abre parenteses)
//
// enquanto foo () equivale a:
// T_STRING (foo)
// T_WHITESPACE (‘ ‘)
// ( (abre parenteses)
//
// e por esse motivo deve ser tratado como diferente
bar ( );
?>

Observe que a declaração da função foi modificada ( function bar ) e a chamada à função também foi modificada (bar ( ); ).
Ótimo, esta pronto ? Não…

Como bem sabemos, é possível executar funções de outras formas em PHP, como por exemplo, utilizando call_user_func, eval, variáveis utilizadas como funções ( $foo(); ), funções utilizadas como callback, …

Então, em uma implementação completa desta função, deveriamos nos atentar a estes fatos.
Para alguns casos, seria relativamente fácil criar uma implementação:
– Em eval, poderiamos utilizar a string que esta sendo executada e, recursivamente, chamar a função changeFunctionName para os tokens obtidos para esta string
– Em call_user_func / call_user_func_array, poderiamos verificar se o token atual é um T_STRING e o valor do token é ‘call_user_func’, desta forma. Se for, analisaria-se os tokens seguintes para ver se não teria relação com a função sendo renomeada;

Já em outros casos, seria necessário um pouco mais de malabarismos:
– Em variáveis utilizadas como funções, provavelmente precisariamos de um trackback das variáveis, para verificar se o valor dela não corresponde ao nome da função que desejamos renomear
– Utilizar um grande switch para verificar todas as funções builtin do PHP que utilizam funções como callback

De qualquer forma, para fontes onde não se utilize a função sendo renomeada de forma mais “avançada”, este exemplo deve funcionar.

change_var_name.php (exemplo anterior)
change_function_name.php





Resposta do Desafio do dia / 2

20 04 2007

Resposta do Desafio do dia / 2

Vamos por partes:

Um dos objetivos deste desafio era demonstrar que blocos de códigos podem ser utilizados em qualquer parte, e não apenas em estruturas de controle, decisão, etc. O seguinte trecho de código é perfeitamente válido e funcional (apesar de “estranho”)

<?php
  
{
      
$foo ‘foo’;
      echo 
$foo;
  }

Como pode se observar no desafio, coloquei o código em diversos blocos, mas apenas para isso: Demonstrar que é possível utilizar em qualquer lugar. Para resolução do desafio, é indispensável conhecimento de operações bit-a-bit. Como não é todo mundo que tem esse conhecimento, um outro objetivo do desafio era despertar a curiosidade de quem não soubesse e quisesse resolver e também falar um pouco sobre isso (na resposta). Segue agora então o mesmo código, comentado, com a resposta do desafio:


<?php
  
// desafio2.php

  // para acompanhar, segue alguns numeros binarios:
  //
  // 000 0
  // 001 1
  // 010 2
  // 011 3
  // 100 4
  // 101 5
  // 110 6
  // 111 7
  // iniciamos $x com valor 2
  $x 2;
  {
    
// aqui temos também um pequeno truque:
    // print é utilizado para exibir uma mensagem,
    // e sempre retorna 1. Logo, sera exibida a
    // mensagem ‘Iniciando ‘ e $x sera incrementado
    // em 1. Neste momento temos:
    // $x = 3
    
$x += print(‘Iniciando ‘);
    {
        
// aqui sera exibido ‘desafio 2’
        
echo ‘desafio 2’;
        
        
// aqui temos a primeira operacão bit-a-bit,
        // um ‘left shift’ de $x em 1 passo. Vejamos
        // o que tinhamos e o que sera retornado para $y
        // 
        // $x = 011 (3)
        //
        // deslocando 1 bit a esquerda teremos 110 (6)
        //
        // nesse momento temos:
        //
        // $x = 3 (nao modificamos $x)
        // $y = 6
        
$y $x << 1;
    }
    
// aqui são feitas várias operações. Vamos começar
    // pelas mais internas e com maior prioridade:
    //
    // $y^$x
    //
    // Aqui temos um $y xor $x. XOR – exclusive or – retornará
    // os bits que estão ativos em $y e os que estão ativos
    // em $x,  mas não os que estão ativos em ambos: veja:
    //
    // $x = 011
    // $y = 110
    // —- 101
    //
    // observe que como o segundo bit estava ativo em ambos,
    // ele nao estara ativo na saída. Entao após esta operação, 
    // o retorno será 5 (101)
    //
    // logo após esta operação, temos um “not”:
    //
    // ~($y^$x), ou seja ~(5) ou ~5.
    //
    // aqui ocorre uma inversão de cada bit: Os que estão ativos
    // serão desativados e os que estão desativados serão ativados:
    //
    // 5 – 101
    // — 010
    //
    // o resultado desta operação então seria 2 (010)
    //
    // Mas tem um pequeno “problema”: A operacao deve ser feita em
    // todos os bits. Logo, ~5 = 11111111111111111111111111111010 e
    // 11111111111111111111111111111010 = -6
    //
    // temos entao que o resultado da operacao ~($y^$x) = -6
    // o resultado disso tudo é retornado por sprintf.
    // 
    // Aproxima operação, por ordem dos operadores, é $y + (-6), ou 
    // seja $y + sprintf(‘%d’,~($y^$x)). Isso porque o operador ‘+’ tem
    // precedência ante ao ‘|’. Então temos
    //
    // $6 + (-6) = 0
    //
    // apos isto, temos “$x | 0”. Aqui o os bits que estão ativos ou 
    // em $x ou em 0 (ou ambos, diferentemente do xor) estarão ativos na
    // saída. Como 0 não tem bits ativos, o resultado da operação sera o
    // própio valor de $x:
    //
    // $x = 011
    // 0  = 000
    // —- 011 (3)
    //
    // finalmente, temos que $y = 3
    //
    // ao final disso entao:
    //
    // $x = 3
    // $y = 3
    
$y $x $y sprintf(‘%d’,~($y^$x));

    // apenas exibimos um “\n”. O $z não é utilizado mais
    
$z = print(“\n”);
    
    
// verificamos se $x é menor do que $y (não é)
    
if ($x $y) {
        
$x ^= $x;
    
// observe que aqui não temos um else. Então esse trecho
    // será executado de qualquer forma (mesmo que o if anterior
    // fosse verdadeiro. Foi utilizado apenas para “confundir” : )
    
} {
        
// Fazemos um xor de $y em $y. Lembre-se que no xor, o retorno
        // são os bits que estão ativos ou em um ou em outro número, mas
        // não em ambos. Como o xor está sendo feito no mesmo número, o que
        // estiver ativo em um também estará ativo em outro. Logo, o retorno
        // será 0 (este tipo de operação é bastante utilizada em assembly, 
        // quando se deseja zerar o valor de algum registrador: “xor eax, eax”
        // “xor ebx, ebx”, …)
        //
        // $y = 011
        // $y = 011
        // —- 000
        //
        //
        
$y ^= $y;
    
// novamente utilizamos um bloco, que será executado de qualquer forma
    
} {
        
// verificamos se $x (3) é maior do que $y (0). Como sabemos que é,
        // será exibido ‘$y menor do que $x’
        
if ($x $y) {
            echo 
‘$y menor do que $x’;
        
// e este trecho será ignorado : )
        
} else {
            echo 
‘$y maior do que $x’;
        }
    }
  }
  
// finalmente, exibimos um último “\n”
  
echo “\n”;
?>

Ao final disso, temos então:

Iniciando desafio 2
$y menor do que $x

É isso, espero que tenha aprendido um pouco sobre bitwise operations neste desafio.





Validação de campos usando XML

23 03 2007

Há quanto tempo pessoas!
Pois é, a vida está bastante corrida, e o tempo anda curto… quando sobra algum, a gente acaba dormindo : | Meleca viu… Mas, lamentações a parte, vamos falar sobre o que realmente interessa: PHP!
Bom, como ando meio sem criatividade para postar, decidi que vou demonstrar uma maneira de realizar validação de campos, utilizando xml para caracterizar essa validação, fazendo com que você não precise sair que nem louco caçando as validações no meio do seu código o dia que precisar alterar alguma. Esse é o tipo de validação que o E. Silva e eu temos utilizado em nosso framework do qual ele já comentou ai em alguns comentários (frase linda).
Algumas idéias aqui, poderão parecer confusas de início para quem não está muito acostumado com PHP, mas como sempre digo, se tem alguma duvida, fique a vontade para mandar email ou deixar comentário que a gente resolve como pode.
Como sempre, gosto de começar com exemplos, então, vamos começar com o XML de exemplo:

validador.xml

<validator>
    <field name=‘nome’ validation=‘validator::isFilled’ when=‘insert,update’ />
    <field name=‘cpf’ validation=‘validator::isCpf’ when=‘insert,update’ />
    <field name=‘data’ validation=‘validator::isData’ when=‘insert’ />
</validator>

Esse ai será o xml que iremos usar para validar nossos campos. Vamos entender algumas coisas:
A propriedade ‘name’, identifica o nome do campo que será validado
A propriedade ‘validation’, define o método que será utilizado para validação
A propriedade ‘when’ define quando a validação deverá ocorrer. Essa propriedade será identificada através da ação que o botão do formulário fornece.

Os exemplos que serão trabalhados, utilizam o método POST para envio dos dados, e tem o action definido na própria página. Preparem-se, pois hoje teremos bastante código.
Vamos dar uma olhada agora, no código do formulário, que ficará no script que chamaremos de index.php

index.php

<form method=“POST”>
<input type=“text” name=“nome” /><br />
<input type=“text” name=“cpf” /><br />
<input type=“text” name=“data” /><br />
<!– agora vem o botão de submit, que identificará a ação que ocorre –>
<input type=“submit” name=“acao” value=“Insert” />
</form>

Esse script ainda não está pronto. Falta ainda o código PHP do mesmo. Agora, vou esclarecer um pouco melhor como será o funcionamento do script.
Todo o formulário, será (ou não), preenchido, e será feito o envio das informações ao se clicar no botão de Inserir. Quando o script receber esses dados, será invocada a classe, que lê o xml de validação, e aplica essa validação aos campos que foram recebidos. Os campos serão validados conforme descrição feita pelo XML, e a partir dai serão geradas as mensagens do sistema. O script que será demonstrado é um exemplo, para dar uma idéia de como se utilizar esse método de validação. Não estou demonstrando exatamente igual ao funcionamento do nosso framework, pelo simples fato, de que eu iria precisar de muitas classes do framework para isso, e então deixaria de ser um post de blog, para virar um livro : |
Veja então a classe que irá fazer o parse do arquivo xml, e validará as entradas fornecidas pelo usuário:

action.class.php


<?php

class Action {

    // Essa propriedade da classe, ira conter
    // o resultado da validacao de cada um dos
    // campos.
    
private $validacao = array( );

    // Essa propriedade ira dizer se ocorreu algum erro
    // para o script 
    
private $error null;

    // Retorna o valor da propriedade $error
    
public function getError( ) {
        return 
$this->error;
    }

    // Retorna o valor da propriedade $validacao
    
public function getValidacao( ) {
        return 
$this->validacao;
    }

    public function run( ) {
        
// normalmente, possuimos uma classe para trabalhar
        // com metodo POST e GET. No nosso caso, de forma a 
        // simplificar a quantidade de codigo, vamos usar a 
        // variavel $_POST diretamente

        // Essas aqui serao as acoes que iremos validar. Como
        // ja comentado, as acoes sao definidas atraves do valor
        // do botao que faz o metodo POST
        $validActions = array(
                            
'insert',
                            
'update',
                            
'delete' 
                        
);

        $acao strtolower$_POST['acao'] );
        if( 
in_array$acao $validActions ) ) {
            
// Inicializar a variavel de controle de erro
            
$this->error true;

            // Se chegou nesse ponto, significa que a acao realizada
            // foi uma das acima. Agora deve-se carregar o xml e começar a
            // fazer o seu parsing
            
$xml simplexml_load_file'validador.xml' );

            // A partir de agora, vamos avaliar cada uma das linhas do xml
            // e realizar a validacao.
            
foreach( $xml->field as $campo ) {

                // Aqui, vamos procurar pelo campo atual que encontramos no xml
                // dentro do array $_POST. Se o campo for encontrado, iremos
                // validar o valor que nos foi enviado.
                
if( array_key_exists( (string)$campo['name'], $_POST ) ) {

                    // Otimo, encontramos o campo no array POST. Porem antes
                    // de validarmos o campo, precisamos verificar se ele
                    // deve ser validado na acao que esta acontecendo.
                    // Lembra-se da propriedade 'when' no xml? Eh aqui
                    // que ela entra, nos informando se o campo deve ou nao
                    // ser validado. As acoes devem ser separadas por uma ','
                    // no xml
                    
$whenToValidate explode(','$campo['when'] );
                    if( 
in_array$acao$whenToValidate ) ) {

                        // Finalmente chegamos! Chegou aqui, entao esta na hora
                        // de validar o campo de acordo com o que esta no xml.
                        // O conteudo especificado na propriedade 'validation'
                        // do xml, define um metodo estatico a ser chamado para
                        // validar o valor que foi enviado no metodo post
                        // nos permitindo assim, assumit que esse metodo recebe
                        // ao menos 1 parametro, que eh o valor a ser validado
                        // Veja a classe com os metodos de validacao, para
                        // entender exatamente o que acontece
                        
$codigo sprintf('$ret = %s( $_POST["%s"] );',
                                          (string)
$campo['validation'],
                                          (string)
$campo['name']
                                         );

                        // Suponto que estamos validando o campo 'nome', que
                        // da maneira como está descrito no xml do tutorial,
                        // teremos $codigo valendo algo como:
                        // $ret = validator::isFilled( $_POST["nome"] );
                        // Executa-se o código de validação agora. A variavel
                        // $ret ira conter o resultado da validacao.
                        
eval( $codigo );

                        // Caso $ret seja diferente de true, estou assumindo
                        // que o retorno é entao uma mensagem de erro
                        
if( $ret === true ) {
                            
$this->validacao[(string)$campo['name']] = true;
                        } else {
                            
$this->error true;
                            
$this->validacao[(string)$campo['name']] = $ret;
                        }
                    }
                }
            }
        }
    }
}

?>

Com esse código em mãos, precisamos também ter a classe que faz validação do conteúdo digitado nos campos. Como foi denifido no xml, a classe que estamos usando é a classe Validator. A que irei apresentar, faz validações hipóteticas, não sendo recomendado o uso delas, da forma que estão. Veja o código:

validator.class.php


<?php

class Validator {

    // Método que verifica se há algum valor em $valor
    
public static function isFilled$valor ) {
        if( empty( 
$valor ) )
            return 
'Campo nao pode ser vazio';

        return true;
    }

    // Metodo que iria validaro valor, para saber se este
    // eh um cpf valido
    
public static function isCpf$valor ) {
        if( 
$valor == '11111111111' )
            return 
'CPF invalido';

        return true;
    }

    // Metodo que verificaria se a data informada
    // eh valida
    
public static function isData$valor ) {
        
$data explode('/',$valor);
        if( 
sizeof$data ) != )
            return 
'Data invalida';

        return true;
    }

}

?>

Já está quase tudo pronto, agora basta adicionarmos o código em nosso index.php, que fará a validação dos campos. Lembra-se daquele nosso primeiro index.php não é mesmo? Ele deve ficar assim agora:

index.php


<?php

include_once('validator.class.php');
include_once(
'action.class.php');
$action = new Action( );
$action->run( );

if( $action->getError( ) === true ) {
    
$camposInvalidos $action->getValidacao( );
    foreach( 
$camposInvalidos as $campo=>$e ) {
        if( 
$e !== true )
            
printf('Erro: campo %s - %s <br>'$campo$e ); 
    }
}

?>

<form method="POST">
    <input type="text" name="nome" /><br />
    <input type="text" name="cpf" /><br />
    <input type="text" name="data" /><br />
    <!-- agora vem o botão de submit, que identificará a ação que ocorre -->
    <input type="submit" name="acao" value="Insert" />
</form>

Nestes exemplos, eu levei em conta todos os arquivos estão em um só diretório, como pode ser observado pelos include_once que faço no início do script.

Esclarecendo tudo então:

  • O arquivo xml é o responsável por descrever a validação
  • A propriedade validation contém o nome da classe e do método, como se fossem uma chamada estática Classe::metodo
  • A propriedade when define em quais acoes o campo deve ser validado, e essa ações são separadas por ‘,’
  • A propriedade name define o nome do cmapo que está sendo validado

Bom, é isso!
Como de costume, os arquivos para visualização do código seguem abaixo, pois o wordpress é tosco, e zoa todo o código, para copiar e colar.
Qualquer dúvida/comentário/sugestão/elogio/doações, não deixe de contatar-me : )

[]s

Index.php
Action.class.php
Validator.class.php
Validador.xml





Cadastro com confirmação por email

26 02 2007

O leitor Erick de Oliveira nos pediu um tutorial sobre cadastro com
confirmação por email. Irei demonstrar de maneira bem simples e rápida uma
maneira de como criar um cadastro desse tipo e tentar sugerir algumas idéias.
Os exemplos irão usar o MySQL como database.
Aproveitando o embalo de já estar falando de MySQL, vamos construir então a
tabela que nos auxiliará nessa tarefa:


create table cadastro (
id_cadastro int not null primary key auto_increment,
nome varchar(50),
email varchar(50),
login varchar(30),
senha varchar(32),
data_ts varchar(14),
uid varchar(50),
ativo bool default 0
);

Nessa tabela, serão salvas as informações básicas de login do usuário. Quero
chamar atenção especial para os campos ‘data_ts’, ‘uid’ e ‘ativo’. Esses serão
os campos que usaremos como controle, para validar o usuário, após ele receber
o email de confirmação de seu cadastro.
O campo uid, irá armazenar um id unico, gerado pela função uniqid do
PHP, e esse id será usado em conjunto com o campo de data_ts, para validarmos
o cadastro do usuário, e ativarmos a sua conta. É mais simples de explicar
como funcionará, mostrnado um pouco de código.
(Obs: Não irei me preocupar aqui, em verificar se o login já não está em uso
no sistema por alguém, ou se as informações foram corretamente preenchidas, já
que a ideia aqui é demonstrar como validar o cadastro através de um email de
confirmação).

Cadastro.php


<?php

// Vou assumir que o arquivo conectar.php existe, e que ele e o responsavel
// por se conectar a seu database
include_once('conectar.php'); 

// Vou assumir que os campos foram preenchidos todos corretamente
// e que ninguem vai tentar se aproveitar do formulario para SQL Injection
// ou coisas do genero

if( isset( $_POST['enviar'] ) ) {

    $nome $_POST['nome'];
    
$email $_POST['email'];
    
$login $_POST['login'];
    
$senha md5$_POST['senha'] );
    
$uid uniqidrand( ), true );
    
$data_ts time( );
    
$ativo 0;

    $sql "insert into cadastro ( id_cadastro, nome, email, login, senha,
data_ts, uid,
ativo ) "
;
    
$sql .= "values
('', '$nome','$email','$login','$senha','$data_ts','$uid','$ativo')"
;

    if( ! mysql_query$sql ) ) {
        echo 
"Houve um erro inserindo o registro ".mysql_error( );
    } else { 
// Registro inserido com sucesso, mandar email

        $id mysql_insert_id$con );

        // Criar as variaveis para validar o email
        
$url =
sprintf'id=%s&email=%s&uid=%s&key=%s',$idmd5($email), md5($uid),
md5($data_ts)
);

        $mensagem 'Para confirmar seu cadastro acesse o link:'."\n";
        
$mensagem .= sprintf('http://www.dominio.com.br/ativar.php?%s',$url);

        // enviar o email
        
mail$email'Confirmacao de cadastro'$mensagem );

        echo "Registro inserido com sucesso";
    }
}

?>

<form method="POST">

    Nome:<input type="text" name="nome"><br>
    Email:<input type="text" name="email"><br>
    Login:<input type="text" name="login"><br>
    Senha:<input type="password" name="senha"><br>
    <br>
    <input type="submit" name="enviar" value="Cadastrar">

</form>

O formulário é bastante simples, e eu gostaria de dar destaque a linha:
$url = sprintf( ‘id=%s&email=%s&uid=%s&key=%s’,$id, md5($email), md5($uid),
md5($data_ts) );

Veja que essas são as variáveis que identificam o usuário que acaba de se
cadastrar. Quando a url para validação for acessada, iremos conferir se o os
dados de unique id e a data conferem com o email especificado, para poder
então ativar o cadastro. O email e data, são passados como hash md5, para
proteger essas informações de quem as possa estar vendo. A variável id
é quem identificará com qual registro na tabela essas informações devem ser
comparadas. O registro será ativado, apenas caso todas as informações
cadastradas sejam iguais as informações vindas pela URL, dessa forma, se
alguém tentar ativar um cadastro sem ser o verdadeiro dono da informação, não
poderá faze-lo, a menos que conheça o segundo exato em que o cadastro foi
feito, o email no qual o cadastro foi feito e a unique id do usuario.
Vamos ver então, como fica o código para ativar o cadastro.

Ativar.php


<?php

// Novamente, nao irei fazer nenhum tipo de checagem para validar os dados
// em busca de SQL Injection ou coisas do genero. Nao se esqueca voce de fazer
// isso.

// Conectar no banco de dados
include_once('conectar.php');

// Dados vindos pela url
$id $_GET['id'];
$emailMd5 $_GET['email'];
$uidMd5 $_GET['uid'];
$dataMd5 $_GET['key'];

//Buscar os dados no banco
$sql "select * from cadastro where id_cadastro = '$id'";
$sql mysql_query$sql );
$rs mysql_fetch_array$sql );

// Comparar os dados que pegamos no banco, com os dados vindos pela url
$valido true;

if( $emailMd5 !== md5$rs['email'] ) )
    
$valido false;

if( $uidMd5 !== md5$rs['uid'] ) )
    
$valido false;

if( $dataMd5 !== md5$rs['data_ts'] ) )
    
$valido false;

// Os dados estão corretos, hora de ativar o cadastro
if( $valido === true ) {
    
$sql "update cadastro set ativo='1' where id_cadastro='$id'";
    
mysql_query$sql );
    echo 
"Cadastro ativado com sucesso!";
} else {
    echo 
"Informacoes invalidas";
}

?>

A partir daí o cadastro já pode ser ativado. Agora quando o usuário for se
logar no sistema, basta verificar se o valor do campo ativo na tabela
para aquele usuário, vale 1. Caso sim, significa que o usuário já teve sua
ativação realizada.

Espero que o texto possa ter ajudado, e qualquer dúvida, basta deixar um
comentário ai : )
Abaixo estão os links para os arquivos com os códigos dos exemplos, pois o
Wordpress faz o favor de estragar as aspas, e consequentemente o código : |

[]s

Cadastro.txt
Ativar.txt





Upload de multiplos arquivos com PHP

22 02 2007

Fala pessoal!
A pedido do leitor Rodrigo Almeida, vou escrever aqui um mini tutorial de como criar um formulário para upload de multiplos arquivos.
Para começar então, vou demonstrar como se cria um formulário para upload de um único arquivo, e depois passaremos para upload de multiplos arquivos.


<?php

if( isset( $_POST['enviar'] ) ) {

    $erro '';
    
$pathToSave '/tmp/';

    // A variavel $_FILES é uma variável do PHP, e é ela a responsável
    // por tratar arquivos que sejam enviados em um formulário

    if( $_FILES['arquivo']['name'] != '' ) {
        
$arquivoTmp $_FILES['arquivo']['tmp_name'];
        
$arquivo $pathToSave.$_FILES['arquivo']['name'];

        // Copia o arquivo para o sistema
        
if( !move_uploaded_file$arquivoTmp$arquivo ) ) {
            
$erro 'Erro no upload. Verifique as permissoes do diretorio';
        }
    } else {
        
$erro 'É necessario escolher um arquivo para enviar';
    } 

}

?>

<form method='POST' enctype='multipart/form-data'>
    Arquivo: <input type='file' name='arquivo'>
    <br>
    <input type='submit' value='Enviar' name='enviar'>
</form>

O exemplo por si só é bastante explanatório. Certifique-se de que você substituiu o caminho no qual deseja salvar o arquivo, e que o usuário que está executando o php tem permissão para escrever nesse diretório.O comando move_uploaded_file é reponsável por pegar o arquivo que foi enviado, e então salva-lo no local que você especificou. Seguindo em frente então, vamos ver como fazer o envio de multiplos arquivos no mesmo formulário.
Vejamos um segundo exemplo:


<?php

if( isset( $_POST['enviar'] ) ) {

    $pathToSave '/tmp/';

    // A variavel $_FILES é uma variável do PHP, e é ela a responsável
    // por tratar arquivos que sejam enviados em um formulário
    // Nesse caso agora, a nossa variável $_FILES é um array com 3 dimensoes
    // e teremos de trata-lo, para realizar o upload dos arquivos
    // Quando é definido o nome de um campo no form html, terminado por []
    // ele é tratado como se fosse um array, e por isso podemos ter varios
    // campos com o mesmo nome
    
$i 0;
    
$msg = array( );
    
$arquivos = array( array( ) );
    foreach(  
$_FILES as $key=>$info ) {
        foreach( 
$info as $key=>$dados ) {
            for( 
$i 0$i sizeof$dados ); $i++ ) {
                
// Aqui, transformamos o array $_FILES de:
                // $_FILES["arquivo"]["name"][0]
                // $_FILES["arquivo"]["name"][1]
                // $_FILES["arquivo"]["name"][2]
                // $_FILES["arquivo"]["name"][3]
                // para
                // $arquivo[0]["name"]
                // $arquivo[1]["name"]
                // $arquivo[2]["name"]
                // $arquivo[3]["name"]
                // Dessa forma, fica mais facil trabalharmos o array depois, para salvar
                // o arquivo
                
$arquivos[$i][$key] = $info[$key][$i];
            }
        }
    }

    $i 1;

    // Fazemos o upload normalmente, igual no exemplo anterior
    
foreach( $arquivos as $file ) {

        // Verificar se o campo do arquivo foi preenchido
        
if( $file['name'] != '' ) {
            
$arquivoTmp $file['tmp_name'];
            
$arquivo $pathToSave.$file['name'];

            if( !move_uploaded_file$arquivoTmp$arquivo ) ) {
                
$msg[$i] = 'Erro no upload do arquivo '.$i;
            } else {
                
$msg[$i] = sprintf('Upload do arquivo %s foi um sucesso!',$i);
            }
        } else {
            
$msg[$i] = sprintf('O arquivo %d nao foi preenchido',$i);
        }

        $i++;
    }

    // Imprimimos as mensagens geradas pelo sistema
    
foreach( $msg as $e ) {
        
printf('%s<br>'$e);
    }

}

?>

<form method='POST' enctype='multipart/form-data'>
    Arquivo1: <input type='file' name='arquivo[]'><br>
    Arquivo2: <input type='file' name='arquivo[]'><br>
    Arquivo3: <input type='file' name='arquivo[]'><br>
    Arquivo4: <input type='file' name='arquivo[]'><br>
    <br>
    <input type='submit' value='Enviar' name='enviar'>
</form>

Veja então, que no exemplo acima, criamos um formulário, onde os campos todos tem o mesmo nome, porém são tratados como um array, por terminarem com ‘[]’. A parte mais trabalhosa é entendero array que recebemos então, e trata-lo. Depois de tratado o array, prosseguimos normalmente com o upload do arquivo.
Espero que tenha ficado claro como fazer esse upload de multiplos arquivos. Se quiser entender como a variável $_FILES é, tente utilizar a funcao var_dump nela, para ver seu conteúdo, e enteder melhor o primeiro conjunto de laços foreach que foi demonstrado no exemplo.
Caso tenha permanecido alguma duvida, afinal trabalhar com arrays de 3 ou mais dimensões é um pouco mais complicado, deixe ai um comentário, que tentarei esclarecer da melhor forma possível.

Novamente, agradeço ao leitor Rodrigo Almeida pela sugestão, e peço para que todos participem, e mandem ai suas dúvidas, críticas e sugestões.

[]s

Edit: Bom, pelo visto pode estar havendo dificuldade em copiar e colar o código dos exemplos diretamente em algum editor de texto, pois o WordPress troca as aspas simples por um tipo de aspa esquisito. Por esse motivo, enviei os códigos em formato txt.
Os links:
upload.txt – Primeiro exemplo
upload2.txt – Segundo exemplo





PHP + GD = Desenhando com PHP

19 02 2007
Bom pessoal, faz algum tempinho que estou sem escrever nada, por falta de
tempo/idéias, mas nesse momento estou sem sono, e tive uma ideia para escrever
algo, então aqui estou.
Vou falar um pouco sobre PHP e GD, e demonstrar como fazer desenhos simples
usando essa extensão do PHP.
Inicialmente você precisa ter o GD compilado e instalado em sua máquina, e
precisa compilar o php com suporte a ele, usando o parametro '--with-gd' na
linha do seu './configure'.
Para averiguar se tudo funcionou corretamente, rode o seguinte comando:

$ php -m |grep gd
gd

    
Se aparecer a palavra gd para você, está tudo correto, e podemos seguir em
frente.
Quando o ambiente está pronto para se trabalhar com GD, o primeiro passo a se
tomar, é criar uma imagem, que servirá de base para trabalharmos. De maneira a
facilitar e agilizar as coisas, irei dar um exemplo, com algumas linhas de
código e explicar o que acontece:

<?php

    // É necessário especificar o header, para informar que se trata de uma
    // imagem
    header('Content-type: image/png');

    $imagem = imagecreatetruecolor( 640, 480 );
    $vermelho = imagecolorallocate( $imagem, 255, 0, 0 );
    imagefilledrectangle( $imagem, 0, 0, 640, 480, $vermelho );

    imagepng( $imagem );
?>

Esse código, irá criar um grande retangulo vermelho.
A nossa imagem é criada na chamada da função imagecreatetruecolor, que
cria uma imagem, e retorna um resource para a mesma. Praticamente todas as
funções (se não todas) que utilizamos com GD a partir disso, farão uso desse
resource. A partir daqui, o que nos resta a fazer é desenhar a nossa imagem.
Praticamente tudo o que desenhamos na imagem utiliza cor, então para isso,
precisamos criar um resource de cor, que será utilizado pelas funções que
desenham coisas propriamente ditas. A função imagecolorallocate toma 4
parametros que lhe permitem criar uma cor. O primeiro define a imagem para qual
essa cor deve ser alocada, os seguintes definem a intensidade de vermelho, verde
e azul (RGB). Logo na sequência desenhamos um retangulo vemelho do tamanho da
imagem, utilizando a função imagefilledrectangle, que toma 6 parametros,
que definem qual a imagem em que o retangulo deve ser desenhado, qual o ponto X
inicial, o ponto Y inicial, o ponto X final, o ponto Y final e a cor do
retangulo. A função imagepng renderiza nossa imagem, e a exibe na tela.
É importante saber que o eixo de cordenadas, começa no canto superior esquerdo
da tela, ou seja, a posicão X=0, Y=0 se encontra no canto superior esquerdo de
nossa imagem. Isso pode confundir um pouco no início, mas com o passar do tempo,
a gente vai pegando o jeito.
Os desenhos, vão sendo feitos na imagem, de acordo com a sequencia em que as
chamadas para as funções são feitas, logo se eu adicionasse uma nova função no
nosso código acima para desenhar um retangulo azul, por cima do vermelho, nós
veriamos apenas o retangulo azul, e não o vermelho.
Vejamos então, algumas funções que podemos utilizar com GD, para desenharmos
coisas na nossa imagem:
    imagerectangle( resource $imagem, int $x0, int $y0, int $x1, int $y1, int
$cor )
        Desenha um retangulo na tela, sem preenchimento. Lembrando que um
quadrado nada mais é que um retangulo de lados iguais.
    imageellipse( resource $imagem, int $cx, int $cy, int $largura, int
$altura, int $cor )
        Desenha uma elipse na tela, também sem preenchimento. A elipse terá o
seu centro definido na posição $cx;$cy, terá $largura de largura e $altura de
altura. Vale lembrar também que um círculo é uma elipse que tem a mesma largura
e altura. $cor, é a cor que criamos usando imagecolorallocate.
    imagestring( resource $imagem, int $font, int $x, int $y, string $s, int
$cor )
        Desenha uma string na imagem, que começa na posição $x,$y. Se fonte for
definido como um numero entre 1 e 5, uma fonte interna do PHP é usada. $sé a
string que desejamos desenhar, e $cor, é a cor que a nossa string deverá ter.
    imagesetpixel( resource $imagem, int $x, int $y, int $cor )]
        Desenha um pixel na posição $x, $y com a cor $color.
Conhecendo essas funções já podemos fazer algumas várias coisinhas. Vejamos
abaixo então um código de exemplo, que mostra a utilização dessas funções e de
mais algumas, que deixará tudo bem claro.

<?php

    // É necessário especificar o header, para informar que se trata de uma
    // imagem
    header('Content-type: image/png');

    $imagem = imagecreatetruecolor( 640, 480 );

    // Criar algumas cores
    $vermelho = imagecolorallocate( $imagem, 255, 0, 0 );
    $verde    = imagecolorallocate( $imagem, 0, 255, 0 );
    $azul     = imagecolorallocate( $imagem, 0, 0, 255 );
    $preto    = imagecolorallocate( $imagem, 0, 0, 0 );
    $branco   = imagecolorallocate( $imagem, 255, 255, 255 );

    // Desenhar um retangulo vermelho
    imagerectangle( $imagem, 10, 100, 250, 150, $vermelho );

    // Escrever dentro do retangulo
    imagestring( $imagem, 3, 11, 120, 'Sou um retangulo sem preenchimento', $branco );

    // Desenhar um circulo azul
    imageellipse( $imagem, 100, 300, 100, 100, $azul );

    // Desenhar um quadrado verde preenchido dentro do circulo
    imagefilledrectangle( $imagem, 75, 275, 125, 325, $verde );

    // Desenhar uma linha de pixels coloridos, na vertical
    for( $i = 0; $i < 200; $i++ ) {
        imagesetpixel( $imagem, 300, $i, imagecolorallocate( $imagem, rand(0,255),rand(0,255),rand(0,255) ) );
    }

    // Desenhar um circulo preenchido verde
    imagefilledellipse( $imagem, 400, 300, 100, 100, $verde );

    imagepng($imagem);

?>

Bem, temos uma imagem com alguns desenhos, porém uma coisa ainda incomoda, e
essa coisa é a cor de fundo. Como mudar a cor de fundo? Basta usar a função
imagefill, que preenche uma determinada area com a cor especificada.
Podemos utilizar essa função para pintarmos apenas a parte interna do nosso
retangulo ou circulo que nao tem preenchimento. Vamos então a um ultimo exemplo
que usa essa função.

<?php

    // É necessário especificar o header, para informar que se trata de uma
    // imagem
    header('Content-type: image/png');

    $imagem = imagecreatetruecolor( 640, 480 );

    // Criar algumas cores
    $vermelho = imagecolorallocate( $imagem, 255, 0, 0 );
    $verde    = imagecolorallocate( $imagem, 0, 255, 0 );
    $azul     = imagecolorallocate( $imagem, 0, 0, 255 );
    $preto    = imagecolorallocate( $imagem, 0, 0, 0 );
    $branco   = imagecolorallocate( $imagem, 255, 255, 255 );

    // pintar o fundo de branco
    imagefill( $imagem, 0, 0, $branco );

    // Desenhar um retangulo vermelho
    imagerectangle( $imagem, 10, 100, 250, 150, $vermelho );

    // Pintar o retangulo de azul
    // 11 e 101, é uma posição dentro do retangulo
    imagefill( $imagem, 11, 101, $azul );

    imagepng($imagem);

?>

Temos ai então uma imagem branca, com um retangulo azul de borda vermelha.
Estamos sempre usando a função imagepng, mas existem outras, para criar a
imagem nos formatos gif e jpg por exemplo, sendo elas:
    imagejpeg
    imagegif

Bom, por hoje é isso. Espero que esse texto possa ter dado uma breve introdução
a extensão GD, e possa te ajudar com alguns problemas, como criação de captchas,
gráficos, e qualquer outra coisa mais que lhe possa ser util (Eu tinha umas
outras ideias, mas sao 3 da manha, e eu ralmente esqueci).
Para maiores informações e mais funções, consulte:
http://php.net/gd

[]s