├── .gitignore ├── CHANGELOG.md ├── Source ├── Directory.php ├── Exception │ ├── Exception.php │ └── FileDoesNotExist.php ├── File.php ├── Finder.php ├── Generic.php ├── Link │ ├── Link.php │ ├── Read.php │ ├── ReadWrite.php │ └── Write.php ├── Read.php ├── ReadWrite.php ├── SplFileInfo.php ├── Temporary │ ├── Read.php │ ├── ReadWrite.php │ ├── Temporary.php │ └── Write.php ├── Watcher.php └── Write.php └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /composer.lock 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.17.07.11 2 | 3 | * doc(api) Rewrite `copy` definition. (Ivan Enderlin, 2017-07-11T09:35:10+02:00) 4 | * fix(directory) Use read-only mode when copying. (Ivan Enderlin, 2017-07-06T13:34:16+02:00) 5 | * chore(cs) Remove an empty line. (Ivan Enderlin, 2017-07-06T13:32:34+02:00) 6 | 7 | # 1.17.01.13 8 | 9 | * Quality: Happy new year! (Alexis von Glasow, 2017-01-10T12:56:03+01:00) 10 | * Quality: Fix CS. (Ivan Enderlin, 2016-10-13T22:51:31+02:00) 11 | * Finder: Better variable semantics in `in`. (Ivan Enderlin, 2016-10-13T22:47:37+02:00) 12 | * Finder: Reduce error vulnerability. (Ivan Enderlin, 2016-10-13T22:43:03+02:00) 13 | * Add Glob pattern handling in the `Finder` (Stéphane HULARD, 2015-11-12T22:28:20+01:00) 14 | * Documentation: Update `support` properties. (Ivan Enderlin, 2016-10-05T20:43:22+02:00) 15 | * Composer: New stable library. (Ivan Enderlin, 2016-01-14T21:55:56+01:00) 16 | 17 | # 1.16.01.15 18 | 19 | * Composer: New stable library. (Ivan Enderlin, 2016-01-14T21:54:42+01:00) 20 | 21 | # 1.16.01.14 22 | 23 | * Quality: Drop PHP5.4. (Ivan Enderlin, 2016-01-11T09:15:26+01:00) 24 | * Quality: Run devtools:cs. (Ivan Enderlin, 2016-01-09T09:02:11+01:00) 25 | * Core: Remove `Hoa\Core`. (Ivan Enderlin, 2016-01-09T08:16:42+01:00) 26 | * Consistency: Use `Hoa\Consistency`. (Ivan Enderlin, 2015-12-08T11:13:39+01:00) 27 | * Event: Use `Hoa\Event`. (Ivan Enderlin, 2015-11-23T22:08:07+01:00) 28 | * Exception: Use `Hoa\Exception`. (Ivan Enderlin, 2015-11-20T07:47:57+01:00) 29 | * Fix API documentation, type-hints & co. (Ivan Enderlin, 2015-11-09T08:09:46+01:00) 30 | 31 | # 0.15.11.09 32 | 33 | * Add a `.gitignore` file. (Stéphane HULARD, 2015-08-03T11:30:34+02:00) 34 | 35 | # 0.15.05.27 36 | 37 | * `getSize` returns `false` if stream is unstatable. (Ivan Enderlin, 2015-05-27T11:05:46+02:00) 38 | * `getStatistic` works on the stream pointer. (Ivan Enderlin, 2015-05-27T11:04:26+02:00) 39 | 40 | # 0.15.05.12 41 | 42 | * Move to PSR-1 and PSR-2. (Ivan Enderlin, 2015-05-12T10:23:01+02:00) 43 | * Remove `from`/`import`. (Ivan Enderlin, 2015-05-12T10:00:37+02:00) 44 | 45 | # 0.15.02.19 46 | 47 | * Add the CHANGELOG.md file. (Ivan Enderlin, 2015-02-19T09:00:34+01:00) 48 | * Happy new year! (Ivan Enderlin, 2015-01-05T14:30:33+01:00) 49 | 50 | # 0.14.12.10 51 | 52 | * Move to PSR-4. (Ivan Enderlin, 2014-12-09T13:48:14+01:00) 53 | 54 | # 0.14.11.26 55 | 56 | * Format the `composer.json` file. (Ivan Enderlin, 2014-11-25T14:08:32+01:00) 57 | * Require `hoa/test`. (Alexis von Glasow, 2014-11-25T13:50:25+01:00) 58 | 59 | # 0.14.11.09 60 | 61 | * Use `hoa/iterator` `~1.0`. (Ivan Enderlin, 2014-11-09T11:00:47+01:00) 62 | 63 | # 0.14.09.23 64 | 65 | * Add `branch-alias`. (Stéphane PY, 2014-09-23T11:50:42+02:00) 66 | 67 | # 0.14.09.17 68 | 69 | * Drop PHP5.3. (Ivan Enderlin, 2014-09-17T17:03:25+02:00) 70 | 71 | (first snapshot) 72 | -------------------------------------------------------------------------------- /Source/Directory.php: -------------------------------------------------------------------------------- 1 | setMode($mode); 79 | parent::__construct($streamName, $context, $wait); 80 | 81 | return; 82 | } 83 | 84 | /** 85 | * Open the stream and return the associated resource. 86 | */ 87 | protected function &_open(string $streamName, Stream\Context $context = null) 88 | { 89 | if (false === is_dir($streamName)) { 90 | if ($this->getMode() == self::MODE_READ) { 91 | throw new Exception\FileDoesNotExist( 92 | 'Directory %s does not exist.', 93 | 0, 94 | $streamName 95 | ); 96 | } else { 97 | self::create( 98 | $streamName, 99 | $this->getMode(), 100 | null !== $context 101 | ? $context->getContext() 102 | : null 103 | ); 104 | } 105 | } 106 | 107 | $out = null; 108 | 109 | return $out; 110 | } 111 | 112 | /** 113 | * Close the current stream. 114 | */ 115 | protected function _close(): bool 116 | { 117 | return true; 118 | } 119 | 120 | /** 121 | * Recursive copy of a directory. 122 | */ 123 | public function copy(string $to, bool $force = Stream\IStream\Touchable::DO_NOT_OVERWRITE): bool 124 | { 125 | if (empty($to)) { 126 | throw new Exception( 127 | 'The destination path (to copy) is empty.', 128 | 1 129 | ); 130 | } 131 | 132 | $from = $this->getStreamName(); 133 | $fromLength = strlen($from) + 1; 134 | $finder = new Finder(); 135 | $finder->in($from); 136 | 137 | self::create($to, self::MODE_CREATE_RECURSIVE); 138 | 139 | foreach ($finder as $file) { 140 | $relative = substr($file->getPathname(), $fromLength); 141 | $_to = $to . DS . $relative; 142 | 143 | if (true === $file->isDir()) { 144 | self::create($_to, self::MODE_CREATE); 145 | 146 | continue; 147 | } 148 | 149 | // This is not possible to do `$file->open()->copy(); 150 | // $file->close();` because the file will be opened in read and 151 | // write mode. In a PHAR for instance, this operation is 152 | // forbidden. So a special care must be taken to open file in read 153 | // only mode. 154 | $handle = null; 155 | 156 | if (true === $file->isFile()) { 157 | $handle = new Read($file->getPathname()); 158 | } elseif (true === $file->isDir()) { 159 | $handle = new self($file->getPathName()); 160 | } elseif (true === $file->isLink()) { 161 | $handle = new Link\Read($file->getPathName()); 162 | } 163 | 164 | if (null !== $handle) { 165 | $handle->copy($_to, $force); 166 | $handle->close(); 167 | } 168 | } 169 | 170 | return true; 171 | } 172 | 173 | /** 174 | * Delete a directory. 175 | */ 176 | public function delete(): bool 177 | { 178 | $from = $this->getStreamName(); 179 | $finder = new Finder(); 180 | $finder->in($from) 181 | ->childFirst(); 182 | 183 | foreach ($finder as $file) { 184 | $file->open()->delete(); 185 | $file->close(); 186 | } 187 | 188 | if (null === $this->getStreamContext()) { 189 | return @rmdir($from); 190 | } 191 | 192 | return @rmdir($from, $this->getStreamContext()->getContext()); 193 | } 194 | 195 | /** 196 | * Create a directory. 197 | */ 198 | public static function create( 199 | string $name, 200 | string $mode = self::MODE_CREATE_RECURSIVE, 201 | string $context = null 202 | ): bool { 203 | if (true === is_dir($name)) { 204 | return true; 205 | } 206 | 207 | if (empty($name)) { 208 | return false; 209 | } 210 | 211 | if (null !== $context) { 212 | if (false === Stream\Context::contextExists($context)) { 213 | throw new Exception( 214 | 'Context %s was not previously declared, cannot retrieve ' . 215 | 'this context.', 216 | 2, 217 | $context 218 | ); 219 | } else { 220 | $context = Stream\Context::getInstance($context); 221 | } 222 | } 223 | 224 | if (null === $context) { 225 | return @mkdir( 226 | $name, 227 | 0755, 228 | self::MODE_CREATE_RECURSIVE === $mode 229 | ); 230 | } 231 | 232 | return @mkdir( 233 | $name, 234 | 0755, 235 | self::MODE_CREATE_RECURSIVE === $mode, 236 | $context->getContext() 237 | ); 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /Source/Exception/Exception.php: -------------------------------------------------------------------------------- 1 | setMode($mode); 119 | 120 | switch ($streamName) { 121 | case '0': 122 | $streamName = 'php://stdin'; 123 | 124 | break; 125 | 126 | case '1': 127 | $streamName = 'php://stdout'; 128 | 129 | break; 130 | 131 | case '2': 132 | $streamName = 'php://stderr'; 133 | 134 | break; 135 | 136 | default: 137 | if (true === ctype_digit($streamName)) { 138 | if (PHP_VERSION_ID >= 50306) { 139 | $streamName = 'php://fd/' . $streamName; 140 | } else { 141 | throw new Exception( 142 | 'You need PHP5.3.6 to use a file descriptor ' . 143 | 'other than 0, 1 or 2 (tried %d with PHP%s).', 144 | 0, 145 | [$streamName, PHP_VERSION] 146 | ); 147 | } 148 | } 149 | } 150 | 151 | parent::__construct($streamName, $context, $wait); 152 | 153 | return; 154 | } 155 | 156 | /** 157 | * Open the stream and return the associated resource. 158 | */ 159 | protected function &_open(string $streamName, Stream\Context $context = null) 160 | { 161 | if (substr($streamName, 0, 4) == 'file' && 162 | false === is_dir(dirname($streamName))) { 163 | throw new Exception( 164 | 'Directory %s does not exist. Could not open file %s.', 165 | 1, 166 | [dirname($streamName), basename($streamName)] 167 | ); 168 | } 169 | 170 | if (null === $context) { 171 | if (false === $out = @fopen($streamName, $this->getMode(), true)) { 172 | throw new Exception( 173 | 'Failed to open stream %s.', 174 | 2, 175 | $streamName 176 | ); 177 | } 178 | 179 | return $out; 180 | } 181 | 182 | $out = @fopen( 183 | $streamName, 184 | $this->getMode(), 185 | true, 186 | $context->getContext() 187 | ); 188 | 189 | if (false === $out) { 190 | throw new Exception( 191 | 'Failed to open stream %s.', 192 | 3, 193 | $streamName 194 | ); 195 | } 196 | 197 | return $out; 198 | } 199 | 200 | /** 201 | * Close the current stream. 202 | */ 203 | protected function _close(): bool 204 | { 205 | return @fclose($this->getStream()); 206 | } 207 | 208 | /** 209 | * Start a new buffer. 210 | * The callable acts like a light filter. 211 | */ 212 | public function newBuffer(callable $callable = null, int $size = null): int 213 | { 214 | $this->setStreamBuffer($size); 215 | 216 | //@TODO manage $callable as a filter? 217 | 218 | return 1; 219 | } 220 | 221 | /** 222 | * Flush the output to a stream. 223 | */ 224 | public function flush(): bool 225 | { 226 | return fflush($this->getStream()); 227 | } 228 | 229 | /** 230 | * Delete buffer. 231 | */ 232 | public function deleteBuffer(): bool 233 | { 234 | return $this->disableStreamBuffer(); 235 | } 236 | 237 | /** 238 | * Get bufffer level. 239 | */ 240 | public function getBufferLevel(): int 241 | { 242 | return 1; 243 | } 244 | 245 | /** 246 | * Get buffer size. 247 | */ 248 | public function getBufferSize(): int 249 | { 250 | return $this->getStreamBufferSize(); 251 | } 252 | 253 | /** 254 | * Portable advisory locking. 255 | */ 256 | public function lock(int $operation): bool 257 | { 258 | return flock($this->getStream(), $operation); 259 | } 260 | 261 | /** 262 | * Rewind the position of a stream pointer. 263 | */ 264 | public function rewind(): bool 265 | { 266 | return rewind($this->getStream()); 267 | } 268 | 269 | /** 270 | * Seek on a stream pointer. 271 | */ 272 | public function seek(int $offset, int $whence = Stream\IStream\Pointable::SEEK_SET): int 273 | { 274 | return fseek($this->getStream(), $offset, $whence); 275 | } 276 | 277 | /** 278 | * Get the current position of the stream pointer. 279 | */ 280 | public function tell(): int 281 | { 282 | $stream = $this->getStream(); 283 | 284 | if (null === $stream) { 285 | return 0; 286 | } 287 | 288 | return ftell($stream); 289 | } 290 | 291 | /** 292 | * Create a file. 293 | */ 294 | public static function create(string $name) 295 | { 296 | if (file_exists($name)) { 297 | return true; 298 | } 299 | 300 | return touch($name); 301 | } 302 | } 303 | 304 | /** 305 | * Flex entity. 306 | */ 307 | Consistency::flexEntity(File::class); 308 | -------------------------------------------------------------------------------- /Source/Finder.php: -------------------------------------------------------------------------------- 1 | _flags = Iterator\FileSystem::KEY_AS_PATHNAME 98 | | Iterator\FileSystem::CURRENT_AS_FILEINFO 99 | | Iterator\FileSystem::SKIP_DOTS; 100 | $this->_first = Iterator\Recursive\Iterator::SELF_FIRST; 101 | 102 | return; 103 | } 104 | 105 | /** 106 | * Select a directory to scan. 107 | */ 108 | public function in($paths): self 109 | { 110 | if (!is_array($paths)) { 111 | $paths = [$paths]; 112 | } 113 | 114 | foreach ($paths as $path) { 115 | if (1 === preg_match('/[\*\?\[\]]/', $path)) { 116 | $iterator = new Iterator\CallbackFilter( 117 | new Iterator\Glob(rtrim($path, DS)), 118 | function ($current) { 119 | return $current->isDir(); 120 | } 121 | ); 122 | 123 | foreach ($iterator as $fileInfo) { 124 | $this->_paths[] = $fileInfo->getPathname(); 125 | } 126 | } else { 127 | $this->_paths[] = $path; 128 | } 129 | } 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * Set max depth for recursion. 136 | */ 137 | public function maxDepth(int $depth): self 138 | { 139 | $this->_maxDepth = $depth; 140 | 141 | return $this; 142 | } 143 | 144 | /** 145 | * Include files in the result. 146 | */ 147 | public function files(): self 148 | { 149 | $this->_types[] = 'file'; 150 | 151 | return $this; 152 | } 153 | 154 | /** 155 | * Include directories in the result. 156 | */ 157 | public function directories(): self 158 | { 159 | $this->_types[] = 'dir'; 160 | 161 | return $this; 162 | } 163 | 164 | /** 165 | * Include links in the result. 166 | */ 167 | public function links(): self 168 | { 169 | $this->_types[] = 'link'; 170 | 171 | return $this; 172 | } 173 | 174 | /** 175 | * Follow symbolink links. 176 | */ 177 | public function followSymlinks(bool $flag = true): self 178 | { 179 | if (true === $flag) { 180 | $this->_flags ^= Iterator\FileSystem::FOLLOW_SYMLINKS; 181 | } else { 182 | $this->_flags |= Iterator\FileSystem::FOLLOW_SYMLINKS; 183 | } 184 | 185 | return $this; 186 | } 187 | 188 | /** 189 | * Include files that match a regex. 190 | * Example: 191 | * $this->name('#\.php$#'); 192 | */ 193 | public function name(string $regex): self 194 | { 195 | $this->_filters[] = function (\SplFileInfo $current) use ($regex) { 196 | return 0 !== preg_match($regex, $current->getBasename()); 197 | }; 198 | 199 | return $this; 200 | } 201 | 202 | /** 203 | * Exclude directories that match a regex. 204 | * Example: 205 | * $this->notIn('#^\.(git|hg)$#'); 206 | */ 207 | public function notIn(string $regex): self 208 | { 209 | $this->_filters[] = function (\SplFileInfo $current) use ($regex) { 210 | foreach (explode(DS, $current->getPathname()) as $part) { 211 | if (0 !== preg_match($regex, $part)) { 212 | return false; 213 | } 214 | } 215 | 216 | return true; 217 | }; 218 | 219 | return $this; 220 | } 221 | 222 | /** 223 | * Include files that respect a certain size. 224 | * The size is a string of the form: 225 | * operator number unit 226 | * where 227 | * • operator could be: <, <=, >, >= or =; 228 | * • number is a positive integer; 229 | * • unit could be: b (default), Kb, Mb, Gb, Tb, Pb, Eb, Zb, Yb. 230 | * Example: 231 | * $this->size('>= 12Kb'); 232 | */ 233 | public function size(string $size): self 234 | { 235 | if (0 === preg_match('#^(<|<=|>|>=|=)\s*(\d+)\s*((?:[KMGTPEZY])b)?$#', $size, $matches)) { 236 | return $this; 237 | } 238 | 239 | $number = floatval($matches[2]); 240 | $unit = $matches[3] ?? 'b'; 241 | $operator = $matches[1]; 242 | 243 | switch ($unit) { 244 | 245 | case 'b': 246 | break; 247 | 248 | // kilo 249 | case 'Kb': 250 | $number <<= 10; 251 | 252 | break; 253 | 254 | // mega. 255 | case 'Mb': 256 | $number <<= 20; 257 | 258 | break; 259 | 260 | // giga. 261 | case 'Gb': 262 | $number <<= 30; 263 | 264 | break; 265 | 266 | // tera. 267 | case 'Tb': 268 | $number *= 1099511627776; 269 | 270 | break; 271 | 272 | // peta. 273 | case 'Pb': 274 | $number *= 1024 ** 5; 275 | 276 | break; 277 | 278 | // exa. 279 | case 'Eb': 280 | $number *= 1024 ** 6; 281 | 282 | break; 283 | 284 | // zetta. 285 | case 'Zb': 286 | $number *= 1024 ** 7; 287 | 288 | break; 289 | 290 | // yota. 291 | case 'Yb': 292 | $number *= 1024 ** 8; 293 | 294 | break; 295 | } 296 | 297 | $filter = null; 298 | 299 | switch ($operator) { 300 | case '<': 301 | $filter = function (\SplFileInfo $current) use ($number) { 302 | return $current->getSize() < $number; 303 | }; 304 | 305 | break; 306 | 307 | case '<=': 308 | $filter = function (\SplFileInfo $current) use ($number) { 309 | return $current->getSize() <= $number; 310 | }; 311 | 312 | break; 313 | 314 | case '>': 315 | $filter = function (\SplFileInfo $current) use ($number) { 316 | return $current->getSize() > $number; 317 | }; 318 | 319 | break; 320 | 321 | case '>=': 322 | $filter = function (\SplFileInfo $current) use ($number) { 323 | return $current->getSize() >= $number; 324 | }; 325 | 326 | break; 327 | 328 | case '=': 329 | $filter = function (\SplFileInfo $current) use ($number) { 330 | return $current->getSize() === $number; 331 | }; 332 | 333 | break; 334 | } 335 | 336 | $this->_filters[] = $filter; 337 | 338 | return $this; 339 | } 340 | 341 | /** 342 | * Whether we should include dots or not (respectively . and ..). 343 | */ 344 | public function dots(bool $flag = true): self 345 | { 346 | if (true === $flag) { 347 | $this->_flags ^= Iterator\FileSystem::SKIP_DOTS; 348 | } else { 349 | $this->_flags |= Iterator\FileSystem::SKIP_DOTS; 350 | } 351 | 352 | return $this; 353 | } 354 | 355 | /** 356 | * Include files that are owned by a certain owner. 357 | */ 358 | public function owner(int $owner): self 359 | { 360 | $this->_filters[] = function (\SplFileInfo $current) use ($owner) { 361 | return $current->getOwner() === $owner; 362 | }; 363 | 364 | return $this; 365 | } 366 | 367 | /** 368 | * Format date. 369 | * Date can have the following syntax: 370 | * date 371 | * since date 372 | * until date 373 | * If the date does not have the “ago” keyword, it will be added. 374 | * Example: “42 hours” is equivalent to “since 42 hours” which is equivalent 375 | * to “since 42 hours ago”. 376 | */ 377 | protected function formatDate(string $date, ?int &$operator): int 378 | { 379 | $operator = -1; 380 | 381 | if (0 === preg_match('#\bago\b#', $date)) { 382 | $date .= ' ago'; 383 | } 384 | 385 | if (0 !== preg_match('#^(since|until)\b(.+)$#', $date, $matches)) { 386 | $time = strtotime($matches[2]); 387 | 388 | if ('until' === $matches[1]) { 389 | $operator = 1; 390 | } 391 | } else { 392 | $time = strtotime($date); 393 | } 394 | 395 | return $time; 396 | } 397 | 398 | /** 399 | * Include files that have been changed from a certain date. 400 | * Example: 401 | * $this->changed('since 13 days'); 402 | */ 403 | public function changed(string $date): self 404 | { 405 | $time = $this->formatDate($date, $operator); 406 | 407 | if (-1 === $operator) { 408 | $this->_filters[] = function (\SplFileInfo $current) use ($time) { 409 | return $current->getCTime() >= $time; 410 | }; 411 | } else { 412 | $this->_filters[] = function (\SplFileInfo $current) use ($time) { 413 | return $current->getCTime() < $time; 414 | }; 415 | } 416 | 417 | return $this; 418 | } 419 | 420 | /** 421 | * Include files that have been modified from a certain date. 422 | * Example: 423 | * $this->modified('since 13 days'); 424 | */ 425 | public function modified(string $date): self 426 | { 427 | $time = $this->formatDate($date, $operator); 428 | 429 | if (-1 === $operator) { 430 | $this->_filters[] = function (\SplFileInfo $current) use ($time) { 431 | return $current->getMTime() >= $time; 432 | }; 433 | } else { 434 | $this->_filters[] = function (\SplFileInfo $current) use ($time) { 435 | return $current->getMTime() < $time; 436 | }; 437 | } 438 | 439 | return $this; 440 | } 441 | 442 | /** 443 | * Add your own filter. 444 | * The callback will receive 3 arguments: $current, $key and $iterator. It 445 | * must return a boolean: true to include the file, false to exclude it. 446 | * Example: 447 | * // Include files that are readable 448 | * $this->filter(function ($current) { 449 | * return $current->isReadable(); 450 | * }); 451 | */ 452 | public function filter(callable $callback): self 453 | { 454 | $this->_filters[] = $callback; 455 | 456 | return $this; 457 | } 458 | 459 | /** 460 | * Sort result by name. 461 | * If \Collator exists (from ext/intl), the $locale argument will be used 462 | * for its constructor. Else, strcmp() will be used. 463 | * Example: 464 | * $this->sortByName('fr_FR'); 465 | */ 466 | public function sortByName(string $locale = 'root'): self 467 | { 468 | if (true === class_exists('Collator', false)) { 469 | $collator = new \Collator($locale); 470 | 471 | $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) use ($collator) { 472 | return $collator->compare($a->getPathname(), $b->getPathname()); 473 | }; 474 | } else { 475 | $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) { 476 | return strcmp($a->getPathname(), $b->getPathname()); 477 | }; 478 | } 479 | 480 | return $this; 481 | } 482 | 483 | /** 484 | * Sort result by size. 485 | * Example: 486 | * $this->sortBySize(); 487 | */ 488 | public function sortBySize(): self 489 | { 490 | $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) { 491 | return $a->getSize() < $b->getSize(); 492 | }; 493 | 494 | return $this; 495 | } 496 | 497 | /** 498 | * Add your own sort. 499 | * The callback will receive 2 arguments: $a and $b. Please see the uasort() 500 | * function. 501 | * Example: 502 | * // Sort files by their modified time. 503 | * $this->sort(function ($a, $b) { 504 | * return $a->getMTime() < $b->getMTime(); 505 | * }); 506 | */ 507 | public function sort(callable $callable): self 508 | { 509 | $this->_sorts[] = $callable; 510 | 511 | return $this; 512 | } 513 | 514 | /** 515 | * Child comes first when iterating. 516 | */ 517 | public function childFirst(): self 518 | { 519 | $this->_first = Iterator\Recursive\Iterator::CHILD_FIRST; 520 | 521 | return $this; 522 | } 523 | 524 | /** 525 | * Get the iterator. 526 | */ 527 | public function getIterator(): iterable 528 | { 529 | $_iterator = new Iterator\Append(); 530 | $types = $this->getTypes(); 531 | 532 | if (!empty($types)) { 533 | $this->_filters[] = function (\SplFileInfo $current) use ($types) { 534 | return in_array($current->getType(), $types); 535 | }; 536 | } 537 | 538 | $maxDepth = $this->getMaxDepth(); 539 | $splFileInfo = $this->getSplFileInfo(); 540 | 541 | foreach ($this->getPaths() as $path) { 542 | if (1 == $maxDepth) { 543 | $iterator = new Iterator\IteratorIterator( 544 | new Iterator\Recursive\Directory( 545 | $path, 546 | $this->getFlags(), 547 | $splFileInfo 548 | ), 549 | $this->getFirst() 550 | ); 551 | } else { 552 | $iterator = new Iterator\Recursive\Iterator( 553 | new Iterator\Recursive\Directory( 554 | $path, 555 | $this->getFlags(), 556 | $splFileInfo 557 | ), 558 | $this->getFirst() 559 | ); 560 | 561 | if (1 < $maxDepth) { 562 | $iterator->setMaxDepth($maxDepth - 1); 563 | } 564 | } 565 | 566 | $_iterator->append($iterator); 567 | } 568 | 569 | foreach ($this->getFilters() as $filter) { 570 | $_iterator = new Iterator\CallbackFilter( 571 | $_iterator, 572 | $filter 573 | ); 574 | } 575 | 576 | $sorts = $this->getSorts(); 577 | 578 | if (empty($sorts)) { 579 | return $_iterator; 580 | } 581 | 582 | $array = iterator_to_array($_iterator); 583 | 584 | foreach ($sorts as $sort) { 585 | uasort($array, $sort); 586 | } 587 | 588 | return new Iterator\Map($array); 589 | } 590 | 591 | /** 592 | * Set SplFileInfo classname. 593 | */ 594 | public function setSplFileInfo(string $splFileInfo): string 595 | { 596 | $old = $this->_splFileInfo; 597 | $this->_splFileInfo = $splFileInfo; 598 | 599 | return $old; 600 | } 601 | 602 | /** 603 | * Get SplFileInfo classname. 604 | */ 605 | public function getSplFileInfo(): string 606 | { 607 | return $this->_splFileInfo; 608 | } 609 | 610 | /** 611 | * Get all paths. 612 | */ 613 | protected function getPaths(): array 614 | { 615 | return $this->_paths; 616 | } 617 | 618 | /** 619 | * Get max depth. 620 | */ 621 | public function getMaxDepth(): int 622 | { 623 | return $this->_maxDepth; 624 | } 625 | 626 | /** 627 | * Get types. 628 | */ 629 | public function getTypes(): array 630 | { 631 | return $this->_types; 632 | } 633 | 634 | /** 635 | * Get filters. 636 | */ 637 | protected function getFilters(): array 638 | { 639 | return $this->_filters; 640 | } 641 | 642 | /** 643 | * Get sorts. 644 | */ 645 | protected function getSorts(): array 646 | { 647 | return $this->_sorts; 648 | } 649 | 650 | /** 651 | * Get flags. 652 | */ 653 | public function getFlags(): int 654 | { 655 | return $this->_flags; 656 | } 657 | 658 | /** 659 | * Get first. 660 | */ 661 | public function getFirst(): int 662 | { 663 | return $this->_first; 664 | } 665 | } 666 | -------------------------------------------------------------------------------- /Source/Generic.php: -------------------------------------------------------------------------------- 1 | getStreamName()); 63 | } 64 | 65 | /** 66 | * Get directory name component of path. 67 | */ 68 | public function getDirname(): string 69 | { 70 | return dirname($this->getStreamName()); 71 | } 72 | 73 | /** 74 | * Get size. 75 | */ 76 | public function getSize(): int 77 | { 78 | if (false === $this->getStatistic()) { 79 | return false; 80 | } 81 | 82 | return filesize($this->getStreamName()); 83 | } 84 | 85 | /** 86 | * Get informations about a file. 87 | */ 88 | public function getStatistic(): array 89 | { 90 | return fstat($this->getStream()); 91 | } 92 | 93 | /** 94 | * Get last access time of file. 95 | */ 96 | public function getATime(): int 97 | { 98 | return fileatime($this->getStreamName()); 99 | } 100 | 101 | /** 102 | * Get inode change time of file. 103 | */ 104 | public function getCTime(): int 105 | { 106 | return filectime($this->getStreamName()); 107 | } 108 | 109 | /** 110 | * Get file modification time. 111 | */ 112 | public function getMTime(): int 113 | { 114 | return filemtime($this->getStreamName()); 115 | } 116 | 117 | /** 118 | * Get file group. 119 | */ 120 | public function getGroup(): int 121 | { 122 | return filegroup($this->getStreamName()); 123 | } 124 | 125 | /** 126 | * Get file owner. 127 | */ 128 | public function getOwner(): int 129 | { 130 | return fileowner($this->getStreamName()); 131 | } 132 | 133 | /** 134 | * Get file permissions. 135 | */ 136 | public function getPermissions(): int 137 | { 138 | return fileperms($this->getStreamName()); 139 | } 140 | 141 | /** 142 | * Get file permissions as a string. 143 | * Result sould be interpreted like this: 144 | * * s: socket; 145 | * * l: symbolic link; 146 | * * -: regular; 147 | * * b: block special; 148 | * * d: directory; 149 | * * c: character special; 150 | * * p: FIFO pipe; 151 | * * u: unknown. 152 | */ 153 | public function getReadablePermissions(): string 154 | { 155 | $p = $this->getPermissions(); 156 | 157 | if (($p & 0xC000) == 0xC000) { 158 | $out = 's'; 159 | } elseif (($p & 0xA000) == 0xA000) { 160 | $out = 'l'; 161 | } elseif (($p & 0x8000) == 0x8000) { 162 | $out = '-'; 163 | } elseif (($p & 0x6000) == 0x6000) { 164 | $out = 'b'; 165 | } elseif (($p & 0x4000) == 0x4000) { 166 | $out = 'd'; 167 | } elseif (($p & 0x2000) == 0x2000) { 168 | $out = 'c'; 169 | } elseif (($p & 0x1000) == 0x1000) { 170 | $out = 'p'; 171 | } else { 172 | $out = 'u'; 173 | } 174 | 175 | $out .= 176 | (($p & 0x0100) ? 'r' : '-') . 177 | (($p & 0x0080) ? 'w' : '-') . 178 | (($p & 0x0040) ? 179 | (($p & 0x0800) ? 's' : 'x') : 180 | (($p & 0x0800) ? 'S' : '-')) . 181 | (($p & 0x0020) ? 'r' : '-') . 182 | (($p & 0x0010) ? 'w' : '-') . 183 | (($p & 0x0008) ? 184 | (($p & 0x0400) ? 's' : 'x') : 185 | (($p & 0x0400) ? 'S' : '-')) . 186 | (($p & 0x0004) ? 'r' : '-') . 187 | (($p & 0x0002) ? 'w' : '-') . 188 | (($p & 0x0001) ? 189 | (($p & 0x0200) ? 't' : 'x') : 190 | (($p & 0x0200) ? 'T' : '-')); 191 | 192 | return $out; 193 | } 194 | 195 | /** 196 | * Check if the file is readable. 197 | */ 198 | public function isReadable(): bool 199 | { 200 | return is_readable($this->getStreamName()); 201 | } 202 | 203 | /** 204 | * Check if the file is writable. 205 | */ 206 | public function isWritable(): bool 207 | { 208 | return is_writable($this->getStreamName()); 209 | } 210 | 211 | /** 212 | * Check if the file is executable. 213 | */ 214 | public function isExecutable(): bool 215 | { 216 | return is_executable($this->getStreamName()); 217 | } 218 | 219 | /** 220 | * Clear file status cache. 221 | */ 222 | public function clearStatisticCache(): void 223 | { 224 | clearstatcache(true, $this->getStreamName()); 225 | } 226 | 227 | /** 228 | * Clear all files status cache. 229 | */ 230 | public static function clearAllStatisticCaches(): void 231 | { 232 | clearstatcache(); 233 | } 234 | 235 | /** 236 | * Set access and modification time of file. 237 | */ 238 | public function touch(?int $time = null, ?int $atime = null): bool 239 | { 240 | if (null === $time) { 241 | $time = time(); 242 | } 243 | 244 | if (null === $atime) { 245 | $atime = $time; 246 | } 247 | 248 | return touch($this->getStreamName(), $time, $atime); 249 | } 250 | 251 | /** 252 | * Copy file. 253 | * Return the destination file path if succeed, false otherwise. 254 | */ 255 | public function copy(string $to, bool $force = Stream\IStream\Touchable::DO_NOT_OVERWRITE): bool 256 | { 257 | $from = $this->getStreamName(); 258 | 259 | if ($force === Stream\IStream\Touchable::DO_NOT_OVERWRITE && 260 | true === file_exists($to)) { 261 | return true; 262 | } 263 | 264 | if (null === $this->getStreamContext()) { 265 | return @copy($from, $to); 266 | } 267 | 268 | return @copy($from, $to, $this->getStreamContext()->getContext()); 269 | } 270 | 271 | /** 272 | * Move a file. 273 | */ 274 | public function move( 275 | string $name, 276 | bool $force = Stream\IStream\Touchable::DO_NOT_OVERWRITE, 277 | bool $mkdir = Stream\IStream\Touchable::DO_NOT_MAKE_DIRECTORY 278 | ): bool { 279 | $from = $this->getStreamName(); 280 | 281 | if ($force === Stream\IStream\Touchable::DO_NOT_OVERWRITE && 282 | true === file_exists($name)) { 283 | return false; 284 | } 285 | 286 | if (Stream\IStream\Touchable::MAKE_DIRECTORY === $mkdir) { 287 | Directory::create( 288 | dirname($name), 289 | Directory::MODE_CREATE_RECURSIVE 290 | ); 291 | } 292 | 293 | if (null === $this->getStreamContext()) { 294 | return @rename($from, $name); 295 | } 296 | 297 | return @rename($from, $name, $this->getStreamContext()->getContext()); 298 | } 299 | 300 | /** 301 | * Delete a file. 302 | */ 303 | public function delete(): bool 304 | { 305 | if (null === $this->getStreamContext()) { 306 | return @unlink($this->getStreamName()); 307 | } 308 | 309 | return @unlink( 310 | $this->getStreamName(), 311 | $this->getStreamContext()->getContext() 312 | ); 313 | } 314 | 315 | /** 316 | * Change file group. 317 | */ 318 | public function changeGroup($group): bool 319 | { 320 | return chgrp($this->getStreamName(), $group); 321 | } 322 | 323 | /** 324 | * Change file mode. 325 | */ 326 | public function changeMode(int $mode): bool 327 | { 328 | return chmod($this->getStreamName(), $mode); 329 | } 330 | 331 | /** 332 | * Change file owner. 333 | */ 334 | public function changeOwner($user): bool 335 | { 336 | return chown($this->getStreamName(), $user); 337 | } 338 | 339 | /** 340 | * Change the current umask. 341 | */ 342 | public static function umask(?int $umask = null): int 343 | { 344 | if (null === $umask) { 345 | return umask(); 346 | } 347 | 348 | return umask($umask); 349 | } 350 | 351 | /** 352 | * Check if it is a file. 353 | */ 354 | public function isFile(): bool 355 | { 356 | return is_file($this->getStreamName()); 357 | } 358 | 359 | /** 360 | * Check if it is a link. 361 | */ 362 | public function isLink(): bool 363 | { 364 | return is_link($this->getStreamName()); 365 | } 366 | 367 | /** 368 | * Check if it is a directory. 369 | */ 370 | public function isDirectory(): bool 371 | { 372 | return is_dir($this->getStreamName()); 373 | } 374 | 375 | /** 376 | * Check if it is a socket. 377 | */ 378 | public function isSocket(): bool 379 | { 380 | return filetype($this->getStreamName()) == 'socket'; 381 | } 382 | 383 | /** 384 | * Check if it is a FIFO pipe. 385 | */ 386 | public function isFIFOPipe(): bool 387 | { 388 | return filetype($this->getStreamName()) == 'fifo'; 389 | } 390 | 391 | /** 392 | * Check if it is character special file. 393 | */ 394 | public function isCharacterSpecial(): bool 395 | { 396 | return filetype($this->getStreamName()) == 'char'; 397 | } 398 | 399 | /** 400 | * Check if it is block special. 401 | */ 402 | public function isBlockSpecial(): bool 403 | { 404 | return filetype($this->getStreamName()) == 'block'; 405 | } 406 | 407 | /** 408 | * Check if it is an unknown type. 409 | */ 410 | public function isUnknown(): bool 411 | { 412 | return filetype($this->getStreamName()) == 'unknown'; 413 | } 414 | 415 | /** 416 | * Set the open mode. 417 | */ 418 | protected function setMode(string $mode): ?string 419 | { 420 | $old = $this->_mode; 421 | $this->_mode = $mode; 422 | 423 | return $old; 424 | } 425 | 426 | /** 427 | * Get the open mode. 428 | */ 429 | public function getMode(): ?string 430 | { 431 | return $this->_mode; 432 | } 433 | 434 | /** 435 | * Get inode. 436 | */ 437 | public function getINode(): int 438 | { 439 | return fileinode($this->getStreamName()); 440 | } 441 | 442 | /** 443 | * Check if the system is case sensitive or not. 444 | */ 445 | public static function isCaseSensitive(): bool 446 | { 447 | return !( 448 | file_exists(mb_strtolower(__FILE__)) && 449 | file_exists(mb_strtoupper(__FILE__)) 450 | ); 451 | } 452 | 453 | /** 454 | * Get a canonicalized absolute pathname. 455 | */ 456 | public function getRealPath(): string 457 | { 458 | if (false === $out = realpath($this->getStreamName())) { 459 | return $this->getStreamName(); 460 | } 461 | 462 | return $out; 463 | } 464 | 465 | /** 466 | * Get file extension (if exists). 467 | */ 468 | public function getExtension(): string 469 | { 470 | return pathinfo( 471 | $this->getStreamName(), 472 | PATHINFO_EXTENSION 473 | ); 474 | } 475 | 476 | /** 477 | * Get filename without extension. 478 | */ 479 | public function getFilename(): string 480 | { 481 | $file = basename($this->getStreamName()); 482 | 483 | if (defined('PATHINFO_FILENAME')) { 484 | return pathinfo($file, PATHINFO_FILENAME); 485 | } 486 | 487 | if (strstr($file, '.')) { 488 | return substr($file, 0, strrpos($file, '.')); 489 | } 490 | 491 | return $file; 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /Source/Link/Link.php: -------------------------------------------------------------------------------- 1 | getStreamName()); 79 | } 80 | 81 | /** 82 | * Change file group. 83 | */ 84 | public function changeGroup($group): bool 85 | { 86 | return lchgrp($this->getStreamName(), $group); 87 | } 88 | 89 | /** 90 | * Change file owner. 91 | */ 92 | public function changeOwner($user): bool 93 | { 94 | return lchown($this->getStreamName(), $user); 95 | } 96 | 97 | /** 98 | * Get file permissions. 99 | */ 100 | public function getPermissions(): int 101 | { 102 | return 41453; // i.e. lrwxr-xr-x 103 | } 104 | 105 | /** 106 | * Get the target of a symbolic link. 107 | */ 108 | public function getTarget(): File\Generic 109 | { 110 | $target = dirname($this->getStreamName()) . DS . 111 | $this->getTargetName(); 112 | $context = null !== $this->getStreamContext() 113 | ? $this->getStreamContext()->getCurrentId() 114 | : null; 115 | 116 | if (true === is_link($target)) { 117 | return new ReadWrite( 118 | $target, 119 | File::MODE_APPEND_READ_WRITE, 120 | $context 121 | ); 122 | } elseif (true === is_file($target)) { 123 | return new File\ReadWrite( 124 | $target, 125 | File::MODE_APPEND_READ_WRITE, 126 | $context 127 | ); 128 | } elseif (true === is_dir($target)) { 129 | return new File\Directory( 130 | $target, 131 | File::MODE_READ, 132 | $context 133 | ); 134 | } 135 | 136 | throw new File\Exception( 137 | 'Cannot find an appropriated object that matches with ' . 138 | 'path %s when defining it.', 139 | 1, 140 | $target 141 | ); 142 | } 143 | 144 | /** 145 | * Get the target name of a symbolic link. 146 | */ 147 | public function getTargetName(): string 148 | { 149 | return readlink($this->getStreamName()); 150 | } 151 | 152 | /** 153 | * Create a link. 154 | */ 155 | public static function create(string $name, string $target): bool 156 | { 157 | if (false != linkinfo($name)) { 158 | return true; 159 | } 160 | 161 | return symlink($target, $name); 162 | } 163 | } 164 | 165 | /** 166 | * Flex entity. 167 | */ 168 | Consistency::flexEntity(Link::class); 169 | -------------------------------------------------------------------------------- /Source/Link/Read.php: -------------------------------------------------------------------------------- 1 | getMode(), $createModes)) { 89 | throw new File\Exception( 90 | 'Open mode are not supported; given %d. Only %s are supported.', 91 | 0, 92 | [$this->getMode(), implode(', ', $createModes)] 93 | ); 94 | } 95 | 96 | preg_match('#^(\w+)://#', $streamName, $match); 97 | 98 | if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && 99 | !file_exists($streamName)) { 100 | throw new File\Exception\FileDoesNotExist( 101 | 'File %s does not exist.', 102 | 1, 103 | $streamName 104 | ); 105 | } 106 | 107 | $out = parent::_open($streamName, $context); 108 | 109 | return $out; 110 | } 111 | 112 | /** 113 | * Test for end-of-file. 114 | * 115 | * @return bool 116 | */ 117 | public function eof(): bool 118 | { 119 | return feof($this->getStream()); 120 | } 121 | 122 | /** 123 | * Read n characters. 124 | * 125 | * @param int $length Length. 126 | * @return string 127 | * @throws \Hoa\File\Exception 128 | */ 129 | public function read(int $length) 130 | { 131 | if (0 > $length) { 132 | throw new File\Exception( 133 | 'Length must be greater than 0, given %d.', 134 | 2, 135 | $length 136 | ); 137 | } 138 | 139 | return fread($this->getStream(), $length); 140 | } 141 | 142 | /** 143 | * Alias of $this->read(). 144 | * 145 | * @param int $length Length. 146 | * @return string 147 | */ 148 | public function readString(int $length) 149 | { 150 | return $this->read($length); 151 | } 152 | 153 | /** 154 | * Read a character. 155 | * 156 | * @return string 157 | */ 158 | public function readCharacter() 159 | { 160 | return fgetc($this->getStream()); 161 | } 162 | 163 | /** 164 | * Read a boolean. 165 | * 166 | * @return bool 167 | */ 168 | public function readBoolean() 169 | { 170 | return (bool) $this->read(1); 171 | } 172 | 173 | /** 174 | * Read an integer. 175 | * 176 | * @param int $length Length. 177 | * @return int 178 | */ 179 | public function readInteger(int $length = 1) 180 | { 181 | return (int) $this->read($length); 182 | } 183 | 184 | /** 185 | * Read a float. 186 | * 187 | * @param int $length Length. 188 | * @return float 189 | */ 190 | public function readFloat(int $length = 1) 191 | { 192 | return (float) $this->read($length); 193 | } 194 | 195 | /** 196 | * Read an array. 197 | * Alias of the $this->scanf() method. 198 | * 199 | * @param string $format Format (see printf's formats). 200 | * @return array 201 | */ 202 | public function readArray(string $format = null) 203 | { 204 | return $this->scanf($format); 205 | } 206 | 207 | /** 208 | * Read a line. 209 | * 210 | * @return string 211 | */ 212 | public function readLine() 213 | { 214 | return fgets($this->getStream()); 215 | } 216 | 217 | /** 218 | * Read all, i.e. read as much as possible. 219 | * 220 | * @param int $offset Offset. 221 | * @return string 222 | */ 223 | public function readAll(int $offset = 0) 224 | { 225 | return stream_get_contents($this->getStream(), -1, $offset); 226 | } 227 | 228 | /** 229 | * Parse input from a stream according to a format. 230 | * 231 | * @param string $format Format (see printf's formats). 232 | * @return array 233 | */ 234 | public function scanf(string $format): array 235 | { 236 | return fscanf($this->getStream(), $format); 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /Source/Link/ReadWrite.php: -------------------------------------------------------------------------------- 1 | getMode(), $createModes)) { 78 | throw new File\Exception( 79 | 'Open mode are not supported; given %d. Only %s are supported.', 80 | 0, 81 | [$this->getMode(), implode(', ', $createModes)] 82 | ); 83 | } 84 | 85 | preg_match('#^(\w+)://#', $streamName, $match); 86 | 87 | if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && 88 | !file_exists($streamName) && 89 | parent::MODE_READ_WRITE == $this->getMode()) { 90 | throw new File\Exception\FileDoesNotExist( 91 | 'File %s does not exist.', 92 | 1, 93 | $streamName 94 | ); 95 | } 96 | 97 | $out = parent::_open($streamName, $context); 98 | 99 | return $out; 100 | } 101 | 102 | /** 103 | * Test for end-of-file. 104 | */ 105 | public function eof(): bool 106 | { 107 | return feof($this->getStream()); 108 | } 109 | 110 | /** 111 | * Read n characters. 112 | */ 113 | public function read(int $length) 114 | { 115 | if (0 > $length) { 116 | throw new File\Exception( 117 | 'Length must be greater than 0, given %d.', 118 | 2, 119 | $length 120 | ); 121 | } 122 | 123 | return fread($this->getStream(), $length); 124 | } 125 | 126 | /** 127 | * Alias of $this->read(). 128 | */ 129 | public function readString(int $length) 130 | { 131 | return $this->read($length); 132 | } 133 | 134 | /** 135 | * Read a character. 136 | */ 137 | public function readCharacter() 138 | { 139 | return fgetc($this->getStream()); 140 | } 141 | 142 | /** 143 | * Read a boolean. 144 | */ 145 | public function readBoolean() 146 | { 147 | return (bool) $this->read(1); 148 | } 149 | 150 | /** 151 | * Read an integer. 152 | */ 153 | public function readInteger(int $length = 1) 154 | { 155 | return (int) $this->read($length); 156 | } 157 | 158 | /** 159 | * Read a float. 160 | */ 161 | public function readFloat(int $length = 1) 162 | { 163 | return (float) $this->read($length); 164 | } 165 | 166 | /** 167 | * Read an array. 168 | * Alias of the $this->scanf() method. 169 | */ 170 | public function readArray(string $format = null) 171 | { 172 | return $this->scanf($format); 173 | } 174 | 175 | /** 176 | * Read a line. 177 | */ 178 | public function readLine() 179 | { 180 | return fgets($this->getStream()); 181 | } 182 | 183 | /** 184 | * Read all, i.e. read as much as possible. 185 | */ 186 | public function readAll(int $offset = 0) 187 | { 188 | return stream_get_contents($this->getStream(), -1, $offset); 189 | } 190 | 191 | /** 192 | * Parse input from a stream according to a format. 193 | */ 194 | public function scanf(string $format): array 195 | { 196 | return fscanf($this->getStream(), $format); 197 | } 198 | 199 | /** 200 | * Write n characters. 201 | */ 202 | public function write(string $string, int $length) 203 | { 204 | if (0 > $length) { 205 | throw new File\Exception( 206 | 'Length must be greater than 0, given %d.', 207 | 3, 208 | $length 209 | ); 210 | } 211 | 212 | return fwrite($this->getStream(), $string, $length); 213 | } 214 | 215 | /** 216 | * Write a string. 217 | */ 218 | public function writeString(string $string) 219 | { 220 | $string = (string) $string; 221 | 222 | return $this->write($string, strlen($string)); 223 | } 224 | 225 | /** 226 | * Write a character. 227 | */ 228 | public function writeCharacter(string $char) 229 | { 230 | return $this->write((string) $char[0], 1); 231 | } 232 | 233 | /** 234 | * Write a boolean. 235 | */ 236 | public function writeBoolean(bool $boolean) 237 | { 238 | return $this->write((string) (bool) $boolean, 1); 239 | } 240 | 241 | /** 242 | * Write an integer. 243 | */ 244 | public function writeInteger(int $integer) 245 | { 246 | $integer = (string) (int) $integer; 247 | 248 | return $this->write($integer, strlen($integer)); 249 | } 250 | 251 | /** 252 | * Write a float. 253 | */ 254 | public function writeFloat(float $float) 255 | { 256 | $float = (string) (float) $float; 257 | 258 | return $this->write($float, strlen($float)); 259 | } 260 | 261 | /** 262 | * Write an array. 263 | */ 264 | public function writeArray(array $array) 265 | { 266 | $array = var_export($array, true); 267 | 268 | return $this->write($array, strlen($array)); 269 | } 270 | 271 | /** 272 | * Write a line. 273 | */ 274 | public function writeLine(string $line) 275 | { 276 | if (false === $n = strpos($line, "\n")) { 277 | return $this->write($line . "\n", strlen($line) + 1); 278 | } 279 | 280 | ++$n; 281 | 282 | return $this->write(substr($line, 0, $n), $n); 283 | } 284 | 285 | /** 286 | * Write all, i.e. as much as possible. 287 | */ 288 | public function writeAll(string $string) 289 | { 290 | return $this->write($string, strlen($string)); 291 | } 292 | 293 | /** 294 | * Truncate a file to a given length. 295 | */ 296 | public function truncate(int $size): bool 297 | { 298 | return ftruncate($this->getStream(), $size); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /Source/Link/Write.php: -------------------------------------------------------------------------------- 1 | getMode(), $createModes)) { 79 | throw new File\Exception( 80 | 'Open mode are not supported; given %d. Only %s are supported.', 81 | 0, 82 | [$this->getMode(), implode(', ', $createModes)] 83 | ); 84 | } 85 | 86 | preg_match('#^(\w+)://#', $streamName, $match); 87 | 88 | if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && 89 | !file_exists($streamName)) { 90 | throw new File\Exception\FileDoesNotExist( 91 | 'File %s does not exist.', 92 | 1, 93 | $streamName 94 | ); 95 | } 96 | 97 | $out = parent::_open($streamName, $context); 98 | 99 | return $out; 100 | } 101 | 102 | /** 103 | * Write n characters. 104 | */ 105 | public function write(string $string, int $length) 106 | { 107 | if (0 > $length) { 108 | throw new File\Exception( 109 | 'Length must be greater than 0, given %d.', 110 | 2, 111 | $length 112 | ); 113 | } 114 | 115 | return fwrite($this->getStream(), $string, $length); 116 | } 117 | 118 | /** 119 | * Write a string. 120 | */ 121 | public function writeString(string $string) 122 | { 123 | $string = (string) $string; 124 | 125 | return $this->write($string, strlen($string)); 126 | } 127 | 128 | /** 129 | * Write a character. 130 | */ 131 | public function writeCharacter(string $char) 132 | { 133 | return $this->write((string) $char[0], 1); 134 | } 135 | 136 | /** 137 | * Write a boolean. 138 | */ 139 | public function writeBoolean(bool $boolean) 140 | { 141 | return $this->write((string) (bool) $boolean, 1); 142 | } 143 | 144 | /** 145 | * Write an integer. 146 | */ 147 | public function writeInteger(int $integer) 148 | { 149 | $integer = (string) (int) $integer; 150 | 151 | return $this->write($integer, strlen($integer)); 152 | } 153 | 154 | /** 155 | * Write a float. 156 | */ 157 | public function writeFloat(float $float) 158 | { 159 | $float = (string) (float) $float; 160 | 161 | return $this->write($float, strlen($float)); 162 | } 163 | 164 | /** 165 | * Write an array. 166 | */ 167 | public function writeArray(array $array) 168 | { 169 | $array = var_export($array, true); 170 | 171 | return $this->write($array, strlen($array)); 172 | } 173 | 174 | /** 175 | * Write a line. 176 | */ 177 | public function writeLine(string $line) 178 | { 179 | if (false === $n = strpos($line, "\n")) { 180 | return $this->write($line . "\n", strlen($line) + 1); 181 | } 182 | 183 | ++$n; 184 | 185 | return $this->write(substr($line, 0, $n), $n); 186 | } 187 | 188 | /** 189 | * Write all, i.e. as much as possible. 190 | */ 191 | public function writeAll(string $string) 192 | { 193 | return $this->write($string, strlen($string)); 194 | } 195 | 196 | /** 197 | * Truncate a file to a given length. 198 | */ 199 | public function truncate(int $size): bool 200 | { 201 | return ftruncate($this->getStream(), $size); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /Source/Read.php: -------------------------------------------------------------------------------- 1 | getMode(), $createModes)) { 74 | throw new Exception( 75 | 'Open mode are not supported; given %d. Only %s are supported.', 76 | 0, 77 | [$this->getMode(), implode(', ', $createModes)] 78 | ); 79 | } 80 | 81 | preg_match('#^(\w+)://#', $streamName, $match); 82 | 83 | if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && 84 | !file_exists($streamName)) { 85 | throw new Exception\FileDoesNotExist( 86 | 'File %s does not exist.', 87 | 1, 88 | $streamName 89 | ); 90 | } 91 | 92 | $out = parent::_open($streamName, $context); 93 | 94 | return $out; 95 | } 96 | 97 | /** 98 | * Test for end-of-file. 99 | */ 100 | public function eof(): bool 101 | { 102 | return feof($this->getStream()); 103 | } 104 | 105 | /** 106 | * Read n characters. 107 | */ 108 | public function read(int $length) 109 | { 110 | if (0 > $length) { 111 | throw new Exception( 112 | 'Length must be greater than 0, given %d.', 113 | 2, 114 | $length 115 | ); 116 | } 117 | 118 | return fread($this->getStream(), $length); 119 | } 120 | 121 | /** 122 | * Alias of $this->read(). 123 | */ 124 | public function readString(int $length) 125 | { 126 | return $this->read($length); 127 | } 128 | 129 | /** 130 | * Read a character. 131 | */ 132 | public function readCharacter() 133 | { 134 | return fgetc($this->getStream()); 135 | } 136 | 137 | /** 138 | * Read a boolean. 139 | */ 140 | public function readBoolean() 141 | { 142 | return (bool) $this->read(1); 143 | } 144 | 145 | /** 146 | * Read an integer. 147 | */ 148 | public function readInteger(int $length = 1) 149 | { 150 | return (int) $this->read($length); 151 | } 152 | 153 | /** 154 | * Read a float. 155 | */ 156 | public function readFloat(int $length = 1) 157 | { 158 | return (float) $this->read($length); 159 | } 160 | 161 | /** 162 | * Read an array. 163 | * Alias of the $this->scanf() method. 164 | */ 165 | public function readArray(string $format = null) 166 | { 167 | return $this->scanf($format); 168 | } 169 | 170 | /** 171 | * Read a line. 172 | */ 173 | public function readLine() 174 | { 175 | return fgets($this->getStream()); 176 | } 177 | 178 | /** 179 | * Read all, i.e. read as much as possible. 180 | */ 181 | public function readAll(int $offset = 0) 182 | { 183 | return stream_get_contents($this->getStream(), -1, $offset); 184 | } 185 | 186 | /** 187 | * Parse input from a stream according to a format. 188 | */ 189 | public function scanf(string $format): array 190 | { 191 | return fscanf($this->getStream(), $format); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /Source/ReadWrite.php: -------------------------------------------------------------------------------- 1 | getMode(), $createModes)) { 77 | throw new Exception( 78 | 'Open mode are not supported; given %d. Only %s are supported.', 79 | 0, 80 | [$this->getMode(), implode(', ', $createModes)] 81 | ); 82 | } 83 | 84 | preg_match('#^(\w+)://#', $streamName, $match); 85 | 86 | if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && 87 | !file_exists($streamName) && 88 | parent::MODE_READ_WRITE == $this->getMode()) { 89 | throw new Exception\FileDoesNotExist( 90 | 'File %s does not exist.', 91 | 1, 92 | $streamName 93 | ); 94 | } 95 | 96 | $out = parent::_open($streamName, $context); 97 | 98 | return $out; 99 | } 100 | 101 | /** 102 | * Test for end-of-file. 103 | */ 104 | public function eof(): bool 105 | { 106 | return feof($this->getStream()); 107 | } 108 | 109 | /** 110 | * Read n characters. 111 | */ 112 | public function read(int $length) 113 | { 114 | if (0 > $length) { 115 | throw new Exception( 116 | 'Length must be greater than 0, given %d.', 117 | 2, 118 | $length 119 | ); 120 | } 121 | 122 | return fread($this->getStream(), $length); 123 | } 124 | 125 | /** 126 | * Alias of $this->read(). 127 | */ 128 | public function readString(int $length) 129 | { 130 | return $this->read($length); 131 | } 132 | 133 | /** 134 | * Read a character. 135 | */ 136 | public function readCharacter() 137 | { 138 | return fgetc($this->getStream()); 139 | } 140 | 141 | /** 142 | * Read a boolean. 143 | */ 144 | public function readBoolean() 145 | { 146 | return (bool) $this->read(1); 147 | } 148 | 149 | /** 150 | * Read an integer. 151 | */ 152 | public function readInteger(int $length = 1) 153 | { 154 | return (int) $this->read($length); 155 | } 156 | 157 | /** 158 | * Read a float. 159 | */ 160 | public function readFloat(int $length = 1) 161 | { 162 | return (float) $this->read($length); 163 | } 164 | 165 | /** 166 | * Read an array. 167 | * Alias of the $this->scanf() method. 168 | */ 169 | public function readArray(string $format = null) 170 | { 171 | return $this->scanf($format); 172 | } 173 | 174 | /** 175 | * Read a line. 176 | */ 177 | public function readLine() 178 | { 179 | return fgets($this->getStream()); 180 | } 181 | 182 | /** 183 | * Read all, i.e. read as much as possible. 184 | */ 185 | public function readAll(int $offset = 0) 186 | { 187 | return stream_get_contents($this->getStream(), -1, $offset); 188 | } 189 | 190 | /** 191 | * Parse input from a stream according to a format. 192 | */ 193 | public function scanf(string $format): array 194 | { 195 | return fscanf($this->getStream(), $format); 196 | } 197 | 198 | /** 199 | * Write n characters. 200 | */ 201 | public function write(string $string, int $length) 202 | { 203 | if (0 > $length) { 204 | throw new Exception( 205 | 'Length must be greater than 0, given %d.', 206 | 3, 207 | $length 208 | ); 209 | } 210 | 211 | return fwrite($this->getStream(), $string, $length); 212 | } 213 | 214 | /** 215 | * Write a string. 216 | */ 217 | public function writeString(string $string) 218 | { 219 | $string = (string) $string; 220 | 221 | return $this->write($string, strlen($string)); 222 | } 223 | 224 | /** 225 | * Write a character. 226 | */ 227 | public function writeCharacter(string $char) 228 | { 229 | return $this->write((string) $char[0], 1); 230 | } 231 | 232 | /** 233 | * Write a boolean. 234 | */ 235 | public function writeBoolean(bool $boolean) 236 | { 237 | return $this->write((string) (bool) $boolean, 1); 238 | } 239 | 240 | /** 241 | * Write an integer. 242 | */ 243 | public function writeInteger(int $integer) 244 | { 245 | $integer = (string) (int) $integer; 246 | 247 | return $this->write($integer, strlen($integer)); 248 | } 249 | 250 | /** 251 | * Write a float. 252 | */ 253 | public function writeFloat(float $float) 254 | { 255 | $float = (string) (float) $float; 256 | 257 | return $this->write($float, strlen($float)); 258 | } 259 | 260 | /** 261 | * Write an array. 262 | */ 263 | public function writeArray(array $array) 264 | { 265 | $array = var_export($array, true); 266 | 267 | return $this->write($array, strlen($array)); 268 | } 269 | 270 | /** 271 | * Write a line. 272 | */ 273 | public function writeLine(string $line) 274 | { 275 | if (false === $n = strpos($line, "\n")) { 276 | return $this->write($line . "\n", strlen($line) + 1); 277 | } 278 | 279 | ++$n; 280 | 281 | return $this->write(substr($line, 0, $n), $n); 282 | } 283 | 284 | /** 285 | * Write all, i.e. as much as possible. 286 | */ 287 | public function writeAll(string $string) 288 | { 289 | return $this->write($string, strlen($string)); 290 | } 291 | 292 | /** 293 | * Truncate a file to a given length. 294 | */ 295 | public function truncate(int $size): bool 296 | { 297 | return ftruncate($this->getStream(), $size); 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /Source/SplFileInfo.php: -------------------------------------------------------------------------------- 1 | isFile()) { 63 | return $this->_stream = new ReadWrite($this->getPathname()); 64 | } elseif (true === $this->isDir()) { 65 | return $this->_stream = new Directory($this->getPathname()); 66 | } elseif (true === $this->isLink()) { 67 | return $this->_stream = new Link\ReadWrite($this->getPathname()); 68 | } 69 | 70 | throw new Exception('%s has an unknown type.', 0, $this->getPathname()); 71 | } 72 | 73 | /** 74 | * Close the opened stream. 75 | */ 76 | public function close() 77 | { 78 | if (null === $this->_stream) { 79 | return; 80 | } 81 | 82 | return $this->_stream->close(); 83 | } 84 | 85 | /** 86 | * Destruct. 87 | */ 88 | public function __destruct() 89 | { 90 | $this->close(); 91 | 92 | return; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Source/Temporary/Read.php: -------------------------------------------------------------------------------- 1 | getMode(), $createModes)) { 75 | throw new File\Exception( 76 | 'Open mode are not supported; given %d. Only %s are supported.', 77 | 0, 78 | [$this->getMode(), implode(', ', $createModes)] 79 | ); 80 | } 81 | 82 | preg_match('#^(\w+)://#', $streamName, $match); 83 | 84 | if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && 85 | !file_exists($streamName)) { 86 | throw new File\Exception\FileDoesNotExist( 87 | 'File %s does not exist.', 88 | 1, 89 | $streamName 90 | ); 91 | } 92 | 93 | $out = parent::_open($streamName, $context); 94 | 95 | return $out; 96 | } 97 | 98 | /** 99 | * Test for end-of-file. 100 | */ 101 | public function eof(): bool 102 | { 103 | return feof($this->getStream()); 104 | } 105 | 106 | /** 107 | * Read n characters. 108 | */ 109 | public function read(int $length) 110 | { 111 | if (0 > $length) { 112 | throw new File\Exception( 113 | 'Length must be greater than 0, given %d.', 114 | 2, 115 | $length 116 | ); 117 | } 118 | 119 | return fread($this->getStream(), $length); 120 | } 121 | 122 | /** 123 | * Alias of $this->read(). 124 | */ 125 | public function readString(int $length) 126 | { 127 | return $this->read($length); 128 | } 129 | 130 | /** 131 | * Read a character. 132 | */ 133 | public function readCharacter() 134 | { 135 | return fgetc($this->getStream()); 136 | } 137 | 138 | /** 139 | * Read a boolean. 140 | */ 141 | public function readBoolean() 142 | { 143 | return (bool) $this->read(1); 144 | } 145 | 146 | /** 147 | * Read an integer. 148 | */ 149 | public function readInteger(int $length = 1) 150 | { 151 | return (int) $this->read($length); 152 | } 153 | 154 | /** 155 | * Read a float. 156 | */ 157 | public function readFloat(int $length = 1) 158 | { 159 | return (float) $this->read($length); 160 | } 161 | 162 | /** 163 | * Read an array. 164 | * Alias of the $this->scanf() method. 165 | */ 166 | public function readArray(string $format = null) 167 | { 168 | return $this->scanf($format); 169 | } 170 | 171 | /** 172 | * Read a line. 173 | */ 174 | public function readLine() 175 | { 176 | return fgets($this->getStream()); 177 | } 178 | 179 | /** 180 | * Read all, i.e. read as much as possible. 181 | */ 182 | public function readAll(int $offset = 0) 183 | { 184 | return stream_get_contents($this->getStream(), -1, $offset); 185 | } 186 | 187 | /** 188 | * Parse input from a stream according to a format. 189 | */ 190 | public function scanf(string $format): array 191 | { 192 | return fscanf($this->getStream(), $format); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /Source/Temporary/ReadWrite.php: -------------------------------------------------------------------------------- 1 | getMode(), $createModes)) { 78 | throw new File\Exception( 79 | 'Open mode are not supported; given %d. Only %s are supported.', 80 | 0, 81 | [$this->getMode(), implode(', ', $createModes)] 82 | ); 83 | } 84 | 85 | preg_match('#^(\w+)://#', $streamName, $match); 86 | 87 | if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && 88 | !file_exists($streamName) && 89 | parent::MODE_READ_WRITE == $this->getMode()) { 90 | throw new File\Exception\FileDoesNotExist( 91 | 'File %s does not exist.', 92 | 1, 93 | $streamName 94 | ); 95 | } 96 | 97 | $out = parent::_open($streamName, $context); 98 | 99 | return $out; 100 | } 101 | 102 | /** 103 | * Test for end-of-file. 104 | */ 105 | public function eof(): bool 106 | { 107 | return feof($this->getStream()); 108 | } 109 | 110 | /** 111 | * Read n characters. 112 | */ 113 | public function read(int $length) 114 | { 115 | if (0 > $length) { 116 | throw new File\Exception( 117 | 'Length must be greater than 0, given %d.', 118 | 2, 119 | $length 120 | ); 121 | } 122 | 123 | return fread($this->getStream(), $length); 124 | } 125 | 126 | /** 127 | * Alias of $this->read(). 128 | */ 129 | public function readString(int $length) 130 | { 131 | return $this->read($length); 132 | } 133 | 134 | /** 135 | * Read a character. 136 | */ 137 | public function readCharacter() 138 | { 139 | return fgetc($this->getStream()); 140 | } 141 | 142 | /** 143 | * Read a boolean. 144 | */ 145 | public function readBoolean() 146 | { 147 | return (bool) $this->read(1); 148 | } 149 | 150 | /** 151 | * Read an integer. 152 | */ 153 | public function readInteger(int $length = 1) 154 | { 155 | return (int) $this->read($length); 156 | } 157 | 158 | /** 159 | * Read a float. 160 | */ 161 | public function readFloat(int $length = 1) 162 | { 163 | return (float) $this->read($length); 164 | } 165 | 166 | /** 167 | * Read an array. 168 | * Alias of the $this->scanf() method. 169 | */ 170 | public function readArray(string $format = null) 171 | { 172 | return $this->scanf($format); 173 | } 174 | 175 | /** 176 | * Read a line. 177 | */ 178 | public function readLine() 179 | { 180 | return fgets($this->getStream()); 181 | } 182 | 183 | /** 184 | * Read all, i.e. read as much as possible. 185 | */ 186 | public function readAll(int $offset = 0) 187 | { 188 | return stream_get_contents($this->getStream(), -1, $offset); 189 | } 190 | 191 | /** 192 | * Parse input from a stream according to a format. 193 | */ 194 | public function scanf(string $format): array 195 | { 196 | return fscanf($this->getStream(), $format); 197 | } 198 | 199 | /** 200 | * Write n characters. 201 | */ 202 | public function write(string $string, int $length) 203 | { 204 | if (0 > $length) { 205 | throw new File\Exception( 206 | 'Length must be greater than 0, given %d.', 207 | 3, 208 | $length 209 | ); 210 | } 211 | 212 | return fwrite($this->getStream(), $string, $length); 213 | } 214 | 215 | /** 216 | * Write a string. 217 | */ 218 | public function writeString(string $string) 219 | { 220 | $string = (string) $string; 221 | 222 | return $this->write($string, strlen($string)); 223 | } 224 | 225 | /** 226 | * Write a character. 227 | */ 228 | public function writeCharacter(string $char) 229 | { 230 | return $this->write((string) $char[0], 1); 231 | } 232 | 233 | /** 234 | * Write a boolean. 235 | */ 236 | public function writeBoolean(bool $boolean) 237 | { 238 | return $this->write((string) (bool) $boolean, 1); 239 | } 240 | 241 | /** 242 | * Write an integer. 243 | */ 244 | public function writeInteger(int $integer) 245 | { 246 | $integer = (string) (int) $integer; 247 | 248 | return $this->write($integer, strlen($integer)); 249 | } 250 | 251 | /** 252 | * Write a float. 253 | */ 254 | public function writeFloat(float $float) 255 | { 256 | $float = (string) (float) $float; 257 | 258 | return $this->write($float, strlen($float)); 259 | } 260 | 261 | /** 262 | * Write an array. 263 | */ 264 | public function writeArray(array $array) 265 | { 266 | $array = var_export($array, true); 267 | 268 | return $this->write($array, strlen($array)); 269 | } 270 | 271 | /** 272 | * Write a line. 273 | */ 274 | public function writeLine(string $line) 275 | { 276 | if (false === $n = strpos($line, "\n")) { 277 | return $this->write($line . "\n", strlen($line) + 1); 278 | } 279 | 280 | ++$n; 281 | 282 | return $this->write(substr($line, 0, $n), $n); 283 | } 284 | 285 | /** 286 | * Write all, i.e. as much as possible. 287 | */ 288 | public function writeAll(string $string) 289 | { 290 | return $this->write($string, strlen($string)); 291 | } 292 | 293 | /** 294 | * Truncate a file to a given length. 295 | */ 296 | public function truncate(int $size): bool 297 | { 298 | return ftruncate($this->getStream(), $size); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /Source/Temporary/Temporary.php: -------------------------------------------------------------------------------- 1 | __construct() that will create a temporary 95 | * file that will be destroy when calling the $this->close() method. 96 | */ 97 | public static function create(string $directory = null, string $prefix = '__hoa_') 98 | { 99 | if (null === $directory || 100 | false === is_dir($directory)) { 101 | $directory = static::getTemporaryDirectory(); 102 | } 103 | 104 | return tempnam($directory, $prefix); 105 | } 106 | 107 | /** 108 | * Get the directory path used for temporary files. 109 | */ 110 | public static function getTemporaryDirectory(): string 111 | { 112 | return sys_get_temp_dir(); 113 | } 114 | } 115 | 116 | /** 117 | * Flex entity. 118 | */ 119 | Consistency::flexEntity(Temporary::class); 120 | -------------------------------------------------------------------------------- /Source/Temporary/Write.php: -------------------------------------------------------------------------------- 1 | getMode(), $createModes)) { 77 | throw new File\Exception( 78 | 'Open mode are not supported; given %d. Only %s are supported.', 79 | 0, 80 | [$this->getMode(), implode(', ', $createModes)] 81 | ); 82 | } 83 | 84 | preg_match('#^(\w+)://#', $streamName, $match); 85 | 86 | if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && 87 | !file_exists($streamName)) { 88 | throw new File\Exception\FileDoesNotExist( 89 | 'File %s does not exist.', 90 | 1, 91 | $streamName 92 | ); 93 | } 94 | 95 | $out = parent::_open($streamName, $context); 96 | 97 | return $out; 98 | } 99 | 100 | /** 101 | * Write n characters. 102 | */ 103 | public function write(string $string, int $length) 104 | { 105 | if (0 > $length) { 106 | throw new File\Exception( 107 | 'Length must be greater than 0, given %d.', 108 | 2, 109 | $length 110 | ); 111 | } 112 | 113 | return fwrite($this->getStream(), $string, $length); 114 | } 115 | 116 | /** 117 | * Write a string. 118 | */ 119 | public function writeString(string $string) 120 | { 121 | $string = (string) $string; 122 | 123 | return $this->write($string, strlen($string)); 124 | } 125 | 126 | /** 127 | * Write a character. 128 | */ 129 | public function writeCharacter(string $char) 130 | { 131 | return $this->write((string) $char[0], 1); 132 | } 133 | 134 | /** 135 | * Write a boolean. 136 | */ 137 | public function writeBoolean(bool $boolean) 138 | { 139 | return $this->write((string) (bool) $boolean, 1); 140 | } 141 | 142 | /** 143 | * Write an integer. 144 | */ 145 | public function writeInteger(int $integer) 146 | { 147 | $integer = (string) (int) $integer; 148 | 149 | return $this->write($integer, strlen($integer)); 150 | } 151 | 152 | /** 153 | * Write a float. 154 | */ 155 | public function writeFloat(float $float) 156 | { 157 | $float = (string) (float) $float; 158 | 159 | return $this->write($float, strlen($float)); 160 | } 161 | 162 | /** 163 | * Write an array. 164 | */ 165 | public function writeArray(array $array) 166 | { 167 | $array = var_export($array, true); 168 | 169 | return $this->write($array, strlen($array)); 170 | } 171 | 172 | /** 173 | * Write a line. 174 | */ 175 | public function writeLine(string $line) 176 | { 177 | if (false === $n = strpos($line, "\n")) { 178 | return $this->write($line . "\n", strlen($line) + 1); 179 | } 180 | 181 | ++$n; 182 | 183 | return $this->write(substr($line, 0, $n), $n); 184 | } 185 | 186 | /** 187 | * Write all, i.e. as much as possible. 188 | */ 189 | public function writeAll(string $string) 190 | { 191 | return $this->write($string, strlen($string)); 192 | } 193 | 194 | /** 195 | * Truncate a file to a given length. 196 | */ 197 | public function truncate(int $size): bool 198 | { 199 | return ftruncate($this->getStream(), $size); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /Source/Watcher.php: -------------------------------------------------------------------------------- 1 | setListener( 67 | new Event\Listener( 68 | $this, 69 | [ 70 | 'new', 71 | 'modify', 72 | 'move' 73 | ] 74 | ) 75 | ); 76 | 77 | if (null !== $latency) { 78 | $this->setLatency($latency); 79 | } 80 | 81 | return; 82 | } 83 | 84 | /** 85 | * Run the watcher. 86 | * 87 | * Listenable events: 88 | * • new, when a file is new, i.e. found by the finder; 89 | * • modify, when a file has been modified; 90 | * • move, when a file has moved, i.e. no longer found by the finder. 91 | */ 92 | public function run(): void 93 | { 94 | $iterator = $this->getIterator(); 95 | $previous = iterator_to_array($iterator); 96 | $current = $previous; 97 | 98 | while (true) { 99 | foreach ($current as $name => $c) { 100 | if (!isset($previous[$name])) { 101 | $this->getListener()->fire( 102 | 'new', 103 | new Event\Bucket([ 104 | 'file' => $c 105 | ]) 106 | ); 107 | 108 | continue; 109 | } 110 | 111 | if (null === $c->getHash()) { 112 | unset($current[$name]); 113 | 114 | continue; 115 | } 116 | 117 | if ($previous[$name]->getHash() != $c->getHash()) { 118 | $this->getListener()->fire( 119 | 'modify', 120 | new Event\Bucket([ 121 | 'file' => $c 122 | ]) 123 | ); 124 | } 125 | 126 | unset($previous[$name]); 127 | } 128 | 129 | foreach ($previous as $p) { 130 | $this->getListener()->fire( 131 | 'move', 132 | new Event\Bucket([ 133 | 'file' => $p 134 | ]) 135 | ); 136 | } 137 | 138 | usleep($this->getLatency() * 1000000); 139 | 140 | $previous = $current; 141 | $current = iterator_to_array($iterator); 142 | } 143 | 144 | return; 145 | } 146 | 147 | /** 148 | * Set latency. 149 | */ 150 | public function setLatency(int $latency): int 151 | { 152 | $old = $this->_latency; 153 | $this->_latency = $latency; 154 | 155 | return $old; 156 | } 157 | 158 | /** 159 | * Get latency. 160 | */ 161 | public function getLatency(): int 162 | { 163 | return $this->_latency; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /Source/Write.php: -------------------------------------------------------------------------------- 1 | getMode(), $createModes)) { 76 | throw new Exception( 77 | 'Open mode are not supported; given %d. Only %s are supported.', 78 | 0, 79 | [$this->getMode(), implode(', ', $createModes)] 80 | ); 81 | } 82 | 83 | preg_match('#^(\w+)://#', $streamName, $match); 84 | 85 | if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && 86 | !file_exists($streamName) && 87 | parent::MODE_TRUNCATE_WRITE == $this->getMode()) { 88 | throw new Exception\FileDoesNotExist( 89 | 'File %s does not exist.', 90 | 1, 91 | $streamName 92 | ); 93 | } 94 | 95 | $out = parent::_open($streamName, $context); 96 | 97 | return $out; 98 | } 99 | 100 | /** 101 | * Write n characters. 102 | */ 103 | public function write(string $string, int $length) 104 | { 105 | if (0 > $length) { 106 | throw new Exception( 107 | 'Length must be greater than 0, given %d.', 108 | 2, 109 | $length 110 | ); 111 | } 112 | 113 | return fwrite($this->getStream(), $string, $length); 114 | } 115 | 116 | /** 117 | * Write a string. 118 | */ 119 | public function writeString(string $string) 120 | { 121 | $string = (string) $string; 122 | 123 | return $this->write($string, strlen($string)); 124 | } 125 | 126 | /** 127 | * Write a character. 128 | */ 129 | public function writeCharacter(string $char) 130 | { 131 | return $this->write((string) $char[0], 1); 132 | } 133 | 134 | /** 135 | * Write a boolean. 136 | */ 137 | public function writeBoolean(bool $boolean) 138 | { 139 | return $this->write((string) (bool) $boolean, 1); 140 | } 141 | 142 | /** 143 | * Write an integer. 144 | */ 145 | public function writeInteger(int $integer) 146 | { 147 | $integer = (string) (int) $integer; 148 | 149 | return $this->write($integer, strlen($integer)); 150 | } 151 | 152 | /** 153 | * Write a float. 154 | */ 155 | public function writeFloat(float $float) 156 | { 157 | $float = (string) (float) $float; 158 | 159 | return $this->write($float, strlen($float)); 160 | } 161 | 162 | /** 163 | * Write an array. 164 | */ 165 | public function writeArray(array $array) 166 | { 167 | $array = var_export($array, true); 168 | 169 | return $this->write($array, strlen($array)); 170 | } 171 | 172 | /** 173 | * Write a line. 174 | */ 175 | public function writeLine(string $line) 176 | { 177 | if (false === $n = strpos($line, "\n")) { 178 | return $this->write($line . "\n", strlen($line) + 1); 179 | } 180 | 181 | ++$n; 182 | 183 | return $this->write(substr($line, 0, $n), $n); 184 | } 185 | 186 | /** 187 | * Write all, i.e. as much as possible. 188 | */ 189 | public function writeAll(string $string) 190 | { 191 | return $this->write($string, strlen($string)); 192 | } 193 | 194 | /** 195 | * Truncate a file to a given length. 196 | */ 197 | public function truncate(int $size): bool 198 | { 199 | return ftruncate($this->getStream(), $size); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "hoa/file", 3 | "description": "The Hoa\\File library.", 4 | "type" : "library", 5 | "keywords" : ["library", "file", "directory", "finder", "link", 6 | "temporary", "socket"], 7 | "homepage" : "https://hoa-project.net/", 8 | "license" : "BSD-3-Clause", 9 | "authors" : [ 10 | { 11 | "name" : "Ivan Enderlin", 12 | "email": "ivan.enderlin@hoa-project.net" 13 | }, 14 | { 15 | "name" : "Hoa community", 16 | "homepage": "https://hoa-project.net/" 17 | } 18 | ], 19 | "support": { 20 | "email" : "support@hoa-project.net", 21 | "irc" : "irc://chat.freenode.net/hoaproject", 22 | "forum" : "https://users.hoa-project.net/", 23 | "docs" : "https://central.hoa-project.net/Documentation/Library/File", 24 | "source": "https://central.hoa-project.net/Resource/Library/File" 25 | }, 26 | "require": { 27 | "php" : ">=7.1", 28 | "hoa/consistency": "dev-master", 29 | "hoa/event" : "dev-master", 30 | "hoa/exception" : "dev-master", 31 | "hoa/stream" : "dev-master", 32 | "hoa/iterator" : "dev-master" 33 | }, 34 | "require-dev": { 35 | "hoa/test": "dev-master" 36 | }, 37 | "autoload": { 38 | "psr-4": { 39 | "Hoa\\File\\": "Source" 40 | } 41 | }, 42 | "extra": { 43 | "branch-alias": { 44 | "dev-master": "1.x-dev" 45 | } 46 | } 47 | } 48 | --------------------------------------------------------------------------------