Failed Conditions
Pull Request — 1.0 (#64)
by Titouan
03:33
created

InMemoryRepository   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 275
Duplicated Lines 4.36 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 98.33%

Importance

Changes 7
Bugs 1 Features 1
Metric Value
wmc 35
c 7
b 1
f 1
lcom 1
cbo 11
dl 12
loc 275
ccs 118
cts 120
cp 0.9833
rs 9

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A get() 0 10 2
A find() 0 15 3
A contains() 0 15 2
B add() 0 30 5
A remove() 0 14 2
A clear() 0 12 1
A listChildren() 0 6 1
A hasChildren() 0 7 1
A ensureDirectoryExists() 0 14 3
B addResource() 0 22 4
A removeResource() 0 19 3
B resolveWithLinks() 0 16 5
A getChildIterator() 12 12 1
A getGlobIterator() 0 8 1

How to fix   Duplicated Code   

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:

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 ArrayIterator;
15
use Puli\Repository\Api\EditableRepository;
16
use Puli\Repository\Api\Resource\PuliResource;
17
use Puli\Repository\Api\ResourceCollection;
18
use Puli\Repository\Api\UnsupportedResourceException;
19
use Puli\Repository\Resource\Collection\ArrayResourceCollection;
20
use Puli\Repository\Resource\GenericResource;
21
use Puli\Repository\Resource\LinkResource;
22
use Webmozart\Assert\Assert;
23
use Webmozart\Glob\Glob;
24
use Webmozart\Glob\Iterator\GlobFilterIterator;
25
use Webmozart\Glob\Iterator\RegexFilterIterator;
26
use Webmozart\PathUtil\Path;
27
28
/**
29
 * An in-memory resource repository.
30
 *
31
 * Resources can be added with the method {@link add()}:
32
 *
33
 * ```php
34
 * use Puli\Repository\InMemoryRepository;
35
 *
36
 * $repo = new InMemoryRepository();
37
 * $repo->add('/css', new DirectoryResource('/path/to/project/res/css'));
38
 * ```
39
 *
40
 * @since  1.0
41
 *
42
 * @author Bernhard Schussek <[email protected]>
43
 */
44
class InMemoryRepository extends AbstractRepository implements EditableRepository
45
{
46
    /**
47
     * @var PuliResource[]
48
     */
49
    private $resources = array();
50
51
    /**
52
     * Creates a new repository.
53
     */
54 79
    public function __construct()
55
    {
56 79
        $this->clear();
57 79
    }
58
59
    /**
60
     * {@inheritdoc}
61
     */
62 47
    public function get($path)
63
    {
64 47
        $path = $this->sanitizePath($path);
65
66 38
        if (!isset($this->resources[$path])) {
67 4
            return $this->resolveWithLinksOrFail($path);
68
        }
69
70 35
        return $this->resources[$path];
71
    }
72
73
    /**
74
     * {@inheritdoc}
75
     */
76 24
    public function find($query, $language = 'glob')
77
    {
78 24
        $this->validateSearchLanguage($language);
79
80 22
        $query = $this->sanitizePath($query);
81 16
        $resources = array();
82
83 16
        if (Glob::isDynamic($query)) {
84 7
            $resources = $this->getGlobIterator($query);
85 16
        } elseif (isset($this->resources[$query])) {
86 9
            $resources = array($this->resources[$query]);
87 9
        }
88
89 16
        return new ArrayResourceCollection($resources);
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95 16
    public function contains($query, $language = 'glob')
96
    {
97 16
        $this->validateSearchLanguage($language);
98
99 15
        $query = $this->sanitizePath($query);
100
101 12
        if (Glob::isDynamic($query)) {
102 1
            $iterator = $this->getGlobIterator($query);
103 1
            $iterator->rewind();
104
105 1
            return $iterator->valid();
106
        }
107
108 11
        return isset($this->resources[$query]);
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114 69
    public function add($path, $resource)
115 1
    {
116 69
        $path = $this->sanitizePath($path);
117
118 66
        if ($resource instanceof ResourceCollection) {
119 2
            $this->ensureDirectoryExists($path);
120 2
            foreach ($resource as $child) {
121 2
                $this->addResource($path.'/'.$child->getName(), $child);
122 2
            }
123
124
            // Keep the resources sorted by file name
125 2
            ksort($this->resources);
126
127 2
            return;
128
        }
129
130 64
        if ($resource instanceof PuliResource) {
131 63
            $this->ensureDirectoryExists(Path::getDirectory($path));
132 63
            $this->addResource($path, $resource);
133
134 63
            ksort($this->resources);
135
136 63
            return;
137
        }
138
139 1
        throw new UnsupportedResourceException(sprintf(
140 1
            'The passed resource must be a PuliResource or ResourceCollection. Got: %s',
141 1
            is_object($resource) ? get_class($resource) : gettype($resource)
142 1
        ));
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148 13
    public function remove($query, $language = 'glob')
149
    {
150 13
        $resources = $this->find($query, $language);
151 9
        $nbOfResources = count($this->resources);
152
153
        // Run the assertion after find(), so that we know that $query is valid
154 9
        Assert::notEmpty(trim($query, '/'), 'The root directory cannot be removed.');
155
156 7
        foreach ($resources as $resource) {
157 7
            $this->removeResource($resource);
158 7
        }
159
160 7
        return $nbOfResources - count($this->resources);
161
    }
162
163
    /**
164
     * {@inheritdoc}
165
     */
166 79
    public function clear()
167
    {
168 79
        $root = new GenericResource('/');
169 79
        $root->attachTo($this);
170
171
        // Subtract root
172 79
        $removed = count($this->resources) - 1;
173
174 79
        $this->resources = array('/' => $root);
175
176 79
        return $removed;
177
    }
178
179
    /**
180
     * {@inheritdoc}
181
     */
182 10
    public function listChildren($path)
183
    {
184 10
        $iterator = $this->getChildIterator($this->get($path));
185
186 6
        return new ArrayResourceCollection($iterator);
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192 6
    public function hasChildren($path)
193
    {
194 6
        $iterator = $this->getChildIterator($this->get($path));
195 2
        $iterator->rewind();
196
197 2
        return $iterator->valid();
198
    }
199
200
    /**
201
     * Recursively creates a directory for a path.
202
     *
203
     * @param string $path A directory path.
204
     */
205 65
    private function ensureDirectoryExists($path)
206
    {
207 65
        if (!isset($this->resources[$path])) {
208
            // Recursively initialize parent directories
209 18
            if ($path !== '/') {
210 18
                $this->ensureDirectoryExists(Path::getDirectory($path));
211 18
            }
212
213 18
            $this->resources[$path] = new GenericResource($path);
214 18
            $this->resources[$path]->attachTo($this);
215
216 18
            return;
217
        }
218 65
    }
219
220 65
    private function addResource($path, PuliResource $resource)
221
    {
222
        // Don't modify resources attached to other repositories
223 65
        if ($resource->isAttached()) {
224 2
            $resource = clone $resource;
225 2
        }
226
227 65
        $basePath = '/' === $path ? $path : $path.'/';
228
229
        // Read children before attaching the resource to this repository
230 65
        $children = $resource->listChildren();
231
232 65
        $resource->attachTo($this, $path);
233
234
        // Add the resource before adding its children, so that the array
235
        // stays sorted
236 65
        $this->resources[$path] = $resource;
237
238 65
        foreach ($children as $name => $child) {
239 30
            $this->addResource($basePath.$name, $child);
240 65
        }
241 65
    }
242
243 7
    private function removeResource(PuliResource $resource)
244
    {
245 7
        $path = $resource->getPath();
246
247
        // Ignore non-existing resources
248 7
        if (!isset($this->resources[$path])) {
249
            return;
250
        }
251
252
        // Recursively register directory contents
253 7
        foreach ($this->getChildIterator($resource) as $child) {
254 5
            $this->removeResource($child);
255 7
        }
256
257 7
        unset($this->resources[$path]);
258
259
        // Detach from locator
260 7
        $resource->detach();
261 7
    }
262
263
    /**
264
     * {@inheritdoc}
265
     */
266 4
    protected function resolveWithLinks($path)
267
    {
268 4
        foreach ($this->resources as $resource) {
269 4
            $resourcePath = rtrim($resource->getPath(), '/');
270
271 4
            if ($resource instanceof LinkResource && 0 === strpos($path, $resourcePath)) {
272 1
                $realpath = substr_replace($path, rtrim($resource->getTargetPath(), '/'), 0, strlen($resourcePath));
273
274 1
                if (array_key_exists($realpath, $this->resources)) {
275 1
                    return $this->resources[$realpath];
276
                }
277
            }
278 4
        }
279
280 3
        return null;
281
    }
282
283
    /**
284
     * Returns an iterator for the children of a resource.
285
     *
286
     * @param PuliResource $resource The resource.
287
     *
288
     * @return RegexFilterIterator The iterator.
289
     */
290 14 View Code Duplication
    private function getChildIterator(PuliResource $resource)
291
    {
292 14
        $staticPrefix = rtrim($resource->getPath(), '/').'/';
293 14
        $regExp = '~^'.preg_quote($staticPrefix, '~').'[^/]+$~';
294
295 14
        return new RegexFilterIterator(
296 14
            $regExp,
297 14
            $staticPrefix,
298 14
            new ArrayIterator($this->resources),
299
            RegexFilterIterator::FILTER_KEY
300 14
        );
301
    }
302
303
    /**
304
     * Returns an iterator for a glob.
305
     *
306
     * @param string $glob The glob.
307
     *
308
     * @return GlobFilterIterator The iterator.
309
     */
310 8
    protected function getGlobIterator($glob)
311
    {
312 8
        return new GlobFilterIterator(
313 8
            $glob,
314 8
            new ArrayIterator($this->resources),
315
            GlobFilterIterator::FILTER_KEY
316 8
        );
317
    }
318
}
319