Lines
83.33%
55 / 66
Methods
36.36%
4 / 11
Classes
0.00%
0 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| __construct | 100.00% | 5 / 5 | 100.00% | 1 / 1 | 1 | ||
| extension | 80.00% | 4 / 5 | 0.00% | 0 / 1 | 3.07 | ||
| pattern | 80.00% | 4 / 5 | 0.00% | 0 / 1 | 3.07 | ||
| recursive | 100.00% | 2 / 2 | 100.00% | 1 / 1 | 1 | ||
| exclude | 63.63% | 7 / 11 | 0.00% | 0 / 1 | 9.36 | ||
| ofType | 75.00% | 3 / 4 | 0.00% | 0 / 1 | 4.25 | ||
| onlyFiles | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| onlyFolders | 0.00% | 0 / 1 | 0.00% | 0 / 1 | 2 | ||
| filesAndFolders | 0.00% | 0 / 1 | 0.00% | 0 / 1 | 2 | ||
| collect | 91.66% | 22 / 24 | 0.00% | 0 / 1 | 14.11 | ||
| matchesAnyPattern | 100.00% | 7 / 7 | 100.00% | 1 / 1 | 5 | ||
| 18 | class FileSystemCollection | |
| 19 | { | |
| 20 | /** | |
| 21 | * Pattern type constants | |
| 22 | */ | |
| 23 | const int PATTERN_REGEX = 0; | |
| 24 | const int PATTERN_WILDCARD = 1; | |
| 25 | ||
| 26 | /** | |
| 27 | * Item type constants | |
| 28 | */ | |
| 29 | const int TYPE_FILE = 0; | |
| 30 | const int TYPE_FOLDER = 1; | |
| 31 | const int TYPE_BOTH = -1; | |
| 32 | ||
| 33 | /** | |
| 34 | * The folder to search in | |
| 35 | * | |
| 36 | * @var Folder | |
| 37 | */ | |
| 38 | private Folder $folder; | |
| 39 | ||
| 40 | /** | |
| 41 | * File extensions to filter by | |
| 42 | * | |
| 43 | * @var array | |
| 44 | */ | |
| 45 | private array $extension; | |
| 46 | ||
| 47 | /** | |
| 48 | * Patterns to match filenames against | |
| 49 | * | |
| 50 | * @var array | |
| 51 | */ | |
| 52 | private array $patterns; | |
| 53 | ||
| 54 | /** | |
| 55 | * Patterns to exclude filenames | |
| 56 | * | |
| 57 | * @var array | |
| 58 | */ | |
| 59 | private array $exclude; | |
| 60 | ||
| 61 | /** | |
| 62 | * Whether to search recursively | |
| 63 | * | |
| 64 | * @var bool | |
| 65 | */ | |
| 66 | private bool $recursive; | |
| 67 | ||
| 68 | /** | |
| 69 | * Type of items to collect (files, folders, or both) | |
| 70 | * | |
| 71 | * @var int | |
| 72 | */ | |
| 73 | private int $itemType = -1; | |
| 74 | ||
| 75 | /** | |
| 76 | * Creates a new FileSystemCollection | |
| 77 | * | |
| 78 | * @param Folder $folder The folder to search in | |
| 79 | */ | |
| 80 | public function __construct(Folder $folder) | |
| 81 | { | |
| 82 | $this->folder = $folder; | |
| 83 | $this->extension = []; | |
| 84 | $this->patterns = []; | |
| 85 | $this->exclude = []; | |
| 86 | $this->recursive = false; | |
| 87 | } | |
| 88 | ||
| 89 | /** | |
| 90 | * Filters by file extension | |
| 91 | * | |
| 92 | * @param string|array $extension One or more extensions to filter by | |
| 93 | * @return FileSystemCollection This collection for method chaining | |
| 94 | */ | |
| 95 | public function extension(string|array $extension) : FileSystemCollection | |
| 96 | { | |
| 97 | if(is_string($extension)) { | |
| 98 | $this->extension[] = strtolower($extension); | |
| 99 | } | |
| 100 | if(is_array($extension)) { | |
| 101 | $this->extension = array_merge($this->extension, array_map('strtolower',$extension)); | |
| 102 | } | |
| 103 | ||
| 104 | return $this; | |
| 105 | } | |
| 106 | ||
| 107 | /** | |
| 108 | * Filters by filename pattern | |
| 109 | * | |
| 110 | * @param string|array $pattern One or more patterns to match against | |
| 111 | * @param int $type The pattern type (PATTERN_REGEX or PATTERN_WILDCARD) | |
| 112 | * @return FileSystemCollection This collection for method chaining | |
| 113 | */ | |
| 114 | public function pattern(string|array $pattern, int $type = 0) : FileSystemCollection | |
| 115 | { | |
| 116 | if(is_string($pattern)) { | |
| 117 | $this->patterns[$type][] = $pattern; | |
| 118 | } | |
| 119 | if(is_array($pattern)) { | |
| 120 | $this->patterns[$type] = array_merge($this->patterns[$type], $pattern); | |
| 121 | } | |
| 122 | return $this; | |
| 123 | } | |
| 124 | ||
| 125 | /** | |
| 126 | * Sets recursive search mode | |
| 127 | * | |
| 128 | * @return FileSystemCollection This collection for method chaining | |
| 129 | */ | |
| 130 | public function recursive() : FileSystemCollection | |
| 131 | { | |
| 132 | $this->recursive = true; | |
| 133 | return $this; | |
| 134 | } | |
| 135 | ||
| 136 | /** | |
| 137 | * Excludes filenames matching the given pattern | |
| 138 | * | |
| 139 | * @param string|array $exclude Pattern(s) to exclude | |
| 140 | * @param int $type The pattern type (PATTERN_REGEX or PATTERN_WILDCARD) | |
| 141 | * @return FileSystemCollection This collection for method chaining | |
| 142 | * @throws Exception If the pattern type is invalid | |
| 143 | */ | |
| 144 | public function exclude(string|array $exclude, int $type = 0) : FileSystemCollection | |
| 145 | { | |
| 146 | if($type > 1 || $type < 0) { | |
| 147 | throw new Exception("Invalid type provided, type must be PATTERN_REGEX or PATTERN_WILDCARD"); | |
| 148 | } | |
| 149 | ||
| 150 | // Initialize the array if it doesn't exist | |
| 151 | if (!isset($this->exclude[$type])) { | |
| 152 | $this->exclude[$type] = []; | |
| 153 | } | |
| 154 | ||
| 155 | if(is_string($exclude)) { | |
| 156 | $this->exclude[$type][] = $exclude; | |
| 157 | } | |
| 158 | if(is_array($exclude)) { | |
| 159 | if (!isset($this->exclude[$type])) { | |
| 160 | $this->exclude[$type] = []; | |
| 161 | } | |
| 162 | $this->exclude[$type] = array_merge($this->exclude[$type], $exclude); | |
| 163 | } | |
| 164 | ||
| 165 | return $this; | |
| 166 | } | |
| 167 | ||
| 168 | /** | |
| 169 | * Specify the type of items to collect (files, folders, or both) | |
| 170 | * | |
| 171 | * @param int $type One of TYPE_FILE, TYPE_FOLDER, or TYPE_BOTH | |
| 172 | * @return FileSystemCollection This collection for method chaining | |
| 173 | * @throws Exception If the type is invalid | |
| 174 | */ | |
| 175 | public function ofType(int $type) : FileSystemCollection | |
| 176 | { | |
| 177 | if ($type !== self::TYPE_FILE && $type !== self::TYPE_FOLDER && $type !== self::TYPE_BOTH) { | |
| 178 | throw new Exception("Invalid type provided, type must be TYPE_FILE, TYPE_FOLDER, or TYPE_BOTH"); | |
| 179 | } | |
| 180 | ||
| 181 | $this->itemType = $type; | |
| 182 | return $this; | |
| 183 | } | |
| 184 | ||
| 185 | /** | |
| 186 | * Shorthand method to collect only files | |
| 187 | * | |
| 188 | * @return FileSystemCollection This collection for method chaining | |
| 189 | */ | |
| 190 | public function onlyFiles() : FileSystemCollection | |
| 191 | { | |
| 192 | return $this->ofType(self::TYPE_FILE); | |
| 193 | } | |
| 194 | ||
| 195 | /** | |
| 196 | * Shorthand method to collect only folders | |
| 197 | * | |
| 198 | * @return FileSystemCollection This collection for method chaining | |
| 199 | */ | |
| 200 | public function onlyFolders() : FileSystemCollection | |
| 201 | { | |
| 202 | return $this->ofType(self::TYPE_FOLDER); | |
| 203 | } | |
| 204 | ||
| 205 | /** | |
| 206 | * Shorthand method to collect both files and folders | |
| 207 | * | |
| 208 | * @return FileSystemCollection This collection for method chaining | |
| 209 | */ | |
| 210 | public function filesAndFolders() : FileSystemCollection | |
| 211 | { | |
| 212 | return $this->ofType(self::TYPE_BOTH); | |
| 213 | } | |
| 214 | ||
| 215 | /** | |
| 216 | * Executes the search and returns the collection of matching items | |
| 217 | * | |
| 218 | * @return array Array of File and/or Folder objects matching the criteria | |
| 219 | */ | |
| 220 | public function collect(): array | |
| 221 | { | |
| 222 | $items = []; | |
| 223 | ||
| 224 | // Use FilesystemIterator for both recursive and non-recursive cases | |
| 225 | if ($this->recursive) { | |
| 226 | $iterator = new RecursiveIteratorIterator( | |
| 227 | new RecursiveDirectoryIterator($this->folder->path, FilesystemIterator::SKIP_DOTS), | |
| 228 | RecursiveIteratorIterator::SELF_FIRST | |
| 229 | ); | |
| 230 | } else { | |
| 231 | $iterator = new FilesystemIterator($this->folder->path, FilesystemIterator::SKIP_DOTS); | |
| 232 | } | |
| 233 | ||
| 234 | foreach ($iterator as $fileInfo) { | |
| 235 | // Skip based on item type | |
| 236 | if (($this->itemType === self::TYPE_FILE && !$fileInfo->isFile()) || | |
| 237 | ($this->itemType === self::TYPE_FOLDER && !$fileInfo->isDir())) { | |
| 238 | continue; | |
| 239 | } | |
| 240 | ||
| 241 | // Handle folders | |
| 242 | if ($fileInfo->isDir()) { | |
| 243 | $items[] = new Folder($fileInfo->getRealPath(), true); | |
| 244 | continue; | |
| 245 | } | |
| 246 | ||
| 247 | // For files, apply filtering | |
| 248 | $filename = $fileInfo->getFilename(); | |
| 249 | ||
| 250 | // Skip if extension doesn't match | |
| 251 | if (!empty($this->extension) && | |
| 252 | !in_array(strtolower(pathinfo($filename, PATHINFO_EXTENSION)), $this->extension)) { | |
| 253 | continue; | |
| 254 | } | |
| 255 | ||
| 256 | // Skip if pattern matching is required but fails | |
| 257 | if (!empty($this->patterns) && !$this->matchesAnyPattern($filename, $this->patterns)) { | |
| 258 | continue; | |
| 259 | } | |
| 260 | ||
| 261 | // Skip if file matches any exclude pattern | |
| 262 | if (!empty($this->exclude) && $this->matchesAnyPattern($filename, $this->exclude)) { | |
| 263 | continue; | |
| 264 | } | |
| 265 | ||
| 266 | $items[] = new File($fileInfo->getRealPath(), "", true); | |
| 267 | } | |
| 268 | ||
| 269 | return $items; | |
| 270 | } | |
| 271 | ||
| 272 | /** | |
| 273 | * Check if a filename matches any pattern in the given pattern sets | |
| 274 | * | |
| 275 | * @param string $filename The filename to check | |
| 276 | * @param array $patternSets Array of patterns to check against | |
| 277 | * @return bool True if the filename matches any pattern, false otherwise | |
| 278 | */ | |
| 279 | private function matchesAnyPattern(string $filename, array $patternSets): bool | |
| 280 | { | |
| 281 | // Check regex patterns | |
| 282 | if (isset($patternSets[self::PATTERN_REGEX])) { | |
| 283 | if (array_any($patternSets[self::PATTERN_REGEX], fn($pattern) => preg_match($pattern, $filename))) { | |
| 284 | return true; | |
| 285 | } | |
| 286 | } | |
| 287 | ||
| 288 | // Check wildcard patterns | |
| 289 | if (isset($patternSets[self::PATTERN_WILDCARD])) { | |
| 290 | if (array_any($patternSets[self::PATTERN_WILDCARD], fn($pattern) => fnmatch($pattern, $filename))) { | |
| 291 | return true; | |
| 292 | } | |
| 293 | } | |
| 294 | ||
| 295 | return false; | |
| 296 | } | |
| 297 | } |