Completed
Push — master ( 347ead...a6e359 )
by Tim
03:33 queued 01:54
created

CleanUpLinkObserver::getLastPrimaryKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

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
nc 1
nop 0
crap 2
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
        // load the row/entity ID of the parent product
124
        $parentId = $this->getLastPrimaryKey();
125
126
        // prepare the links for the found link types and clean-up
127
        foreach ($this->linkTypes as $linkTypeCode => $columns) {
128
            // shift the column with the header information from the stack
129
            list ($columnNameChildSkus, $callbackChildSkus) = array_shift($columns);
130
131
            // query whether or not, we've up sell, cross sell or relation products
132
            $links = $this->getValue($columnNameChildSkus, [], $callbackChildSkus);
133
134
            // start the clean-up process, if the appropriate flag has been
135
            // activated, otherwise we've to figure out if the column value
136
            // contains the `__EMPTY__VALUE__` constant. In that case,
137
            // the clean-up columns functionality in the
138
            // AttributeObserverTrait::clearRow() method has NOT unset the
139
            // column which indicates the column has to be cleaned-up.
140
            if ($this->cleanUpLinks === true || ($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...
141
                // clean-up the links in the database
142
                $this->doCleanUp($parentId, $linkTypeCode, $links);
143
            }
144
        }
145
    }
146
147
    /**
148
     * Delete not exists import links from database.
149
     *
150
     * @param int    $parentProductId The ID of the parent product
151
     * @param string $linkTypeCode    The link type code to prepare the artefacts for
152
     * @param array  $childData       The array of variants
153
     *
154
     * @return void
155
     */
156
    protected function doCleanUp($parentProductId, $linkTypeCode, array $childData)
157
    {
158
159
        // load the SKU of the parent product
160
        $parentSku = $this->getValue(ColumnKeys::SKU);
161
162
        // extract the link type code from the row
163
        $linkTypeId = $this->mapLinkTypeCodeToLinkTypeId($linkTypeCode);
164
165
        // remove the old variantes from the database
166
        $this->getProductLinkProcessor()
167
            ->deleteProductLink(
168
                array(
169
                    MemberNames::PRODUCT_ID   => $parentProductId,
170
                    MemberNames::SKU          => $childData,
171
                    MemberNames::LINK_TYPE_ID => $linkTypeId,
172
                )
173
            );
174
175
        // log a debug message that the image has been removed
176
        $this->getSubject()
177
            ->getSystemLogger()
178
            ->debug(
179
                $this->getSubject()->appendExceptionSuffix(
180
                    sprintf(
181
                        'Successfully clean up links for product with SKU "%s" except "%s"',
182
                        $parentSku,
183
                        implode(', ', $childData)
184
                    )
185
                )
186
            );
187
    }
188
189
    /**
190
     * Return's the PK to create the product => variant relation.
191
     *
192
     * @return integer The PK to create the relation with
193
     */
194
    protected function getLastPrimaryKey()
195
    {
196
        return $this->getLastEntityId();
197
    }
198
199
    /**
200
     * Return the link type ID for the passed link type code.
201
     *
202
     * @param string $linkTypeCode The link type code to return the link type ID for
203
     *
204
     * @return integer The mapped link type ID
205
     * @throws \TechDivision\Import\Product\Exceptions\MapLinkTypeCodeToIdException Is thrown if the link type code is
206
     *     not mapped yet
207
     */
208
    protected function mapLinkTypeCodeToLinkTypeId($linkTypeCode)
209
    {
210
        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...
211
    }
212
}
213