Se você estiver tendo problemas com uma query especifica que morre sem deixar vestigios, sem imprimir nada na tela, pode se beneficiar de mais informações registrando um listener generico para imprimir os eventos e também melhorando o flush para ele realmente mostrar algo. antes de fazer a query, registre o listener na conexão:
$conn = Doctrine_Manager::connection();
$conn->setListener(new MyDebugger());
$result = $query->execute();
E também declare ele e o flush melhorado:
class MyDebugger implements Doctrine_Overloadable
{
public function __call($methodName, $args)
{
echo $methodName . ' called !';
flush2();
}
}
function flush2 (){
echo(str_repeat(' ',256));
// check that buffer is actually set before flushing
if (ob_get_length()){
@ob_flush();
@flush();
@ob_end_flush();
}
@ob_start();
}
Mostrando postagens com marcador doctrine. Mostrar todas as postagens
Mostrando postagens com marcador doctrine. Mostrar todas as postagens
terça-feira, 7 de dezembro de 2010
quarta-feira, 30 de junho de 2010
Doctrine - Operações em lote ou "batch updates" - algums dicas
O Doctrine não é uma ferramenta feita para operações em lote, ele é um ORM. Mas tem alguns momentos que já temos o método na nossa camada de negócio e queremos chama-lo um monte de vezes para executar alguma manutenção no banco de dados ou algo assim.
Nestes momentos nem sempre é necessário reescrever tudo em um sql otimizado, em casos mais simples ou não tão críticos, podemos usar o que já temos. Porém, fazer esse tipo de operação no Doctrine requer alguns cuidados que temos aprendido com o tempo. Coloco aqui algumas dicas para quem quiser pular a fase do aprendizado pelo erro. Mais abaixo também coloco um exemplo de código para o tão famigerado e entretanto tão usado copy and paste.
Dicas:
1 - Faça um looping iniciando e "commitando" a transação. Isso aumenta em muito a velocidade de insert/update no banco. A quantidade de registros atualizados a cada iteração vai ter que ser definida na tentativa e erro, mas quanto mais simples a operação, maior a quantidade de itens que podem ser incluidos. Vale perceber que mesmo que a transação funcione, se forem muitos itens, o resultado final pode ficar mais lento mesmo assim.
2 - Sempre chame o método free() passando o parâmetro true para seus objetos instanciados pelo doctrine para que ele libere a memória usada por aquele objeto. Eu chamo também um unset() logo em seguida.
3 - Se você usar uma action no symfony (isso é, se é que você usa o symfony), ele tem uma mania de querer repetir a operação depois que termina. Por algum motivo que ainda não descobri, ele faz um redirect ou forward para a mesma action, então eu coloco aquele die() no final. Sei que ideal seria uma task rodando do shell, mas faço assim pq acho mais simples, principalmente para operações que vão rodar uma vez só. Se for algo que vai ser ativado pelo cron, aí não tem como fugir das tasks.
Para recortar e colar (divirta-se):
Nestes momentos nem sempre é necessário reescrever tudo em um sql otimizado, em casos mais simples ou não tão críticos, podemos usar o que já temos. Porém, fazer esse tipo de operação no Doctrine requer alguns cuidados que temos aprendido com o tempo. Coloco aqui algumas dicas para quem quiser pular a fase do aprendizado pelo erro. Mais abaixo também coloco um exemplo de código para o tão famigerado e entretanto tão usado copy and paste.
Dicas:
1 - Faça um looping iniciando e "commitando" a transação. Isso aumenta em muito a velocidade de insert/update no banco. A quantidade de registros atualizados a cada iteração vai ter que ser definida na tentativa e erro, mas quanto mais simples a operação, maior a quantidade de itens que podem ser incluidos. Vale perceber que mesmo que a transação funcione, se forem muitos itens, o resultado final pode ficar mais lento mesmo assim.
2 - Sempre chame o método free() passando o parâmetro true para seus objetos instanciados pelo doctrine para que ele libere a memória usada por aquele objeto. Eu chamo também um unset() logo em seguida.
3 - Se você usar uma action no symfony (isso é, se é que você usa o symfony), ele tem uma mania de querer repetir a operação depois que termina. Por algum motivo que ainda não descobri, ele faz um redirect ou forward para a mesma action, então eu coloco aquele die() no final. Sei que ideal seria uma task rodando do shell, mas faço assim pq acho mais simples, principalmente para operações que vão rodar uma vez só. Se for algo que vai ser ativado pelo cron, aí não tem como fugir das tasks.
Para recortar e colar (divirta-se):
function executeRecalculateAllRatings() {
set_time_limit(3600);
$min = 0;
$increment = 1000;
$last = 50000;
$count = 0;
while ($min <= $last) { echo "<>NEW TRANSACTION - $min <>";
Doctrine_Manager::connection()->beginTransaction();
$comments = CommentTable::build()->findByIdRange($min, $min + $increment);
foreach($comments as $comment) {
$res = RatingLogTable::build()->calculateCommentRatingTotal($comment['id']);
$comment->setRating($res[0],$res[1]);
$comment->save();
$comment->free(true);
unset($comment);
echo $count++ . ' - ';
}
$min = $min + $increment;
Doctrine_Manager::connection()->commit();
}
die();
}
Marcadores:
batch,
doctrine,
memory,
php,
phpdoctrine
quarta-feira, 11 de fevereiro de 2009
Criando testes no PHPUnit/Selenium que rodem com o symfony/doctrine/phing/phpunit
Ingredientes
symfony+doctrine: framework de desenvolvimento e ORM
phpunit: para testes unitários
phing: para fazer o build, rodar os testes, gerar a documentação, lavar a roupa, etc...
Problemas a se Vencer:
O phpunit, quando roda pela linha de comando, não carrega as configurações do projeto do symfony.
Alguns testes do selenium precisam de acesso à api do symfony para configurar o ambiente e ter acesso aonde o browser não tem.
Descrição da Solução:
Para isso precisamos carregar as configurações do symfony/docrine. Claro que é bom fazer isto uma vez só para não pesar nos testes. Porém precisamos fazer isto de uma maneira que não prenda muito os testes a um diretório específico e que não seja repetitiva.
O phpunit, quando rodado pelo phing, não considera as suites de teste, rodando todos os testes separadamente. Por isso, o setUp() da suite, que poderia parecer um bom local, na verdade não é pois ele será desconsiderado pelo phing. Escolhemos então o setUp de cada classe teste. Para não repetir o código, criamos uma classe da qual extendemos todos os nossos testes. Assim nossas classes já herdam o setUp. Como o autoload do symfony ainda não foi carregado, precisamos informar para o phpunit o caminho da nossa classe caso ela não esteja no include.
Solução Proposta:
Conclusão:
Claro que devem existir inúmeras soluções para este problema. Caso você tenha algo a acrescentar ou alguma crítica construtiva, certamente isto vai melhorar ainda mais a nossa proposta.
symfony+doctrine: framework de desenvolvimento e ORM
phpunit: para testes unitários
phing: para fazer o build, rodar os testes, gerar a documentação, lavar a roupa, etc...
Problemas a se Vencer:
O phpunit, quando roda pela linha de comando, não carrega as configurações do projeto do symfony.
Alguns testes do selenium precisam de acesso à api do symfony para configurar o ambiente e ter acesso aonde o browser não tem.
Descrição da Solução:
Para isso precisamos carregar as configurações do symfony/docrine. Claro que é bom fazer isto uma vez só para não pesar nos testes. Porém precisamos fazer isto de uma maneira que não prenda muito os testes a um diretório específico e que não seja repetitiva.
O phpunit, quando rodado pelo phing, não considera as suites de teste, rodando todos os testes separadamente. Por isso, o setUp() da suite, que poderia parecer um bom local, na verdade não é pois ele será desconsiderado pelo phing. Escolhemos então o setUp de cada classe teste. Para não repetir o código, criamos uma classe da qual extendemos todos os nossos testes. Assim nossas classes já herdam o setUp. Como o autoload do symfony ainda não foi carregado, precisamos informar para o phpunit o caminho da nossa classe caso ela não esteja no include.
Solução Proposta:
### classe pai
class Sf_PHPUnit_Extensions_SeleniumTestCase extends PHPUnit_Extensions_SeleniumTestCase {
function setUp()
{
if ( SF_APP_NAME != '' )
{
define( 'SF_APP_NAME', 'frontend' );
define( 'SF_ENV', 'test' );
define( 'SF_CONN', 'doctrine' );
// ajuste o caminho abaixo de acordo com o local onde vc por esta classe
require_once(dirname(__FILE__).'/../../../../config/ProjectConfiguration.class.php');
// busca as configurações do projeto
$configuration = ProjectConfiguration::getApplicationConfiguration( SF_APP_NAME , SF_ENV, true);
sfContext::createInstance($configuration);
sfContext::getInstance()->getUser()->initSession('phpunit');
// em alguns contextos as linhas abaixo podem ser úteis, mas não lembro quais agora :)
//$databaseManager = new sfDatabaseManager($configuration);
//$databaseManager->loadConfiguration();
}
$this->setBrowser("firefox");
// "solução de contorno" para definir se o teste está no ambiente de desenv ou homolog.
if(isset($_SERVER[HOMEDRIVE]) && $_SERVER[HOMEDRIVE] == 'C:') {
$this->setBrowserUrl("http://aliaslocal/");
}
else {
// ip ou alias do servidor...
$this->setBrowserUrl("http://10.1.1.234/");
}
}
}
### classe filha
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';
// lembrar que este require é necessário pois o autoload do symfony ainda não foi carregado neste ponto.
require_once dirname(__FILE__).'/../../../plugins/sfUtil/lib/test/SF_PHPUnit_Extensions_SeleniumTestCase.php';
class LoginSeleniumTest extends SF_PHPUnit_Extensions_SeleniumTestCase
{
function setUp() {
// lembrar de chamar o setUp do pai caso precise extender aqui...
parent::setUp();
$this->doSomeMoreSetup();
}
// ...
Conclusão:
Claro que devem existir inúmeras soluções para este problema. Caso você tenha algo a acrescentar ou alguma crítica construtiva, certamente isto vai melhorar ainda mais a nossa proposta.
Assinar:
Postagens (Atom)