AbstractFeedTypeTestCase   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 336
Duplicated Lines 22.62 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 39
lcom 1
cbo 13
dl 76
loc 336
ccs 0
cts 177
cp 0
rs 9.28
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 6 1
A tearDown() 0 8 2
A assertOriginalId() 0 7 1
A assertOriginalUrl() 0 7 1
A assertFixture() 30 30 3
B assertValue() 15 15 7
B normalizeValues() 20 20 9
A getItemFixture() 11 11 2
A getActualItemFixture() 0 19 2
A getExpectedItemFixture() 0 20 3
A getFeed() 0 10 1
A createEventDispatcher() 0 18 1
A createReader() 0 13 1
A getImportRegistry() 0 4 1
A getOriginalId() 0 4 1
A getOriginalUrl() 0 4 1
A getFeedTypeOptions() 0 11 1
A getReaderTypeOptions() 0 10 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace TreeHouse\IoBundle\Test\Import\FeedType;
4
5
use Symfony\Component\EventDispatcher\EventDispatcher;
6
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
7
use TreeHouse\Feeder\Event\InvalidItemEvent;
8
use TreeHouse\Feeder\Event\ItemNotModifiedEvent;
9
use TreeHouse\Feeder\FeedEvents;
10
use TreeHouse\Feeder\Reader\ReaderInterface;
11
use TreeHouse\IoBundle\Entity\Feed;
12
use TreeHouse\IoBundle\Import\Feed\FeedBuilder;
13
use TreeHouse\IoBundle\Import\Feed\FeedItemBag;
14
use TreeHouse\IoBundle\Import\Feed\TransportFactory;
15
use TreeHouse\IoBundle\Import\ImportRegistry;
16
use TreeHouse\IoBundle\Import\Reader\ReaderBuilder;
17
use TreeHouse\IoBundle\Item\ItemBag;
18
use TreeHouse\IoBundle\Test\Item\ItemFixture;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, TreeHouse\IoBundle\Test\...rt\FeedType\ItemFixture.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
19
use TreeHouse\IoBundle\Test\TestCase;
20
21
/**
22
 * AbstractFeedTypeTest is the base class for a functional feed type test.
23
 * It doesn't contain any tests itself, only the basic functionality to
24
 * test a feed type with fixtures.
25
 *
26
 * If you want to test a feed type, extend {@link FeedTypeTestCase} instead.
27
 */
28
abstract class AbstractFeedTypeTestCase extends TestCase
29
{
30
    /**
31
     * @var string[]
32
     */
33
    protected $loadedFixtures = [];
34
35
    /**
36
     * @inheritdoc
37
     */
38
    protected function setUp()
39
    {
40
        $this->loadedFixtures = [];
41
42
        parent::setUp();
43
    }
44
45
    /**
46
     * Removes all downloaded fixtures (they're saved in tmp folder).
47
     */
48
    protected function tearDown()
49
    {
50
        foreach ($this->loadedFixtures as $fixture) {
51
            unlink($fixture);
52
        }
53
54
        parent::tearDown();
55
    }
56
57
    /**
58
     * @param ItemFixture $fixture
59
     */
60
    protected function assertOriginalId(ItemFixture $fixture)
61
    {
62
        $this->assertEquals(
63
            $this->getOriginalId($fixture->getExpectedItem()),
64
            $this->getOriginalId($fixture->getActualItem())
65
        );
66
    }
67
68
    /**
69
     * @param ItemFixture $fixture
70
     */
71
    protected function assertOriginalUrl(ItemFixture $fixture)
72
    {
73
        $this->assertEquals(
74
            $this->getOriginalUrl($fixture->getExpectedItem()),
75
            $this->getOriginalUrl($fixture->getActualItem())
76
        );
77
    }
78
79
    /**
80
     * @param ItemFixture $fixture
81
     */
82 View Code Duplication
    protected function assertFixture(ItemFixture $fixture)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
83
    {
84
        $this->assertOriginalId($fixture);
85
        $this->assertOriginalUrl($fixture);
86
87
        $expected = $fixture->getExpectedItem()->all();
88
        $actual = $fixture->getActualItem()->all();
89
90
        foreach ($expected as $key => $expectedValue) {
91
            $this->assertArrayHasKey(
92
                $key,
93
                $actual,
94
                sprintf('Key "%s" is not in item, when it should be', $key)
95
            );
96
97
            $actualValue = $actual[$key];
98
99
            $this->normalizeValues($key, $expectedValue, $actualValue);
100
101
            $this->assertValue($key, $expectedValue, $actualValue);
102
103
            unset($actual[$key]);
104
        }
105
106
        if (!empty($actual)) {
107
            $this->fail(
108
                sprintf('The following keys in the modified item are not tested: %s', json_encode(array_keys($actual)))
109
            );
110
        }
111
    }
112
113
    /**
114
     * Asserts a value.
115
     *
116
     * @param $key
117
     * @param $expectedValue
118
     * @param $actualValue
119
     */
120 View Code Duplication
    protected function assertValue($key, $expectedValue, $actualValue)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
121
    {
122
        // if either actual or expected is an object, use equality assertion (==),
123
        // otherwise use identity assertion (===)
124
        $isObject = false;
125
        foreach ([$expectedValue, $actualValue] as $test) {
126
            $isObject = is_object($test) || (is_array($test) && isset($test[0]) && is_object($test[0]));
127
            if ($isObject) {
128
                break;
129
            }
130
        }
131
132
        $assert = $isObject ? 'assertEquals' : 'assertSame';
133
        $this->$assert($expectedValue, $actualValue, sprintf('Key "%s" is not modified properly', $key));
134
    }
135
136
    /**
137
     * Normalizes values before asserting them.
138
     *
139
     * @param string $key
140
     * @param mixed  $expectedValue
141
     * @param mixed  $actualValue
142
     */
143 View Code Duplication
    protected function normalizeValues($key, &$expectedValue, &$actualValue)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
144
    {
145
        // some integers are modified to doubles, this is ok though
146
        if (is_integer($expectedValue) && is_double($actualValue)) {
147
            $expectedValue = (double) $expectedValue;
148
        }
149
150
        // the order of non-associative arrays does not matter
151
        if (is_array($expectedValue) && is_numeric(key($expectedValue)) && is_array($actualValue)) {
152
            sort($expectedValue);
153
            sort($actualValue);
154
        }
155
156
        // only test the day of dates
157
        foreach (['expectedValue', 'actualValue'] as $var) {
158
            if (is_string($$var) && preg_match('/^(\d{4}\-\d{2}\-\d{2})T[0-9\:\+]+$/', $$var, $matches)) {
159
                $$var = $matches[1];
160
            }
161
        }
162
    }
163
164
    /**
165
     * @param string $name     The fixture name
166
     * @param string $feedType The feed type
167
     *
168
     * @throws \RuntimeException
169
     *
170
     * @return ItemFixture
171
     */
172 View Code Duplication
    protected function getItemFixture($name, $feedType)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
173
    {
174
        if (null === $feed = $this->getFeed($feedType)) {
175
            $this->markTestSkipped(sprintf('Add an origin with a %s feed to the database first', $feedType));
176
        }
177
178
        $actual = $this->getActualItemFixture($feed, $name, $feedType);
179
        $expected = $this->getExpectedItemFixture($feed, $name, $feedType);
180
181
        return new ItemFixture($actual, $expected);
182
    }
183
184
    /**
185
     * @param Feed   $feedEntity
186
     * @param string $name       The fixture name
187
     * @param string $feedType   The feed type
188
     *
189
     * @throws \RuntimeException
190
     *
191
     * @return ItemBag
192
     */
193
    protected function getActualItemFixture(Feed $feedEntity, $name, $feedType)
194
    {
195
        $type = $this->getImportRegistry()->getFeedType($feedType);
196
197
        $dispatcher = $this->createEventDispatcher();
198
199
        $options = $this->getReaderTypeOptions($feedEntity);
200
        $reader = $this->createReader($feedEntity, $name, $dispatcher, $options);
201
202
        $builder = new FeedBuilder($dispatcher);
203
        $options = $this->getFeedTypeOptions($feedEntity);
204
        $feed = $builder->build($type, $reader, $options);
0 ignored issues
show
Bug introduced by
It seems like $reader defined by $this->createReader($fee... $dispatcher, $options) on line 200 can also be of type null; however, TreeHouse\IoBundle\Impor...ed\FeedBuilder::build() does only seem to accept object<TreeHouse\Feeder\Reader\ReaderInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
205
206
        if (null === $item = $feed->getNextItem()) {
207
            throw new \RuntimeException('Expecting a non-filtered feed item, got nothing.');
208
        }
209
210
        return $item;
211
    }
212
213
    /**
214
     * @param Feed   $feed
215
     * @param string $name
216
     * @param string $feedType
217
     *
218
     * @return FeedItemBag
219
     */
220
    protected function getExpectedItemFixture(Feed $feed, $name, $feedType)
221
    {
222
        $refl = new \ReflectionClass(get_class($this));
223
        $phpFile = sprintf('%s/fixtures/%s/%s.php', dirname($refl->getFilename()), $feedType, $name);
224
225
        /** @var array $expected */
226
        $expected = include $phpFile;
227
228
        $item = new FeedItemBag($feed, $expected['id'], $expected['item']);
229
230
        if (isset($expected['url'])) {
231
            $item->setOriginalUrl($expected['url']);
232
        }
233
234
        if (isset($expected['date'])) {
235
            $item->setDatetimeModified($expected['date']);
236
        }
237
238
        return $item;
239
    }
240
241
    /**
242
     * Returns a feed entity for a specific feed type.
243
     *
244
     * @param string $type
245
     *
246
     * @return mixed
247
     */
248
    protected function getFeed($type)
249
    {
250
        return $this
251
            ->getEntityManager()
252
            ->createQuery('SELECT f, o FROM TreeHouseIoBundle:Feed f JOIN f.origin o WHERE f.type = :type')
253
            ->setParameter('type', $type)
254
            ->setMaxResults(1)
255
            ->getOneOrNullResult()
256
        ;
257
    }
258
259
    /**
260
     * @return EventDispatcherInterface
261
     */
262
    protected function createEventDispatcher()
263
    {
264
        $dispatcher = new EventDispatcher();
265
266
        $dispatcher->addListener(FeedEvents::ITEM_FILTERED, function (ItemNotModifiedEvent $e) {
267
            throw new \RuntimeException(sprintf('Feed item filtered, reason: %s', $e->getReason()));
268
        });
269
270
        $dispatcher->addListener(FeedEvents::ITEM_FAILED, function (ItemNotModifiedEvent $e) {
271
            throw new \RuntimeException(sprintf('Item modification failed, reason: %s', $e->getReason()));
272
        });
273
274
        $dispatcher->addListener(FeedEvents::ITEM_INVALID, function (InvalidItemEvent $e) {
275
            throw new \RuntimeException(sprintf('Invalid item, reason: %s', $e->getReason()));
276
        });
277
278
        return $dispatcher;
279
    }
280
281
    /**
282
     * @param Feed                     $feed
283
     * @param string                   $fixtureName
284
     * @param EventDispatcherInterface $dispatcher
285
     * @param array                    $options
286
     *
287
     * @return ReaderInterface
288
     */
289
    protected function createReader(Feed $feed, $fixtureName, EventDispatcherInterface $dispatcher, array $options = [])
290
    {
291
        $readerType = $this->getImportRegistry()->getReaderType($feed->getReaderType());
292
293
        $refl = new \ReflectionClass(get_class($this));
294
        $xml = sprintf('%s/fixtures/%s/%s.xml', dirname($refl->getFileName()), $feed->getType(), $fixtureName);
295
296
        $transportConfig = TransportFactory::createConfigFromFile($xml);
297
298
        $builder = new ReaderBuilder($dispatcher, sys_get_temp_dir() . '/' . $feed->getType());
299
300
        return $builder->build($readerType, $transportConfig, $builder::RESOURCE_TYPE_MAIN, $options);
301
    }
302
303
    /**
304
     * @return ImportRegistry
305
     */
306
    protected function getImportRegistry()
307
    {
308
        return $this->get('tree_house.io.import.registry');
309
    }
310
311
    /**
312
     * @param ItemBag $item
313
     *
314
     * @return string
315
     */
316
    protected function getOriginalId(ItemBag $item)
317
    {
318
        return $item->getOriginalId();
319
    }
320
321
    /**
322
     * @param ItemBag $item
323
     *
324
     * @return string
325
     */
326
    protected function getOriginalUrl(ItemBag $item)
327
    {
328
        return $item->getOriginalUrl();
329
    }
330
331
    /**
332
     * @param Feed $feed
333
     *
334
     * @return array
335
     */
336
    protected function getFeedTypeOptions(Feed $feed)
337
    {
338
        return array_merge(
339
            [
340
                'forced' => true,
341
                'feed' => $feed,
342
                'default_values' => $feed->getDefaultValues(),
343
            ],
344
            $feed->getOptions()
345
        );
346
    }
347
348
    /**
349
     * @param Feed $feed
350
     *
351
     * @return array
352
     */
353
    protected function getReaderTypeOptions(Feed $feed)
354
    {
355
        return array_merge(
356
            [
357
                'partial' => $feed->isPartial(),
358
                'forced' => true,
359
            ],
360
            $feed->getReaderOptions()
361
        );
362
    }
363
}
364