Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
Rebuild
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
2 / 2
10
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 run
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
9
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\References;
6
7use Celema\Quma\Database;
8use Cosray\Field;
9
10/**
11 * Rebuilds both reference indexes from scratch: wipe, then rescan all
12 * live nodes, their working copies, every user's fields, and every menu
13 * item's references (image icons, asset links, and the node a `node` or `children` item
14 * points at).
15 * Everything in the indexes is derived, so a rebuild is always safe; it
16 * is the recovery path after restores, imports, or content migrations.
17 */
18final class Rebuild
19{
20    private readonly Scanner $scanner;
21    private readonly Sync $sync;
22
23    public function __construct(
24        private readonly Database $db,
25        ?Field\Index $index = null,
26    ) {
27        $this->scanner = new Scanner($index);
28        $this->sync = new Sync($db);
29    }
30
31    /** @return array{owners: int, assets: int, nodes: int, skipped: int} */
32    public function run(): array
33    {
34        $this->db->references->wipe()->run();
35
36        $owners = 0;
37        $scanned = 0;
38
39        $sources = [
40            'node' => $this->db->references->nodeContents()->lazy(),
41            'draft' => $this->db->drafts->contents()->lazy(),
42            'user' => $this->db->references->userContents()->lazy(),
43        ];
44
45        foreach ($sources as $ownerType => $rows) {
46            foreach ($rows as $row) {
47                $content = json_decode((string) $row['content'], true);
48                $refs = $this->scanner->scan(is_array($content) ? $content : []);
49
50                if ($refs['assets'] === [] && $refs['nodes'] === []) {
51                    continue;
52                }
53
54                $this->sync->replace($ownerType, (string) $row['uid'], $refs);
55                $owners++;
56                $scanned += count($refs['assets']) + count($refs['nodes']);
57            }
58        }
59
60        // Merged per item before writing: `Sync::replace()` is a full replace
61        // per owner, so two passes would have the second wipe the first.
62        $menus = [];
63
64        foreach ($this->db->references->menuAssets()->lazy() as $row) {
65            $menus[(string) $row['item']]['assets'][] = (string) $row['uid'];
66        }
67
68        foreach ($this->db->references->menuNodes()->lazy() as $row) {
69            $menus[(string) $row['item']]['nodes'][] = (string) $row['uid'];
70        }
71
72        foreach ($menus as $item => $refs) {
73            $refs += ['assets' => [], 'nodes' => []];
74            $this->sync->replace('menu', (string) $item, $refs);
75            $owners++;
76            $scanned += count($refs['assets']) + count($refs['nodes']);
77        }
78
79        $counts = $this->db->references->counts()->one();
80        $assets = (int) $counts['assets'];
81        $nodes = (int) $counts['nodes'];
82
83        return [
84            'owners' => $owners,
85            'assets' => $assets,
86            'nodes' => $nodes,
87            'skipped' => $scanned - $assets - $nodes,
88        ];
89    }
90}