Completed
Pull Request — master (#162)
by Tim
18:25 queued 08:27
created

UrlKeyObserver::process()   C

Complexity

Conditions 11
Paths 30

Size

Total Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 0
Metric Value
dl 0
loc 52
ccs 0
cts 19
cp 0
rs 6.9006
c 0
b 0
f 0
cc 11
nc 30
nop 0
crap 132

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Observers\UrlKeyObserver
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import-product
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\Observers;
22
23
use Zend\Filter\FilterInterface;
24
use TechDivision\Import\Utils\StoreViewCodes;
25
use TechDivision\Import\Utils\UrlKeyUtilInterface;
26
use TechDivision\Import\Utils\Filter\UrlKeyFilterTrait;
27
use TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface;
28
use TechDivision\Import\Product\Utils\MemberNames;
29
use TechDivision\Import\Product\Utils\ColumnKeys;
30
use TechDivision\Import\Product\Utils\ConfigurationKeys;
31
use TechDivision\Import\Product\Services\ProductBunchProcessorInterface;
32
use TechDivision\Import\Utils\Generators\GeneratorInterface;
33
34
/**
35
 * Observer that extracts the URL key from the product name and adds a two new columns
36
 * with the their values.
37
 *
38
 * @author    Tim Wagner <[email protected]>
39
 * @copyright 2016 TechDivision GmbH <[email protected]>
40
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
41
 * @link      https://github.com/techdivision/import-product
42
 * @link      http://www.techdivision.com
43
 */
44
class UrlKeyObserver extends AbstractProductImportObserver
45
{
46
47
    /**
48
     * The trait that provides string => URL key conversion functionality.
49
     *
50
     * @var \TechDivision\Import\Utils\Filter\UrlKeyFilterTrait
51
     */
52
    use UrlKeyFilterTrait;
53
54
    /**
55
     * The URL key utility instance.
56
     *
57
     * @var \TechDivision\Import\Utils\UrlKeyUtilInterface
58
     */
59
    protected $urlKeyUtil;
60
61
    /**
62
     * The product bunch processor instance.
63
     *
64
     * @var \TechDivision\Import\Product\Services\ProductBunchProcessorInterface
65
     */
66
    protected $productBunchProcessor;
67
68
    /**
69
     * The reverse sequence generator instance.
70
     *
71
     * @var \TechDivision\Import\Utils\Generators\GeneratorInterface
72
     */
73
    protected $reverseSequenceGenerator;
74
75
    /**
76
     * Initialize the observer with the passed product bunch processor and filter instance.
77
     *
78
     * @param \TechDivision\Import\Product\Services\ProductBunchProcessorInterface $productBunchProcessor    The product bunch processor instance
79
     * @param \Zend\Filter\FilterInterface                                         $convertLiteralUrlFilter  The URL filter instance
80
     * @param \TechDivision\Import\Utils\UrlKeyUtilInterface                       $urlKeyUtil               The URL key utility instance
81
     * @param \TechDivision\Import\Utils\Generators\GeneratorInterface             $reverseSequenceGenerator The reverse sequence generator instance
82
     */
83
    public function __construct(
84
        ProductBunchProcessorInterface $productBunchProcessor,
85
        FilterInterface $convertLiteralUrlFilter,
86
        UrlKeyUtilInterface $urlKeyUtil,
87
        GeneratorInterface $reverseSequenceGenerator
88
    ) {
89
        $this->productBunchProcessor = $productBunchProcessor;
90
        $this->convertLiteralUrlFilter = $convertLiteralUrlFilter;
91
        $this->urlKeyUtil = $urlKeyUtil;
92
        $this->reverseSequenceGenerator = $reverseSequenceGenerator;
93
    }
94
95
    /**
96
     * Return's the product bunch processor instance.
97
     *
98
     * @return \TechDivision\Import\Product\Services\ProductBunchProcessorInterface The product bunch processor instance
99
     */
100
    protected function getProductBunchProcessor() : ProductBunchProcessorInterface
101
    {
102
        return $this->productBunchProcessor;
103
    }
104
105
    /**
106
     * Returns the URL key utility instance.
107
     *
108
     * @return \TechDivision\Import\Utils\UrlKeyUtilInterface The URL key utility instance
109
     */
110
    protected function getUrlKeyUtil() : UrlKeyUtilInterface
111
    {
112
        return $this->urlKeyUtil;
113
    }
114
115
    /**
116
     * Returns the reverse sequence generator instance.
117
     *
118
     * @return \TechDivision\Import\Utils\Generators\GeneratorInterface The reverse sequence generator
119
     */
120
    protected function getReverseSequenceGenerator() : GeneratorInterface
121
    {
122
        return $this->reverseSequenceGenerator;
123
    }
124
125
    /**
126
     * Process the observer's business logic.
127
     *
128
     * @return void
129
     * @throws \Exception Is thrown, if either column "url_key" or "name" have a value set
130
     */
131
    protected function process()
132
    {
133
134
        // initialize the URL key, the entity and the product
135
        $urlKey = null;
136
        $entity = null;
0 ignored issues
show
Unused Code introduced by
$entity is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
137
        $product = array();
138
139
        // prepare the store view code
140
        $this->getSubject()->prepareStoreViewCode();
141
142
        // set the entity ID for the product with the passed SKU
143
        if ($entity = $this->loadProduct($sku = $this->getValue(ColumnKeys::SKU))) {
144
            $this->setIds($product = $entity);
145
        } else {
146
            $this->setIds(array());
147
            $product[MemberNames::ENTITY_ID] = $this->getReverseSequenceGenerator()->generate();
148
        }
149
150
        // query whether or not the URL key column has a value
151
        if ($this->hasValue(ColumnKeys::URL_KEY)) {
152
            $urlKey = $this->getValue(ColumnKeys::URL_KEY);
153
        } else {
154
            // query whether or not the existing product `url_key` should be re-created from the product name
155
            if (is_array($entity) && !$this->getSubject()->getConfiguration()->getParam(ConfigurationKeys::UPDATE_URL_KEY_FROM_NAME, true)) {
156
                // if the product already exists and NO re-creation from the product name has to
157
                // be done, load the original `url_key`from the product and use that to proceed
158
                $urlKey = $this->loadUrlKey($this->getSubject(), $this->getPrimaryKey());
0 ignored issues
show
Documentation introduced by
$this->getSubject() is of type object<TechDivision\Impo...jects\SubjectInterface>, but the function expects a object<TechDivision\Impo...yAwareSubjectInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
159
            }
160
161
            // try to load the value from column `name` if URL key is still
162
            // empty, because we need it to process the the rewrites later on
163
            if ($urlKey === null || $urlKey === '' && $this->hasValue(ColumnKeys::NAME)) {
164
                $urlKey = $this->convertNameToUrlKey($this->getValue(ColumnKeys::NAME));
165
            }
166
        }
167
168
        // stop processing, if no URL key is available
169
        if ($urlKey === null || $urlKey === '') {
170
            // throw an exception, that the URL key can not be
171
            // initialized and we're in the default store view
172
            if ($this->getStoreViewCode(StoreViewCodes::ADMIN) === StoreViewCodes::ADMIN) {
173
                throw new \Exception(sprintf('Can\'t initialize the URL key for product "%s" because columns "url_key" or "name" have a value set for default store view', $sku));
174
            }
175
            // stop processing, because we're in a store
176
            // view row and a URL key is not mandatory
177
            return;
178
        }
179
180
        // set the unique URL key for further processing
181
        $this->setValue(ColumnKeys::URL_KEY, $this->makeUnique($this->getSubject(), $product, $urlKey, $this->getUrlPaths()));
0 ignored issues
show
Documentation introduced by
$this->getSubject() is of type object<TechDivision\Impo...jects\SubjectInterface>, but the function expects a object<TechDivision\Impo...yAwareSubjectInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
182
    }
183
184
    /**
185
     * Extract's the category from the comma separeted list of categories
186
     * in column `categories` and return's an array with their URL paths.
187
     *
188
     * @return string[] Array with the URL paths of the categories found in column `categories`
189
     */
190
    protected function getUrlPaths()
191
    {
192
193
        // initialize the array for the URL paths of the cateogries
194
        $urlPaths = array();
195
196
        // extract the categories from the column `categories`
197
        $categories = $this->getValue(ColumnKeys::CATEGORIES, array(), array($this, 'explode'));
198
199
        // the URL paths are store view specific, so we need
200
        // the store view code to load the appropriate ones
201
        $storeViewCode = $this->getStoreViewCode(StoreViewCodes::ADMIN);
202
203
        // iterate of the found categories, load their URL path as well as the URL path of
204
        // parent categories, if they have the anchor flag activated and add it the array
205
        foreach ($categories as $path) {
206
            // load the category based on the category path
207
            $category = $this->getCategoryByPath($path, $storeViewCode);
208
            // try to resolve the URL paths recursively
209
            $this->resolveUrlPaths($urlPaths, $category, $storeViewCode);
210
        }
211
212
        // return the array with the recursively resolved URL paths
213
        // of the categories that are related with the products
214
        return $urlPaths;
215
    }
216
217
    /**
218
     * Recursively resolves an array with the store view specific
219
     * URL paths of the passed category.
220
     *
221
     * @param array  $urlPaths       The array to append the URL paths to
222
     * @param array  $category       The category to resolve the list with URL paths
223
     * @param string $storeViewCode  The store view code to resolve the URL paths for
224
     * @param bool   $directRelation the flag whether or not the passed category is a direct relation to the product and has to added to the list
225
     *
226
     * @return void
227
     */
228
    protected function resolveUrlPaths(array &$urlPaths, array $category, string $storeViewCode, bool $directRelation = true)
229
    {
230
231
        // load the root category
232
        $rootCategory = $this->getRootCategory();
233
234
        // try to resolve the parent category IDs, but only if either
235
        // the actual category nor it's parent is a root category
236
        if ($rootCategory[MemberNames::ENTITY_ID] !== $category[MemberNames::ENTITY_ID] &&
237
            $rootCategory[MemberNames::ENTITY_ID] !== $category[MemberNames::PARENT_ID]
238
        ) {
239
            // load the parent category
240
            $parent = $this->getCategory($category[MemberNames::PARENT_ID], $storeViewCode);
241
            // also resolve the URL paths for the parent category
242
            $this->resolveUrlPaths($urlPaths, $parent, $storeViewCode, false);
243
        }
244
245
        // query whether or not the URL path already
246
        // is part of the list (to avoid duplicates)
247
        if (in_array($category[MemberNames::URL_PATH], $urlPaths)) {
248
            return;
249
        }
250
251
        // append the URL path, either if we've a direct relation
252
        // or the category has the anchor flag actvated
253
        if ($directRelation === true || ($category[MemberNames::IS_ANCHOR] === 1 && $directRelation === false)) {
254
            $urlPaths[] = $category[MemberNames::URL_PATH];
255
        }
256
    }
257
258
    /**
259
     * Temporarily persist's the IDs of the passed product.
260
     *
261
     * @param array $product The product to temporarily persist the IDs for
262
     *
263
     * @return void
264
     */
265
    protected function setIds(array $product)
266
    {
267
        $this->setLastEntityId(isset($product[MemberNames::ENTITY_ID]) ? $product[MemberNames::ENTITY_ID] : null);
268
    }
269
270
    /**
271
     * Set's the ID of the product that has been created recently.
272
     *
273
     * @param string $lastEntityId The entity ID
274
     *
275
     * @return void
276
     */
277
    protected function setLastEntityId($lastEntityId)
278
    {
279
        $this->getSubject()->setLastEntityId($lastEntityId);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method setLastEntityId() does only exist in the following implementations of said interface: TechDivision\Import\Plugins\ExportableSubjectImpl, TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Subjects\ExportableTraitImpl.

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...
280
    }
281
282
    /**
283
     * Return's the PK to of the product.
284
     *
285
     * @return integer The PK to create the relation with
286
     */
287
    protected function getPrimaryKey()
288
    {
289
        $this->getSubject()->getLastEntityId();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getLastEntityId() does only exist in the following implementations of said interface: TechDivision\Import\Observers\EntitySubjectImpl, TechDivision\Import\Plugins\ExportableSubjectImpl, TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Subjects\ExportableTraitImpl.

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...
290
    }
291
292
    /**
293
     * Load's and return's the product with the passed SKU.
294
     *
295
     * @param string $sku The SKU of the product to load
296
     *
297
     * @return array The product
298
     */
299
    protected function loadProduct($sku)
300
    {
301
        return $this->getProductBunchProcessor()->loadProduct($sku);
302
    }
303
304
    /**
305
     * Load's and return's the url_key with the passed primary ID.
306
     *
307
     * @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject      The subject to load the URL key
308
     * @param int                                                       $primaryKeyId The ID from product
309
     *
310
     * @return string|null url_key or null
311
     */
312
    protected function loadUrlKey(UrlKeyAwareSubjectInterface $subject, $primaryKeyId)
313
    {
314
        return $this->getUrlKeyUtil()->loadUrlKey($subject, $primaryKeyId);
315
    }
316
317
    /**
318
     * Return's the category with the passed path.
319
     *
320
     * @param string $path          The path of the category to return
321
     * @param string $storeViewCode The code of a store view, defaults to admin
322
     *
323
     * @return array The category
324
     */
325
    protected function getCategoryByPath($path, $storeViewCode = StoreViewCodes::ADMIN)
0 ignored issues
show
Unused Code introduced by
The parameter $storeViewCode is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
326
    {
327
        return $this->getSubject()->getCategoryByPath($path);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getCategoryByPath() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject.

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...
328
    }
329
330
    /**
331
     * Make's the passed URL key unique by adding the next number to the end.
332
     *
333
     * @param \TechDivision\Import\Subjects\UrlKeyAwareSubjectInterface $subject  The subject to make the URL key unique for
334
     * @param array                                                     $entity   The entity to make the URL key unique for
335
     * @param string                                                    $urlKey   The URL key to make unique
336
     * @param array                                                     $urlPaths The URL paths to make unique
337
     *
338
     * @return string The unique URL key
339
     */
340
    protected function makeUnique(UrlKeyAwareSubjectInterface $subject, array $entity, string $urlKey, array $urlPaths = array())
341
    {
342
        return $this->getUrlKeyUtil()->makeUnique($subject, $entity, $urlKey, $urlPaths);
343
    }
344
345
    /**
346
     * Return's the root category for the actual view store.
347
     *
348
     * @return array The store's root category
349
     * @throws \Exception Is thrown if the root category for the passed store code is NOT available
350
     */
351
    protected function getRootCategory()
352
    {
353
        return $this->getSubject()->getRootCategory();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getRootCategory() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Subjects\AbstractEavSubject, TechDivision\Import\Subjects\AbstractSubject, TechDivision\Import\Subjects\MoveFilesSubject, TechDivision\Import\Subjects\ValidatorSubject.

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...
354
    }
355
356
    /**
357
     * Return's the category with the passed ID.
358
     *
359
     * @param integer $categoryId    The ID of the category to return
360
     * @param string  $storeViewCode The code of a store view, defaults to "admin"
361
     *
362
     * @return array The category data
363
     */
364
    protected function getCategory($categoryId, $storeViewCode = StoreViewCodes::ADMIN)
365
    {
366
        return $this->getSubject()->getCategory($categoryId, $storeViewCode);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getCategory() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject.

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...
367
    }
368
}
369