Completed
Pull Request — 1.0 (#58)
by Titouan
05:41 queued 02:34
created

OptimizedPathMappingRepository::removePath()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3.0176

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 16
ccs 7
cts 8
cp 0.875
rs 9.4286
cc 3
eloc 7
nc 3
nop 1
crap 3.0176
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 Iterator;
16
use Puli\Repository\Api\EditableRepository;
17
use Puli\Repository\Api\Resource\FilesystemResource;
18
use Puli\Repository\Api\Resource\PuliResource;
19
use Puli\Repository\Api\ResourceNotFoundException;
20
use Puli\Repository\Api\UnsupportedLanguageException;
21
use Puli\Repository\Resource\Collection\ArrayResourceCollection;
22
use Puli\Repository\Resource\LinkResource;
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 optimized path mapping resource repository.
31
 * When a resource is added, all its children are resolved
32
 * and getting them is much faster.
33
 *
34
 * Resources can be added with the method {@link add()}:
35
 *
36
 * ```php
37
 * use Puli\Repository\OptimizedPathMappingRepository;
38
 *
39
 * $repo = new OptimizedPathMappingRepository();
40
 * $repo->add('/css', new DirectoryResource('/path/to/project/res/css'));
41
 * ```
42
 *
43
 * This repository only supports instances of FilesystemResource.
44
 *
45
 * @since  1.0
46
 *
47
 * @author Bernhard Schussek <[email protected]>
48
 * @author Titouan Galopin <[email protected]>
49
 */
50
class OptimizedPathMappingRepository extends AbstractPathMappingRepository implements EditableRepository
51
{
52
    /**
53
     * {@inheritdoc}
54
     */
55 47
    public function get($path)
56
    {
57 47
        $path = $this->sanitizePath($path);
58
59 38
        if (!$this->store->exists($path)) {
60 3
            throw ResourceNotFoundException::forPath($path);
61
        }
62
63 35
        return $this->createResource($this->store->get($path), $path);
64
    }
65
66
    /**
67
     * {@inheritdoc}
68
     */
69 11
    public function find($query, $language = 'glob')
70
    {
71 11
        $this->validateSearchLanguage($language);
72
73 10
        $query = $this->sanitizePath($query);
74 7
        $resources = new ArrayResourceCollection();
75
76 7
        if (Glob::isDynamic($query)) {
77 5
            $resources = $this->iteratorToCollection($this->getGlobIterator($query));
78 2
        } elseif ($this->store->exists($query)) {
79 2
            $resources = new ArrayResourceCollection(array(
80 2
                $this->createResource($this->store->get($query), $query),
81
            ));
82
        }
83
84 7
        return $resources;
85
    }
86
87
    /**
88
     * {@inheritdoc}
89
     */
90 19
    public function contains($query, $language = 'glob')
91
    {
92 19
        if ('glob' !== $language) {
93 1
            throw UnsupportedLanguageException::forLanguage($language);
94
        }
95
96 18
        $query = $this->sanitizePath($query);
97
98 15
        if (Glob::isDynamic($query)) {
99 1
            $iterator = $this->getGlobIterator($query);
100 1
            $iterator->rewind();
101
102 1
            return $iterator->valid();
103
        }
104
105 14
        return $this->store->exists($query);
106
    }
107
108
    /**
109
     * {@inheritdoc}
110
     */
111 13
    public function remove($query, $language = 'glob')
112
    {
113 13
        $this->validateSearchLanguage($language);
114
115 12
        $query = $this->sanitizePath($query);
116
117 9
        Assert::notEmpty(trim($query, '/'), 'The root directory cannot be removed.');
118
119
        // Find resources to remove
120
        // (more efficient that find() as we do not need to unserialize them)
121 7
        $paths = array();
122
123 7
        if (Glob::isDynamic($query)) {
124 2
            $paths = $this->getGlobIterator($query);
125 5
        } elseif ($this->store->exists($query)) {
126 5
            $paths = array($query);
127
        }
128
129
        // Remove the resources found
130 7
        $nbOfResources = $this->countStore();
131
132 7
        foreach ($paths as $path) {
133 7
            $this->removePath($path);
134
        }
135
136 7
        return $nbOfResources - $this->countStore();
137
    }
138
139
    /**
140
     * {@inheritdoc}
141
     */
142 11
    public function listChildren($path)
143
    {
144 11
        $iterator = $this->getChildIterator($this->get($path));
145
146 7
        return $this->iteratorToCollection($iterator);
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152 7
    public function hasChildren($path)
153
    {
154 7
        $iterator = $this->getChildIterator($this->get($path));
155 3
        $iterator->rewind();
156
157 3
        return $iterator->valid();
158
    }
159
160
    /**
161
     * @param string             $path
162
     * @param FilesystemResource $resource
163
     */
164 68
    protected function addFilesystemResource($path, FilesystemResource $resource)
165
    {
166
        // Read children before attaching the resource to this repository
167 68
        $children = $resource->listChildren();
168
169 68
        $resource->attachTo($this, $path);
170
171
        // Add the resource before adding its children, so that the array stays sorted
172 68
        $this->store->set($path, Path::makeRelative($resource->getFilesystemPath(), $this->baseDirectory));
173
174 68
        $basePath = '/' === $path ? $path : $path.'/';
175
176 68
        foreach ($children as $name => $child) {
177 31
            $this->addFilesystemResource($basePath.$name, $child);
178
        }
179 68
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184 2
    protected function addLinkResource($path, LinkResource $resource)
185
    {
186 2
        $resource->attachTo($this, $path);
187 2
        $this->store->set($path, 'l:'.$resource->getTargetPath());
188 2
    }
189
190
    /**
191
     * @param string $path
192
     */
193 7
    private function removePath($path)
194
    {
195 7
        if (!$this->store->exists($path)) {
196
            return;
197
        }
198
199
        // Remove children first
200 7
        $children = $this->getRecursivePathChildIterator($path);
201
202 7
        foreach ($children as $child) {
203 5
            $this->store->remove($child);
204
        }
205
206
        // Remove the resource
207 7
        $this->store->remove($path);
208 7
    }
209
210
    /**
211
     * Returns an iterator for the children paths of a resource.
212
     *
213
     * @param PuliResource $resource The resource.
214
     *
215
     * @return RegexFilterIterator The iterator of paths.
216
     */
217 9 View Code Duplication
    private function getChildIterator(PuliResource $resource)
218
    {
219 9
        $staticPrefix = rtrim($resource->getPath(), '/').'/';
220 9
        $regExp = '~^'.preg_quote($staticPrefix, '~').'[^/]+$~';
221
222 9
        return new RegexFilterIterator(
223
            $regExp,
224
            $staticPrefix,
225 9
            new ArrayIterator($this->store->keys())
226
        );
227
    }
228
229
    /**
230
     * Returns a recursive iterator for the children paths under a given path.
231
     *
232
     * @param string $path The path.
233
     *
234
     * @return RegexFilterIterator The iterator of paths.
235
     */
236 7 View Code Duplication
    private function getRecursivePathChildIterator($path)
237
    {
238 7
        $staticPrefix = rtrim($path, '/').'/';
239 7
        $regExp = '~^'.preg_quote($staticPrefix, '~').'.+$~';
240
241 7
        return new RegexFilterIterator(
242
            $regExp,
243
            $staticPrefix,
244 7
            new ArrayIterator($this->store->keys())
245
        );
246
    }
247
248
    /**
249
     * Returns an iterator for a glob.
250
     *
251
     * @param string $glob The glob.
252
     *
253
     * @return GlobFilterIterator The iterator of paths.
254
     */
255 8
    private function getGlobIterator($glob)
256
    {
257 8
        return new GlobFilterIterator(
258
            $glob,
259 8
            new ArrayIterator($this->store->keys())
260
        );
261
    }
262
263
    /**
264
     * Transform an iterator of paths into a collection of resources.
265
     *
266
     * @param Iterator $iterator
267
     *
268
     * @return ArrayResourceCollection
269
     */
270 12
    private function iteratorToCollection(Iterator $iterator)
271
    {
272 12
        $filesystemPaths = $this->store->getMultiple(iterator_to_array($iterator));
273 12
        $collection = new ArrayResourceCollection();
274
275 12
        foreach ($filesystemPaths as $path => $filesystemPath) {
276 9
            $collection->add($this->createResource($filesystemPath, $path));
277
        }
278
279 12
        return $collection;
280
    }
281
}
282