Completed
Pull Request — master (#28)
by Tim
15:52 queued 05:52
created

CleanUpLinkObserver::hasColumn()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 2
nc 2
nop 1
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\Observers\StateDetectorInterface;
24
use TechDivision\Import\Product\Link\Services\ProductLinkProcessorInterface;
25
use TechDivision\Import\Product\Link\Utils\ColumnKeys;
26
use TechDivision\Import\Product\Link\Utils\ConfigurationKeys;
27
use TechDivision\Import\Product\Link\Utils\MemberNames;
28
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
29
30
/**
31
 * Observer that cleaned up a product's link information.
32
 *
33
 * @author    Martin Eisenführer <[email protected]>
34
 * @copyright 2020 TechDivision GmbH <[email protected]>
35
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
36
 * @link      https://github.com/techdivision/import-product-variant
37
 * @link      http://www.techdivision.com
38
 */
39
class CleanUpLinkObserver extends AbstractProductImportObserver
40
{
41
42
    /**
43
     * The product link processor instance.
44
     *
45
     * @var \TechDivision\Import\Product\Link\Services\ProductLinkProcessorInterface
46
     */
47
    protected $productLinkProcessor;
48
49
    /**
50
     * Initialize the observer with the passed product link processor instance.
51
     *
52
     * @param \TechDivision\Import\Product\Link\Services\ProductLinkProcessorInterface $productLinkProcessor The
53
     *                                                                                                       product link processor instance
54
     * @param StateDetectorInterface|null                                              $stateDetector        The state detector instance to use
55
     */
56
    public function __construct(
57
        ProductLinkProcessorInterface $productLinkProcessor,
58
        StateDetectorInterface $stateDetector = null
59
    ) {
60
61
        // pass the state detector to the parent constructor
62
        parent::__construct($stateDetector);
63
64
        // initialize the product link processor instance
65
        $this->productLinkProcessor = $productLinkProcessor;
66
    }
67
68
    /**
69
     * Return's the product variant processor instance.
70
     *
71
     * @return \TechDivision\Import\Product\Link\Services\ProductLinkProcessorInterface The product variant processor
72
     *     instance
73
     */
74
    protected function getProductLinkProcessor()
75
    {
76
        return $this->productLinkProcessor;
77
    }
78
79
    /**
80
     * Process the observer's business logic.
81
     *
82
     * @return void
83
     * @throws \Exception
84
     */
85
    protected function process()
86
    {
87
88
        // load the row/entity ID of the parent product
89
        $parentId = $this->getLastPrimaryKey();
90
91
        // load the link type mappings
92
        $linkTypes = $this->getSubject()->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...
93
94
        // prepare the links for the found link types and clean up
95
        foreach ($linkTypes as $linkTypeCode => $columns) {
96
            // shift the column with the header information from the stack
97
            list ($columnNameChildSkus, $callbackChildSkus) = array_shift($columns);
98
            // query whether or not, we've up sell, cross sell or relation products
99
            $links = $this->getValue($columnNameChildSkus, array(), $callbackChildSkus);
100
101
            // query whether or not the product links has to be cleaned up
102
            if ($this->getSubject()->getConfiguration()->hasParam(ConfigurationKeys::CLEAN_UP_LINKS) &&
103
                $this->getSubject()->getConfiguration()->getParam(ConfigurationKeys::CLEAN_UP_LINKS)
104
            ) {
105
                // start the clean-up process
106
                $this->cleanUpLinks($parentId, $linkTypeCode, $links);
107
108
            } else {
109
                // this handles the case with the `__EMPTY__VALUE__` constant
110
                // when the directive `clean-up-links` has been deactivated
111
                if ($this->hasColumn($columnNameChildSkus) && sizeof($links) === 0) {
112
                    $this->cleanUpLinks($parentId, $linkTypeCode, $links);
113
                }
114
            }
115
        }
116
    }
117
118
    /**
119
     * This method queries whether or not  the column with the passed name is available or
120
     * not. This  method uses the `isset()` function to make sure the column is available
121
     * and has not been removed somehow before, because it has an empty value for example.
122
     *
123
     * @param string $name
124
     *
125
     * @return boolean TRUE if the columen is available, FALSE otherwise
126
     */
127
    protected function hasColumn(string $name) : bool
128
    {
129
130
        // query whether or not the header is available, if yes try
131
        // to load the key and query whether the column is available
132
        if ($this->hasHeader($name)) {
133
            return isset($this->row[$this->getHeader($name)]);
134
        }
135
136
        // return FALSE if not
137
        return false;
138
    }
139
140
    /**
141
     * Delete not exists import links from database.
142
     *
143
     * @param int    $parentProductId The ID of the parent product
144
     * @param string $linkTypeCode    The link type code to prepare the artefacts for
145
     * @param array  $childData       The array of variants
146
     *
147
     * @return void
148
     */
149
    protected function cleanUpLinks($parentProductId, $linkTypeCode, array $childData)
150
    {
151
152
        // load the SKU of the parent product
153
        $parentSku = $this->getValue(ColumnKeys::SKU);
154
155
        // extract the link type code from the row
156
        $linkTypeId = $this->mapLinkTypeCodeToLinkTypeId($linkTypeCode);
157
158
        // remove the old variantes from the database
159
        $this->getProductLinkProcessor()
160
            ->deleteProductLink(
161
                array(
162
                    MemberNames::PRODUCT_ID   => $parentProductId,
163
                    MemberNames::SKU          => $childData,
164
                    MemberNames::LINK_TYPE_ID => $linkTypeId,
165
                )
166
            );
167
168
        // log a debug message that the image has been removed
169
        $this->getSubject()
170
            ->getSystemLogger()
171
            ->debug(
172
                $this->getSubject()->appendExceptionSuffix(
173
                    sprintf(
174
                        'Successfully clean up links for product with SKU "%s" except "%s"',
175
                        $parentSku,
176
                        implode(', ', $childData)
177
                    )
178
                )
179
            );
180
    }
181
182
    /**
183
     * Return's the PK to create the product => variant relation.
184
     *
185
     * @return integer The PK to create the relation with
186
     */
187
    protected function getLastPrimaryKey()
188
    {
189
        return $this->getLastEntityId();
190
    }
191
192
    /**
193
     * Return the link type ID for the passed link type code.
194
     *
195
     * @param string $linkTypeCode The link type code to return the link type ID for
196
     *
197
     * @return integer The mapped link type ID
198
     * @throws \TechDivision\Import\Product\Exceptions\MapLinkTypeCodeToIdException Is thrown if the link type code is
199
     *     not mapped yet
200
     */
201
    protected function mapLinkTypeCodeToLinkTypeId($linkTypeCode)
202
    {
203
        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...
204
    }
205
}
206