Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
93.10% covered (success)
93.10%
27 / 29
CRAP
98.10% covered (success)
98.10%
155 / 158
Dir
0.00% covered (danger)
0.00%
0 / 1
93.10% covered (success)
93.10%
27 / 29
101
98.10% covered (success)
98.10%
155 / 158
 __construct
100.00% covered (success)
100.00%
1 / 1
10
100.00% covered (success)
100.00%
20 / 20
 count
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getIterator
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 setAbsolute
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
 setRelative
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
4 / 4
 setRecursive
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setFilesOnly
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 isAbsolute
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isRelative
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isRecursive
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 isFilesOnly
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getPath
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getFiles
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getTree
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 copyTo
100.00% covered (success)
100.00%
1 / 1
6
100.00% covered (success)
100.00%
13 / 13
 fileExists
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 deleteFile
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 emptyDir
100.00% covered (success)
100.00%
1 / 1
8
100.00% covered (success)
100.00%
13 / 13
 __get
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 __isset
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 __set
0.00% covered (danger)
0.00%
0 / 1
1.12
50.00% covered (warning)
50.00%
1 / 2
 __unset
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 offsetExists
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
3 / 3
 offsetGet
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 offsetSet
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 offsetUnset
0.00% covered (danger)
0.00%
0 / 1
7.18
84.62% covered (success)
84.62%
11 / 13
 traverse
100.00% covered (success)
100.00%
1 / 1
18
100.00% covered (success)
100.00%
27 / 27
 traverseRecursively
100.00% covered (success)
100.00%
1 / 1
19
100.00% covered (success)
100.00%
27 / 27
 buildTree
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
10 / 10
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2021 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Dir;
15
16/**
17 * Directory class
18 *
19 * @category   Pop
20 * @package    Pop\Dir
21 * @author     Nick Sagona, III <dev@nolainteractive.com>
22 * @copyright  Copyright (c) 2009-2021 NOLA Interactive, LLC. (http://www.nolainteractive.com)
23 * @license    http://www.popphp.org/license     New BSD License
24 * @version    3.2.0
25 */
26class Dir implements \ArrayAccess, \Countable, \IteratorAggregate
27{
28
29    /**
30     * The directory path
31     * @var string
32     */
33    protected $path = null;
34
35    /**
36     * The files within the directory
37     * @var array
38     */
39    protected $files = [];
40
41    /**
42     * The nested tree map of the directory and its files
43     * @var array
44     */
45    protected $tree = [];
46
47    /**
48     * Flag to store the absolute path.
49     * @var boolean
50     */
51    protected $absolute = false;
52
53    /**
54     * Flag to store the relative path.
55     * @var boolean
56     */
57    protected $relative = false;
58
59    /**
60     * Flag to dig recursively.
61     * @var boolean
62     */
63    protected $recursive = false;
64
65    /**
66     * Flag to include only files and no directories
67     * @var boolean
68     */
69    protected $filesOnly = false;
70
71    /**
72     * Constructor
73     *
74     * Instantiate a directory object
75     *
76     * @param  string  $dir
77     * @param  array   $options
78     * @throws Exception
79     */
80    public function __construct($dir, array $options = [])
81    {
82        // Set the directory path.
83        if ((strpos($dir, "\\") !== false) && (DIRECTORY_SEPARATOR != "\\")) {
84            $this->path = str_replace("\\", '/', $dir);
85        } else {
86            $this->path = $dir;
87        }
88
89        // Check to see if the directory exists.
90        if (!file_exists($this->path)) {
91            throw new Exception("Error: The directory '" . $this->path . "' does not exist");
92        }
93
94        // Trim the trailing slash.
95        if (strrpos($this->path, DIRECTORY_SEPARATOR) == (strlen($this->path) - 1)) {
96            $this->path = substr($this->path, 0, -1);
97        }
98
99        if (isset($options['absolute'])) {
100            $this->setAbsolute($options['absolute']);
101        }
102        if (isset($options['relative'])) {
103            $this->setRelative($options['relative']);
104        }
105        if (isset($options['recursive'])) {
106            $this->setRecursive($options['recursive']);
107        }
108        if (isset($options['filesOnly'])) {
109            $this->setFilesOnly($options['filesOnly']);
110        }
111
112        $this->tree[realpath($this->path)] = $this->buildTree(new \DirectoryIterator($this->path));
113
114        if ($this->recursive) {
115            $this->traverseRecursively();
116        } else {
117            $this->traverse();
118        }
119    }
120
121    /**
122     * Method to get the count of files in the directory
123     *
124     * @return int
125     */
126    public function count()
127    {
128        return count($this->files);
129    }
130
131    /**
132     * Method to iterate over the files
133     *
134     * @return \ArrayIterator
135     */
136    public function getIterator()
137    {
138        return new \ArrayIterator($this->files);
139    }
140
141    /**
142     * Set absolute
143     *
144     * @param  boolean $absolute
145     * @return Dir
146     */
147    public function setAbsolute($absolute)
148    {
149        $this->absolute = (bool)$absolute;
150        if (($this->absolute) && ($this->isRelative())) {
151            $this->setRelative(false);
152        }
153        return $this;
154    }
155
156    /**
157     * Set relative
158     *
159     * @param  boolean $relative
160     * @return Dir
161     */
162    public function setRelative($relative)
163    {
164        $this->relative = (bool)$relative;
165        if (($this->relative) && ($this->isAbsolute())) {
166            $this->setAbsolute(false);
167        }
168        return $this;
169    }
170
171    /**
172     * Set recursive
173     *
174     * @param  boolean $recursive
175     * @return Dir
176     */
177    public function setRecursive($recursive)
178    {
179        $this->recursive = (bool)$recursive;
180        return $this;
181    }
182
183    /**
184     * Set files only
185     *
186     * @param  boolean $filesOnly
187     * @return Dir
188     */
189    public function setFilesOnly($filesOnly)
190    {
191        $this->filesOnly = (bool)$filesOnly;
192        return $this;
193    }
194
195    /**
196     * Is absolute
197     *
198     * @return boolean
199     */
200    public function isAbsolute()
201    {
202        return $this->absolute;
203    }
204
205    /**
206     * Is relative
207     *
208     * @return boolean
209     */
210    public function isRelative()
211    {
212        return $this->relative;
213    }
214
215    /**
216     * Is recursive
217     *
218     * @return boolean
219     */
220    public function isRecursive()
221    {
222        return $this->recursive;
223    }
224
225    /**
226     * Is files only
227     *
228     * @return boolean
229     */
230    public function isFilesOnly()
231    {
232        return $this->filesOnly;
233    }
234
235    /**
236     * Get the path
237     *
238     * @return string
239     */
240    public function getPath()
241    {
242        return $this->path;
243    }
244
245    /**
246     * Get the files
247     *
248     * @return array
249     */
250    public function getFiles()
251    {
252        return $this->files;
253    }
254
255    /**
256     * Get the tree
257     *
258     * @return array
259     */
260    public function getTree()
261    {
262        return $this->tree;
263    }
264
265    /**
266     * Copy an entire directory recursively to another destination directory
267     *
268     * @param  string  $destination
269     * @param  boolean $full
270     * @return void
271     */
272    public function copyTo($destination, $full = true)
273    {
274        if ($full) {
275            if (strpos($this->path, DIRECTORY_SEPARATOR) !== false) {
276                $folder = substr($this->path, (strrpos($this->path, DIRECTORY_SEPARATOR) + 1));
277            }
278
279            if (!file_exists($destination . DIRECTORY_SEPARATOR . $folder)) {
280                mkdir($destination . DIRECTORY_SEPARATOR . $folder);
281            }
282            $destination = $destination . DIRECTORY_SEPARATOR . $folder;
283        }
284
285        foreach (
286            $iterator = new \RecursiveIteratorIterator(
287                new \RecursiveDirectoryIterator($this->path, \RecursiveDirectoryIterator::SKIP_DOTS),
288                \RecursiveIteratorIterator::SELF_FIRST) as $item
289        ) {
290            if ($item->isDir()) {
291                mkdir($destination . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
292            } else {
293                copy($item, $destination . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
294            }
295        }
296    }
297
298    /**
299     * File exists
300     *
301     * @param  string  $file
302     * @return boolean
303     */
304    public function fileExists($file)
305    {
306        return $this->offsetExists($file);
307    }
308
309    /**
310     * Delete a file
311     *
312     * @param  string  $file
313     * @throws Exception
314     * @return void
315     */
316    public function deleteFile($file)
317    {
318        $this->offsetUnset($file);
319    }
320
321    /**
322     * Empty an entire directory
323     *
324     * @param  boolean $remove
325     * @param  string  $path
326     * @throws Exception
327     * @return void
328     */
329    public function emptyDir($remove = false, $path = null)
330    {
331        if (null === $path) {
332            $path = $this->path;
333        }
334
335        // Get a directory handle.
336        if (!($dh = @opendir($path))) {
337            throw new Exception('Error: Unable to open the directory path "' . $path . '"');
338        }
339
340        // Recursively dig through the directory, deleting files where applicable.
341        while (false !== ($obj = readdir($dh))) {
342            if ($obj == '.' || $obj == '..') {
343                continue;
344            }
345            if (!@unlink($path . DIRECTORY_SEPARATOR . $obj)) {
346                $this->emptyDir(true, $path . DIRECTORY_SEPARATOR . $obj);
347            }
348        }
349
350        // Close the directory handle.
351        closedir($dh);
352
353        // If the delete flag was passed, remove the top level directory.
354        if ($remove) {
355            @rmdir($path);
356        }
357    }
358
359    /**
360     * Get a file
361     *
362     * @param  string $name
363     * @return mixed
364     */
365    public function __get($name)
366    {
367        return $this->offsetGet($name);
368    }
369
370    /**
371     * Does file exist
372     *
373     * @param  string $name
374     * @return boolean
375     */
376    public function __isset($name)
377    {
378        return $this->offsetExists($name);
379    }
380
381    /**
382     * Set method
383     *
384     * @param  string $name
385     * @param  mixed $value
386     * @throws Exception
387     * @return void
388     */
389    public function __set($name, $value)
390    {
391        $this->offsetSet($name, $value);
392    }
393
394    /**
395     * Unset method
396     *
397     * @param  string $name
398     * @throws Exception
399     * @return void
400     */
401    public function __unset($name)
402    {
403        $this->offsetUnset($name);
404    }
405
406    /**
407     * ArrayAccess offsetExists
408     *
409     * @param  mixed $offset
410     * @return boolean
411     */
412    public function offsetExists($offset)
413    {
414        if (!is_numeric($offset) && in_array($offset, $this->files)) {
415            $offset = array_search($offset, $this->files);
416        }
417        return isset($this->files[$offset]);
418    }
419
420    /**
421     * ArrayAccess offsetGet
422     *
423     * @param  mixed $offset
424     * @return mixed
425     */
426    public function offsetGet($offset)
427    {
428        return (isset($this->files[$offset])) ? $this->files[$offset] : null;
429    }
430
431    /**
432     * ArrayAccess offsetSet
433     *
434     * @param  string $offset
435     * @param  mixed  $value
436     * @throws Exception
437     * @return void
438     */
439    public function offsetSet($offset, $value)
440    {
441        throw new Exception('Error: The directory object is read-only');
442    }
443
444    /**
445     * ArrayAccess offsetUnset
446     *
447     * @param  string $offset
448     * @throws Exception
449     * @return void
450     */
451    public function offsetUnset($offset)
452    {
453        if (!is_numeric($offset) && in_array($offset, $this->files)) {
454            $offset = array_search($offset, $this->files);
455        }
456        if (isset($this->files[$offset])) {
457            if (is_dir($this->path . DIRECTORY_SEPARATOR . $this->files[$offset])) {
458                throw new Exception("Error: The file '" . $this->path . DIRECTORY_SEPARATOR . $this->files[$offset] . "' is a directory");
459            } else if (!file_exists($this->path . DIRECTORY_SEPARATOR . $this->files[$offset])) {
460                throw new Exception("Error: The file '" . $this->path . DIRECTORY_SEPARATOR . $this->files[$offset] . "' does not exist");
461            } else if (!is_writable($this->path . DIRECTORY_SEPARATOR . $this->files[$offset])) {
462                throw new Exception("Error: The file '" . $this->path . DIRECTORY_SEPARATOR . $this->files[$offset] . "' is read-only");
463            } else {
464                unlink($this->path . DIRECTORY_SEPARATOR . $this->files[$offset]);
465                unset($this->files[$offset]);
466            }
467        } else {
468            throw new Exception("Error: The file does not exist");
469        }
470    }
471
472    /**
473     * Traverse the directory
474     *
475     * @return void
476     */
477    protected function traverse()
478    {
479        foreach (new \DirectoryIterator($this->path) as $fileInfo) {
480            if(!$fileInfo->isDot()) {
481                // If absolute path flag was passed, store the absolute path.
482                if ($this->absolute) {
483                    $f = null;
484                    if (!$this->filesOnly) {
485                        $f = ($fileInfo->isDir()) ?
486                            ($this->path . DIRECTORY_SEPARATOR . $fileInfo->getFilename() . DIRECTORY_SEPARATOR) :
487                            ($this->path . DIRECTORY_SEPARATOR . $fileInfo->getFilename());
488                    } else if (!$fileInfo->isDir()) {
489                        $f = $this->path . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
490                    }
491                    if (($f !== false) && (null !== $f)) {
492                        $this->files[] = $f;
493                    }
494                // If relative path flag was passed, store the relative path.
495                } else if ($this->relative) {
496                    $f = null;
497                    if (!$this->filesOnly) {
498                        $f = ($fileInfo->isDir()) ?
499                            ($this->path . DIRECTORY_SEPARATOR . $fileInfo->getFilename() . DIRECTORY_SEPARATOR) :
500                            ($this->path . DIRECTORY_SEPARATOR . $fileInfo->getFilename());
501                    } else if (!$fileInfo->isDir()) {
502                        $f = $this->path . DIRECTORY_SEPARATOR . $fileInfo->getFilename();
503                    }
504                    if (($f !== false) && (null !== $f)) {
505                        $this->files[] = substr($f, (strlen(realpath($this->path)) + 1));
506                    }
507                // Else, store only the directory or file name.
508                } else {
509                    if (!$this->filesOnly) {
510                        $this->files[] = ($fileInfo->isDir()) ? ($fileInfo->getFilename()) : $fileInfo->getFilename();
511                    } else if (!$fileInfo->isDir()) {
512                        $this->files[] = $fileInfo->getFilename();
513                    }
514                }
515            }
516        }
517    }
518
519    /**
520     * Traverse the directory recursively
521     *
522     * @return void
523     */
524    protected function traverseRecursively()
525    {
526        $objects = new \RecursiveIteratorIterator(
527            new \RecursiveDirectoryIterator($this->path), \RecursiveIteratorIterator::SELF_FIRST
528        );
529        foreach ($objects as $fileInfo) {
530            if (($fileInfo->getFilename() != '.') && ($fileInfo->getFilename() != '..')) {
531                // If absolute path flag was passed, store the absolute path.
532                if ($this->absolute) {
533                    $f = null;
534                    if (!$this->filesOnly) {
535                        $f = ($fileInfo->isDir()) ?
536                            (realpath($fileInfo->getPathname())) : realpath($fileInfo->getPathname());
537                    } else if (!$fileInfo->isDir()) {
538                        $f = realpath($fileInfo->getPathname());
539                    }
540                    if (($f !== false) && (null !== $f)) {
541                        $this->files[] = $f;
542                    }
543                // If relative path flag was passed, store the relative path.
544                } else if ($this->relative) {
545                    $f = null;
546                    if (!$this->filesOnly) {
547                        $f = ($fileInfo->isDir()) ?
548                            (realpath($fileInfo->getPathname())) : realpath($fileInfo->getPathname());
549                    } else if (!$fileInfo->isDir()) {
550                        $f = realpath($fileInfo->getPathname());
551                    }
552                    if (($f !== false) && (null !== $f)) {
553                        $this->files[] = substr($f, (strlen(realpath($this->path)) + 1));
554                    }
555                // Else, store only the directory or file name.
556                } else {
557                    if (!$this->filesOnly) {
558                        $this->files[] = ($fileInfo->isDir()) ? ($fileInfo->getFilename()) : $fileInfo->getFilename();
559                    } else if (!$fileInfo->isDir()) {
560                        $this->files[] = $fileInfo->getFilename();
561                    }
562                }
563            }
564        }
565    }
566
567    /**
568     * Build the directory tree
569     *
570     * @param  \DirectoryIterator $it
571     * @return array
572     */
573    protected function buildTree(\DirectoryIterator $it)
574    {
575        $result = [];
576
577        foreach ($it as $key => $child) {
578            if ($child->isDot()) {
579                continue;
580            }
581
582            $name = $child->getBasename();
583
584            if ($child->isDir()) {
585                $subdir = new \DirectoryIterator($child->getPathname());
586                $result[DIRECTORY_SEPARATOR . $name] = $this->buildTree($subdir);
587            } else {
588                $result[] = $name;
589            }
590        }
591
592        return $result;
593    }
594
595}