Completed
Push — 1.0 ( 0e2cc5...14cdbe )
by Bernhard
02:42
created

InMemoryRepository   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 262
Duplicated Lines 4.58 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 99.11%

Importance

Changes 6
Bugs 1 Features 1
Metric Value
wmc 30
c 6
b 1
f 1
lcom 1
cbo 11
dl 12
loc 262
ccs 111
cts 112
cp 0.9911
rs 10

14 Methods

Rating   Name   Duplication   Size   Complexity  
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 removeResource() 0 19 3
A getChildIterator() 12 12 1
A getGlobIterator() 0 8 1
A ensureDirectoryExists() 0 14 3
A __construct() 0 6 1
B addResource() 0 24 4

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\ChangeStream\ChangeStream;
16
use Puli\Repository\Api\Resource\PuliResource;
17
use Puli\Repository\Api\ResourceCollection;
18
use Puli\Repository\Api\ResourceNotFoundException;
19
use Puli\Repository\Api\UnsupportedResourceException;
20
use Puli\Repository\Resource\Collection\ArrayResourceCollection;
21
use Puli\Repository\Resource\GenericResource;
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 AbstractEditableRepository
45
{
46
    /**
47
     * @var PuliResource[]
48
     */
49
    private $resources = array();
50
51
    /**
52
     * Create the repository.
53
     *
54
     * @param ChangeStream|null $changeStream If provided, the repository will log
55
     *                                        resources changes in this change stream.
56
     */
57 93
    public function __construct(ChangeStream $changeStream = null)
58
    {
59 93
        parent::__construct($changeStream);
60
61 93
        $this->clear();
62 93
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 46
    public function get($path)
68
    {
69 46
        $path = $this->sanitizePath($path);
70
71 37
        if (!isset($this->resources[$path])) {
72 3
            throw ResourceNotFoundException::forPath($path);
73
        }
74
75 34
        return $this->resources[$path];
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81 24
    public function find($query, $language = 'glob')
82
    {
83 24
        $this->validateSearchLanguage($language);
84
85 22
        $query = $this->sanitizePath($query);
86 16
        $resources = array();
87
88 16
        if (Glob::isDynamic($query)) {
89 7
            $resources = $this->getGlobIterator($query);
90 16
        } elseif (isset($this->resources[$query])) {
91 9
            $resources = array($this->resources[$query]);
92 9
        }
93
94 16
        return new ArrayResourceCollection($resources);
0 ignored issues
show
Bug introduced by
It seems like $resources defined by array() on line 86 can also be of type array; however, Puli\Repository\Resource...llection::__construct() does only seem to accept array<integer,object<Pul...e>>|object<Traversable>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100 16
    public function contains($query, $language = 'glob')
101
    {
102 16
        $this->validateSearchLanguage($language);
103
104 15
        $query = $this->sanitizePath($query);
105
106 12
        if (Glob::isDynamic($query)) {
107 1
            $iterator = $this->getGlobIterator($query);
108 1
            $iterator->rewind();
109
110 1
            return $iterator->valid();
111
        }
112
113 11
        return isset($this->resources[$query]);
114 1
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119 71
    public function add($path, $resource)
120
    {
121 71
        $path = $this->sanitizePath($path);
122
123 68
        if ($resource instanceof ResourceCollection) {
124 2
            $this->ensureDirectoryExists($path);
125 2
            foreach ($resource as $child) {
126 2
                $this->addResource($path.'/'.$child->getName(), $child);
127 2
            }
128
129
            // Keep the resources sorted by file name
130 2
            ksort($this->resources);
131
132 2
            return;
133
        }
134
135 66
        if ($resource instanceof PuliResource) {
136 65
            $this->ensureDirectoryExists(Path::getDirectory($path));
137 65
            $this->addResource($path, $resource);
138
139 65
            ksort($this->resources);
140
141 65
            return;
142
        }
143
144 1
        throw new UnsupportedResourceException(sprintf(
145 1
            'The passed resource must be a PuliResource or ResourceCollection. Got: %s',
146 1
            is_object($resource) ? get_class($resource) : gettype($resource)
147 1
        ));
148
    }
149
150
    /**
151
     * {@inheritdoc}
152
     */
153 13
    public function remove($query, $language = 'glob')
154
    {
155 13
        $resources = $this->find($query, $language);
156 9
        $nbOfResources = count($this->resources);
157
158
        // Run the assertion after find(), so that we know that $query is valid
159 9
        Assert::notEmpty(trim($query, '/'), 'The root directory cannot be removed.');
160
161 7
        foreach ($resources as $resource) {
162 7
            $this->removeResource($resource);
163 7
        }
164
165 7
        return $nbOfResources - count($this->resources);
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171 93
    public function clear()
172
    {
173 93
        $root = new GenericResource('/');
174 93
        $root->attachTo($this);
175
176
        // Subtract root
177 93
        $removed = count($this->resources) - 1;
178
179 93
        $this->resources = array('/' => $root);
180
181 93
        return $removed;
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187 10
    public function listChildren($path)
188
    {
189 10
        $iterator = $this->getChildIterator($this->get($path));
190
191 6
        return new ArrayResourceCollection($iterator);
192
    }
193
194
    /**
195
     * {@inheritdoc}
196
     */
197 6
    public function hasChildren($path)
198
    {
199 6
        $iterator = $this->getChildIterator($this->get($path));
200 2
        $iterator->rewind();
201
202 2
        return $iterator->valid();
203
    }
204
205
    /**
206
     * Recursively creates a directory for a path.
207
     *
208
     * @param string $path A directory path.
209
     */
210 67
    private function ensureDirectoryExists($path)
211
    {
212 67
        if (!isset($this->resources[$path])) {
213
            // Recursively initialize parent directories
214 20
            if ($path !== '/') {
215 20
                $this->ensureDirectoryExists(Path::getDirectory($path));
216 20
            }
217
218 20
            $this->resources[$path] = new GenericResource($path);
219 20
            $this->resources[$path]->attachTo($this);
220
221 20
            return;
222
        }
223 67
    }
224
225 67
    private function addResource($path, PuliResource $resource)
226
    {
227
        // Don't modify resources attached to other repositories
228 67
        if ($resource->isAttached()) {
229 3
            $resource = clone $resource;
230 3
        }
231
232 67
        $basePath = '/' === $path ? $path : $path.'/';
233
234
        // Read children before attaching the resource to this repository
235 67
        $children = $resource->listChildren();
236
237 67
        $resource->attachTo($this, $path);
238
239
        // Add the resource before adding its children, so that the array
240
        // stays sorted
241 67
        $this->resources[$path] = $resource;
242
243 67
        foreach ($children as $name => $child) {
244 29
            $this->addResource($basePath.$name, $child);
245 67
        }
246
247 67
        $this->appendToChangeStream($resource);
248 67
    }
249
250 7
    private function removeResource(PuliResource $resource)
251
    {
252 7
        $path = $resource->getPath();
253
254
        // Ignore non-existing resources
255 7
        if (!isset($this->resources[$path])) {
256
            return;
257
        }
258
259
        // Recursively register directory contents
260 7
        foreach ($this->getChildIterator($resource) as $child) {
261 5
            $this->removeResource($child);
262 7
        }
263
264 7
        unset($this->resources[$path]);
265
266
        // Detach from locator
267 7
        $resource->detach();
268 7
    }
269
270
    /**
271
     * Returns an iterator for the children of a resource.
272
     *
273
     * @param PuliResource $resource The resource.
274
     *
275
     * @return RegexFilterIterator The iterator.
276
     */
277 14 View Code Duplication
    private function getChildIterator(PuliResource $resource)
278
    {
279 14
        $staticPrefix = rtrim($resource->getPath(), '/').'/';
280 14
        $regExp = '~^'.preg_quote($staticPrefix, '~').'[^/]+$~';
281
282 14
        return new RegexFilterIterator(
283 14
            $regExp,
284 14
            $staticPrefix,
285 14
            new ArrayIterator($this->resources),
286
            RegexFilterIterator::FILTER_KEY
287 14
        );
288
    }
289
290
    /**
291
     * Returns an iterator for a glob.
292
     *
293
     * @param string $glob The glob.
294
     *
295
     * @return GlobFilterIterator The iterator.
296
     */
297 8
    protected function getGlobIterator($glob)
298
    {
299 8
        return new GlobFilterIterator(
300 8
            $glob,
301 8
            new ArrayIterator($this->resources),
302
            GlobFilterIterator::FILTER_KEY
303 8
        );
304
    }
305
}
306