FileStorageIndex   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 24
lcom 1
cbo 2
dl 0
loc 228
c 0
b 0
f 0
rs 10

12 Methods

Rating   Name   Duplication   Size   Complexity  
B resolve_matching_type() 0 24 5
A get_root() 0 4 1
A __construct() 0 4 1
B add() 0 24 5
A delete() 0 14 3
A find() 0 15 1
A find_by_key() 0 6 2
A find_by_id() 0 4 1
A find_by_encoded_uuid() 0 4 1
A find_by_uuid() 0 4 1
A find_by_hash() 0 4 1
A matching() 0 14 2
1
<?php
2
3
/*
4
 * This file is part of the Icybee package.
5
 *
6
 * (c) Olivier Laviale <[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 Icybee\Modules\Files\Storage;
13
14
use ICanBoogie\Accessor\AccessorTrait;
15
16
/**
17
 * Am index of the managed files.
18
 *
19
 * @property-read string $root
20
 */
21
class FileStorageIndex
22
{
23
	use AccessorTrait;
24
25
	const MATCH_BY_KEY = 1;
26
	const MATCH_BY_ID = 2;
27
	const MATCH_BY_UUID = 3;
28
	const MATCH_BY_HASH = 4;
29
30
	/**
31
	 * Resolves matching type.
32
	 *
33
	 * @param IndexKey|int|string $key_or_id_or_uuid_or_hash A {@link IndexKey}, a node
34
	 * identifier, a v4 UUID, or a hash.
35
	 *
36
	 * @return int
37
	 *
38
	 * @throws \InvalidArgumentException if `$key_or_id_or_uuid_or_hash` is not one of
39
	 * the required type.
40
	 */
41
	static private function resolve_matching_type($key_or_id_or_uuid_or_hash)
42
	{
43
		if ($key_or_id_or_uuid_or_hash instanceof IndexKey)
44
		{
45
			return self::MATCH_BY_KEY;
46
		}
47
48
		if (is_numeric($key_or_id_or_uuid_or_hash))
49
		{
50
			return self::MATCH_BY_ID;
51
		}
52
53
		if (strlen($key_or_id_or_uuid_or_hash) === IndexKey::UUID_LENGTH)
54
		{
55
			return self::MATCH_BY_UUID;
56
		}
57
58
		if (strlen($key_or_id_or_uuid_or_hash) === IndexKey::HASH_LENGTH)
59
		{
60
			return self::MATCH_BY_HASH;
61
		}
62
63
		throw new \InvalidArgumentException("Expected IndexKey instance, node identifier, v4 UUID, or a hash. Got: $key_or_id_or_uuid_or_hash");
64
	}
65
66
	/**
67
	 * Root directory including a trailing `DIRECTORY_SEPARATOR`.
68
	 *
69
	 * @var string
70
	 */
71
	private $root;
72
73
	protected function get_root()
74
	{
75
		return $this->root;
76
	}
77
78
	/**
79
	 * @param string $root FileStorageIndex root directory.
80
	 */
81
	public function __construct($root)
82
	{
83
		$this->root = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
84
	}
85
86
	/**
87
	 * Adds a key to the index.
88
	 *
89
	 * Composite keys using the same `uid` or `uuid` are replaced by this one.
90
	 *
91
	 * @param IndexKey $key
92
	 */
93
	public function add(IndexKey $key)
94
	{
95
		touch($this->root . $key);
96
97
		foreach ($this->find_by_id($key->id) as $match)
98
		{
99
			if ($match === $key)
100
			{
101
				continue;
102
			}
103
104
			$this->delete($match);
105
		}
106
107
		foreach ($this->find_by_uuid($key->uuid) as $match)
108
		{
109
			if ($match === $key)
110
			{
111
				continue;
112
			}
113
114
			$this->delete($match);
115
		}
116
	}
117
118
	/**
119
	 * Deletes key(s) from the index.
120
	 *
121
	 * @param IndexKey|int|string $key_or_id_or_uuid_or_hash
122
	 *
123
	 * @throws \InvalidArgumentException if `$key_or_id_or_uuid_or_hash` is not of the expected type.
124
	 */
125
	public function delete($key_or_id_or_uuid_or_hash)
126
	{
127
		$matching = $this->find($key_or_id_or_uuid_or_hash);
128
129
		if (!$matching)
0 ignored issues
show
Bug Best Practice introduced by
The expression $matching of type Icybee\Modules\Files\Storage\IndexKey[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
130
		{
131
			return;
132
		}
133
134
		foreach ($matching as $match)
135
		{
136
			unlink($this->root . $match);
137
		}
138
	}
139
140
	/**
141
	 * Finds composite keys.
142
	 *
143
	 * @param IndexKey|int|string $key_or_id_or_uuid_or_hash
144
	 *
145
	 * @return IndexKey[]
146
	 *
147
	 * @throws \InvalidArgumentException if `$key_or_id_or_uuid_or_hash` is not of the expected type.
148
	 */
149
	public function find($key_or_id_or_uuid_or_hash)
150
	{
151
		static $methods = [
152
153
			self::MATCH_BY_KEY => 'key',
154
			self::MATCH_BY_ID => 'id',
155
			self::MATCH_BY_UUID => 'uuid',
156
			self::MATCH_BY_HASH => 'hash'
157
158
		];
159
160
		$type = $methods[self::resolve_matching_type($key_or_id_or_uuid_or_hash)];
161
162
		return $this->{ 'find_by_' . $type }($key_or_id_or_uuid_or_hash);
163
	}
164
165
	/**
166
	 * Returns the composite key matching the composite key.
167
	 *
168
	 * @param IndexKey|string $key
169
	 *
170
	 * @return IndexKey[]
171
	 */
172
	protected function find_by_key($key)
173
	{
174
		$pathname = $this->root . $key;
175
176
		return file_exists($pathname) ? [ $key ] : null;
177
	}
178
179
	/**
180
	 * Returns the composite keys match an identifier.
181
	 *
182
	 * @param int $id The node identifier to match.
183
	 *
184
	 * @return IndexKey[]
185
	 */
186
	protected function find_by_id($id)
187
	{
188
		return $this->matching('#^' . preg_quote(IndexKey::encode_id($id)) . '\-#');
189
	}
190
191
	/**
192
	 * Returns the composite keys match a v4 UUID.
193
	 *
194
	 * @param string $encoded_uuid The UUID to match.
195
	 *
196
	 * @return IndexKey[]
197
	 */
198
	protected function find_by_encoded_uuid($encoded_uuid)
199
	{
200
		return $this->matching("#^.{" . IndexKey::ENCODED_ID_LENGTH . '}\-' . preg_quote($encoded_uuid) . '\-#');
201
	}
202
203
	/**
204
	 * Returns the composite keys match a v4 UUID.
205
	 *
206
	 * @param string $uuid The UUID to match.
207
	 *
208
	 * @return IndexKey[]
209
	 */
210
	protected function find_by_uuid($uuid)
211
	{
212
		return $this->find_by_encoded_uuid(IndexKey::encode_uuid($uuid));
213
	}
214
215
	/**
216
	 * Returns the composite keys match a hash.
217
	 *
218
	 * @param string $hash A hash from {@link Pathname::hash()}
219
	 *
220
	 * @return IndexKey[]
221
	 */
222
	protected function find_by_hash($hash)
223
	{
224
		return $this->matching("#^.{" . IndexKey::ENCODED_ID_LENGTH . '}\-.{' . IndexKey::ENCODED_UUID_LENGTH . '}\-' . preg_quote($hash) . '#');
225
	}
226
227
	/**
228
	 * Returns the composite keys matching a pattern.
229
	 *
230
	 * @param string $pattern The pattern to match.
231
	 *
232
	 * @return IndexKey[]
233
	 */
234
	protected function matching($pattern)
235
	{
236
		$matches = [];
237
238
		$di = new \DirectoryIterator($this->root);
239
		$di = new \RegexIterator($di, $pattern);
240
241
		foreach ($di as $match)
242
		{
243
			$matches[] = IndexKey::from($match->getFilename());
244
		}
245
246
		return $matches;
247
	}
248
}
249