Ir para conteúdo

POWERED BY:

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

Bruno Augusto

Problemas com Recursividade...

Recommended Posts

Apesar de esse tópico conter uma informação ou outra a respieto do Zend Framework, o foco principal é a recursividade.

 

Acontece que eu, na boa intenção de ampliar a funcionalidade de um método de limpeza de arrays, principal mas não exclusivamente os vindos por formulário, decidi implementar a possibilidade de remoção dos filtros a serem aplicados pela cadeia de filtragem, recurso do Zend_Filter.

 

Antes, eu apenas pegava os filtros principais e montava a cadeia, agora eu estruturo a cadeia num array e só adiciono a instância do filtro à cadeia se ele (o filtro) não tiver sido escolhido para remoção.

 

Ficou assim:

 

public function clean( array $data, array $exclude = array() ) {

       if( ! empty( $data ) ) {

           // Preparing filters chain

           $chain = array( 'StringTrim'      => new Zend_Filter_StringTrim(),
                           'StripTags'       => new Zend_Filter_StripTags(),
                           'StripNewLines'   => new Zend_Filter_StripNewlines(),
                           'HtmlEntities'    => new Zend_Filter_HtmlEntities() );

           // Looping through POSTDATA array...

           foreach( $data as $field => $value ) {

               $filters[ $field ] = new Zend_Filter();

               // Looping through Filters

            foreach( $chain as $name => $object ) {

                /**
                 * If the data-key is present in exclusion rules AND
                 * the filter name it's not one of these rules...
                 */

                if( isset( $exclude[ $field ] ) ) {

                       if( ! in_array( $name, $exclude[ $field ] ) ) {

                           // ... we'll add it

                           $filters[ $field ] -> addFilter( $object );
                       }

                } else {

                    // Otherwise, we'll add it anyway

                       $filters[ $field ] -> addFilter( $object );
                }
            }

            //-------------------------

               // Filtering...

               if( is_array( $value ) ) {

                   // ... recursively

                   $data[ $field ] = $this -> clean( $value, $exclude );

               } else {

                   // only if we have filters

                   $f = $filters[ $field ] -> getFilters();

                   if( ! empty( $f ) ) {

                       $data[ $field ] = $filters[ $field ] -> filter( $value );
                   }
               }
           }
       }

       return $data;
   }

Funciona perfeitamente, ou quase. >.<

 

A linha onde consta o comentário "recursively", que é onde a recursão é aplicada está com um problema irritante que, eu acredito ter identificado como sendo o responsável o parâmetro $exclude.

 

O teste se deu através de:

 

$data = $this -> form -> clean( $this -> getRequest() -> getPost(),

                                           array( 'teste' => array( 'StringTrim',
                                                                    'StripTags', 'StripNewLines',
                                                                    'HtmlEntities'
                                                                  ),

                                                  'title' => array( 'StringTrim',
                                                                    'StripTags',
                                                                    'StripNewLines',
                                                                    'HtmlEntities'
                                                                   )
                                        ) );

$this -> getRequest() -> getPost() equivale ao $_POST do ZF.

 

O problema é que os campos de formulário que levam "[]" e são interpretados como arrays precisam da recursão para que sejam tratados também.

 

Esse índice teste refere-se à um dropdiwn de múltipla seleção.

 

Como é possível observar, a intenção seria bloquear todo e qualquer filtro para os campos teste e title.

 

Mas não importa o que eu faça, o HtmlEntities continua sendo aplicado no campo teste, como se não estivesse passando pela verificação que o exclui dacadeia de filtragem.

 

Mas se eu comento a linha de $chain referente ao HtmlEntities, óbviamente passo a receber o resultado espero, isto é, a não conversão.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Exato André. Vejam, solucionar ainda não consegui, mas consegui reproduzir ocomportamento esperado:

 

public function clean( array $data, array $exclude = array(), $recursion = FALSE ) {

       // Preparing filters chain

       $chain = array( 'StringTrim'      => new Zend_Filter_StringTrim(),
                       'StripTags'       => new Zend_Filter_StripTags(),
                       'StripNewLines'   => new Zend_Filter_StripNewlines(),
                       'HtmlEntities'    => new Zend_Filter_HtmlEntities() );

       if( ! empty( $data ) ) {

           foreach( $data as $field => $value ) {

               if( ! isset( $filters[ $field ] ) || ! $filters[ $field ] instanceof Zend_Filter ) {

                   $filters[ $field ] = new Zend_Filter();
               }

               // Injeção inválida de parâmetro

               if( ! $recursion ) {

                foreach( $chain as $name => $object ) {

                    if( isset( $exclude[ $field ] ) ) {

                        if( ! in_array( $name, $exclude[ $field ] ) ) {

                            $filters[ $field ] -> addFilter( $object );
                        }

                    } else {

                        $filters[ $field ] -> addFilter( $object );
                    }
                }
               }

               //----------------------

               if( is_array( $value ) ) {

                   // Inutilização do parâmetro $exclude

                   $data[ $field ] = $this -> clean( $value, array(), TRUE );

               } else {

                   $f = $filters[ $field ] -> getFilters();

                   if( ! empty( $f ) ) {

                       $data[ $field ] = $filters[ $field ] -> filter( $value );
                   }
               }
           }
       }

       return $data;
   }

Removi os comentários definitivos dexando apenas os necessários paraesta modificação.

 

O que eu achava ser o $exclude o culpado, na verdadeera o foreach sobre $chain que, na recursão, caia no else do isset().

 

Injetando um parâmetro extra, condicionando, consegui reproduzir o efeito esperado, mas isso nem de longe é o correto. :(

Compartilhar este post


Link para o post
Compartilhar em outros sites

Longe de mim querer um POST UP mas, mesmo que eu pudesse editar a mensagem anterior, ficaria confuso.

 

Voltando ao básico do básico da recursividade, sem mencionar questões de filtros, cadeias, coleções nem nada, apenas... a essência da coisa:

 

    public function clean( array $data, array $exclude = array() ) {

   	foreach( $data as $field => $value ) {

   		/**
   		 * Aqui, $field representa os atributos "name" do HTML,
   		 * logo 'teste' pode ser um dos valores de $field, já
   		 * que existe um campo HTML com esse valor
   		 */
   		//print $field . '<br />';

   		if( is_array( $value ) ) {

   			/**
   			 * Como 'teste' é um dropdown de múltipla seleção
   			 * precisa ser tratado recursivamente
   			 * 
   			 * Invocando o método seja com $value ou com $data[ $field ]~
   			 * como primeiro argumento...
   			 */

               $this -> clean( $data[ $field ], $exclude );

   		} else {

   			/**
   			 * ... faz com que aquele 'teste' que antes existia, não
   			 * exista mais, sendo substituído por seus valores recebidos
   			 */
   			print $field . '<br />';
   		}
   	}

   	return $data;
   }

Pelos comentários no código consegui, até certo ponto explicar o problema.

 

O foreach inicial, sobre o array de entrada inicial (vindo do formulário, por exemplo) possui ´todos os índices e valores esperados, oque pode ser observado pelo primeiro print, comentado.

 

Já no segundo print, após a rotina ter satifeito a condição de recursividade e onde o foco do recurso será aplicado, os índices mais importantes que são justamente os que interpretados como arrays, como dropdowns de múltipla seleção ou estruturas mais complexas de informações, simplesmente somem, ficando APENAS OS VALORES.

 

Por esta imagem pode se comparar ambos os print, lado a lado:

 

Imagem Postada

 

Nota: A indentação foi feita manualmente, apenas para ilustrar a hierarquia.

 

Agora sim, voltando ao assunto principal, dessa forma, não consigo utilizar o recurso do segundo parâmetro a contento pois preciso comparar se um dos índices "pai" existe nele.

 

Mas se eles estão sumindo, torna-se impossível. A pergunta, por fim, é como a recursividade deve ser corretamente aplicada. Já que desse jeito (e outros que já tentei) não funcionam?

 

Esses outros jeitos seriam:

 

$data[ $field ] = $this -> clean( $data[ $field ], $exclude );
$data[ $field ] = $this -> clean( $value, $exclude );

 

Já estou perdendo a calma com essa bobeira que, por não utilizar constantemente, não me vêm à mente a solução. :angry:

Compartilhar este post


Link para o post
Compartilhar em outros sites

Recursividade trabalha bem com while.

 

O problema de usar while é que ele é um 'pouco' descontrolado. Para isso podemos definir índices fora dele.

 

Podemos, por exemplo armazenar o nível de árvore que estamos trabalhando. Funções como shift e pop podem ajudar também.

 

Não trabalho com ZF, por isso não consegui ajudar nas outras necessidades, pois não consegui entender que tipo de objeto o Filtro espera, e que tipo de retorno ele dá.

 

Resumindo, até o momento não tava dando pra sacar o que você envia de entrada e o que você quer de saída, que tipo de retorno deve acontecer.

 

Desde que você certifique-se que nenhum índice da árvore armazenará o valor (bool)false, você pode também aplicar um

while(false !== ($index = next($array)))

que vai trabalhar perfeitamente na recursividade

Compartilhar este post


Link para o post
Compartilhar em outros sites

Sobre o ZF, foi justamente por isso que fiz esse UP indevido, porém necessário. Ele espera uma string, mas eu passo um array que seria meu $_POST.

 

Justamente por ele só esperar strings é que preciso da recursividade, para ir iterando array adentro até ter alcançar a última dimensão existente e poder passar o que o ZF espera.

 

Mas, voltando ao problema principal, com foreach que é o mais adequado para iterar sobre arrays não seria possível? Como eu disse, por não mexer com isso constantemente, não consigo enxergar o motivo de estar tendo esse problema.

 

Tá certo que não trabalhar com freqüência com dropdown's de múltipla seleção. Mas se a lógica estiver certa, isso não importa, pois o tratamento estará sendo feito corretamente.

Compartilhar este post


Link para o post
Compartilhar em outros sites

O problema Evandro, como eu disse no post UP indevido, foi a necessidade da aplicabilidade do segundo parâmetro.

 

Para que ele funcione eu preciso do índice do array passado ao foreach. Perceba que o type hinting já obriga o parâmetro a ser um array, para que já possa "de cara" usar um foreach e pegar as chaves e os valores.

 

E só se o valor da iteração corrente for um array que aplico a recursividade voltando ao início do método passando, como primeiro argumento esse array uma dimensão adentro.

 

A imagem que eu postei ilustra o valor de $field dentro da recursão (coluna à direita), isto é, no else do is_array() e fora dela (à esquerda), fora desse condicional, logo após abrir o bloco foreach.

Compartilhar este post


Link para o post
Compartilhar em outros sites

if( is_array( $value ) ) {

 

// ... recursively

 

$data[ $field ] = $this -> clean( $value, $exclude );

 

note que ::clean() SEMPRE retorna um array()

 

aplicando a recursividade dentro de arrays vai causar redundância do seu sistema.

 

 

$var[campo] = array(valores) -> clean() -> return -> array(filtros)

 

você muda o conteúdo de $var[campo] mas sem mudar o tipo dela. SEMPRE que ela entrar em clean, ela será filtrada, mesmo já limpa, pois é um array

 

clean - ou um método auxiliar (private) alternativo - deve retornar um booleano indicando se ele executou ou não limpeza

 

aí você aplica um while(::limpador()){} que iterará até que limpador não efetue limpezas

 

ao meu ver, vai dar uma arrastadinha pra baixo na performance, mas é a primeira saída que eu estou encontrando

 

 

Bruno, seria possível me dar um var_dump da entrada e da saída desejada???

Compartilhar este post


Link para o post
Compartilhar em outros sites

Hmmm...

 

Eu já não enxergo dessa forma que você colocou.

Eu vejo que o primeiro foreach comanda tudo. Se valor da iteração corrente for um array a recursão faz ele voltar pro início da função, ao invés de ir até o final pra depois iniciar de novo. Esquematicamente:

 

-> inicia iteração
-> verifica valor
-> não é array, cai no else e avança o ponteiro de iteração
-> não é array, cai no else e avança o ponteiro de iteração
-> é array, cai no if e volta começo
-> não é array, cai no else e avança o ponteiro de iteração, por ter adentrado uma dimensão na iteração anterior, volta uma antes de avançar
-> acabou o array, encerra iteração e retorna
Ou estou errado?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Bruno, seria possível me dar um var_dump da entrada e da saída desejada???

certo, isso é o que vai daqui pra função, agora poste como você acha que volta da função pra ca ;)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Evandro, você ainda não entendeu o dilema e como não há código tátil, apenas fragmentos lógicos mal compreendidos, um var_dump() não ajudaria. Vou simplificar ainda mais o código, voltando bem ao básico:

 

public function clean( array $data, array $exclude = array() ) {

   	foreach( $data as $field => $value ) {

   		if( is_array( $value ) ) {

   			$this -> clean( $value );

   		} else {

   			print $field . '<br />';
   		}
   	}
   }

Isso com um array de entrada:

 

Array ( [title] => foo [prefix] => [suffix] => [permissions] => Array ( [basic] => Array ( [view] => 1 [read] => 1 [write] => 1 [comment] => 1 [download] => 1 [upload] => 1 ) [admin] => Array ( [management] => Array ( [publish] => 1 [media] => 1 [users] => 1 ) [custom] => Array ( [plugins] => 1 [themes] => 1 [language] => 1 ) [advanced] => Array ( [settings] => 1 [maintenance] => 1 ) ) ) [teste] => Array ( [0] => O'neil [1] => Johnson & Johnsons ) )

Notas: Inline, para poupar espaço. Valores fictícios, apenas para terem mostras daquilo que deverá ser tratado.

 

Eu esperaria que o retorno fosse, para os índices que é o foco do problema, dentro do else (que caracteriza já haver passado pela recursão), isso:

 

title

prefix

suffix

permissions

basic

view

read

write

comment

download

upload

admin

management

publish

media

users

custom

plugins

themes

language

advanced

settings

maintenance

teste

0

1

Os itens em negrito são os índices dos valores que são arrays e entraram na recursão, por exemplo, o índice view corresponde, no HTML a, permissions[basic][view].

 

Com isso, eu esperaria que as chaves presentes fossem permissions, basic E view. E não apenas view.

 

O mesmo vale para os outros índices. Depois de passar pela recursão (else) eu gostaria de, noutro exemplo, ter o teste disponível para manipulamento, mas ele não está chegando.

 

Uma "solução" (entre aspas porque me dá coceira só de olhar) foi adicionar um:

 

$keys = array_keys( $data );

Antes do foreach. Assim, dentro do else eu operaria sob $keys ao invés de sob $field.

Compartilhar este post


Link para o post
Compartilhar em outros sites

é, tá difícil hehehehe

você postou uma entrada muito clara, não vejo o problema de postar a saída nos mesmos moldes...

 

 

uma pergunta meio boba aqui, já experimentou walk_recursive?

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.