Completed
Push — 1.0 ( de14e4...537877 )
by Bernhard
57:08 queued 17:45
created

InMemoryRepository::removeResource()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.009

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 21
ccs 9
cts 10
cp 0.9
rs 9.3143
cc 3
eloc 9
nc 3
nop 1
crap 3.009
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 140
    public function __construct(ChangeStream $changeStream = null)
58
    {
59 140
        parent::__construct($changeStream);
60
61 140
        $this->clear();
62 140
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67 57
    public function get($path)
68
    {
69 57
        $path = $this->sanitizePath($path);
70
71 48
        if (!isset($this->resources[$path])) {
72 4
            throw ResourceNotFoundException::forPath($path);
73
        }
74
75 44
        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 12
        } elseif (isset($this->resources[$query])) {
91 12
            $resources = array($this->resources[$query]);
92
        }
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
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119 90
    public function add($path, $resource)
120
    {
121 90
        $path = $this->sanitizePath($path);
122
123 87
        if ($resource instanceof ResourceCollection) {
124 1
            $this->ensureDirectoryExists($path);
125 1
            foreach ($resource as $child) {
126 1
                $this->addResource($path.'/'.$child->getName(), $child);
127
            }
128
129
            // Keep the resources sorted by file name
130 1
            ksort($this->resources);
131
132 1
            return;
133
        }
134
135 86
        if ($resource instanceof PuliResource) {
136 85
            $this->ensureDirectoryExists(Path::getDirectory($path));
137 85
            $this->addResource($path, $resource);
138
139 85
            ksort($this->resources);
140
141 85
            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
        ));
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
        }
164
165 10
        return $nbOfResources - count($this->resources);
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171 140
    public function clear()
172
    {
173 140
        $root = new GenericResource('/');
174 140
        $root->attachTo($this);
175
176
        // Subtract root
177 140
        $removed = count($this->resources) - 1;
178
179 140
        $this->resources = array('/' => $root);
180
181 140
        $this->clearVersions();
182 140
        $this->storeVersion($root);
183
184 140
        return $removed;
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190 12
    public function listChildren($path)
191
    {
192 12
        $iterator = $this->getChildIterator($this->get($path));
193
194 8
        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 86
    private function ensureDirectoryExists($path)
214
    {
215 86
        if (!isset($this->resources[$path])) {
216
            // Recursively initialize parent directories
217 24
            if ($path !== '/') {
218 24
                $this->ensureDirectoryExists(Path::getDirectory($path));
219
            }
220
221 24
            $this->resources[$path] = new GenericResource($path);
222 24
            $this->resources[$path]->attachTo($this);
223
224 24
            return;
225
        }
226 86
    }
227
228 86
    private function addResource($path, PuliResource $resource)
229
    {
230 86
        $basePath = '/' === $path ? $path : $path.'/';
231
232
        // Read children before attaching the resource to this repository
233 86
        $children = $resource->listChildren();
234
235 86
        $resource = clone $resource;
236 86
        $resource->attachTo($this, $path);
237
238
        // Add the resource before adding its children, so that the array
239
        // stays sorted
240 86
        $this->resources[$path] = $resource;
241
242 86
        foreach ($children as $name => $child) {
243 46
            $this->addResource($basePath.$name, $child);
244
        }
245
246 86
        $this->storeVersion($resource);
247 86
    }
248
249 10
    private function removeResource(PuliResource $resource)
250
    {
251 10
        $path = $resource->getPath();
252
253 10
        $this->removeVersions($path);
254
255
        // Ignore non-existing resources
256 10
        if (!isset($this->resources[$path])) {
257
            return;
258
        }
259
260
        // Recursively remove directory contents
261 10
        foreach ($this->getChildIterator($resource) as $child) {
262 6
            $this->removeResource($child);
263
        }
264
265 10
        unset($this->resources[$path]);
266
267
        // Detach from locator
268 10
        $resource->detach();
269 10
    }
270
271
    /**
272
     * Returns an iterator for the children of a resource.
273
     *
274
     * @param PuliResource $resource The resource.
275
     *
276
     * @return RegexFilterIterator The iterator.
277
     */
278 19
    private function getChildIterator(PuliResource $resource)
279
    {
280 19
        $staticPrefix = rtrim($resource->getPath(), '/').'/';
281 19
        $regExp = '~^'.preg_quote($staticPrefix, '~').'[^/]+$~';
282
283 19
        return new RegexFilterIterator(
284
            $regExp,
285
            $staticPrefix,
286 19
            new ArrayIterator($this->resources),
287 19
            RegexFilterIterator::FILTER_KEY
288
        );
289
    }
290
291
    /**
292
     * Returns an iterator for a glob.
293
     *
294
     * @param string $glob The glob.
295
     *
296
     * @return GlobFilterIterator The iterator.
297
     */
298 8
    protected function getGlobIterator($glob)
299
    {
300 8
        return new GlobFilterIterator(
301
            $glob,
302 8
            new ArrayIterator($this->resources),
303 8
            GlobFilterIterator::FILTER_KEY
304
        );
305
    }
306
}
307