Completed
Pull Request — master (#87)
by Tim
03:03
created

UrlKeyObserver::getProductBunchProcessor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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\Product\Utils\MemberNames;
26
use TechDivision\Import\Product\Utils\ColumnKeys;
27
use TechDivision\Import\Utils\Filter\UrlKeyFilterTrait;
28
use TechDivision\Import\Product\Services\ProductBunchProcessorInterface;
29
30
/**
31
 * Observer that extracts the URL key from the product name and adds a two new columns
32
 * with the their values.
33
 *
34
 * @author    Tim Wagner <[email protected]>
35
 * @copyright 2016 TechDivision GmbH <[email protected]>
36
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
37
 * @link      https://github.com/techdivision/import-product
38
 * @link      http://www.techdivision.com
39
 */
40
class UrlKeyObserver extends AbstractProductImportObserver
41
{
42
43
    /**
44
     * The trait that provides string => URL key conversion functionality.
45
     *
46
     * @var \TechDivision\Import\Utils\Filter\UrlKeyFilterTrait
47
     */
48
    use UrlKeyFilterTrait;
49
50
    /**
51
     * The product bunch processor instance.
52
     *
53
     * @var \TechDivision\Import\Product\Services\ProductBunchProcessorInterface
54
     */
55
    protected $productBunchProcessor;
56
57
    /**
58
     * Initialize the observer with the passed product bunch processor and filter instance.
59
     *
60
     * @param \TechDivision\Import\Product\Services\ProductBunchProcessorInterface $productBunchProcessor   The product bunch processor instance
61
     * @param \Zend\Filter\FilterInterface                                         $convertLiteralUrlFilter The URL filter instance
62
     */
63
    public function __construct(ProductBunchProcessorInterface $productBunchProcessor, FilterInterface $convertLiteralUrlFilter)
64
    {
65
        $this->productBunchProcessor = $productBunchProcessor;
66
        $this->convertLiteralUrlFilter = $convertLiteralUrlFilter;
67
    }
68
69
    /**
70
     * Return's the product bunch processor instance.
71
     *
72
     * @return \TechDivision\Import\Product\Services\ProductBunchProcessorInterface The product bunch processor instance
73
     */
74
    protected function getProductBunchProcessor()
75
    {
76
        return $this->productBunchProcessor;
77
    }
78
79
    /**
80
     * Process the observer's business logic.
81
     *
82
     * @return void
83
     */
84
    protected function process()
85
    {
86
87
        // prepare the store view code
88
        $this->getSubject()->prepareStoreViewCode();
89
90
        // query whether or not the URL key column has a value
91 View Code Duplication
        if ($this->hasValue(ColumnKeys::URL_KEY)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
92
            $this->setValue(ColumnKeys::URL_KEY, $this->makeUrlKeyUnique($this->getValue(ColumnKeys::URL_KEY)));
93
            return;
94
        }
95
96
        // query whether or not a product name is available
97 View Code Duplication
        if ($this->hasValue(ColumnKeys::NAME)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
98
            $this->setValue(ColumnKeys::URL_KEY, $this->makeUrlKeyUnique($this->convertNameToUrlKey($this->getValue(ColumnKeys::NAME))));
99
            return;
100
        }
101
102
        // throw an exception, that the URL key can not be initialized
103
        $this->getSystemLogger()->debug(
0 ignored issues
show
Deprecated Code introduced by
The method TechDivision\Import\Obse...rver::getSystemLogger() has been deprecated with message: Will be removed with version 1.0.0, use subject method instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
104
            sprintf(
105
                'Can\'t initialize the URL key in CSV file %s on line %d',
106
                $this->getSubject()->getFilename(),
107
                $this->getSubject()->getLineNumber()
108
            )
109
        );
110
    }
111
112
    /**
113
     * Make's the passed URL key unique by adding the next number to the end.
114
     *
115
     * @param string $urlKey The URL key to make unique
116
     *
117
     * @return string The unique URL key
118
     */
119
    protected function makeUrlKeyUnique($urlKey)
120
    {
121
122
        // initialize the entity type ID
123
        $entityType = $this->getEntityType();
124
        $entityTypeId = (integer) $entityType[MemberNames::ENTITY_TYPE_ID];
125
126
        // initialize the store view ID, use the admin store view if no store view has
127
        // been set, because the default url_key value has been set in admin store view
128
        $storeId = $this->getSubject()->getRowStoreId(StoreViewCodes::ADMIN);
0 ignored issues
show
Bug introduced by
The method getRowStoreId() does not exist on TechDivision\Import\Subjects\SubjectInterface. Did you maybe mean getRow()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
129
130
        // initialize the counter
131
        $counter = 0;
132
133
        // initialize the counters
134
        $matchingCounters = array();
135
        $notMatchingCounters = array();
136
137
        // pre-initialze the URL key to query for
138
        $value = $urlKey;
139
140
        do {
141
            // try to load the attribute
142
            $productVarcharAttribute = $this->getProductBunchProcessor()
143
                                            ->loadProductVarcharAttributeByAttributeCodeAndEntityTypeIdAndStoreIdAndValue(
144
                                                MemberNames::URL_KEY,
145
                                                $entityTypeId,
146
                                                $storeId,
147
                                                $value
148
                                            );
149
150
            // try to load the product's URL key
151
            if ($productVarcharAttribute) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $productVarcharAttribute of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
152
                // this IS the URL key of the passed entity
153
                if ($this->isUrlKeyOf($productVarcharAttribute)) {
154
                    $matchingCounters[] = $counter;
155
                } else {
156
                    $notMatchingCounters[] = $counter;
157
                }
158
159
                // prepare the next URL key to query for
160
                $value = sprintf('%s-%d', $urlKey, ++$counter);
161
            }
162
0 ignored issues
show
Coding Style introduced by
Blank line found at end of control structure
Loading history...
163
        } while ($productVarcharAttribute);
0 ignored issues
show
Bug Best Practice introduced by
The expression $productVarcharAttribute of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
164
165
        // sort the array ascending according to the counter
166
        asort($matchingCounters);
167
        asort($notMatchingCounters);
168
169
        // this IS the URL key of the passed entity => we've an UPDATE
170
        if (sizeof($matchingCounters) > 0) {
171
            // load highest counter
172
            $counter = end($matchingCounters);
173
            // if the counter is > 0, we've to append it to the new URL key
174
            if ($counter > 0) {
175
                $urlKey = sprintf('%s-%d', $urlKey, $counter);
176
            }
177
        } elseif (sizeof($notMatchingCounters) > 0) {
178
            // create a new URL key by raising the counter
179
            $newCounter = end($notMatchingCounters);
180
            $urlKey = sprintf('%s-%d', $urlKey, ++$newCounter);
181
        }
182
183
        // return the passed URL key, if NOT
184
        return $urlKey;
185
    }
186
187
    /**
188
     * Return's the entity type for the configured entity type code.
189
     *
190
     * @return array The requested entity type
191
     * @throws \Exception Is thrown, if the requested entity type is not available
192
     */
193
    protected function getEntityType()
194
    {
195
        return $this->getSubject()->getEntityType();
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 getEntityType() does only exist in the following implementations of said interface: 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...
196
    }
197
198
    /**
199
     * Return's TRUE, if the passed URL key varchar value IS related with the actual PK.
200
     *
201
     * @param array $productVarcharAttribute The varchar value to check
202
     *
203
     * @return boolean TRUE if the URL key is related, else FALSE
204
     */
205
    protected function isUrlKeyOf(array $productVarcharAttribute)
206
    {
207
        return $this->getSubject()->isUrlKeyOf($productVarcharAttribute);
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 isUrlKeyOf() does only exist in the following implementations of said interface: 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...
208
    }
209
}
210