Completed
Push — master ( 7263e0...dafc3a )
by Vladimir
10s
created

FileExplorer::__toString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @copyright 2016 Vladimir Jimenez
5
 * @license   https://github.com/allejo/stakx/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\stakx\System;
9
10
use Symfony\Component\Finder\SplFileInfo;
11
12
class FileExplorer extends \RecursiveFilterIterator
13
{
14
    /**
15
     * A list of version control folders to ignore
16
     *
17
     * @var string[]
18
     */
19
    public static $vcsPatterns =  array('.git', '.hg', '.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr');
20
21
    /**
22
     * A list of phrases to exclude from the search
23
     *
24
     * @var string[]
25
     */
26
    private $excludes;
27
28
    /**
29
     * A list of phrases to explicitly include in the search
30
     *
31
     * @var string[]
32
     */
33
    private $includes;
34
35
    /**
36
     * FileExplorer constructor.
37
     *
38
     * @param \RecursiveIterator $iterator
39
     * @param array              $excludes
40
     * @param array              $includes
41
     */
42
    public function __construct(\RecursiveIterator $iterator, array $excludes = array(), array $includes = array())
43
    {
44
        parent::__construct($iterator);
45
46
        $this->excludes = array_merge(self::$vcsPatterns, $excludes);
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge(self::$vcsPatterns, $excludes) of type array is incompatible with the declared type array<integer,string> of property $excludes.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
47
        $this->includes = $includes;
48
    }
49
50
    /**
51
     * @return string
52
     */
53
    public function __toString()
54
    {
55
        return $this->current()->getFilename();
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61
    public function accept ()
62
    {
63
        $filePath = str_replace(getcwd() . '/', '', $this->current()->getPathname());
64
65
        if ($this->strpos_array($filePath, $this->includes)) { return true; }
66
        if (preg_match('#(^|/)\..+(/|$)#', $filePath) === 1) { return false; }
67
68
        return ($this->strpos_array($filePath, $this->excludes) === false);
69
    }
70
71
    /**
72
     * Get the current SplFileInfo object
73
     *
74
     * @return SplFileInfo
75
     */
76
    public function current()
77
    {
78
        /** @var \SplFileInfo $current */
79
        $current = parent::current();
80
81
        return (new SplFileInfo(
82
            $current->getPathname(),
83
            $this->getRelativePath($current->getPath()),
84
            $this->getRelativePath($current->getPathname())
85
        ));
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91
    public function getChildren()
92
    {
93
        return (new self(
94
            $this->getInnerIterator()->getChildren(),
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Iterator as the method getChildren() does only exist in the following implementations of said interface: DoctrineTest\InstantiatorTestAsset\PharAsset, PHPUnit_Runner_Filter_GroupFilterIterator, PHPUnit_Runner_Filter_Group_Exclude, PHPUnit_Runner_Filter_Group_Include, PHPUnit_Runner_Filter_Test, PHPUnit_Util_TestSuiteIterator, PHP_CodeCoverage_Report_Node_Iterator, ParentIterator, Phar, PharData, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveRegexIterator, SimpleXMLIterator, SplFileObject, SplTempFileObject, Symfony\Component\Finder...DirectoryFilterIterator, Symfony\Component\Finder...ursiveDirectoryIterator, allejo\stakx\System\FileExplorer.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
95
            $this->excludes,
96
            $this->includes
97
        ));
98
    }
99
100
    /**
101
     * Get an Iterator with all of the files that have met the search requirements
102
     *
103
     * @return \RecursiveIteratorIterator
104
     */
105
    public function getExplorer ()
106
    {
107
        return (new \RecursiveIteratorIterator($this));
108
    }
109
110
    /**
111
     * Create an instance of FileExplorer from a directory path as a string
112
     *
113
     * @param  string   $folder   The path to the folder we're scanning
114
     * @param  string[] $excludes
115
     * @param  string[] $includes
116
     *
117
     * @return FileExplorer
118
     */
119
    public static function create ($folder, $excludes = array(), $includes = array())
120
    {
121
        $iterator = new \RecursiveDirectoryIterator($folder, \RecursiveDirectoryIterator::SKIP_DOTS);
122
123
        return (new self($iterator, $excludes, $includes));
124
    }
125
126
    /**
127
     * Search a given string for an array of possible elements
128
     *
129
     * @param  string   $haystack
130
     * @param  string[] $needle
131
     * @param  int      $offset
132
     *
133
     * @return bool True if an element from the given array was found in the string
134
     */
135
    private function strpos_array ($haystack, $needle, $offset = 0)
0 ignored issues
show
Coding Style introduced by
This method is not in camel caps format.

This check looks for method names that are not written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection seeker becomes databaseConnectionSeeker.

Loading history...
136
    {
137
        if (!is_array($needle))
138
        {
139
            $needle = array($needle);
140
        }
141
142
        foreach ($needle as $query)
143
        {
144
            if (strpos($haystack, $query, $offset) !== false) // stop on first true result
145
            {
146
                return true;
147
            }
148
        }
149
150
        return false;
151
    }
152
153
    /**
154
     * Strip the current working directory from an absolute path
155
     *
156
     * @param  string $path An absolute path
157
     *
158
     * @return string
159
     */
160
    private function getRelativePath ($path)
161
    {
162
        return str_replace(getcwd() . DIRECTORY_SEPARATOR, '', $path);
163
    }
164
}
165