Completed
Pull Request — 1.0 (#58)
by Titouan
02:39
created

InMemoryRepository::get()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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