Resolvendo Problemas com Aspas Duplas do Zend_Db_Table_Abstract
Autor: Blaine | Publicado: 2011-09-09T14:02:00-07:00

Resumo,



Quando você tentar apontar o Zend_Db_Table_Abstract do Zend Framework com o Oracle, para gerar comandos SQL é possível que você enfrente o mesmo problema que nós enfrentamos, aspas duplas aparecem e fazem seus comandos SQL inválidos. Para resolver isso adicione &nbsp;a seguinte linha:



resources.db.params.options.autoQuoteIdentifiers = 0



no seu application.ini e seja feliz.



Versão Longa,



Quando o&nbsp;Zend_Db_Table_Abstract gera erros ao tentar executar seus comandos SQL triviais, gerados automaticamente, qual é o 1º passo? Eu costumo gerar o assemble() do comando SQL para poder testa-lo diretamente no banco de dados.&nbsp;



Como o problema que temos é com qualquer método, então testamos no mais simples, sobrescrevendo o getAll e fazendo isso:




	public function getAll($where = null, $order = null, $count = null, $offset = null)

	{

		var_export( $this-&gt;select()-&gt;assemble() );

		exit();

	}




Então obtemos:



SELECT "tb_estado".* FROM "db_projeto"."tb_estado"



Sabemos agora qual é o problema: As consultas geradas automaticamente pelo Zend estão colocando aspas duplas no nome das tabelas e do schema, o que não é aceito no Oracle.



Após pesquisar um pouco achei esse link&nbsp;http://doczf.mikaelkael.fr/1.10/pt-br/zend.db.select.html&nbsp;que informa:



O método&nbsp;quoteIdentifier()&nbsp;usa aspas no SQL para delimitar o identificador, o que deixa claro que ele é um identificador de uma tabela ou coluna e não parte da síntaxe SQL.



Agora qual é o problema: As consultas geradas automaticamente pelo Zend estão colocando aspas duplas no nomes devido ao método&nbsp;_quoteIdentifier, que funciona do seguinte modo:



http://www.tig12.net/downloads/apidocs/zf/Db/Adapter/Abstract.php.source.html#line1016

1009&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/**1010&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;Quote&nbsp;an&nbsp;identifier.1011&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*1012&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@param&nbsp;&nbsp;string&nbsp;$value&nbsp;The&nbsp;identifier&nbsp;or&nbsp;expression.1013&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@param&nbsp;boolean&nbsp;$auto&nbsp;If&nbsp;true,&nbsp;heed&nbsp;the&nbsp;AUTO_QUOTE_IDENTIFIERS&nbsp;config&nbsp;option.1014&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@return&nbsp;string&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;The&nbsp;quoted&nbsp;identifier&nbsp;and&nbsp;alias.1015&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/1016&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;protected&nbsp;function&nbsp;_quoteIdentifier($value,&nbsp;$auto=false)1017&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{1018&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;($auto&nbsp;===&nbsp;false&nbsp;||&nbsp;$this-&gt;_autoQuoteIdentifiers&nbsp;===&nbsp;true)&nbsp;{1019&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$q&nbsp;=&nbsp;$this-&gt;getQuoteIdentifierSymbol();1020&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;($q&nbsp;.&nbsp;str_replace("$q",&nbsp;"$q$q",&nbsp;$value)&nbsp;.&nbsp;$q);1021&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}1022&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;$value;1023&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;



O nosso problema agora tem um foco bem mais específico. Como fazer o atributo protegido&nbsp;_autoQuoteIdentifiers ser falso. Logicamente se o atributo fosse público ou ao menos tivesse um setAutoQuoteIndentifiers ou algo similar, estaria agora resolvido o problema, na nossa classe pai que herda o&nbsp;Zend_Db_Table_Abstract, algo muito parecido com:




&nbsp; &nbsp; public function init()

&nbsp; &nbsp; {

		$this-&gt;_db-&gt;setAutoQuoteIndentifiers( false );&nbsp;

&nbsp; &nbsp;&nbsp;}




Mas, para nossa tristeza e agonia esse método setter não existe. Então precisamos descobrir como esse atributo recebe um valor.



Fazendo uma busca na classe, vemos que esse atributo pode ter seu valor atribuído caso ele venha no Array que é recebido no método construtor da classe&nbsp;



http://www.tig12.net/downloads/apidocs/zf/Db/Adapter/Abstract.php.source.html#line240

238&nbsp;239&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;obtain&nbsp;quoting&nbsp;property&nbsp;if&nbsp;there&nbsp;is&nbsp;one240&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(array_key_exists(Zend_Db::AUTO_QUOTE_IDENTIFIERS,&nbsp;$options))&nbsp;{241&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$this-&gt;_autoQuoteIdentifiers&nbsp;=&nbsp;(bool)&nbsp;$options[Zend_Db::AUTO_QUOTE_IDENTIFIERS];242&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}243&nbsp;



Tentando entender como essa variável $options é montada, vemos que essa variável é um Array que é inicializado com os valores padrões:



185&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$options&nbsp;=&nbsp;array(186&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Zend_Db::CASE_FOLDING&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;$this-&gt;_caseFolding,187&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Zend_Db::AUTO_QUOTE_IDENTIFIERS&nbsp;=&gt;&nbsp;$this-&gt;_autoQuoteIdentifiers188&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;);



Mas que tem esses valores alterados conforme os elementos que são recebidos no campo "options" do $config. Sendo $config o Array que é recebido no construtor.



190&nbsp;191&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*192&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;normalize&nbsp;the&nbsp;config&nbsp;and&nbsp;merge&nbsp;it&nbsp;with&nbsp;the&nbsp;defaults193&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/194&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(array_key_exists('options',&nbsp;$config))&nbsp;{195&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;can't&nbsp;use&nbsp;array_merge()&nbsp;because&nbsp;keys&nbsp;might&nbsp;be&nbsp;integers196&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;foreach&nbsp;((array)&nbsp;$config['options']&nbsp;as&nbsp;$key&nbsp;=&gt;&nbsp;$value)&nbsp;{197&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$options[$key]&nbsp;=&nbsp;$value;198&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}199&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}



Procurando então em que local do&nbsp;Zend_Db_Table_Abstract que é instanciado o atributo&nbsp;$this-&gt;_db

Chegamos nas seguintes linhas:

575&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/**576&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@param&nbsp;&nbsp;mixed&nbsp;$db&nbsp;Either&nbsp;an&nbsp;Adapter&nbsp;object,&nbsp;or&nbsp;a&nbsp;string&nbsp;naming&nbsp;a&nbsp;Registry&nbsp;key577&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@return&nbsp;Zend_Db_Table_Abstract&nbsp;Provides&nbsp;a&nbsp;fluent&nbsp;interface578&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/579&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;protected&nbsp;function&nbsp;_setAdapter($db)580&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{581&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$this-&gt;_db&nbsp;=&nbsp;self::_setupAdapter($db);582&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;$this;583&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;



Seguindo o rastro, vamos ao método _setupAdapter:



594&nbsp;595&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/**596&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@param&nbsp;&nbsp;mixed&nbsp;$db&nbsp;Either&nbsp;an&nbsp;Adapter&nbsp;object,&nbsp;or&nbsp;a&nbsp;string&nbsp;naming&nbsp;a&nbsp;Registry&nbsp;key597&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@return&nbsp;Zend_Db_Adapter_Abstract598&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*&nbsp;@throws&nbsp;Zend_Db_Table_Exception599&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*/600&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;protected&nbsp;static&nbsp;function&nbsp;_setupAdapter($db)601&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{602&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;($db&nbsp;===&nbsp;null)&nbsp;{603&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;null;604&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}605&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(is_string($db))&nbsp;{606&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;require_once&nbsp;'Zend/Registry.php';607&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$db&nbsp;=&nbsp;Zend_Registry::get($db);608&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}609&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if&nbsp;(!$db&nbsp;instanceof&nbsp;Zend_Db_Adapter_Abstract)&nbsp;{610&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;require_once&nbsp;'Zend/Db/Table/Exception.php';611&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw&nbsp;new&nbsp;Zend_Db_Table_Exception('Argument&nbsp;must&nbsp;be&nbsp;of&nbsp;type&nbsp;Zend_Db_Adapter_Abstract,&nbsp;or&nbsp;a&nbsp;Registry&nbsp;key&nbsp;where&nbsp;a&nbsp;Zend_Db_Adapter_Abstract&nbsp;object&nbsp;is&nbsp;stored');612&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}613&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;$db;614&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;




Vemos então que esse método consulta o Zend_Registry, o que já era esperado. Sem querer entrar na complexidade desse Registry, nós sabemos que o Zend_Registry do Banco de Dados é informado no application.ini. É lá que informamos dados como login, senha, nome do banco de dados, etc. Então, será que os dados que o construtor do Zend_Db_Table_Abstract apenas lê os dados vindos do application.ini, na parte dos "resources.db"?&nbsp;



Para testar essa hipótese eu vou imprir o $config do construtor do&nbsp;Zend_Db_Table_Abstract. Como qualquer alteração feita no código do Zend Framework isso é apenas para auxiliar o nosso entendimento e deverá ser desfeita em breve.



Ao adicionar no construtor a seguinte linha:




&nbsp; &nbsp; public function __construct($config)

&nbsp; &nbsp; {

		var_export( $config );

		exit();

		// (...)




Obtemos o seguinte resultado:




array (

&nbsp; 'host' =&gt; 'banco_do_projeto',

&nbsp; 'username' =&gt; 'usuario_do_servidor',

&nbsp; 'password' =&gt; '******',

&nbsp; 'dbname' =&gt; 'db_projeto',

&nbsp; 'charset' =&gt; 'utf8',

&nbsp; 'profiler' =&gt;&nbsp;

&nbsp; array (

&nbsp; &nbsp; 'enabled' =&gt; '1',

&nbsp; ),

)





Precisamos adicionar uma linha no nosso application.ini para testar se essa chegará até o $config. Mas qual seria o valor dessa linha. Por semelhança aos demais elementos que chegaram, deve ser algo similar a&nbsp;resources.db.params.options.xxxxxxxxxxx = 0. Mas, para encontrarmos o valor que desejamos, precisamos saber qual é o valor da chave. Esse valor nós podemos ver aqui:



http://www.tig12.net/downloads/apidocs/zf/Db/Adapter/Abstract.php.source.html#line187

185&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$options&nbsp;=&nbsp;array(186&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Zend_Db::CASE_FOLDING&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;$this-&gt;_caseFolding,187&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Zend_Db::AUTO_QUOTE_IDENTIFIERS&nbsp;=&gt;&nbsp;$this-&gt;_autoQuoteIdentifiers188&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;);&nbsp;





Usando o CRTL+Click da IDE, já fui direto para a declaração desta constante, conforme:



http://www.tig12.net/downloads/apidocs/zf/Db.php.source.html#line48

const&nbsp;AUTO_QUOTE_IDENTIFIERS&nbsp;=&nbsp;'autoQuoteIdentifiers';&nbsp;



Logo a nossa nova linha no application.ini, deve ser:




resources.db.params.options.autoQuoteIdentifiers = 0




Obtemos o seguinte resultado:




array (

&nbsp; 'host' =&gt; 'banco_do_projeto',

&nbsp; 'username' =&gt; 'usuario_do_servidor',

&nbsp; 'password' =&gt;&nbsp;'******',

&nbsp; 'dbname' =&gt; 'db_projeto',

&nbsp; 'charset' =&gt; 'utf8',

&nbsp; 'options' =&gt;&nbsp;

&nbsp; array (

&nbsp; &nbsp; 'autoQuoteIdentifiers' =&gt; '1',

&nbsp; ),

&nbsp; 'profiler' =&gt;&nbsp;

&nbsp; array (

&nbsp; &nbsp; 'enabled' =&gt; '1',

&nbsp; ),

)




Esse novo elemento no $config vai alterar o atributo protegido&nbsp;_autoQuoteIdentifiers&nbsp;retirando as aspas dos SQL gerados e resolvendo o nosso problema.



Espero que isso tenha ajudado.



Um grande abraço.