JournalWriterImpl::buildExternalReference()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5.4742

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 11
cts 15
cp 0.7332
rs 9.1768
c 0
b 0
f 0
cc 5
nc 5
nop 1
crap 5.4742
1
<?php
2
/**
3
 * File was created 28.04.2016 07:05
4
 */
5
6
namespace PeekAndPoke\Component\Slumber\Data\Addon\Journal;
7
8
use PeekAndPoke\Component\Slumber\Data\Addon\Journal\DomainModel\JournalEntry;
9
use PeekAndPoke\Component\Slumber\Data\Addon\Journal\DomainModel\JournalStats;
10
use PeekAndPoke\Component\Slumber\Data\Addon\Journal\DomainModel\Record;
11
use PeekAndPoke\Component\Slumber\Data\Addon\Journal\DomainModel\RecordableHistory;
12
use PeekAndPoke\Component\Slumber\Data\Addon\Journal\Exception\JournalRuntimeException;
13
use PeekAndPoke\Component\Slumber\Data\Storage;
14
use Psr\Log\LoggerInterface;
15
use Psr\Log\NullLogger;
16
17
/**
18
 * @author Karsten J. Gerber <[email protected]>
19
 */
20
class JournalWriterImpl implements JournalWriter
21
{
22
    /** @var Storage */
23
    private $storage;
24
    /** @var JournalEntryRepository */
25
    private $repository;
26
    /** @var LoggerInterface */
27
    private $logger;
28
29
    /**
30
     * JournalWriter constructor.
31
     *
32
     * @param Storage                $storage
33
     * @param JournalEntryRepository $repository
34
     * @param LoggerInterface        $logger
35
     */
36
    public function __construct(Storage $storage, JournalEntryRepository $repository, LoggerInterface $logger = null)
37
    {
38
        $this->storage    = $storage;
39
        $this->repository = $repository;
40
        $this->logger     = $logger ?: new NullLogger();
41
    }
42
43
    /**
44
     * @param mixed $subject
45
     * @param array $serializedData
46
     */
47 4
    public function write($subject, $serializedData)
48
    {
49 4
        $entry = JournalEntry::create(
50 4
            $this->buildExternalReference($subject),
51 4
            $serializedData
52
        );
53
54 4
        $this->repository->save($entry);
55 4
    }
56
57
    /**
58
     * @return JournalStats
59
     */
60 2
    public function getStats()
61
    {
62 2
        $numRecords   = $this->repository->getRecordsCount();
63 2
        $numCompacted = $this->repository->getCompactedRecordsCount();
64
65 2
        return new JournalStats($numRecords, $numCompacted);
66
    }
67
68
    /**
69
     * @param mixed|string $subject
70
     *
71
     * @return RecordableHistory
72
     */
73 3
    public function getHistory($subject)
74
    {
75 3
        $externalRef = $this->buildExternalReference($subject);
76
77
        /** @var Record[] $records */
78 3
        $records = $this->repository->findByExternalReference($externalRef)->toArray();
79
80 3
        return new RecordableHistory($records);
81
    }
82
83
    /**
84
     * @param string $externalReference
85
     *
86
     * @return RecordableHistory
87
     *
88
     * @throws \Exception
89
     */
90 2
    public function compact($externalReference)
91
    {
92 2
        $history = $this->getHistory($externalReference);
93
94
        // create the compacted entry
95 2
        $compactedEntry = $this->repository->createRecord();
96 2
        $compactedEntry->setChangeDate($history->getFinalRecord()->getChangeDate());
97 2
        $compactedEntry->setExternalReference($externalReference);
98 2
        $compactedEntry->setCompactedHistory($history);
99
100 2
        $this->repository->save($compactedEntry);
101
102
        // delete all the other entries
103 2
        foreach ($history->getRecords() as $record) {
104 2
            $this->repository->remove($record);
105
        }
106
107 2
        return $history;
108
    }
109
110
    /**
111
     * Compacts the given number of recorded journal histories
112
     *
113
     * @param int $batchSize
114
     */
115 1
    public function compactOldest($batchSize)
116
    {
117 1
        $subBatchSize = 1000;
118
119 1
        for ($i = 0; $i < $batchSize; $i += $subBatchSize) {
120
121 1
            $oldestEntries = $this->repository->findOldestNotCompacted($subBatchSize);
122
123 1
            $this->logger->info('Will compact ' . ($i + $subBatchSize) . ' / ' . $batchSize . ' / ' . \count($oldestEntries) . ' of the oldest entries');
124
125 1
            if (\count($oldestEntries) === 0) {
126
                return;
127
            }
128
129 1
            foreach ($oldestEntries as $oldest) {
130
131 1
                $extRef = $oldest->getExternalReference();
132
133
                try {
134 1
                    $history = $this->compact($extRef);
135
136 1
                    $this->logger->info('Compacted ' . $extRef . ' with ' . \count($history->getDiffs()) . ' entries');
137
                } catch (\Exception $e) {
138 1
                    $this->logger->error('Cannot compact ' . $extRef . ': ' . $e->getMessage());
139
                }
140
            }
141
142
            // do not hold to many references and free up memory
143 1
            $this->storage->getEntityPool()->clear();
144
        }
145 1
    }
146
147
    /**
148
     * @param mixed|string $subject
149
     *
150
     * @return string
151
     * @throws JournalRuntimeException
152
     */
153 4
    public function buildExternalReference($subject)
154
    {
155 4
        if (is_scalar($subject)) {
156 2
            return (string) $subject;
157
        }
158
159 4
        $repo = $this->storage->getRepositoryByEntity($subject);
160
161 4
        if ($repo === null) {
162
            return null;
163
        }
164
165 4
        $repoName = $repo->getName();
166 4
        $reflect  = new \ReflectionClass($subject);
167
168 4
        if ($reflect->hasProperty('reference')) {
169 4
            $prop = $reflect->getProperty('reference');
170
        } else if ($reflect->hasProperty('id')) {
171
            $prop = $reflect->getProperty('id');
172
        } else {
173
            throw new JournalRuntimeException('Cannot calculate external reference for ' . $reflect->name . '. Needs property "id" or "reference"');
174
        }
175
176 4
        $prop->setAccessible(true);
177
178 4
        return $repoName . '-' . (string) $prop->getValue($subject);
179
    }
180
}
181