Skip to content

fix(posts): return empty Eloquent collection for posts without category - #2

Open
Guajir0-code wants to merge 2 commits into
sourcevortex:mainfrom
Guajir0-code:fix/related-posts-return-type
Open

fix(posts): return empty Eloquent collection for posts without category#2
Guajir0-code wants to merge 2 commits into
sourcevortex:mainfrom
Guajir0-code:fix/related-posts-return-type

Conversation

@Guajir0-code

@Guajir0-code Guajir0-code commented Aug 5, 2026

Copy link
Copy Markdown

Problema

Um post cuja busca de categoria não retorna nada derruba a própria página com HTTP 500. Não é degradação — a página não abre.

Evidência

Reproduzido localmente com o teste incluído neste PR, rodando contra o código atual:

TypeError: App\Services\WpPostService::getRelatedPosts(): Return value must be of type
Illuminate\Database\Eloquent\Collection, Illuminate\Support\Collection returned

O PostController::show() chama getRelatedPosts() no caminho de render da página do post, então o erro sobe direto para a resposta.

Causa

app/Services/WpPostService.php:111 declara o retorno como Eloquent\Collection (o use no topo do arquivo aponta para Illuminate\Database\Eloquent\Collection):

public static function getRelatedPosts(WpPost $post, int $limit = 3): Collection

Mas o caminho de saída antecipada devolve outra coisa:

if (! $categorySlug) {
    return collect();      // Illuminate\Support\Collection
}

O helper collect() constrói um Support\Collection. A hierarquia é Eloquent\Collection extends Support\Collection — nunca o contrário. Então o valor devolvido não satisfaz o tipo declarado e o PHP lança TypeError.

Quando isso acontece na prática

$categorySlug vem de:

$post->terms()->where('wp_term_taxonomy.taxonomy', 'category')->first()?->slug;

Fica nulo sempre que o post não tem nenhum termo da taxonomia category. O WordPress normalmente atribui "Sem categoria" por padrão, o que torna o caso raro — mas ele aparece em posts importados de outra instalação, migrados entre taxonomias, ou que tiveram a categoria removida direto no banco. E quando aparece, o resultado é a página inteira fora do ar.

Solução

if (! $categorySlug) {
    // collect() builds a Support\Collection, which is not an
    // Eloquent\Collection and fails this method's return type.
    return new Collection;
}

Collection aqui já é o Eloquent\Collection importado no topo do arquivo — nenhum use novo foi necessário. O comentário fica no código porque collect() é o reflexo natural de quem estiver editando esse trecho depois.

Como validar

php vendor/bin/pest --filter=RelatedPostsTest
✓ it returns an Eloquent collection when the post has no category
✓ it still returns posts from the same category

Tests: 2 passed (5 assertions)

Verificado nos dois sentidos — contra o código anterior, o primeiro teste falha exatamente com o TypeError descrito acima:

⨯ it returns an Eloquent collection when the post has no category
   TypeError: ... Illuminate\Support\Collection returned

O segundo teste existe para garantir que o caminho normal (post com categoria) continua devolvendo os posts relacionados e excluindo o próprio post da lista — ou seja, que a correção não trocou um bug por outro.

Impacto

  • Comportamento: posts sem categoria passam a renderizar com a seção de relacionados vazia, em vez de 500. Nenhuma mudança para posts com categoria.
  • Schema: nenhuma alteração de banco.
  • Compatibilidade: nenhuma variável de ambiente nova.
  • Rollback: reverter o commit.

Nota sobre phpunit.xml

Contém a mesma alteração de duas linhas do PR #1 (fixar sqlite / :memory:), porque sem ela a suíte não inicia e o teste deste PR não roda:

QueryException: Database file at path [testing] does not exist.

As duas branches saíram de main, então a mudança aparece nos dois PRs. Se o PR #1 for mesclado primeiro, esta parte vira no-op — o conteúdo é idêntico, então não deve gerar conflito.

A outra metade: o mesmo dado quebra o front

O segundo commit blinda a mesma condição no cliente. PostCard.vue e PostContentCover.vue indexavam categories[0] sem verificar se o array tem elementos:

 <Link
+    v-if="post.categories?.length"
     :href="'/category/' + post.categories[0].slug"

Sem isso, corrigir só o servidor trocaria um 500 por um erro de render no navegador para exatamente os mesmos posts. O selo de categoria simplesmente não aparece quando não há categoria.

Fora de escopo

  • O getRelatedPosts roda duas queries por post sem cache quente e a relação terms não é eager-loaded em lugar nenhum. É problema de performance, tratado no PR dedicado a N+1.
  • As tabelas criadas no beforeEach são o recorte mínimo do schema que este método toca. Um conjunto reutilizável vem no PR de infraestrutura de testes.

Guajir0-code and others added 2 commits August 5, 2026 09:53
getRelatedPosts() declares Eloquent\Collection but returned collect(), which
builds a Support\Collection. Support\Collection is not a subclass of
Eloquent\Collection, so any post whose category lookup comes back empty raises
a TypeError and the post page answers 500.

Return an empty Eloquent\Collection instead. Covered by a regression test that
reproduces the TypeError against the previous code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same data condition as the server-side fix: PostCard and PostContentCover
indexed categories[0] unconditionally, so a post with an empty categories
array threw at render time on the client.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant