Solucionado (ver solução)
Solucionado
(ver solução)
16
respostas

Problemas com ordenação

Estou tendo problemas com ordenação dos resultados. Tudo funciona perfeitamente, exceto quando preciso trazer determinados resultados ordenados.

Não vou colocar código aqui porque já entendi o problema. Só não sei como resolver.

Para resumir, vou dar um exemplo.

Vamos supor que eu esteja consultando uma lista de pagamentos. Esta consulta envolve duas entidades: Pagamento e Parcela. O objetivo seria retornar a lista ordenada por vencimento da parcela.

Lá no repositório, o procedimento de consulta é feito por dois métodos: Um que efetivamente faz a consulta no banco e envia o statement para o segundo método, que faz a hidratação.

O statement está ordenado. O problema está quando hidratamos estes dados.

O método hidratarListaPagamentos($stmt) recebe o statement e retorna uma lista de acordo com o modelo orientado a objetos. É aqui que mora o problema. Assim eu perco a ordenação justamente por conta deste formato orientado a objetos.

Cheguei na conclusão que é impossível ordenar os resultados quando retornamos uma lista com objetos. Até pensei em alguma lógica aqui mas perderia todo o sentido do modelo orientado a objetos.

Pergunto: Tem alguma forma de fazer isso sem usar um ORM?

16 respostas

Flavio, eu precisaria ver o seu código que realiza o hidrate dos objetos, mas normalmente a ordenação não é sobreposta não. Estranho...

Segue:

  public function hidratarListaPagtos($stmt)
  {
    $res = $stmt->fetchAll();
    // aqui, com var_dump() dá para ver que estão ordenados.

    $listaPagtos = [];

    foreach ($res as $pagto) {

      if (!array_key_exists($pagto->id, $listaPagtos)) {
        $listaPagtos[$pagto->id] = new Pagamento($pagto->id, new DataBr($pagto->data_inclusao), $pagto->descricao);
        // setando os atributos não obrigatórios
        $listaPagtos[$pagto->id]->setDocto($pagto->docto);
        $listaPagtos[$pagto->id]->setValorTotal(new Real($pagto->valor_total));
        $listaPagtos[$pagto->id]->setDataEntradaNF(new DataBr($pagto->data_entrada_nf));
        $listaPagtos[$pagto->id]->setNumeroRequisicao($pagto->num_requisicao);
        $listaPagtos[$pagto->id]->setQtdeParcelas($pagto->qtde_parcelas);
        $listaPagtos[$pagto->id]->setObs($pagto->obs);
      }

      $parcela = new Parcela(
        $pagto->id_parcela,
        $pagto->num_parcela,
        new DataBr($pagto->vcto_parcela),
        new Real($pagto->valor_parcela),
        new Status($pagto->status, $pagto->vcto_parcela),
        new DataBr($pagto->data_pagto),
        new Real($pagto->valor_pago),
        new Real($pagto->desconto_parcela)
      );

      // somente id e nome fantasia são obrigatórios
      $fornec = new Fornecedor($pagto->id_fornec, 'a', $pagto->nome_fantasia, 'a', 'a', 'a', 'a');

      $os = new OS($pagto->id_os, $pagto->numero);

      $listaPagtos[$pagto->id]->setFornec($fornec);
      $listaPagtos[$pagto->id]->addParcela($parcela);
      $listaPagtos[$pagto->id]->setOS($os);
    }

    return $listaPagtos;
  }

Não coube o var_dump($listaPagtos) :P

Se precisar, incluo na próxima respota.

Acho que entendi o problema, Fávio. Você ordena por Parcela mas faz o hydrate partindo do Pagamento.

A solução mais simples é fazer a ordenação desse array (php.net/usort) depois de realizar o hydrate. :-)

Oi Vinicius. Estou voltando aqui porque não consegui fazer, embora tenha tentando bastante.

Percebi que mesmo se eu ordenar o array (na verdade com uasort(), para não redefinir as keys :), quando o hydrate for feito, o array resultante será algo assim:

$pagtos = [
  0 => [
    'descricao' => 'pagamento1',
    'parcelas' => [
      0 => [
        'vcto' => '01/04/2020',
      ],
      1 => [
        'vcto' => '01/05/2020',
      ],
    ],
  ],
  1 => [
    'descricao' => 'pagamento2',
    'parcelas' => [
      0 => [
        'vcto' => '01/03/2020',
      ],
      1 => [
        'vcto' => '01/06/2020',
      ],
    ],
  ],
];

Então não sei se daria para construir um array desta forma usando OO. Parece que a visualização dos dados ordenados nunca irá funcionar corretamente se os dados que eu desejo exibir ordenados forem das parcelas (no caso, data de vencimento e valor).

A menos que eu crie alguma classe que faça a ordenação à parte ou não use OO, não consegui de maneira nenhuma ordenar no hydrate :(

Flávio, minha sugestão era ordenar o array $listaPagtos após fazer o hydrate, não antes.

Ha sim, verdade. Desculpa.

De qualquer forma não resolve. Testei antes, depois, durante :D

Cheguei na conclusão que realmente não dá assim apenas retornando o que o repositório manda.

As parcelas já estão ordenadas antes do hydrate e o objeto Pagamento já as recebe ordenadas. Por isso não tem o que ordenar após o hydrate.

Mesmo que se fosse o caso de ordenar, a ordem será feita dentro das parcelas de cada objeto Pagamento, individualmente. O que também não resolveria.

Mas agora analisando friamente, acho que é impossível exibir na View as parcelas ordenadas pela data do vencimento (ou qualquer outro campo proveniente das parcelas, como o valor da parcela, por exemplo). Isso porque as parcelas são um array, dentro de outro array (o objeto Pagamento), dentro de outro array (a lista completa de pagamentos).

Lá na View as iterações ocorrem no pagamento. Cada iteração insere na tela os dados do pagamento. Logo, em cada iteração só irá exibir as parcelas daquele pagamento. Aí não tem como ordenar tudo mesmo, pois os vencimentos das parcelas do pagamento da iteração atual pode ser maior ou menor do que os vencimentos das parcelas do pagamento da próxima iteração.

O negócio é bolar uma lógica à parte só para isso.

Ou usar o Doctrine... Como será que ele faz isso (internamente).. :)

public function hidratarListaPagtos($stmt)
  {
    $res = $stmt->fetchAll();
    // aqui, com var_dump() dá para ver que estão ordenados.

    $listaPagtos = [];

    foreach ($res as $pagto) {

      if (!array_key_exists($pagto->id, $listaPagtos)) {
        $listaPagtos[$pagto->id] = new Pagamento($pagto->id, new DataBr($pagto->data_inclusao), $pagto->descricao);
        // setando os atributos não obrigatórios
        $listaPagtos[$pagto->id]->setDocto($pagto->docto);
        $listaPagtos[$pagto->id]->setValorTotal(new Real($pagto->valor_total));
        $listaPagtos[$pagto->id]->setDataEntradaNF(new DataBr($pagto->data_entrada_nf));
        $listaPagtos[$pagto->id]->setNumeroRequisicao($pagto->num_requisicao);
        $listaPagtos[$pagto->id]->setQtdeParcelas($pagto->qtde_parcelas);
        $listaPagtos[$pagto->id]->setObs($pagto->obs);
      }

      $parcela = new Parcela(
        $pagto->id_parcela,
        $pagto->num_parcela,
        new DataBr($pagto->vcto_parcela),
        new Real($pagto->valor_parcela),
        new Status($pagto->status, $pagto->vcto_parcela),
        new DataBr($pagto->data_pagto),
        new Real($pagto->valor_pago),
        new Real($pagto->desconto_parcela)
      );

      // somente id e nome fantasia são obrigatórios
      $fornec = new Fornecedor($pagto->id_fornec, 'a', $pagto->nome_fantasia, 'a', 'a', 'a', 'a');

      $os = new OS($pagto->id_os, $pagto->numero);

      $listaPagtos[$pagto->id]->setFornec($fornec);
      $listaPagtos[$pagto->id]->addParcela($parcela);
      $listaPagtos[$pagto->id]->setOS($os);
    }

    // Essa linha aqui ordena conforme você quer. Só acertar a regra pra pegar o vencimento correto
    usort($listaPagtos, fn ($pagtoA, $pagtoB) => $pagtoA->vencimentoParcela() <=> $pagtoB->vencimentoParcela());

    return $listaPagtos;
  }

Não vai :(

Até ordenaria, mas só com as parcelas dentro de cada pagamento:

Um exemplo da saída com dois objetos Pagamento Cada objeto Pagamento tem duas parcelas. O objeto tem mais atributos que, mas só deixei os que interessam para o exemplo.

// ============ primeiro objeto Pagamento
  [4]=>
  object(Fb\Sistemas\Models\Entidades\Pagamento)#102 (14) {
    ["id":"Fb\Sistemas\Models\Entidades\Pagamento":private]=>
    string(2) "13"
    ["descricao":"Bonetti\Sistemas\Models\Entidades\Pagamento":private]=>
    string(6) "Pagto1"
//----------------------------  este pagamento possui 2 parcelas
    ["parcelas":"Fb\Sistemas\Models\Entidades\Pagamento":private]=>
    array(2) {
      [0]=>
      object(Fb\Sistemas\Models\Entidades\Parcela)#106 (10) {
        ["id":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        string(2) "17"
        ["numero":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        string(1) "4"
        ["vcto":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        object(Fb\Sistemas\Helper\DataBr)#107 (1) {
          ["dataFormatada":"Fb\Sistemas\Helper\DataBr":private]=>
          string(10) "12/07/2020"
        }
        ["valor":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        object(Fb\Sistemas\Helper\Real)#108 (1) {
          ["numeroFormatado":"Fb\Sistemas\Helper\Real":private]=>
          string(5) "80,00"
        }
      }
      [1]=>
      object(Fb\Sistemas\Models\Entidades\Parcela)#153 (10) {
        ["id":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        string(2) "18"
        ["vcto":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        object(Fb\Sistemas\Helper\DataBr)#152 (1) {
          ["dataFormatada":"Fb\Sistemas\Helper\DataBr":private]=>
          string(10) "19/07/2020"
        }
        ["valor":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        object(Fb\Sistemas\Helper\Real)#162 (1) {
          ["numeroFormatado":"Fb\Sistemas\Helper\Real":private]=>
          string(5) "80,00"
        }
      }
    }
  }

// ================= segundo objeto Pagamento
  [16]=>
  object(Fb\Sistemas\Models\Entidades\Pagamento)#102 (14) {
    ["id":"Fb\Sistemas\Models\Entidades\Pagamento":private]=>
    string(2) "35"
    ["descricao":"Bonetti\Sistemas\Models\Entidades\Pagamento":private]=>
    string(6) "Pagto2"
//----------------------------  também possui 2 parcelas
    ["parcelas":"Fb\Sistemas\Models\Entidades\Pagamento":private]=>
    array(2) {
      [0]=>
      object(Fb\Sistemas\Models\Entidades\Parcela)#106 (10) {
        ["id":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        string(2) "26"
        ["vcto":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        object(Fb\Sistemas\Helper\DataBr)#107 (1) {
          ["dataFormatada":"Fb\Sistemas\Helper\DataBr":private]=>
          string(10) "13/07/2020"
        }
        ["valor":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        object(Fb\Sistemas\Helper\Real)#108 (1) {
          ["numeroFormatado":"Fb\Sistemas\Helper\Real":private]=>
          string(5) "100,00"
        }
      }
      [1]=>
      object(Fb\Sistemas\Models\Entidades\Parcela)#153 (10) {
        ["id":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        string(2) "27"
        ["vcto":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        object(Fb\Sistemas\Helper\DataBr)#152 (1) {
          ["dataFormatada":"Fb\Sistemas\Helper\DataBr":private]=>
          string(10) "16/07/2020"
        }
        ["valor":"Fb\Sistemas\Models\Entidades\Parcela":private]=>
        object(Fb\Sistemas\Helper\Real)#162 (1) {
          ["numeroFormatado":"Fb\Sistemas\Helper\Real":private]=>
          string(5) "100,00"
        }
      }
    }
  }

As parcelas até são ordenadas, mas veja que não resolve muito porque elas também são um array. Então ordena individualmente dentro de cada objeto Pagamento.

Então, lá na view quando eu faço o foreach para exibir na tabela, sempre vai percorrer cada pagamento e carregar as parcelas do pagamento da iteração atual.

Ou seja, as datas:

12/07/2020

19/07/2020

Na próxima iteração, irá carregar as parcelas do próximo pagamento:

13/07/2020

16/07/2020

O resultado final será:

12/07/2020

19/07/2020

13/07/2020

16/07/2020

:(

Acho que vc não entendeu a sugestão. Sua ordenação é pra ser pela primeira parcela de cada pagamento, certo?

Ex.:

  • Pagto 1 tem o primeiro vencimento pra dia 03
  • Pagto 2 tem o primeiro vencimento pra dia 01

O pagto 2 deve vir primeiro. Certo?

Se for assim, aquele usort resolve.

Não é isso não. Talvez eu não tenha me explicado muito bem. As parcelas, lá la view (em uma tabela) devem aparecer todas ordenadas. Não apenas do pagamento.

Da forma que vc me disse está ok, desde o início. Elas já vieram ordenadas do repositório (usei order by pelas parcelas).

O problema acontece quando eu quero exibir todas elas ordenadas na View. Lá tem uma tabela onde são exibidos todas as parcelas dados dos pagamentos. Para isso faço um foreach na $listaPagtos.

Aí não fica mesmo. Pois percorre o pagamento atual e mostra as parcelas ordenadas deste pagamento. Mas quando vai para a próxima iteração, o próximo pagamento pode ter parcelas com vcto. maior ou menor do que o anterior.

Entendeu?

Por isso imagino que eu teria que fazer um tratamento em $listaPagtos. Ele está ordenado por objetos Pagamento. Então realmente será impossível apenas fazer um foreach e exibir todas as parcelas ordenadas na tabela.

Tive um problema destes uma vez usando Laravel. Não ordenava de forma nenhuma usando o Eloquent mesmo passando os parâmetros de ordenação corretamente. Só quando usei a Facade DB que foi possível ordenar.

Realmente eu não tô entendendo o que você quer. Rsrsrs

Você buscou pagamentos mas quer exibir somente as parcelas? Não tá fazendo sentido pra mim.

De qualquer forma você pode pegar o array de parcelas de todos os pagamentos e ordenar ele.

Embora eu tenha me convencido que realmente precisarei tratar o array para fazer o que preciso, vou tentar explicar de outra forma.

Vou representar o array do objeto 'Pagamento' que mostrei antes, de forma mais simples para ficar melhor de exemplificar:

Seja com o Order By do repositório ou com usort() as parcelas ficarão ordenadas dentro do array hidratado:

$listaPagtos = [
  19 => [
    'descricao' => 'Pagto1',
    'parcelas'  => [
      0 => [
        'vcto'  => '12/07/2020',
        'valor' => '80,00'
      ],
      1 => [
        'vcto'  => '19/07/2020',
        'valor' => '80,00'
      ]
    ]
  ],

  23 => [
    'descricao' => 'Pagto2',
    'parcelas'  => [
      0 => [
        'vcto'  => '13/07/2020',
        'valor' => '10,00'
      ],
      1 => [
        'vcto'  => '16/07/2020',
        'valor' => '100,00'
      ]
    ]
  ]
];

Veja que as parcelas dentro de cada pagamento estão ordenadas.

Então o próximo passo é mostrar todas elas na View:

// na View:
foreach ($listaPagtos as $pagtos) {
  foreach ($pagtos['parcelas'] as $parcelas) {
    echo $parcelas['vcto'] . '<br>';
  }
}

// Obs: A título de curiosidade, obviamente, o foreach acima esta é uma representação simples. O verdadeiro seria algo como:
foreach ($listagemPagtos as $pagto) {
  foreach ($pagto->parcelas() as $parcela) {
    echo $parcela->getVcto() . '<br>';
  }
}

Isso irá produzir o resultado final:

12/07/2020
 19/07/2020
 13/07/2020
 16/07/2020

Sendo que o objetivo final seria:

12/07/2020
 13/07/2020
 16/07/2020
 19/07/2020

Percebe? A ordenação com usort() ordena somente as parcelas dentro do pagamento. Mas quando eu as recupero com o foreach para mostrar todas elas ordenadas na View, não há como.

Esse é meu ponto. Se você precisa das parcelas ordenadas, não faz o menor sentido fazer foreach na lista de pagamentos.

Faz um map de pagamentos pra suas parcelas. Algo como:

$listaParcelas = array_map(fn (Pagamento $pagto) => $pagto->parcelas(), $listaPagtos);
$iteratorParcelas = new RecursiveArrayIterator($listaParcelas);
$iteratorParcelas->uasort(fn ($a, $b) => $a->vencimento() <=> $b->vencimento());

foreach ($iteratorParcelas as $parcela) {
    $parcela->vencimento();
}

Entendeu?

Entendi. Esse array_map já deu uma ótima clareada aqui

Eu faço o foreach para mostrar as informações das parcelas mas também dos pagamentos aos quais elas pertencem. Se eu mostrar na View somente as parcelas, o usuário não saberá a qual pagamento ela pertence :)

Deve haver uma correspondência entre parcela e pagamento.

descricao - vcto parcela - valor parcela
---------------------------------------------------------
pagto1 - 12/07/2020 - 80,00 
pagto2 - 13/07/2020 - 100,00
pagto2 - 16/07/2020 - 100,00 
pagto1 - 19/07/2020 - 80,00

Por isso as parcelas precisam ser exibidas ordenadas de forma global, tipo uma tabela do Excel.

Da forma que fiz até então para percorrer o array de pagamentos é impossível obter este resultado.

Mas o array_map que vc exemplificou já ajudou bastante. Em $listaParcelas já estão todas as parcelas ordenadas por vencimento.

Se fosse exibir apenas as parcelas, estaria resolvido. Mas também preciso da informação do pagamento a qual ela pertence.

Mas acho que dá para me virar daqui para frente :)

solução!

Olha, baseado no que você precisa, talvez você tenha um problema de modelagem. Ao invés de Pagamento ter um array de parcelas, eu teria a classe Parcela com o atributo pagamento, entende?

Dessa forma você buscaria as parcelas e a partir delas pegaria a descrição do pagamento.

Mas isso depende do seu negócio, do seu domínio. Só conversando com um especialista de domínio pra saber se isso faz sentido.

Boooa. Não tinha pensado desta forma.

O especialista do domínio sou eu mesmo, então acho uma ótima ideia, hahaha!

Obrigado mais uma vez pela paciência e boa vontade. Caso resolvido!