Completed
Push — issue/270 ( cf7824...00d313 )
by Alex
01:35
created

CheckCommand::runChecks()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 34

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
dl 0
loc 34
ccs 0
cts 27
cp 0
rs 9.376
c 0
b 0
f 0
cc 4
nc 5
nop 2
crap 20
1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of the feed-io package.
4
 *
5
 * (c) Alexandre Debril <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace FeedIo\Command;
12
13
use FeedIo\Command\Check\CheckerAbstract;
14
use FeedIo\Command\Check\CountChecker;
15
use FeedIo\Command\Check\HistoryChecker;
16
use FeedIo\Factory;
17
use FeedIo\Feed;
18
use FeedIo\Feed\ItemInterface;
19
use FeedIo\FeedInterface;
20
use FeedIo\FeedIo;
21
use Symfony\Component\Console\Command\Command;
22
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
23
use Symfony\Component\Console\Input\InputArgument;
24
use Symfony\Component\Console\Input\InputInterface;
25
use Symfony\Component\Console\Input\InputOption;
26
use Symfony\Component\Console\Output\OutputInterface;
27
28
class CheckCommand extends Command
29
{
30
31
    const UPDATE_PROBLEM = "<warn>Issues found on readSince. Please consider filtering this feed using its public ids</warn>";
32
33
    protected function configure()
34
    {
35
        $this->setName('check')
36
            ->setDescription('checks if a feed gets correctly updated')
37
            ->addArgument(
38
                'url',
39
                InputArgument::REQUIRED,
40
                'Please provide the feed\' URL'
41
            )
42
        ;
43
    }
44
45
    protected function execute(InputInterface $input, OutputInterface $output)
46
    {
47
        $this->configureOutput($output);
48
        $url = $input->getArgument('url');
49
50
        if( ! $this->runChecks($output, $url) ) {
0 ignored issues
show
Bug introduced by
It seems like $url defined by $input->getArgument('url') on line 48 can also be of type array<integer,string> or null; however, FeedIo\Command\CheckCommand::runChecks() does only seem to accept string, 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...
51
            $output->writeln("<error>This feed cannot be properly used by feed-io. Please read the above error message and if you think it's a mistake, feel free to submit an issue on Github</error>");
52
            return 1;
53
        }
54
55
        $output->writeln("<success>This feed can be consumed by feed-io</success>");
56
        return 0;
57
    }
58
59
    protected function runChecks(OutputInterface $output, string $url): bool
60
    {
61
        $feedIo = Factory::create()->getFeedIo();
62
        $feed = $feedIo->read($url)->getFeed();
63
64
        $output->writeln("<info>first access to {$feed->getTitle()}</info>");
65
66
        $count = count($feed);
67
        if ($count == 0) {
68
            $output->writeln("<error>empty feed</error>");
69
            return false;
70
        }
71
72
        $output->writeln("<info>found {$count} items</info>");
73
74
        $firstHitResult = $this->checkFirstHit($output, $feed);
75
76
        $updateStatus = true;
77
        if ($this->checkSecondHit($output, $feedIo, $url, $firstHitResult)) {
78
            $output->writeln("<info>readSince works fine</info>");
79
        } else {
80
            $updateStatus = false;
81
            $output->writeln(self::UPDATE_PROBLEM);
82
        }
83
84
        if ($this->checkHitInTheFuture($feedIo, $url)) {
85
            $output->writeln("<info>a call in the future is empty as expected</info>");
86
        } else {
87
            $updateStatus = false;
88
            $output->writeln(self::UPDATE_PROBLEM);
89
        }
90
91
        return $updateStatus;
92
    }
93
94
    private function checkFirstHit(OutputInterface $output, FeedInterface $feed): array
95
    {
96
        $lastModifiedDates = [];
97
        $publicIds = [];
98
        /** @var \FeedIo\Feed\ItemInterface $item */
99
        foreach ($feed as $i => $item) {
100
            $lastModifiedDates[] = $item->getLastModified();
101
            $publicIds[] = $item->getPublicId();
102
        }
103
104
        if (! $this->checkPublicIds($publicIds)) {
105
            $output->writeln("<warn>duplicated publicIds found</warn>");
106
        }
107
108
        sort($lastModifiedDates);
109
        $first = current($lastModifiedDates);
110
        $last = end($lastModifiedDates);
111
112
        $normalDateFlow = true;
113
        if ($last > $first) {
114
            $output->writeln("<info>first item was published on {$first->format(\DateTime::ATOM)}</info>");
115
            $output->writeln("<info>last item was published on {$last->format(\DateTime::ATOM)}</info>");
116
        } else {
117
            $output->writeln("<warn>All items have the same date</warn>");
118
            $normalDateFlow = false;
119
        }
120
121
        return [
122
            'lastModifiedDates' => $lastModifiedDates,
123
            'normalDateFlow' => $normalDateFlow,
124
            'publicIds' => $publicIds,
125
        ];
126
    }
127
128
    private function checkSecondHit(OutputInterface $output, FeedIo $feedIo, string $url, array $firstResult): bool
129
    {
130
        $count = count($firstResult['lastModifiedDates']);
131
        $last = end($firstResult['lastModifiedDates']);
132
        if ($firstResult['normalDateFlow']) {
133
            $pick = intval($count / 2);
134
            $lastModified = $firstResult['lastModifiedDates'][$pick];
135
        } else {
136
            $lastModified = $last->sub(new \DateInterval('P1D'));
137
        }
138
139
        $secondFeed = $feedIo->readSince($url, $lastModified)->getFeed();
140
141
        $count = count($secondFeed);
142
        if ($count == 0) {
143
            $output->writeln("<error>The feed is empty on second call, it should have a partial result</error>");
144
            return false;
145
        }
146
147
        $output->writeln("<info>found {$count} items on second call</info>");
148
        /** @var \FeedIo\Feed\ItemInterface $item */
149
        foreach ($secondFeed as $item) {
150
            if(! in_array($item->getPublicId(), $firstResult['publicIds'])) {
151
                $output->writeln("<warn>Unknown public ID detected, you should retry to see if it was just a new item published during the check process</warn>");
152
            }
153
        }
154
155
        return true;
156
    }
157
158
    private function checkHitInTheFuture( FeedIo $feedIo, string $url): bool
159
    {
160
        $feed = $feedIo->readSince($url, new \DateTime("+1 week"))->getFeed();
161
162
        return count($feed) == 0;
163
    }
164
165
    private function checkPublicIds(array $publicIds): bool
166
    {
167
        $deduplicated = array_unique($publicIds);
168
        return count($deduplicated) == count($publicIds);
169
    }
170
171
    private function configureOutput(OutputInterface $output): void
172
    {
173
        $output->getFormatter()->setStyle(
174
            'warn',
175
            new OutputFormatterStyle('black', 'magenta', ['bold'])
176
        );
177
178
        $output->getFormatter()->setStyle(
179
            'success',
180
            new OutputFormatterStyle('black', 'green', ['bold'])
181
        );
182
    }
183
}
184