Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
40.58% covered (danger)
40.58%
28 / 69
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
FindDupesCommand
40.58% covered (danger)
40.58%
28 / 69
20.00% covered (danger)
20.00%
1 / 5
77.63
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 execute
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
3
 findDuplicatesForWaypoint
35.29% covered (danger)
35.29%
6 / 17
0.00% covered (danger)
0.00%
0 / 1
15.75
 handleDuplicate
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 applyUserChoice
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2
3declare(strict_types=1);
4
5namespace App\Command;
6
7use Symfony\Component\Console\Helper\QuestionHelper;
8use App\Entity\Waypoint;
9use App\Repository\WaypointRepository;
10use Doctrine\ORM\EntityManagerInterface;
11use Symfony\Component\Console\Attribute\AsCommand;
12use Symfony\Component\Console\Command\Command;
13use Symfony\Component\Console\Helper\ProgressBar;
14use Symfony\Component\Console\Input\InputInterface;
15use Symfony\Component\Console\Output\OutputInterface;
16use Symfony\Component\Console\Question\ChoiceQuestion;
17use Symfony\Component\Console\Style\SymfonyStyle;
18
19#[AsCommand(
20    name: 'app:find-dupes',
21    description: 'Find duplicated entries'
22)]
23class FindDupesCommand extends Command
24{
25    public function __construct(
26        private readonly EntityManagerInterface $entityManager,
27        private readonly WaypointRepository $waypointRepository
28    )
29    {
30        parent::__construct();
31    }
32
33    protected function execute(
34        InputInterface $input,
35        OutputInterface $output
36    ): int
37    {
38        $io = new SymfonyStyle($input, $output);
39        $waypoints = $this->waypointRepository->findAll();
40        $progressBar = new ProgressBar($output, count($waypoints));
41        /** @var QuestionHelper $helper */
42        $helper = $this->getHelper('question');
43
44        $choices = [
45            'Remove A',
46            'Remove B',
47            'Change name A with B',
48            'Change name B with A',
49            'Skip',
50        ];
51
52        $question = new ChoiceQuestion('Please select [Skip]', $choices, 4);
53        $question->setErrorMessage('Choice %s is invalid.');
54
55        $removals = 0;
56
57        foreach ($waypoints as $waypoint) {
58            $removals += $this->findDuplicatesForWaypoint($waypoint, $waypoints, $io, $helper, $input, $output, $choices, $question);
59            $progressBar->advance();
60        }
61
62        $progressBar->finish();
63
64        if ($removals !== 0) {
65            $io->warning(sprintf('%d duplicates have been removed.', $removals));
66        } else {
67            $io->success('Database is clean :)');
68        }
69
70        return Command::SUCCESS;
71    }
72
73    /**
74     * @param Waypoint[] $waypoints
75     * @param string[] $choices
76     */
77    private function findDuplicatesForWaypoint(
78        Waypoint $waypoint,
79        array $waypoints,
80        SymfonyStyle $io,
81        QuestionHelper $helper,
82        InputInterface $input,
83        OutputInterface $output,
84        array $choices,
85        ChoiceQuestion $question
86    ): int {
87        foreach ($waypoints as $test) {
88            if ($test->getId() === $waypoint->getId()) {
89                continue;
90            }
91
92            if ($test->getGuid() === $waypoint->getGuid()) {
93                $io->warning('@TODO Duplicated GUID found for: '.$waypoint->getName());
94            }
95
96            if ($test->getLat() === $waypoint->getLat() && $test->getLon() === $waypoint->getLon()) {
97                return $this->handleDuplicate(
98                    $waypoint,
99                    $test,
100                    $io,
101                    $helper,
102                    $input,
103                    $output,
104                    $choices,
105                    $question
106                );
107            }
108        }
109
110        return 0;
111    }
112
113    /**
114     * @param string[] $choices
115     */
116    private function handleDuplicate(
117        Waypoint $waypoint,
118        Waypoint $test,
119        SymfonyStyle $io,
120        QuestionHelper $helper,
121        InputInterface $input,
122        OutputInterface $output,
123        array $choices,
124        ChoiceQuestion $question
125    ): int {
126        $io->text([
127            '',
128            sprintf('A: %s - %d', $waypoint->getName(), $waypoint->getId()),
129            sprintf('B: %s - %d', $test->getName(), $test->getId()),
130        ]);
131
132        if ($waypoint->getName() === $test->getName()) {
133            $this->entityManager->remove($test);
134            $this->entityManager->flush();
135            return 1;
136        }
137
138        $io->warning('Name mismatch!');
139        /** @var string $choice */
140        $choice = $helper->ask($input, $output, $question);
141
142        return $this->applyUserChoice($choice, $waypoint, $test, $choices);
143    }
144
145    /**
146     * @param string[] $choices
147     */
148    private function applyUserChoice(
149        string $choice,
150        Waypoint $waypoint,
151        Waypoint $test,
152        array $choices
153    ): int {
154        if ($choice === $choices[0]) {
155            $this->entityManager->remove($waypoint);
156            $this->entityManager->flush();
157            return 1;
158        }
159
160        if ($choice === $choices[1]) {
161            $this->entityManager->remove($test);
162            $this->entityManager->flush();
163            return 1;
164        }
165
166        if ($choice === $choices[2]) {
167            $waypoint->setName((string)$test->getName());
168            $this->entityManager->persist($waypoint);
169            $this->entityManager->flush();
170        } elseif ($choice === $choices[3]) {
171            $test->setName((string)$waypoint->getName());
172            $this->entityManager->persist($test);
173            $this->entityManager->flush();
174        }
175
176        return 0;
177    }
178}