Storeman   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 262
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 10

Importance

Changes 0
Metric Value
wmc 42
lcom 2
cbo 10
dl 0
loc 262
rs 9.0399
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 2
A getContainer() 0 4 1
A getConfiguration() 0 4 1
A getVaultContainer() 0 4 1
A getIndexBuilder() 0 4 1
A getFileReader() 0 4 1
A getLocalIndex() 0 7 2
A synchronize() 0 22 5
A restore() 0 15 3
A dump() 0 15 3
A getLastRevision() 0 26 5
A getMetadataDirectoryPath() 0 4 1
A acquireLocks() 0 10 3
A releaseLocks() 0 10 3
A getVaultForDownload() 0 27 5
A initMetadataDirectory() 0 14 3
A getLocalIndexExclusionPatterns() 0 7 1
A getLogger() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Storeman often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Storeman, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Storeman;
4
5
use Psr\Log\LoggerInterface;
6
use Storeman\Config\Configuration;
7
use Storeman\Index\Index;
8
use Storeman\IndexBuilder\IndexBuilderInterface;
9
use Storeman\SynchronizationProgressListener\SynchronizationProgressListenerInterface;
10
11
/**
12
 * Represents a local archive and this coordinates task executions with potentially multiple vaults.
13
 */
14
class Storeman
15
{
16
    public const CONFIG_FILE_NAME = 'storeman.json';
17
    public const METADATA_DIRECTORY_NAME = '.storeman';
18
19
    /**
20
     * @var Container
21
     */
22
    protected $container;
23
24
    public function __construct(Container $container = null)
25
    {
26
        $this->container = $container ?: new Container();
27
        $this->container->injectStoreman($this);
28
    }
29
30
    /**
31
     * Returns the DI container of this storeman instance.
32
     * Some service names resolve to different implementations depending on the current vault which can be set as a context.
33
     * E.g. "storageAdapter" resolves to the storage adapter implementation configured for the current vault.
34
     *
35
     * @param Vault $vault
36
     * @return Container
37
     */
38
    public function getContainer(Vault $vault = null): Container
39
    {
40
        return $this->container->selectVault($vault);
41
    }
42
43
    /**
44
     * Returns the configuration.
45
     *
46
     * @return Configuration
47
     */
48
    public function getConfiguration(): Configuration
49
    {
50
        return $this->container->get('configuration');
51
    }
52
53
    /**
54
     * Returns a container of the configured vaults.
55
     *
56
     * @return VaultContainer
57
     */
58
    public function getVaultContainer(): VaultContainer
59
    {
60
        return $this->container->getVaultContainer();
61
    }
62
63
    /**
64
     * Returns configured index builder.
65
     *
66
     * @return IndexBuilderInterface
67
     */
68
    public function getIndexBuilder(): IndexBuilderInterface
69
    {
70
        return $this->container->get('indexBuilder');
71
    }
72
73
    /**
74
     * @return FileReader
75
     */
76
    public function getFileReader(): FileReader
77
    {
78
        return $this->container->get('fileReader');
79
    }
80
81
    /**
82
     * Builds and returns an index representing the current local state.
83
     *
84
     * @param string $path
85
     * @return Index
86
     */
87
    public function getLocalIndex(string $path = null): Index
88
    {
89
        return $this->getIndexBuilder()->buildIndex(
90
            $path ?: $this->getConfiguration()->getPath(),
91
            $this->getLocalIndexExclusionPatterns()
92
        );
93
    }
94
95
    public function synchronize(array $vaultTitles = null, SynchronizationProgressListenerInterface $progressListener = null): OperationResultList
96
    {
97
        $this->getLogger()->notice(sprintf("Synchronizing to these vaults: %s", $vaultTitles ? implode(', ', $vaultTitles) : '-all-'));
98
99
        /** @var Vault[] $vaults */
100
        $vaults = ($vaultTitles === null) ? $this->getVaultContainer() : $this->getVaultContainer()->getVaultsByTitles($vaultTitles);
101
102
        // vault list order is ensured to be consistent across instances by the VaultContainer which is mandatory for deadlock prevention
103
        $this->acquireLocks($vaults, Vault::LOCK_SYNC);
0 ignored issues
show
Documentation introduced by
$vaults is of type array<integer,object<Storeman\Vault>>, but the function expects a object<Traversable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
104
105
        $newRevision = ($this->getLastRevision() ?: 0) + 1;
106
107
        $return = new OperationResultList();
108
        foreach ($vaults as $vault)
109
        {
110
            $return->append($vault->synchronize($newRevision, $progressListener));
111
        }
112
113
        $this->releaseLocks($vaults, Vault::LOCK_SYNC);
0 ignored issues
show
Documentation introduced by
$vaults is of type array<integer,object<Storeman\Vault>>, but the function expects a object<Traversable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
114
115
        return $return;
116
    }
117
118
    public function restore(int $toRevision = null, string $fromVault = null, SynchronizationProgressListenerInterface $progressListener = null): OperationResultList
119
    {
120
        $this->getLogger()->notice(sprintf("Restoring from %s...", $toRevision ? "r{$toRevision}": 'latest revision'));
121
122
        $vault = $this->getVaultForDownload($toRevision, $fromVault);
123
124
        if ($vault === null)
125
        {
126
            return new OperationResultList();
127
        }
128
129
        $operationResultList = $vault->restore($toRevision, $progressListener);
130
131
        return $operationResultList;
132
    }
133
134
    public function dump(string $targetPath, int $revision = null, string $fromVault = null, SynchronizationProgressListenerInterface $progressListener = null): OperationResultList
135
    {
136
        $this->getLogger()->notice(sprintf("Dumping from %s to {$targetPath}...", $revision ? "r{$revision}": 'latest revision'));
137
138
        $vault = $this->getVaultForDownload($revision, $fromVault);
139
140
        if ($vault === null)
141
        {
142
            return new OperationResultList();
143
        }
144
145
        $operationResultList = $vault->dump($targetPath, $revision, $progressListener);
146
147
        return $operationResultList;
148
    }
149
150
    /**
151
     * Returns the highest revision number across all vaults.
152
     *
153
     * @return int
154
     */
155
    public function getLastRevision(): ?int
156
    {
157
        $this->getLogger()->debug("Determining max revision...");
158
159
        $max = 0;
160
161
        foreach ($this->getVaultContainer() as $vault)
162
        {
163
            /** @var Vault $vault */
164
165
            if ($lastSynchronization = $vault->getVaultLayout()->getLastSynchronization())
166
            {
167
                $this->getLogger()->debug("Vault {$vault->getIdentifier()} is at r{$lastSynchronization->getRevision()}");
168
169
                $max = max($max, $lastSynchronization->getRevision());
170
            }
171
            else
172
            {
173
                $this->getLogger()->debug("Vault {$vault->getIdentifier()} has no synchronizations yet");
174
            }
175
        }
176
177
        $this->getLogger()->info("Found max revision to be " . ($max ?: '-'));
178
179
        return $max ?: null;
180
    }
181
182
    public function getMetadataDirectoryPath(): string
183
    {
184
        return $this->initMetadataDirectory();
185
    }
186
187
    /**
188
     * @param Vault[] $vaults
189
     * @param string $lockName
190
     */
191
    protected function acquireLocks(\Traversable $vaults, string $lockName)
192
    {
193
        foreach ($vaults as $vault)
194
        {
195
            if (!$vault->getLockAdapter()->acquireLock($lockName))
196
            {
197
                throw new Exception("Failed to acquire lock for vault {$vault->getIdentifier()}");
198
            }
199
        }
200
    }
201
202
    /**
203
     * @param Vault[] $vaults
204
     * @param string $lockName
205
     */
206
    protected function releaseLocks(\Traversable $vaults, string $lockName)
207
    {
208
        foreach ($vaults as $vault)
209
        {
210
            if (!$vault->getLockAdapter()->releaseLock($lockName))
211
            {
212
                throw new Exception("Failed to release lock for vault {$vault->getIdentifier()}");
213
            }
214
        }
215
    }
216
217
    protected function getVaultForDownload(?int $revision, ?string $vaultTitle): ?Vault
218
    {
219
        $revision = $revision ?: $this->getLastRevision();
220
        if ($revision === null)
221
        {
222
            return null;
223
        }
224
225
        $vaultContainer = $this->getVaultContainer();
226
227
        if ($vaultTitle)
228
        {
229
            $vault = $vaultContainer->getVaultByTitle($vaultTitle);
230
        }
231
        else
232
        {
233
            $vaults = $vaultContainer->getVaultsHavingRevision($revision);
234
            $vault = $vaultContainer->getPrioritizedVault($vaults);
235
        }
236
237
        if ($vault === null)
238
        {
239
            throw new Exception("Cannot find requested revision #{$revision} in any configured vault.");
240
        }
241
242
        return $vault;
243
    }
244
245
    protected function initMetadataDirectory(): string
246
    {
247
        $path = $this->getConfiguration()->getPath() . static::METADATA_DIRECTORY_NAME;
248
249
        if (!is_dir($path))
250
        {
251
            if (!mkdir($path))
252
            {
253
                throw new Exception("mkdir() failed for {$path}");
254
            }
255
        }
256
257
        return "{$path}/";
258
    }
259
260
    /**
261
     * @return string[]
262
     */
263
    protected function getLocalIndexExclusionPatterns(): array
264
    {
265
        return array_merge($this->getConfiguration()->getExclude(), [
266
            sprintf('/(^|\/)%s$/', preg_quote(static::CONFIG_FILE_NAME)),
267
            sprintf('/(^|\/)%s($|\/)/', preg_quote(static::METADATA_DIRECTORY_NAME)),
268
        ]);
269
    }
270
271
    protected function getLogger(): LoggerInterface
272
    {
273
        return $this->container->getLogger();
274
    }
275
}
276