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

InMemoryRepository::removeResource()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 23
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.3074

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 23
ccs 10
cts 13
cp 0.7692
rs 8.5907
cc 5
eloc 10
nc 5
nop 1
crap 5.3074
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
     * This array contains a copy of each LinkResource in $resources
53
     * for performance reasons.
54
     *
55
     * @var LinkResource[]
56
     */
57
    private $links = array();
58
59
    /**
60
     * Creates a new repository.
61
     */
62 79
    public function __construct()
63
    {
64 79
        $this->clear();
65 79
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 47
    public function get($path)
71
    {
72 47
        $path = $this->sanitizePath($path);
73
74 38
        if (!isset($this->resources[$path])) {
75 4
            return $this->followLinksOrFail($path);
76
        }
77
78 35
        return $this->resources[$path];
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84 24
    public function find($query, $language = 'glob')
85
    {
86 24
        $this->validateSearchLanguage($language);
87
88 22
        $query = $this->sanitizePath($query);
89 16
        $resources = array();
90
91 16
        if (Glob::isDynamic($query)) {
92 7
            $resources = $this->getGlobIterator($query);
93 16
        } elseif (isset($this->resources[$query])) {
94 9
            $resources = array($this->resources[$query]);
95 9
        }
96
97 16
        return new ArrayResourceCollection($resources);
0 ignored issues
show
Bug introduced by
It seems like $resources defined by array() on line 89 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...
98
    }
99
100
    /**
101
     * {@inheritdoc}
102
     */
103 16
    public function contains($query, $language = 'glob')
104
    {
105 16
        $this->validateSearchLanguage($language);
106
107 15
        $query = $this->sanitizePath($query);
108
109 12
        if (Glob::isDynamic($query)) {
110 1
            $iterator = $this->getGlobIterator($query);
111 1
            $iterator->rewind();
112
113 1
            return $iterator->valid();
114 2
        }
115
116 11
        return isset($this->resources[$query]);
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122 69
    public function add($path, $resource)
123
    {
124 69
        $path = $this->sanitizePath($path);
125
126 66
        if ($resource instanceof ResourceCollection) {
127 2
            $this->ensureDirectoryExists($path);
128 2
            foreach ($resource as $child) {
129 2
                $this->addResource($path.'/'.$child->getName(), $child);
130 2
            }
131
132
            // Keep the resources sorted by file name
133 2
            ksort($this->resources);
134
135 2
            return;
136
        }
137
138 64
        if ($resource instanceof PuliResource) {
139 63
            $this->ensureDirectoryExists(Path::getDirectory($path));
140 63
            $this->addResource($path, $resource);
141
142 63
            ksort($this->resources);
143
144 63
            return;
145
        }
146
147 1
        throw new UnsupportedResourceException(sprintf(
148 1
            'The passed resource must be a PuliResource or ResourceCollection. Got: %s',
149 1
            is_object($resource) ? get_class($resource) : gettype($resource)
150 1
        ));
151
    }
152
153
    /**
154
     * {@inheritdoc}
155
     */
156 13
    public function remove($query, $language = 'glob')
157
    {
158 13
        $resources = $this->find($query, $language);
159 9
        $nbOfResources = count($this->resources);
160
161
        // Run the assertion after find(), so that we know that $query is valid
162 9
        Assert::notEmpty(trim($query, '/'), 'The root directory cannot be removed.');
163
164 7
        foreach ($resources as $resource) {
165 7
            $this->removeResource($resource);
166 7
        }
167
168 7
        return $nbOfResources - count($this->resources);
169
    }
170
171
    /**
172
     * {@inheritdoc}
173
     */
174 79
    public function clear()
175
    {
176 79
        $root = new GenericResource('/');
177 79
        $root->attachTo($this);
178
179
        // Subtract root
180 79
        $removed = count($this->resources) - 1;
181
182 79
        $this->resources = array('/' => $root);
183 79
        $this->links = array();
184
185 79
        return $removed;
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191 10
    public function listChildren($path)
192
    {
193 10
        $iterator = $this->getChildIterator($this->get($path));
194
195 6
        return new ArrayResourceCollection($iterator);
196
    }
197
198
    /**
199
     * {@inheritdoc}
200
     */
201 6
    public function hasChildren($path)
202
    {
203 6
        $iterator = $this->getChildIterator($this->get($path));
204 2
        $iterator->rewind();
205
206 2
        return $iterator->valid();
207
    }
208
209
    /**
210
     * Recursively creates a directory for a path.
211
     *
212
     * @param string $path A directory path.
213
     */
214 65
    private function ensureDirectoryExists($path)
215
    {
216 65
        if (!isset($this->resources[$path])) {
217
            // Recursively initialize parent directories
218 19
            if ($path !== '/') {
219 19
                $this->ensureDirectoryExists(Path::getDirectory($path));
220 19
            }
221
222 19
            $this->resources[$path] = new GenericResource($path);
223 19
            $this->resources[$path]->attachTo($this);
224
225 19
            return;
226
        }
227 65
    }
228
229 65
    private function addResource($path, PuliResource $resource)
230
    {
231
        // Don't modify resources attached to other repositories
232 65
        if ($resource->isAttached()) {
233 2
            $resource = clone $resource;
234 2
        }
235
236 65
        $basePath = '/' === $path ? $path : $path.'/';
237
238
        // Read children before attaching the resource to this repository
239 65
        $children = $resource->listChildren();
240
241 65
        $resource->attachTo($this, $path);
242
243
        // Add the resource before adding its children, so that the array
244
        // stays sorted
245 65
        $this->resources[$path] = $resource;
246
247 65
        if ($resource instanceof LinkResource) {
248 3
            $this->links[$path] = $resource;
249 3
        }
250
251 65
        foreach ($children as $name => $child) {
252 30
            $this->addResource($basePath.$name, $child);
253 65
        }
254 65
    }
255
256 7
    private function removeResource(PuliResource $resource)
257
    {
258 7
        $path = $resource->getPath();
259
260
        // Ignore non-existing resources
261 7
        if (!isset($this->resources[$path])) {
262
            return;
263
        }
264
265
        // Recursively register directory contents
266 7
        foreach ($this->getChildIterator($resource) as $child) {
267 5
            $this->removeResource($child);
268 7
        }
269
270 7
        unset($this->resources[$path]);
271
272 7
        if ($resource instanceof LinkResource && array_key_exists($path, $this->links)) {
273
            unset($this->links[$path]);
274
        }
275
276
        // Detach from locator
277 7
        $resource->detach();
278 7
    }
279
280
    /**
281
     * {@inheritdoc}
282
     */
283 4
    protected function followLinks($path)
284
    {
285 4
        foreach ($this->links as $link) {
286 1
            $linkPath = rtrim($link->getPath(), '/');
287
288 1
            if (0 === strpos($path, $linkPath.'/')) {
289 1
                $realTargetPath = rtrim($this->getRealTarget($link)->getPath(), '/');
290 1
                $realPath = substr_replace($path, $realTargetPath, 0, strlen($linkPath));
291
292 1
                if (array_key_exists($realPath, $this->resources)) {
293 1
                    return $this->resources[$realPath];
294
                }
295
            }
296 4
        }
297
298 3
        return null;
299
    }
300
301
    /**
302
     * Find the real target of a given link (resolve all the links until
303
     * a non-link resource is found).
304
     *
305
     * @param LinkResource $linkResource
306
     *
307
     * @return PuliResource
308
     */
309 1
    private function getRealTarget(LinkResource $linkResource)
310
    {
311 1
        $target = $linkResource->getTarget();
312
313 1
        if (!$target instanceof LinkResource) {
314 1
            return $target;
315
        }
316
317 1
        return $this->getRealTarget($target);
318
    }
319
320
    /**
321
     * Returns an iterator for the children of a resource.
322
     *
323
     * @param PuliResource $resource The resource.
324
     *
325
     * @return RegexFilterIterator The iterator.
326
     */
327 14 View Code Duplication
    private function getChildIterator(PuliResource $resource)
328
    {
329 14
        $staticPrefix = rtrim($resource->getPath(), '/').'/';
330 14
        $regExp = '~^'.preg_quote($staticPrefix, '~').'[^/]+$~';
331
332 14
        return new RegexFilterIterator(
333 14
            $regExp,
334 14
            $staticPrefix,
335 14
            new ArrayIterator($this->resources),
336
            RegexFilterIterator::FILTER_KEY
337 14
        );
338
    }
339
340
    /**
341
     * Returns an iterator for a glob.
342
     *
343
     * @param string $glob The glob.
344
     *
345
     * @return GlobFilterIterator The iterator.
346
     */
347 8
    protected function getGlobIterator($glob)
348
    {
349 8
        return new GlobFilterIterator(
350 8
            $glob,
351 8
            new ArrayIterator($this->resources),
352
            GlobFilterIterator::FILTER_KEY
353 8
        );
354
    }
355
}
356