Completed
Pull Request — master (#28)
by Tim
04:19 queued 02:59
created

CleanUpLinkObserver::process()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 33
rs 9.0808
c 0
b 0
f 0
cc 5
nc 4
nop 0
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Link\Observers\CleanUpLinkObserver
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    Martin Eisenführer <[email protected]>
15
 * @copyright 2020 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-variant
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\Link\Observers;
22
23
use TechDivision\Import\Subjects\SubjectInterface;
24
use TechDivision\Import\Observers\StateDetectorInterface;
25
use TechDivision\Import\Observers\ObserverFactoryInterface;
26
use TechDivision\Import\Product\Link\Utils\ColumnKeys;
27
use TechDivision\Import\Product\Link\Utils\MemberNames;
28
use TechDivision\Import\Product\Link\Utils\ConfigurationKeys;
29
use TechDivision\Import\Product\Link\Services\ProductLinkProcessorInterface;
30
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
31
32
/**
33
 * Observer that cleans-up product link relation information.
34
 *
35
 * @author    Martin Eisenführer <[email protected]>
36
 * @copyright 2020 TechDivision GmbH <[email protected]>
37
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
38
 * @link      https://github.com/techdivision/import-product-variant
39
 * @link      http://www.techdivision.com
40
 */
41
class CleanUpLinkObserver extends AbstractProductImportObserver implements ObserverFactoryInterface
42
{
43
44
    /**
45
     * The product link processor instance.
46
     *
47
     * @var \TechDivision\Import\Product\Link\Services\ProductLinkProcessorInterface
48
     */
49
    protected $productLinkProcessor;
50
51
    /**
52
     * The array with the link types.
53
     *
54
     * @var array
55
     */
56
    protected $linkTypes = array();
57
58
    /**
59
     * The flag to query whether or not the link types has to be cleaned-up.
60
     *
61
     * @var boolean
62
     */
63
    protected $cleanUpLinks = false;
64
65
    /**
66
     * Initialize the observer with the passed product link processor instance.
67
     *
68
     * @param \TechDivision\Import\Product\Link\Services\ProductLinkProcessorInterface $productLinkProcessor The
69
     *                                                                                                       product link processor instance
70
     * @param StateDetectorInterface|null                                              $stateDetector        The state detector instance to use
71
     */
72
    public function __construct(
73
        ProductLinkProcessorInterface $productLinkProcessor,
74
        StateDetectorInterface $stateDetector = null
75
    ) {
76
77
        // pass the state detector to the parent constructor
78
        parent::__construct($stateDetector);
79
80
        // initialize the product link processor instance
81
        $this->productLinkProcessor = $productLinkProcessor;
82
    }
83
84
    /**
85
     * Will be invoked by the observer visitor when a factory has been defined to create the observer instance.
86
     *
87
     * @param \TechDivision\Import\Subjects\SubjectInterface $subject The subject instance
88
     *
89
     * @return \TechDivision\Import\Observers\ObserverInterface The observer instance
90
     */
91
    public function createObserver(SubjectInterface $subject)
92
    {
93
94
        // load the link type mappings
95
        $this->linkTypes = $subject->getLinkTypeMappings();
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 getLinkTypeMappings() does only exist in the following implementations of said interface: TechDivision\Import\Prod...nk\Subjects\LinkSubject, 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...
96
97
        // query whether or not the product links has to be cleaned-up
98
        $this->cleanUpLinks = $subject->getConfiguration()->hasParam(ConfigurationKeys::CLEAN_UP_LINKS) &&
99
                              $subject->getConfiguration()->getParam(ConfigurationKeys::CLEAN_UP_LINKS, false);
100
101
        // return the initialized instance
102
        return $this;
103
    }
104
105
    /**
106
     * Return's the product variant processor instance.
107
     *
108
     * @return \TechDivision\Import\Product\Link\Services\ProductLinkProcessorInterface The product variant processor
109
     *     instance
110
     */
111
    protected function getProductLinkProcessor()
112
    {
113
        return $this->productLinkProcessor;
114
    }
115
116
    /**
117
     * Process the observer's business logic.
118
     *
119
     * @return void
120
     */
121
    protected function process()
122
    {
123
124
        // load the row/entity ID of the parent product
125
        $parentId = $this->getLastPrimaryKey();
126
127
        // prepare the links for the found link types and clean-up
128
        foreach ($this->linkTypes as $linkTypeCode => $columns) {
129
            // shift the column with the header information from the stack
130
            list ($columnNameChildSkus, $callbackChildSkus) = array_shift($columns);
131
132
            // start the clean-up process, if the appropriate flag has been
133
            // activated, otherwise we've to figure out if the column value
134
            // contains the `__EMPTY__VALUE__` constant
135
            if ($this->cleanUpLinks === true) {
136
                // query whether or not, we've up sell, cross sell or relation products
137
                $links = $this->getValue($columnNameChildSkus, array(), $callbackChildSkus);
138
                // clean-up the links in the database
139
                $this->doCleanUp($parentId, $linkTypeCode, $links);
140
            } else {
141
                // query whether or not, we've up sell, cross sell or relation products
142
                $links = $this->getValue($columnNameChildSkus, array(), $callbackChildSkus);
143
                // This handles the case with the `__EMPTY__VALUE__` constant
144
                // when the directive `clean-up-links` has been deactivated.
145
                // In that case, the clean-up columns functionality in the
146
                // AttributeObserverTrait::clearRow() method has NOT unset the
147
                // column which indicates the column has to be cleaned-up.
148
                if ($this->hasColumn($columnNameChildSkus) && sizeof($links) === 0) {
0 ignored issues
show
Bug introduced by
The method hasColumn() does not seem to exist on object<TechDivision\Impo...rs\CleanUpLinkObserver>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
149
                    $this->doCleanUp($parentId, $linkTypeCode, $links);
150
                }
151
            }
152
        }
153
    }
154
155
    /**
156
     * Delete not exists import links from database.
157
     *
158
     * @param int    $parentProductId The ID of the parent product
159
     * @param string $linkTypeCode    The link type code to prepare the artefacts for
160
     * @param array  $childData       The array of variants
161
     *
162
     * @return void
163
     */
164
    protected function doCleanUp($parentProductId, $linkTypeCode, array $childData)
165
    {
166
167
        // load the SKU of the parent product
168
        $parentSku = $this->getValue(ColumnKeys::SKU);
169
170
        // extract the link type code from the row
171
        $linkTypeId = $this->mapLinkTypeCodeToLinkTypeId($linkTypeCode);
172
173
        // remove the old variantes from the database
174
        $this->getProductLinkProcessor()
175
            ->deleteProductLink(
176
                array(
177
                    MemberNames::PRODUCT_ID   => $parentProductId,
178
                    MemberNames::SKU          => $childData,
179
                    MemberNames::LINK_TYPE_ID => $linkTypeId,
180
                )
181
            );
182
183
        // log a debug message that the image has been removed
184
        $this->getSubject()
185
            ->getSystemLogger()
186
            ->debug(
187
                $this->getSubject()->appendExceptionSuffix(
188
                    sprintf(
189
                        'Successfully clean up links for product with SKU "%s" except "%s"',
190
                        $parentSku,
191
                        implode(', ', $childData)
192
                    )
193
                )
194
            );
195
    }
196
197
    /**
198
     * Return's the PK to create the product => variant relation.
199
     *
200
     * @return integer The PK to create the relation with
201
     */
202
    protected function getLastPrimaryKey()
203
    {
204
        return $this->getLastEntityId();
205
    }
206
207
    /**
208
     * Return the link type ID for the passed link type code.
209
     *
210
     * @param string $linkTypeCode The link type code to return the link type ID for
211
     *
212
     * @return integer The mapped link type ID
213
     * @throws \TechDivision\Import\Product\Exceptions\MapLinkTypeCodeToIdException Is thrown if the link type code is
214
     *     not mapped yet
215
     */
216
    protected function mapLinkTypeCodeToLinkTypeId($linkTypeCode)
217
    {
218
        return $this->getSubject()->mapLinkTypeCodeToLinkTypeId($linkTypeCode);
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 mapLinkTypeCodeToLinkTypeId() does only exist in the following implementations of said interface: TechDivision\Import\Prod...nk\Subjects\LinkSubject, 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...
219
    }
220
}
221