Failed Conditions
Pull Request — 1.0 (#64)
by Titouan
02:55
created

AbstractPathMappingRepository   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 340
Duplicated Lines 3.24 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 93.22%

Importance

Changes 12
Bugs 4 Features 5
Metric Value
wmc 40
c 12
b 4
f 5
lcom 1
cbo 13
dl 11
loc 340
ccs 110
cts 118
cp 0.9322
rs 8.2609

19 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A add() 11 20 3
C addResource() 0 26 7
addFilesystemResource() 0 1 ?
addLinkResource() 0 1 ?
A clear() 0 10 1
A ensureDirectoryExists() 0 13 3
A createRoot() 0 8 2
A countStore() 0 8 2
A sortStore() 0 8 2
A createResource() 0 18 4
A createLinkResource() 0 7 1
A createFilesystemResource() 0 21 4
A createVirtualResource() 0 7 1
A resolveRelativePath() 0 9 2
A resolveRelativePaths() 0 8 2
A isLinkTarget() 0 4 1
A createLinkTarget() 0 8 2
A getLinkTarget() 0 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like AbstractPathMappingRepository 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 AbstractPathMappingRepository, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the puli/repository package.
5
 *
6
 * (c) Bernhard Schussek <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Puli\Repository;
13
14
use Puli\Repository\Api\Resource\FilesystemResource;
15
use Puli\Repository\Api\Resource\PuliResource;
16
use Puli\Repository\Api\ResourceCollection;
17
use Puli\Repository\Api\UnsupportedResourceException;
18
use Puli\Repository\Resource\DirectoryResource;
19
use Puli\Repository\Resource\FileResource;
20
use Puli\Repository\Resource\GenericResource;
21
use Puli\Repository\Resource\LinkResource;
22
use RuntimeException;
23
use Webmozart\KeyValueStore\Api\CountableStore;
24
use Webmozart\KeyValueStore\Api\KeyValueStore;
25
use Webmozart\KeyValueStore\Api\SortableStore;
26
use Webmozart\KeyValueStore\Decorator\CountableDecorator;
27
use Webmozart\KeyValueStore\Decorator\SortableDecorator;
28
use Webmozart\PathUtil\Path;
29
30
/**
31
 * Abstract base for Path mapping repositories.
32
 *
33
 * @since  1.0
34
 *
35
 * @author Bernhard Schussek <[email protected]>
36
 * @author Titouan Galopin <[email protected]>
37
 */
38
abstract class AbstractPathMappingRepository extends AbstractRepository
39
{
40
    /**
41
     * @var KeyValueStore
42
     */
43
    protected $store;
44
45
    /**
46
     * @var string
47
     */
48
    protected $baseDirectory;
49
50
    /**
51
     * Creates a new repository.
52
     *
53
     * @param KeyValueStore $store         The store of all the paths.
54
     * @param string        $baseDirectory The store of all the paths.
55
     */
56 168
    public function __construct(KeyValueStore $store, $baseDirectory)
57
    {
58 168
        $this->store = $store;
59 168
        $this->baseDirectory = $baseDirectory;
60
61 168
        $this->createRoot();
62 168
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 146
    public function add($path, $resource)
68
    {
69 146
        $path = $this->sanitizePath($path);
70
71 140 View Code Duplication
        if ($resource instanceof ResourceCollection) {
72 2
            $this->ensureDirectoryExists($path);
73
74 2
            foreach ($resource as $child) {
75 2
                $this->addResource($path.'/'.$child->getName(), $child);
76 2
            }
77
78 2
            $this->sortStore();
79
80 2
            return;
81
        }
82
83 138
        $this->ensureDirectoryExists(Path::getDirectory($path));
84 138
        $this->addResource($path, $resource);
85 134
        $this->sortStore();
86 134
    }
87
88
    /**
89
     * Add the resource (internal method after checks of add()).
90
     *
91
     * @param string       $path
92
     * @param PuliResource $resource
93
     */
94 140
    private function addResource($path, $resource)
95
    {
96 140
        if (!($resource instanceof FilesystemResource || $resource instanceof LinkResource)) {
97 2
            throw new UnsupportedResourceException(sprintf(
98 2
                'PathMapping repositories only supports FilesystemResource and LinkedResource. Got: %s',
99 2
                is_object($resource) ? get_class($resource) : gettype($resource)
100 2
            ));
101
        }
102
103
        // Don't modify resources attached to other repositories
104 138
        if ($resource->isAttached()) {
105 2
            $resource = clone $resource;
106 2
        }
107
108 138
        if ($resource instanceof LinkResource) {
109 6
            $this->addLinkResource($path, $resource);
110 138
        } elseif (Path::isBasePath($this->baseDirectory, $resource->getFilesystemPath())) {
111 136
            $this->addFilesystemResource($path, $resource);
112 136
        } else {
113 2
            throw new UnsupportedResourceException(sprintf(
114 2
                'Can only add resources from %s. Tried to add %s.',
115 2
                $this->baseDirectory,
116 2
                $resource->getFilesystemPath()
117 2
            ));
118
        }
119 136
    }
120
121
    /**
122
     * Add the filesystem resource.
123
     *
124
     * @param string             $path
125
     * @param FilesystemResource $resource
126
     */
127
    abstract protected function addFilesystemResource($path, FilesystemResource $resource);
128
129
    /**
130
     * Add the link resource.
131
     *
132
     * @param string       $path
133
     * @param LinkResource $resource
134
     */
135
    abstract protected function addLinkResource($path, LinkResource $resource);
136
137
    /**
138
     * {@inheritdoc}
139
     */
140 2
    public function clear()
141
    {
142
        // Subtract root
143 2
        $removed = $this->countStore() - 1;
144
145 2
        $this->store->clear();
146 2
        $this->createRoot();
147
148 2
        return $removed;
149
    }
150
151
    /**
152
     * Recursively creates a directory for a path.
153
     *
154
     * @param string $path A directory path.
155
     */
156 140
    protected function ensureDirectoryExists($path)
157
    {
158 140
        if ($this->store->exists($path)) {
159 140
            return;
160
        }
161
162
        // Recursively initialize parent directories
163 42
        if ('/' !== $path) {
164 42
            $this->ensureDirectoryExists(Path::getDirectory($path));
165 42
        }
166
167 42
        $this->store->set($path, null);
168 42
    }
169
170
    /**
171
     * Create the repository root.
172
     */
173 168
    protected function createRoot()
174
    {
175 168
        if ($this->store->exists('/')) {
176 6
            return;
177
        }
178
179 168
        $this->store->set('/', null);
180 168
    }
181
182
    /**
183
     * Count the number of elements in the store.
184
     *
185
     * @return int
186
     */
187 9
    protected function countStore()
188
    {
189 9
        if (!$this->store instanceof CountableStore) {
190
            $this->store = new CountableDecorator($this->store);
191
        }
192
193 9
        return $this->store->count();
194
    }
195
196
    /**
197
     * Sort the store by keys.
198
     */
199 136
    protected function sortStore()
200
    {
201 136
        if (!$this->store instanceof SortableStore) {
202
            $this->store = new SortableDecorator($this->store);
203
        }
204
205 136
        $this->store->sort();
206 136
    }
207
208
    /**
209
     * Create a filesystem or generic resource.
210
     *
211
     * @param string|null $filesystemPath
212
     *
213
     * @return DirectoryResource|FileResource|GenericResource
214
     */
215 89
    protected function createResource($filesystemPath, $path = null)
216
    {
217
        // Link resource
218 89
        if (0 === strpos($filesystemPath, 'l:')) {
219 6
            return $this->createLinkResource(substr($filesystemPath, 2), $path);
220
        }
221
222
        // Filesystem resource
223 89
        if (is_string($filesystemPath)) {
224 87
            $filesystemPath = $this->resolveRelativePath($filesystemPath);
225
226 87
            if (file_exists($filesystemPath)) {
227 87
                return $this->createFilesystemResource($filesystemPath, $path);
228
            }
229
        }
230
231 11
        return $this->createVirtualResource($path);
232
    }
233
234
    /**
235
     * Create a link resource to another resource of the repository.
236
     *
237
     * @param string      $targetPath The target path.
238
     * @param string|null $path       The repository path.
239
     *
240
     * @return LinkResource The link resource.
241
     *
242
     * @throws RuntimeException If the targeted resource does not exist.
243
     */
244 6
    protected function createLinkResource($targetPath, $path = null)
245
    {
246 6
        $resource = new LinkResource($targetPath);
247 6
        $resource->attachTo($this, $path);
248
249 6
        return $resource;
250
    }
251
252
    /**
253
     * Create a resource using its filesystem path.
254
     *
255
     * If the filesystem path is a directory, a DirectoryResource will be created.
256
     * If the filesystem path is a file, a FileResource will be created.
257
     * If the filesystem does not exists, a GenericResource will be created.
258
     *
259
     * @param string      $filesystemPath The filesystem path.
260
     * @param string|null $path           The repository path.
261
     *
262
     * @return DirectoryResource|FileResource The created resource.
263
     *
264
     * @throws RuntimeException If the file / directory does not exist.
265
     */
266 87
    protected function createFilesystemResource($filesystemPath, $path = null)
267
    {
268 87
        $resource = null;
269
270 87
        if (is_dir($filesystemPath)) {
271 53
            $resource = new DirectoryResource($filesystemPath);
272 87
        } elseif (is_file($filesystemPath)) {
273 62
            $resource = new FileResource($filesystemPath);
274 62
        }
275
276 87
        if ($resource) {
277 87
            $resource->attachTo($this, $path);
278
279 87
            return $resource;
280
        }
281
282
        throw new RuntimeException(sprintf(
283
            'Trying to create a FilesystemResource on a non-existing file or directory "%s"',
284
            $filesystemPath
285
        ));
286
    }
287
288
    /**
289
     * @param string|null $path
290
     *
291
     * @return GenericResource
292
     */
293 11
    protected function createVirtualResource($path = null)
294
    {
295 11
        $resource = new GenericResource();
296 11
        $resource->attachTo($this, $path);
297
298 11
        return $resource;
299
    }
300
301
    /**
302
     * Transform a relative path into an absolute path.
303
     *
304
     * @param string $relativePath
305
     *
306
     * @return string
307
     */
308 92
    protected function resolveRelativePath($relativePath)
309
    {
310 92
        if (0 === strpos($relativePath, 'l:')) {
311
            // Link
312 3
            return $relativePath;
313
        }
314
315 92
        return Path::makeAbsolute($relativePath, $this->baseDirectory);
316
    }
317
318
    /**
319
     * Transform a collection of relative paths into a collection of absolute paths.
320
     *
321
     * @param string[] $relativePaths
322
     *
323
     * @return string[]
324
     */
325 56
    protected function resolveRelativePaths($relativePaths)
326
    {
327 56
        foreach ($relativePaths as $key => $relativePath) {
328 56
            $relativePaths[$key] = $this->resolveRelativePath($relativePath);
329 56
        }
330
331 56
        return $relativePaths;
332
    }
333
334
    /**
335
     * Check if the given target path represents a link.
336
     *
337
     * @param string $targetPath
338
     *
339
     * @return bool
340
     */
341 10
    public static function isLinkTarget($targetPath)
342
    {
343 10
        return 0 === strpos($targetPath, 'l:');
344
    }
345
346
    /**
347
     * Add a link tag at the beginning of the given target path to represent a link.
348
     *
349
     * @param string $targetPath
350
     *
351
     * @return string
352
     */
353 6
    public static function createLinkTarget($targetPath)
354
    {
355 6
        if (! self::isLinkTarget($targetPath)) {
356 6
            $targetPath = 'l:'.$targetPath;
357 6
        }
358
359 6
        return $targetPath;
360
    }
361
362
    /**
363
     * Get the target path of a link by removing the link tag.
364
     *
365
     * @param string $targetPath
366
     *
367
     * @return string
368
     */
369 2
    public static function getLinkTarget($targetPath)
370
    {
371 2
        if (self::isLinkTarget($targetPath)) {
372 2
            $targetPath = substr($targetPath, 2);
373 2
        }
374
375 2
        return $targetPath;
376
    }
377
}
378