Completed
Push — 19.x ( 8328ae...eef76a )
by Tim
05:57
created

ProductInventoryObserver::createObserver()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 5
cts 5
cp 1
rs 9.7
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Observers\ProductInventoryObserver
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 TechDivision\Import\Observers\StateDetectorInterface;
24
use TechDivision\Import\Observers\AttributeLoaderInterface;
25
use TechDivision\Import\Observers\DynamicAttributeObserverInterface;
26
use TechDivision\Import\Product\Utils\ColumnKeys;
27
use TechDivision\Import\Product\Utils\MemberNames;
28
use TechDivision\Import\Product\Services\ProductBunchProcessorInterface;
29
use TechDivision\Import\Subjects\SubjectInterface;
30
use TechDivision\Import\Observers\ObserverFactoryInterface;
31
use TechDivision\Import\Observers\StateDetectorAwareObserverInterface;
32
33
/**
34
 * Observer that creates/updates the product's inventory.
35
 *
36
 * @author    Tim Wagner <[email protected]>
37
 * @copyright 2016 TechDivision GmbH <[email protected]>
38
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
39
 * @link      https://github.com/techdivision/import-product
40
 * @link      http://www.techdivision.com
41
 */
42
class ProductInventoryObserver extends AbstractProductImportObserver implements DynamicAttributeObserverInterface, StateDetectorAwareObserverInterface, ObserverFactoryInterface
43
{
44
45
    /**
46
     * The product bunch processor instance.
47
     *
48
     * @var \TechDivision\Import\Product\Services\ProductBunchProcessorInterface
49
     */
50
    protected $productBunchProcessor;
51
52
    /**
53
     * The attribute loader instance.
54
     *
55
     * @var \TechDivision\Import\Observers\AttributeLoaderInterface
56
     */
57
    protected $attributeLoader;
58
59
    /**
60
     * The array with the column mappings that has to be computed.
61 2
     *
62
     * @var array
63
     */
64
    protected $columns = array();
65 2
66 2
    /**
67 2
     * Initialize the observer with the passed product bunch processor instance.
68
     *
69
     * @param \TechDivision\Import\Product\Services\ProductBunchProcessorInterface $productBunchProcessor The product bunch processor instance
70
     * @param \TechDivision\Import\Observers\StateDetectorInterface|null           $stateDetector         The state detector instance to use
71
     */
72
    public function __construct(
73
        ProductBunchProcessorInterface $productBunchProcessor,
74 1
        AttributeLoaderInterface $attributeLoader,
75
        StateDetectorInterface $stateDetector = null
76 1
    ) {
77
78
        // initialize the bunch processor and the attribute loader instance
79
        $this->productBunchProcessor = $productBunchProcessor;
80
        $this->attributeLoader = $attributeLoader;
81
82
        // pass the state detector to the parent method
83
        parent::__construct($stateDetector);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class TechDivision\Import\Prod...ctProductImportObserver as the method __construct() does only exist in the following sub-classes of TechDivision\Import\Prod...ctProductImportObserver: TechDivision\Import\Prod...ProductRelationObserver, TechDivision\Import\Prod...tRelationUpdateObserver, TechDivision\Import\Prod...CategoryProductObserver, TechDivision\Import\Prod...ryProductUpdateObserver, TechDivision\Import\Prod...servers\CleanUpObserver, TechDivision\Import\Prod...rs\ClearProductObserver, TechDivision\Import\Prod...EntityIdMappingObserver, TechDivision\Import\Prod...rs\LastEntityIdObserver, TechDivision\Import\Prod...PreLoadEntityIdObserver, TechDivision\Import\Prod...roductInventoryObserver, TechDivision\Import\Prod...InventoryUpdateObserver, TechDivision\Import\Prod...servers\ProductObserver, TechDivision\Import\Prod...\ProductWebsiteObserver, TechDivision\Import\Prod...ctWebsiteUpdateObserver, TechDivision\Import\Prod...bservers\UrlKeyObserver. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
84 2
    }
85
86
    /**
87
     * Will be invoked by the observer visitor when a factory has been defined to create the observer instance.
88 2
     *
89 1
     * @param \TechDivision\Import\Subjects\SubjectInterface $subject The subject instance
90
     *
91
     * @return \TechDivision\Import\Observers\ObserverInterface The observer instance
92
     */
93 1
    public function createObserver(SubjectInterface $subject)
94 1
    {
95
96
        // load the header stock mappings from the subject
97
        $headerStockMappings = $subject->getHeaderStockMappings();
0 ignored issues
show
Bug introduced by
The method getHeaderStockMappings() does not exist on TechDivision\Import\Subjects\SubjectInterface. Did you maybe mean getHeader()?

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...
98
99
        // prepare the array with column name => type mapping
100
        foreach ($headerStockMappings as $columnName => $mappings) {
101 1
            // explode the mapping details
102
            list (, $type) = $mappings;
103
            // add the column name => type mapping
104
            $this->columns[$columnName] = $type;
105 1
        }
106
107
        // return the instance itself
108 1
        return $this;
109
    }
110
111 1
    /**
112
     * Returns an array of the columns with their types to detect state.
113 1
     *
114 1
     * @return array The array with the column names as key and their type as value
115 1
     */
116
    public function getColumns()
117
    {
118
        return array_intersect_key($this->columns, $this->getHeaders());
119
    }
120
121
    /**
122
     * Return's the product bunch processor instance.
123
     *
124
     * @return \TechDivision\Import\Product\Services\ProductBunchProcessorInterface The product bunch processor instance
125 1
     */
126
    protected function getProductBunchProcessor()
127 1
    {
128
        return $this->productBunchProcessor;
129
    }
130
131
    /**
132
     * Process the observer's business logic.
133
     *
134
     * @return array The processed row
135
     */
136
    protected function process()
137 1
    {
138
139 1
        // query whether or not, we've found a new SKU => means we've found a new product
140
        if ($this->hasBeenProcessed($this->getValue(ColumnKeys::SKU))) {
141
            return;
142
        }
143
144
        // prepare, initialize and persist the stock status/item
145
        if ($this->hasChanges($entity = $this->initializeStockItem($this->prepareStockItemAttributes()))) {
146
            $this->persistStockItem($entity);
147 1
        }
148
    }
149 1
150
    /**
151
     * Prepare the basic attributes of the stock status/item entity that has to be persisted.
152
     *
153
     * @return array The prepared stock status/item attributes
154
     */
155
    protected function prepareAttributes()
156
    {
157
158
        // load the ID of the product that has been created recently
159 1
        $lastEntityId = $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...
160
161 1
        // initialize the stock status data
162 1
        $websiteId =  $this->getValue(ColumnKeys::WEBSITE_ID, 0);
163
164
        // return the prepared stock status
165
        return $this->initializeEntity(
166
            array(
167
                MemberNames::PRODUCT_ID   => $lastEntityId,
168
                MemberNames::WEBSITE_ID   => $websiteId,
169
                MemberNames::STOCK_ID     => 1
170
            )
171
        );
172
    }
173
174
    /**
175
     * Prepare the stock item attributes of the entity that has to be persisted.
176
     *
177
     * @return array The prepared stock status item
178
     */
179
    protected function prepareStockItemAttributes()
180
    {
181
        return array_merge($this->prepareAttributes(), $this->attributeLoader->load($this, $this->getHeaderStockMappings()));
182
    }
183
184
    /**
185
     * Initialize the stock item with the passed attributes and returns an instance.
186
     *
187
     * @param array $attr The stock item attributes
188
     *
189
     * @return array The initialized stock item
190
     */
191
    protected function initializeStockItem(array $attr)
192
    {
193
        return $attr;
194
    }
195
196
    /**
197
     * Return's the appings for the table column => CSV column header.
198
     *
199
     * @return array The header stock mappings
200
     */
201
    protected function getHeaderStockMappings()
202
    {
203
        return $this->getSubject()->getHeaderStockMappings();
0 ignored issues
show
Bug introduced by
The method getHeaderStockMappings() does not exist on TechDivision\Import\Subjects\SubjectInterface. Did you maybe mean getHeader()?

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...
204
    }
205
206
    /**
207
     * Persist's the passed stock item data and return's the ID.
208
     *
209
     * @param array $stockItem The stock item data to persist
210
     *
211
     * @return void
212
     */
213
    protected function persistStockItem($stockItem)
214
    {
215
        $this->getProductBunchProcessor()->persistStockItem($stockItem);
216
    }
217
}
218