Completed
Push — 1.0 ( fe7a2f...22ab07 )
by Bernhard
12:18
created

InMemoryRepository::removeResource()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.0068

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 21
ccs 10
cts 11
cp 0.9091
rs 9.3143
cc 3
eloc 9
nc 3
nop 1
crap 3.0068
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 122
    public function __construct(ChangeStream $changeStream = null)
58
    {
59 122
        parent::__construct($changeStream);
60
61 122
        $this->clear();
62 122
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 49
    public function get($path)
68
    {
69 49
        $path = $this->sanitizePath($path);
70
71 40
        if (!isset($this->resources[$path])) {
72 4
            throw ResourceNotFoundException::forPath($path);
73
        }
74
75 36
        return $this->resources[$path];
76
    }
77
78
    /**
79
     * {@inheritdoc}
80
     */
81 27
    public function find($query, $language = 'glob')
82
    {
83 27
        $this->failUnlessGlob($language);
84
85 25
        $query = $this->sanitizePath($query);
86 19
        $resources = array();
87
88 19
        if (Glob::isDynamic($query)) {
89 7
            $resources = $this->getGlobIterator($query);
90 19
        } elseif (isset($this->resources[$query])) {
91 12
            $resources = array($this->resources[$query]);
92 12
        }
93
94 19
        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->failUnlessGlob($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 84
    public function add($path, $resource)
120
    {
121 84
        $path = $this->sanitizePath($path);
122
123 81
        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 79
        if ($resource instanceof PuliResource) {
136 78
            $this->ensureDirectoryExists(Path::getDirectory($path));
137 78
            $this->addResource($path, $resource);
138
139 78
            ksort($this->resources);
140
141 78
            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 16
    public function remove($query, $language = 'glob')
154
    {
155 16
        $resources = $this->find($query, $language);
156 12
        $nbOfResources = count($this->resources);
157
158
        // Run the assertion after find(), so that we know that $query is valid
159 12
        Assert::notEmpty(trim($query, '/'), 'The root directory cannot be removed.');
160
161 10
        foreach ($resources as $resource) {
162 10
            $this->removeResource($resource);
163 10
        }
164
165 10
        return $nbOfResources - count($this->resources);
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171 122
    public function clear()
172
    {
173 122
        $root = new GenericResource('/');
174 122
        $root->attachTo($this);
175
176
        // Subtract root
177 122
        $removed = count($this->resources) - 1;
178
179 122
        $this->resources = array('/' => $root);
180
181 122
        $this->clearVersions();
182 122
        $this->storeVersion($root);
183
184 122
        return $removed;
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190 10
    public function listChildren($path)
191
    {
192 10
        $iterator = $this->getChildIterator($this->get($path));
193
194 6
        return new ArrayResourceCollection($iterator);
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200 6
    public function hasChildren($path)
201
    {
202 6
        $iterator = $this->getChildIterator($this->get($path));
203 2
        $iterator->rewind();
204
205 2
        return $iterator->valid();
206
    }
207
208
    /**
209
     * Recursively creates a directory for a path.
210
     *
211
     * @param string $path A directory path.
212
     */
213 80
    private function ensureDirectoryExists($path)
214
    {
215 80
        if (!isset($this->resources[$path])) {
216
            // Recursively initialize parent directories
217 28
            if ($path !== '/') {
218 28
                $this->ensureDirectoryExists(Path::getDirectory($path));
219 28
            }
220
221 28
            $this->resources[$path] = new GenericResource($path);
222 28
            $this->resources[$path]->attachTo($this);
223
224 28
            return;
225
        }
226 80
    }
227
228 80
    private function addResource($path, PuliResource $resource)
229
    {
230
        // Don't modify resources attached to other repositories
231 80
        if ($resource->isAttached()) {
232 3
            $resource = clone $resource;
233 3
        }
234
235 80
        $basePath = '/' === $path ? $path : $path.'/';
236
237
        // Read children before attaching the resource to this repository
238 80
        $children = $resource->listChildren();
239
240 80
        $resource->attachTo($this, $path);
241
242
        // Add the resource before adding its children, so that the array
243
        // stays sorted
244 80
        $this->resources[$path] = $resource;
245
246 80
        foreach ($children as $name => $child) {
247 36
            $this->addResource($basePath.$name, $child);
248 80
        }
249
250 80
        $this->storeVersion($resource);
251 80
    }
252
253 10
    private function removeResource(PuliResource $resource)
254
    {
255 10
        $path = $resource->getPath();
256
257 10
        $this->removeVersions($path);
258
259
        // Ignore non-existing resources
260 10
        if (!isset($this->resources[$path])) {
261
            return;
262
        }
263
264
        // Recursively remove directory contents
265 10
        foreach ($this->getChildIterator($resource) as $child) {
266 6
            $this->removeResource($child);
267 10
        }
268
269 10
        unset($this->resources[$path]);
270
271
        // Detach from locator
272 10
        $resource->detach();
273 10
    }
274
275
    /**
276
     * Returns an iterator for the children of a resource.
277
     *
278
     * @param PuliResource $resource The resource.
279
     *
280
     * @return RegexFilterIterator The iterator.
281
     */
282 17
    private function getChildIterator(PuliResource $resource)
283
    {
284 17
        $staticPrefix = rtrim($resource->getPath(), '/').'/';
285 17
        $regExp = '~^'.preg_quote($staticPrefix, '~').'[^/]+$~';
286
287 17
        return new RegexFilterIterator(
288 17
            $regExp,
289 17
            $staticPrefix,
290 17
            new ArrayIterator($this->resources),
291
            RegexFilterIterator::FILTER_KEY
292 17
        );
293
    }
294
295
    /**
296
     * Returns an iterator for a glob.
297
     *
298
     * @param string $glob The glob.
299
     *
300
     * @return GlobFilterIterator The iterator.
301
     */
302 8
    protected function getGlobIterator($glob)
303
    {
304 8
        return new GlobFilterIterator(
305 8
            $glob,
306 8
            new ArrayIterator($this->resources),
307
            GlobFilterIterator::FILTER_KEY
308 8
        );
309
    }
310
}
311