Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
Sync
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
3 / 3
8
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 replace
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Fulltext;
6
7use Celema\Quma\Database;
8use Cosray\Exception\RuntimeException;
9use Cosray\Locales;
10use Throwable;
11
12final class Sync
13{
14    private readonly Configurations $configurations;
15
16    public function __construct(
17        private readonly Database $db,
18        private readonly Builder $builder,
19    ) {
20        $this->configurations = new Configurations($db);
21    }
22
23    public function validate(Locales $locales): void
24    {
25        $this->configurations->load($locales);
26    }
27
28    /**
29     * The caller must hold the node's row lock until the transaction ends.
30     *
31     * @param class-string $class
32     * @param array<mixed> $content
33     * @param array<string, mixed> $titles
34     * @return array{indexed: int, missingTitles: int}
35     */
36    public function replace(
37        int $node,
38        string $uid,
39        string $class,
40        array $content,
41        array $titles,
42        Locales $locales,
43    ): array {
44        if (!$this->db->getConn()->inTransaction()) {
45            throw new RuntimeException('Fulltext synchronization requires a transaction and a locked node row.');
46        }
47
48        $documents = [];
49        $missingTitles = 0;
50        foreach ($locales as $locale) {
51            $config = $this->configurations->for($locale);
52            try {
53                $document = $this->builder->build($class, $content, $titles, $locale);
54            } catch (Throwable $e) {
55                throw new RuntimeException(
56                    "Fulltext node '{$uid}', locale '{$locale->id}': " . $e->getMessage(),
57                    previous: $e,
58                );
59            }
60            $missingTitles += (int) $document->missingTitle;
61            if ($document->contributions !== []) {
62                $documents[] = [
63                    'locale' => $locale->id,
64                    'config' => $config,
65                    'contributions' => $document->contributions,
66                    'source' => $document->source,
67                ];
68            }
69        }
70
71        $this->db->fulltext->delete(['node' => $node])->run();
72        $indexed = 0;
73        if ($documents !== []) {
74            // The insert writes nothing for a node deleted meanwhile.
75            $indexed = count(
76                $this->db->fulltext->insert([
77                    'node' => $node,
78                    'uid' => $uid,
79                    'documents' => json_encode($documents, JSON_THROW_ON_ERROR),
80                ])->all(),
81            );
82        }
83
84        return ['indexed' => $indexed, 'missingTitles' => $missingTitles];
85    }
86}