Fix countArchived() loading all members into memory (Closes #201)
Database Portability Tests / Unit Tests (PlatformHelper) (pull_request) Failing after 38s
Database Portability Tests / Integration (mysql) (pull_request) Has been skipped
Database Portability Tests / Integration (postgres) (pull_request) Has been skipped
Database Portability Tests / Integration (sqlite) (pull_request) Has been skipped
Database Portability Tests / Verify no MySQL-specific SQL (pull_request) Successful in 5s
Database Portability Tests / Unit Tests (PlatformHelper) (pull_request) Failing after 38s
Database Portability Tests / Integration (mysql) (pull_request) Has been skipped
Database Portability Tests / Integration (postgres) (pull_request) Has been skipped
Database Portability Tests / Integration (sqlite) (pull_request) Has been skipped
Database Portability Tests / Verify no MySQL-specific SQL (pull_request) Successful in 5s
Replace in-memory filtering with a SQL COUNT(*) query in the mapper, matching the pattern of the existing countAll() method.
This commit is contained in:
@@ -372,6 +372,29 @@ class MemberMapper extends QBMapper {
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count all soft-deleted (archived) members.
|
||||
*
|
||||
* Uses a single SQL COUNT query instead of loading all members
|
||||
* into memory.
|
||||
*
|
||||
* Part of Issue #201.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function countArchived(): int {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select($qb->createFunction('COUNT(*)'))
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->isNotNull('deleted_at'));
|
||||
|
||||
$result = $qb->executeQuery();
|
||||
$count = (int)$result->fetchOne();
|
||||
$result->closeCursor();
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
// ── Joined fetch methods (N+1 avoidance) ────────────────────────
|
||||
|
||||
/**
|
||||
|
||||
@@ -761,11 +761,13 @@ class MemberService {
|
||||
/**
|
||||
* Count all soft-deleted (archived) members.
|
||||
*
|
||||
* Part of Issue #201: uses a single SQL COUNT query via the mapper
|
||||
* instead of loading all members into memory.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function countArchived(): int {
|
||||
$allMembers = $this->memberMapper->findAll(null, null, true);
|
||||
return count(array_filter($allMembers, fn(Member $m) => $m->getDeletedAt() !== null));
|
||||
return $this->memberMapper->countArchived();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -543,6 +543,20 @@ class MapperTest extends TestCase {
|
||||
$this->assertSame(0, $count);
|
||||
}
|
||||
|
||||
public function testMemberMapperCountArchived(): void {
|
||||
$this->configureResultCount(7);
|
||||
$mapper = new MemberMapper($this->db);
|
||||
$count = $mapper->countArchived();
|
||||
$this->assertSame(7, $count);
|
||||
}
|
||||
|
||||
public function testMemberMapperCountArchivedZero(): void {
|
||||
$this->configureResultCount(0);
|
||||
$mapper = new MemberMapper($this->db);
|
||||
$count = $mapper->countArchived();
|
||||
$this->assertSame(0, $count);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
// ── FamilyMapper ─────────────────────────────────────────────────
|
||||
// ══════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -1299,17 +1299,19 @@ class MemberServiceTest extends TestCase {
|
||||
// ── countArchived() ──────────────────────────────────────────────
|
||||
|
||||
public function testCountArchivedReturnsCorrectCount(): void {
|
||||
$activeMember = $this->createMember(1);
|
||||
$deletedMember = $this->createMember(2);
|
||||
$deletedMember->setDeletedAt('2026-01-01 00:00:00');
|
||||
|
||||
$this->memberMapper->method('findAll')
|
||||
->with(null, null, true)
|
||||
->willReturn([$activeMember, $deletedMember]);
|
||||
// Issue #201: countArchived() now delegates to MemberMapper::countArchived()
|
||||
// which uses a SQL COUNT query instead of loading all members into memory.
|
||||
$this->memberMapper->method('countArchived')->willReturn(1);
|
||||
|
||||
$this->assertSame(1, $this->service->countArchived());
|
||||
}
|
||||
|
||||
public function testCountArchivedReturnsZeroWhenNoArchived(): void {
|
||||
$this->memberMapper->method('countArchived')->willReturn(0);
|
||||
|
||||
$this->assertSame(0, $this->service->countArchived());
|
||||
}
|
||||
|
||||
// ── Joined methods: findAllWithRelations ──────────────────────
|
||||
|
||||
public function testFindAllWithRelationsReturnsMemberWithNestedSubEntities(): void {
|
||||
|
||||
Reference in New Issue
Block a user