GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

LayeredNavigation::extract()   B
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 40
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 40
rs 8.439
c 0
b 0
f 0
cc 5
eloc 24
nc 5
nop 0
1
<?php
2
3
namespace Magium\Magento\Extractors\Catalog\LayeredNavigation;
4
5
use Magium\Magento\AbstractMagentoTestCase;
6
use Magium\Magento\Themes\AbstractThemeConfiguration;
7
use Magium\WebDriver\WebDriver;
8
use Magium\Extractors\AbstractExtractor;
9
use Magium\Magento\Extractors\Catalog\LayeredNavigation\FilterTypes\AbstractFilterType;
10
11
/**
12
 * This is not part of the category namespace because it can be reused for search
13
 *
14
 * Class LayeredNavigation
15
 * @package Magium\Magento\Extractors\Catalog
16
 */
17
18
class LayeredNavigation extends AbstractExtractor
19
{
20
    const EXTRACTOR = 'Catalog\LayeredNavigation\LayeredNavigation';
21
22
    const FILTER_TYPE_DEFAULT = 'DefaultFilter';
23
    const FILTER_TYPE_PRICE = 'PriceFilter';
24
    const FILTER_TYPE_SWATCH = 'SwatchFilter';
25
26
    protected $filterTypes = [];
27
28
    protected $baseNamespace = 'Magium\Magento\Extractors\Catalog\LayeredNavigation\FilterTypes';
29
30
    protected $filterNames = [];
31
    protected $filterValues = [];
32
33
34
    public function __construct(
35
        WebDriver           $webDriver,
36
        AbstractMagentoTestCase    $testCase,
37
        AbstractThemeConfiguration $theme
38
    ) {
39
        parent::__construct($webDriver, $testCase, $theme);
40
        $this->addFilterType(self::FILTER_TYPE_DEFAULT);
41
        $this->addFilterType(self::FILTER_TYPE_PRICE);
42
        $this->addFilterType(self::FILTER_TYPE_SWATCH);
43
    }
44
45
46
    /**
47
     * The purpose of this method is to allow shorthand calls to FilterTypes/* classes.
48
     *
49
     * @param $type
50
     * @return string
51
     * @throws InvalidFilterException
52
     */
53
54
    protected function filterTypeName($type)
55
    {
56
        $returnType = $type;
57
        if (strpos($type, '\\') === false) {
58
            $returnType = $this->baseNamespace . '\\' . $type;
59
            if (!class_exists($returnType)) {
60
                $returnType = $type;
61
                if (!class_exists($returnType)) {
62
                    throw new InvalidFilterException('Filter type must exist: ' . $returnType);
63
                }
64
            }
65
        }
66
        $this->validateFilterType($returnType);
67
        return $returnType;
68
    }
69
70
    protected function validateFilterType($returnType)
71
    {
72
        if ($returnType == $this->baseNamespace . '\AbstractFilterType') {
73
            return true;
74
        }
75
        $reflection = new \ReflectionClass($returnType);
76
        if (!$reflection->isSubclassOf($this->baseNamespace . '\AbstractFilterType')) {
77
            throw new InvalidFilterException('Filter type must extend AbstractFilterType: ' . $returnType);
78
        }
79
        return true;
80
    }
81
82
83
    public function addFilterType($type)
84
    {
85
        $type = $this->filterTypeName($type);
86
87
        array_unshift($this->filterTypes, $type);
88
    }
89
90
91
    public function removeFilterType($type)
92
    {
93
        $type = $this->filterTypeName($type);
94
        $key = array_search($type, $this->filterTypes);
95
        if ($key !== false) {
96
            unset($this->filterTypes[$key]);
97
        }
98
    }
99
100
    public function replaceFilterType($replace, $with)
101
    {
102
        $replace = $this->filterTypeName($replace);
103
        $with = $this->filterTypeName($with);
104
        $key = array_search($replace, $this->filterTypes);
105
        if ($key !== false) {
106
            $this->filterTypes[$key] = $with;
107
        }
108
    }
109
110
    /**
111
     * For the purposes of this extractor filters refer both to filters and facets
112
     *
113
     * @return array
114
     */
115
116
    public function getFilterNames()
117
    {
118
        return $this->filterNames;
119
    }
120
121
    /**
122
     * @param $filter
123
     * @return AbstractFilterType
124
     * @throws InvalidFilterException
125
     */
126
127
    public function getFilter($filter)
128
    {
129
        $filterKey = strtolower($filter);
130
        if (isset($this->filterValues[$filterKey])) {
131
            return $this->filterValues[$filterKey];
132
        }
133
134
        throw new MissingFilterException('Could not find the filter: ' . $filter);
135
    }
136
137
    public function extract()
138
    {
139
140
        $filters = $this->webDriver->byXpath($this->theme->getLayeredNavigationBaseXpath());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Magium\Themes\ThemeConfigurationInterface as the method getLayeredNavigationBaseXpath() does only exist in the following implementations of said interface: Magium\Magento\Themes\AbstractThemeConfiguration, Magium\Magento\Themes\Magento18\ThemeConfiguration, Magium\Magento\Themes\Magento19\ThemeConfiguration, Magium\Magento\Themes\Ma...E112\ThemeConfiguration, Magium\Magento\Themes\Ma...E113\ThemeConfiguration, Magium\Magento\Themes\Ma...E114\ThemeConfiguration.

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...
141
        /* I cannot find any way that normalizes filter types without requiring XPath 2 or other shenanigans.  For that
142
         * reason I am pulling just the HTML and doing direct XPath queries on it; so I can normalize the filter types
143
         */
144
        $html = $filters->getAttribute('innerHTML');
145
        $doc = new \DOMDocument();
146
        $doc->loadHTML($html);
147
148
        $xPath = new \DOMXPath($doc);
149
        $filters = $xPath->query($this->theme->getLayeredNavigationFilterNameXpath());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Magium\Themes\ThemeConfigurationInterface as the method getLayeredNavigationFilterNameXpath() does only exist in the following implementations of said interface: Magium\Magento\Themes\AbstractThemeConfiguration, Magium\Magento\Themes\Magento18\ThemeConfiguration, Magium\Magento\Themes\Magento19\ThemeConfiguration, Magium\Magento\Themes\Ma...E112\ThemeConfiguration, Magium\Magento\Themes\Ma...E113\ThemeConfiguration, Magium\Magento\Themes\Ma...E114\ThemeConfiguration.

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...
150
        /* @var $filters \DOMElement[] */
151
        foreach ($filters as $filter) {
152
            $this->filterNames[] = $filter = trim($filter->nodeValue);
153
            $filterKey = strtolower($filter);
154
            $this->filterValues[$filterKey] = [];
155
156
            foreach ($this->filterTypes as $type) {
157
                if (is_subclass_of($type, $this->filterTypeName('AbstractFilterType'))) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $this->filterTypeName('AbstractFilterType') can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
158
                    $type = new $type(
159
                        $filter,
160
                        $doc,
161
                        $this->testCase,
162
                        $this->theme,
163
                        $this->webDriver
164
                    );
165
                    if ($type->filterApplies()) {
166
                        $this->filterValues[$filterKey] = $type;
167
                        break;
168
                    }
169
                } else {
170
                    throw new InvalidFilterException('Filter type does not extend AbstractFilterType: ' . $type);
171
                }
172
            }
173
        }
174
175
176
    }
177
}