В SQL необходимая информация будет извлекаться с помощью чего-то подобного (я не знаю имени ассоциативной таблицы или внешнего и первичного ключей, поэтому при необходимости измените это):
SELECT articles.*,
(
SELECT COUNT(tags.id)
FROM tags
JOIN articles_tags at ON tags.id = at.tag_id
WHERE tags.name in ('A', 'B', 'C') /* acquired from $article->tags->pluck('name') */
AND at.article_id = a.id
) as tags_in_common
FROM articles a
WHERE articles.id != {$id}
AND tags_in_common >= 1
ORDER BY tags_in_common DESC
LIMIT 10;
Теперь, используя queryBuilder, мы должны немного испачкаться, но это может выглядеть примерно так:
//Fetches pairs: [article_id, numberInCommon]
$tagsInCommon = DB::table('tags')
join('articles_tags', 'tags.id', '=', 'articles_tags.tag_id')
->select('articles_tags.article_id', DB::raw('COUNT(*) as numberInCommon'))
->whereIn('tags.name', $article->tags->pluck('name'))
->groupBy('articles_tags.article_id')
$relatedArticle = Article::
->joinSub($tagsInCommon, 'tags_in_common', function($join){
$join->on('id', '=', 'tags_in_common.article_id')
})
->where('id', '!=', $article->id)
->orderBy('tags_in_common.numberInCommon', 'desc')
->take(10)
->get();