Completed
Push — 3.0 ( 50b99c...306061 )
by Olivier
02:25
created

Pathname::short_hash()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
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
 * Representation of a hash pathname.
18
 *
19
 * @property-read string $root Root directory of the managed files.
20
 * @property-read string $hash A hash from {@link Pathname::hash()}
21
 * @property-read string $short_hash The first 8 characters of {@link $hash}.
22
 * @property-read string $random Some random string added to the filename.
23
 * @property-read string $relative Returns a path relative to {@link \ICanBoogie\DOCUMENT_ROOT}.
24
 * @property-read string $filename Returns the file name.
25
 */
26
class Pathname
27
{
28
	use AccessorTrait;
29
30
	const HASH_ALGO = 'sha384';
31
	const HASH_LENGTH = 64;
32
	const SHORT_HASH_LENGTH = 8;
33
	const RANDOM_LENGTH = 16;
34
	const BASE64URL_CHARACTER_CLASS = 'A-Za-z0-9\-_';
35
	const FILENAME_REGEX = '([A-Za-z0-9\-_]{64})-([A-Za-z0-9\-_]{16})';
36
37
	/**
38
	 * Hash a file into a {@link HASH_LENGTH} character length string.
39
	 *
40
	 * @param string $pathname Absolute pathname to the file.
41
	 *
42
	 * @return string The hash of the file.
43
	 *
44
	 * @throws \LogicException if the file cannot be hashed.
45
	 */
46
	static public function hash($pathname)
47
	{
48
		$hash = hash_file(self::HASH_ALGO, $pathname, true);
49
50
		if (!$hash)
51
		{
52
			throw new \LogicException("Unable to hash file: $pathname.");
53
		}
54
55
		return Base64::encode($hash);
56
	}
57
58
	/**
59
	 * Hash a file into a {@link SHORT_HASH_LENGTH} character length string.
60
	 *
61
	 * @param string $pathname Absolute pathname to the file.
62
	 *
63
	 * @return string A short hash of the file.
64
	 */
65
	static public function short_hash($pathname)
66
	{
67
		return substr(self::hash($pathname), 0, self::SHORT_HASH_LENGTH);
68
	}
69
70
	/**
71
	 * Creates a random base64url encoded 16 characters wide string.
72
	 *
73
	 * @return string
74
	 */
75
	static public function random()
76
	{
77
		return Base64::encode(openssl_random_pseudo_bytes(12));
78
	}
79
80
	/**
81
	 * @var string
82
	 */
83
	private $root;
84
85
	/**
86
	 * @return string
87
	 */
88
	protected function get_root()
89
	{
90
		return $this->root;
91
	}
92
93
	/**
94
	 * @var string
95
	 */
96
	private $hash;
97
98
	/**
99
	 * @return string
100
	 */
101
	protected function get_hash()
102
	{
103
		return $this->hash;
104
	}
105
106
	/**
107
	 * @return string
108
	 */
109
	protected function get_short_hash()
110
	{
111
		return substr($this->hash, 0, self::SHORT_HASH_LENGTH);
112
	}
113
114
	/**
115
	 * @var string
116
	 */
117
	private $random;
118
119
	/**
120
	 * @return string
121
	 */
122
	protected function get_random()
123
	{
124
		return $this->random;
125
	}
126
127
	/**
128
	 * Returns a path relative to {@link \ICanBoogie\DOCUMENT_ROOT}.
129
	 *
130
	 * @return string
131
	 */
132
	protected function get_relative()
133
	{
134
		return substr((string) $this, strlen(\ICanBoogie\DOCUMENT_ROOT) - 1);
135
	}
136
137
	/**
138
	 * Returns the file name.
139
	 *
140
	 * @return string
141
	 */
142
	protected function get_filename()
143
	{
144
		return "{$this->hash}-{$this->random}";
145
	}
146
147
	/**
148
	 * Creates a new {@link Pathname} instance.
149
	 *
150
	 * @param string|array $pathname_or_parts The absolute pathname of a hash pathname or an array
151
	 * with the following values:
152
	 *
153
	 * - Root directory of the managed files.
154
	 * - A hash from {@link Pathname::hash()}.
155
	 * - A random string from {@link Pathname::random()}, which is created if empty.
156
	 *
157
	 * @return static
158
	 */
159
	static public function from($pathname_or_parts)
160
	{
161
		if ($pathname_or_parts instanceof self)
162
		{
163
			return $pathname_or_parts;
164
		}
165
166
		if (is_array($pathname_or_parts))
167
		{
168
			return static::from_parts($pathname_or_parts);
169
		}
170
171
		if (is_string($pathname_or_parts))
172
		{
173
			return static::from_pathname($pathname_or_parts);
174
		}
175
176
		throw new \InvalidArgumentException("Expected an array or a string, got: " . gettype($pathname_or_parts) . ".");
177
	}
178
179
	/**
180
	 * Creates a new {@link Pathname} instance from parts.
181
	 *
182
	 * @param array $parts An array with the following values:
183
	 *
184
	 * - Root directory of the managed files.
185
	 * - A hash from {@link Pathname::hash()}.
186
	 * - A random string from {@link Pathname::random()}, which is created if empty.
187
	 *
188
	 * @return static
189
	 */
190
	static protected function from_parts(array $parts)
191
	{
192
		list($root, $hash, $random) = $parts + [ 2 => null];
193
194
		$random = $random ?: self::random();
195
196
		return new static($root, $hash, $random);
197
	}
198
199
	/**
200
	 * Creates an instance from a pathname.
201
	 *
202
	 * @param string $pathname The absolute pathname of a hash pathname.
203
	 *
204
	 * @return static
205
	 */
206
	static protected function from_pathname($pathname)
207
	{
208
		$ds = preg_quote(DIRECTORY_SEPARATOR);
209
210
		if (!preg_match("#(.+{$ds})" . self::FILENAME_REGEX . '$#', $pathname, $matches))
211
		{
212
			throw new \InvalidArgumentException("Invalid hash pathname: $pathname.");
213
		}
214
215
		return static::from_parts(array_slice($matches, 1));
216
	}
217
218
	/**
219
	 * @param string $root Root directory of the managed files.
220
	 * @param string|null A hash from {@link Pathname::hash()}.
221
	 * @param string $random A random string from {@link Pathname::random()}.
222
	 */
223
	public function __construct($root, $hash, $random)
224
	{
225
		$this->root = $root;
226
		$this->hash = $hash;
227
		$this->random = $random;
228
	}
229
230
	/**
231
	 * Returns the hash pathname.
232
	 *
233
	 * @return string
234
	 */
235
	public function __toString()
236
	{
237
		return $this->root . $this->filename;
238
	}
239
}
240