Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
76.32% covered (warning)
76.32%
29 / 38
60.00% covered (warning)
60.00%
6 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
FSFileBackendList
78.38% covered (warning)
78.38%
29 / 37
60.00% covered (warning)
60.00%
6 / 10
19.92
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 initIterator
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 key
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 current
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 next
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
2.50
 rewind
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
2.50
 valid
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getLastError
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 filterViaNext
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRelPath
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 * @ingroup FileBackend
6 */
7
8namespace Wikimedia\FileBackend\FileIteration;
9
10use DirectoryIterator;
11use FilesystemIterator;
12use Iterator;
13use RecursiveDirectoryIterator;
14use RecursiveIteratorIterator;
15use UnexpectedValueException;
16use Wikimedia\FileBackend\FileBackendError;
17
18/**
19 * Wrapper around RecursiveDirectoryIterator/DirectoryIterator that
20 * catches exception or does any custom behavior that we may want.
21 * Do not use this class from places outside FSFileBackend.
22 *
23 * @ingroup FileBackend
24 */
25abstract class FSFileBackendList implements Iterator {
26    /** @var Iterator|null */
27    protected $iter;
28    /** @var string */
29    protected $lastError;
30
31    /** @var int */
32    protected $suffixStart;
33
34    /** @var int */
35    protected $pos = 0;
36
37    /** @var array */
38    protected $params = [];
39
40    /**
41     * @param string $dir File system directory
42     * @param array $params
43     */
44    public function __construct( $dir, array $params ) {
45        $path = realpath( $dir ); // normalize
46        if ( $path === false ) {
47            $path = $dir;
48        }
49        $this->suffixStart = strlen( $path ) + 1; // size of "path/to/dir/"
50        $this->params = $params;
51
52        try {
53            $this->iter = $this->initIterator( $path );
54        } catch ( UnexpectedValueException $e ) {
55            $this->iter = null; // bad permissions? deleted?
56            $this->lastError = $e->getMessage();
57        }
58    }
59
60    /**
61     * Return an appropriate iterator object to wrap
62     *
63     * @param string $dir File system directory
64     * @return Iterator
65     * @throws UnexpectedValueException
66     */
67    protected function initIterator( $dir ) {
68        if ( !empty( $this->params['topOnly'] ) ) { // non-recursive
69            # Get an iterator that will get direct sub-nodes
70            return new DirectoryIterator( $dir );
71        } else { // recursive
72            # Get an iterator that will return leaf nodes (non-directories)
73            # RecursiveDirectoryIterator extends FilesystemIterator.
74            # FilesystemIterator::SKIP_DOTS default is inconsistent in PHP 5.3.x.
75            $flags = FilesystemIterator::CURRENT_AS_SELF | FilesystemIterator::SKIP_DOTS;
76
77            return new RecursiveIteratorIterator(
78                new RecursiveDirectoryIterator( $dir, $flags ),
79                RecursiveIteratorIterator::CHILD_FIRST // include dirs
80            );
81        }
82    }
83
84    /**
85     * @see Iterator::key()
86     * @return int
87     */
88    public function key(): int {
89        return $this->pos;
90    }
91
92    /**
93     * @see Iterator::current()
94     * @return string|false
95     */
96    #[\ReturnTypeWillChange]
97    public function current() {
98        return $this->getRelPath( $this->iter->current()->getPathname() );
99    }
100
101    /**
102     * @see Iterator::next()
103     * @throws FileBackendError
104     */
105    public function next(): void {
106        try {
107            $this->iter->next();
108            $this->filterViaNext();
109        } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
110            $this->lastError = $e->getMessage();
111
112            throw new FileBackendError( "File iterator gave UnexpectedValueException." );
113        }
114        ++$this->pos;
115    }
116
117    /**
118     * @see Iterator::rewind()
119     * @throws FileBackendError
120     */
121    public function rewind(): void {
122        $this->pos = 0;
123        try {
124            $this->iter->rewind();
125            $this->filterViaNext();
126        } catch ( UnexpectedValueException $e ) { // bad permissions? deleted?
127            $this->lastError = $e->getMessage();
128
129            throw new FileBackendError( "File iterator gave UnexpectedValueException." );
130        }
131    }
132
133    /**
134     * @see Iterator::valid()
135     * @return bool
136     */
137    public function valid(): bool {
138        return $this->iter && $this->iter->valid();
139    }
140
141    /**
142     * @return string|null The last caught exception message
143     */
144    public function getLastError() {
145        return $this->lastError;
146    }
147
148    /**
149     * Filter out items by advancing to the next ones
150     */
151    protected function filterViaNext() {
152    }
153
154    /**
155     * Return only the relative path and normalize slashes to FileBackend-style.
156     * Uses the "real path" since the suffix is based upon that.
157     *
158     * @param string $dir
159     * @return string
160     */
161    protected function getRelPath( $dir ) {
162        $path = realpath( $dir );
163        if ( $path === false ) {
164            $path = $dir;
165        }
166
167        return strtr( substr( $path, $this->suffixStart ), '\\', '/' );
168    }
169}
170
171/** @deprecated class alias since 1.43 */
172class_alias( FSFileBackendList::class, 'FSFileBackendList' );