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 )
}
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]);
}
}
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]);
}
}
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";
}
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;
}
}
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";
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.