Olá pessoal, no artigo de hoje vamos tentar entender como funciona o Iterator em PHP. Os iterators são utilizados para simplificar a carga e proteger os dados.

Nesse artigo vamos ver também como criar uma coleção recarregável, ou seja, uma coleção que a matriz é carregada apenas quando for necessária.

Como um primeiro passo, vamos criar as classes que irão nos proporcionar as funções de base para a nossa classe Collection.

Criando a classe Collection

Em primeiro lugar, vamos precisar criar o nosso iterator para a coleção. Vamos usar a interface fornecida pelo próprio PHP, para termos certeza de que todos os métodos necessários sejam definidos. Abaixo podemos ver um exemplo de uma interface:


Iterator extends Traversable {
  /* Methods */
  abstract public mixed current ( void )
  abstract public scalar key ( void )
  abstract public void next ( void )
  abstract public void rewind ( void )
  abstract public boolean valid ( void )
}
Listagem 1. Interface de Iterator

Quando usamos essa interface como base, podemos criar a nossa classe CollectionIterator. Como mostra a listagem 2.


class CollectionIterator implements Iterator {
  /**
   * Essa é nossa collection class
   */
  private $Collection = null;
  /**
   * Current index
   */
  private $currentIndex = 0;
  /**
   * Keys in collection
   */
  private $keys = null;

  /**
   * Collection iterator constructor
   *
   */
  public function __construct(Collection $Collection){
    // assign collection
    $this->Collection = $Collection;
    // assign keys from collection
    $this->keys = $Collection->keys();
  }

  /**
   * Implementação do método
   *
   * Esse metodo retorna o item atual na coleção em currentIndex.
   */
  public function current(){
    return $this->Collection->get($this->key());
  }

  /**
   * Get current key
   *
   */
  public function key(){
    return $this->keys[$this->currentIndex];
  }

  /**
   * Move to next idex
   *
   * Esse método adiciona currentIndex em um.
   */
  public function next(){
    ++$this->currentIndex;
  }

  /**
   * Rewind
   *
   * Esse metodo reseta o currentIndex, fazendo-o tornar zero
   */
  public function rewind(){
    $this->currentIndex = 0;
  }

  /**
   * Verifica se o currentIndex é valido
   *
   * Esse método verifica se o currentIndex é valido pelas chaves do arrays
   */
  public function valid(){
    return isset($this->keys[$this->currentIndex]);
  }
}
Listagem 2. Classe CollectionIterator

Como podemos ver no código o Iterator é, agora, definido. Existem alguns métodos que precisam ser definidos em nossa classe para que possa funcionar perfeitamente. No próximo passo nós vamos criar a classe Collection e começar a brincar com ela.

Vamos usar a interface IteratorAggregate para definir o método getIterator e então poder satisfazer os requisitos da interface.

No código abaixo vamos definir duas exceções, são elas:

  • ECollectionKeyInUse: Será lançada quando se tentar inserir um item em arrecadação com a chave que já existe.
  • ECollectionKeyInvalid: Será lançada quando a chave que for buscada não puder ser encontrada na coleta.

class ECollectionKeyInUse extends Exception {

  public function __construct($key){
    parent::__construct('Key ' . $key . ' already exists in collection');
  }
}

class ECollectionKeyInvalid extends Exception {

  public function __construct($key){
    parent::__construct('Key ' . $key . ' does not exist in collection');
  }
}
class Collection implements IteratorAggregate {

  
  private $data = array();

  
  public function getIterator(){
    return new CollectionIterator($this);
  }

  
  public function add($item, $key = null){
    if ($key === null){
      // key is null, simply insert new data
      $this->data[] = $item;
    }
    else {
      // key was specified, check if key exists
      if (isset($this->data[$key]))
        throw new ECollectionKeyInUse($key);
      else
        $this->data[$key] = $item;
    }
  }
  public function get($key){
    if (isset($this->data[$key]))
      return $this->data[$key];
    else
      throw new ECollectionKeyInvalid($key);
  }
  public function remove($key){
    // check if key exists
    if (!isset($this->data[$key]))
      throw new ECollectionKeyInvalid($key);
    else
      unset($this->data[$key]);
  }

  public function getAll(){
    return $this->data;
  }
  public function keys(){
    return array_keys($this->data);
  }

  public function length(){
    return count($this->data);
  }

  public function clear(){
    $this->data = array();
  }

  public function exists($key){
    return isset($this->data[$key]);
  }
}
Listagem 3. Criando exceções

Agora, temos uma classe Collection que trabalha com métodos como adicionar, remover, limpar(), etc. Abaixo podemos ver um exemplo simples de script para essa classe Collection.


/**
 * Código de exemplo
 *
 * Cria uma nova coleção com três itens, exibe a coleção
 * remove um e mostra a coleção de novo
 */

$Collection = new Collection;

$Collection->add('Circle'); // 0
$Collection->add('Square'); // 1
$Collection->add('Line');   // 2

foreach($Collection as $key => $item){
  echo "Item($key): $itemn";
}

$Collection->remove(1);

foreach($Collection as $item){
  echo "Item($key): $itemn";
}
Listagem 4. Exemplo simples de script para a classe Collection

Quando o código da listagem 4 for executado, você irá ver um resultado parecido com o que mostramos abaixo:


# ./collection.php
Item(0): Circle
Item(1): Square
Item(2): Line
Item(0): Circle
Item(1): Line

Criando a classe Collection carregável

Vamos imaginar um cenário onde temos vários objetos voando ao redor e você não sabe qual vai usar e qual carga, etc. Uma maneira de lidar com isso é simplesmente carregar tudo, o que pode consumir muita memória desnecessária.

Uma solução interessante e muito melhor é ter uma classe carregável, o eu basicamente significa que quando você solicita um item de coleção, ou qualquer propriedade de coleção, a coleção vai primeiro se carregar.

Abaixo podemos ver um exemplo de extensão da classe Collection, que permite que essa funcionalidade funcione.


require('Collection.class.php');

class LoadableCollection extends Collection {

  private $onLoad = null;  
  private $isLoaded = false;

  public function setLoadCallback($callback){
    if (!is_callable($callback, false, $callableName))
      throw new ECollectionCallbackInvalid($callableName . ' is not 
                                callable as a parameter to onLoad');
    }
    else {
      $this->onLoad = $callback;
    }
  }

  
  protected function checkCallback(){
    if (!$this->isLoaded){
      $this->isLoaded = true;
      if ($this->onLoad === NULL){
        
        if (method_exists($this, 'load')){
          $this->onLoad = array($this, 'load');
        }
        else {
          throw new ECollectionCallbackInvalid('No valid 
                  callback set and no load() method found');
        }
      }
      call_user_func($this->onLoad, $this);
    }
  }

  public function addItem($item, $key = null){
    $this->checkCallback();
    parent::addItem($item, $key);
  }

  public function getItem($key){
    $this->checkCallback();
    return parent::getItem($key);
  }

  public function getItems(){
    $this->checkCallback();
    return parent::getItems();
  }

  public function removeItem($key){
    $this->checkCallback();
    parent::removeItem($key);
  }

  public function exists($key){
    $this->checkCallback();
    return parent::exists($key);
  }
  public function keys(){
    $this->checkCallback();
    return parent::keys();
  }

  public function length(){
    $this->checkCallback();
    return parent::length();
  }
  
  public function clear(){
    $this->checkCallback();
    return parent::clear();
  }

  
  public function unload(){
    $this->clear();
    $this->isLoaded = false;
    $this->onLoad = null;
  }
}
Listagem 5. Criando classe carregável

Acima vimos como trabalhar com classes carregáveis, o que acham de vermos um exemplo prático disso?

Abaixo iremos ver um script de exemplo para aplicar isso.


require('LoadableCollection.class.php');

class MyCars extends LoadableCollection {

  public function load(){
    // carrega a coleção toda
    echo "Carregando collection ...n";
    $this->addItem('Audi');
    $this->addItem('BMW');
    $this->addItem('Mercedes');
    echo " Collection carregadan";
  }

}

echo "Criando o objeto MyCars.n";
$c = new MyCars;
echo "Objeto MyCars criadon";
echo "O objeto não foi carregado aindan";

foreach($c as $key => $value){
  echo "$key: $valuen";
}
echo "Feito.n";
Listagem 6. Script de exemplo para testar a funcionalidade

O resultado do código acima deve ser parecido com o abaixo:


# ./example.php
Creating MyCars object
Objeto MyCars criado 
O objeto não foi carregado ainda.
Carregando collection ...
Collection carregada ...
0: Audi
1: BMW
2: Mercedes
Feito.

Conclusão

Nesse artigo tentamos abordar de maneira clara e objetiva o uso de Iterators em PHP, essa técnica é, sem dúvidas, uma que pode melhorar e muito a produtividade de seus códigos em sistemas web.