Completed
Push — master ( da8bf6...ffff83 )
by Tim
27s queued 17s
created

CleanupCommandController::reIndex()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * Cleanup the event models.
5
 */
6
declare(strict_types=1);
7
8
namespace HDNET\Calendarize\Command;
9
10
use HDNET\Calendarize\Domain\Model\Event;
11
use HDNET\Calendarize\Domain\Repository\EventRepository;
12
use HDNET\Calendarize\Event\CleanupEvent;
13
use HDNET\Calendarize\Service\IndexerService;
14
use HDNET\Calendarize\Utility\DateTimeUtility;
15
use HDNET\Calendarize\Utility\HelperUtility;
16
use Psr\EventDispatcher\EventDispatcherInterface;
17
use Symfony\Component\Console\Command\Command;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Input\InputOption;
20
use Symfony\Component\Console\Output\OutputInterface;
21
use Symfony\Component\Console\Style\SymfonyStyle;
22
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
23
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
24
use TYPO3\CMS\Core\Utility\ClassNamingUtility;
25
use TYPO3\CMS\Core\Utility\GeneralUtility;
26
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
27
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
28
use TYPO3\CMS\Extbase\Persistence\Generic\PersistenceManager;
29
use TYPO3\CMS\Extbase\Persistence\Repository;
30
31
/**
32
 * Cleanup the event models.
33
 */
34
class CleanupCommandController extends Command
35
{
36
    const MODUS_HIDDEN = 'hide';
37
    const MODUS_DELETED = 'delete';
38
    const DEFAULT_WAIT_PERIOD = 14;
39
    const DEFAULT_CLEANUP_REPOSITORY = \HDNET\Calendarize\Domain\Repository\EventRepository::class;
40
41
    /**
42
     * @var PersistenceManager
43
     */
44
    protected $persistenceManager;
45
46
    /**
47
     * @var EventDispatcherInterface
48
     */
49
    protected $eventDispatcher;
50
51
    /**
52
     * @var DataMapper
53
     */
54
    protected $dataMapper;
55
56
    /**
57
     * @var IndexerService
58
     */
59
    protected $indexerService;
60
61
    /**
62
     * @param PersistenceManager $persistenceManager
63
     */
64
    public function injectPersistenceManager(PersistenceManager $persistenceManager): void
65
    {
66
        $this->persistenceManager = $persistenceManager;
67
    }
68
69
    /**
70
     * @param EventDispatcherInterface $eventDispatcher
71
     */
72
    public function injectEventDispatcher(EventDispatcherInterface $eventDispatcher): void
73
    {
74
        $this->eventDispatcher = $eventDispatcher;
75
    }
76
77
    /**
78
     * @param DataMapper $dataMapper
79
     */
80
    public function injectDataMapper(DataMapper $dataMapper): void
81
    {
82
        $this->dataMapper = $dataMapper;
83
    }
84
85
    /**
86
     * @param IndexerService $indexerService
87
     */
88
    public function injectIndexerService(IndexerService $indexerService): void
89
    {
90
        $this->indexerService = $indexerService;
91
    }
92
93
    protected function configure()
94
    {
95
        $this->setDescription('Remove outdated events to keep a small footprint')
96
            ->addOption(
97
                'repositoryName',
98
                'r',
99
                InputOption::VALUE_REQUIRED,
100
                'The repository of the event to clean up',
101
                self::DEFAULT_CLEANUP_REPOSITORY
102
            )
103
            ->addOption(
104
                'modus',
105
                'm',
106
                InputOption::VALUE_REQUIRED,
107
                'What to do with cleaned Events? Set them \'hide\' or \'delete\'',
108
                self::MODUS_HIDDEN
109
            )
110
            ->addOption(
111
                'waitingPeriod',
112
                'w',
113
                InputOption::VALUE_REQUIRED,
114
                'How many days to wait after ending the Event before \'hide/delete\' it',
115
                self::DEFAULT_WAIT_PERIOD
116
            )->addOption(
117
                'dry-run',
118
                null,
119
                InputOption::VALUE_NONE,
120
                'If this option is set, it only outputs the amount of records which would have been updated'
121
            );
122
    }
123
124
    /**
125
     * Cleanup the event models.
126
     * Remove outdated events to keep a small footprint. This gain maybe a little more performance.
127
     *
128
     * @param InputInterface  $input
129
     * @param OutputInterface $output
130
     *
131
     * @return int 0 if everything went fine, or an exit code
132
     */
133
    protected function execute(InputInterface $input, OutputInterface $output): int
134
    {
135
        $io = new SymfonyStyle($input, $output);
136
137
        $repositoryName = $input->getOption('repositoryName');
138
        $modus = $input->getOption('modus');
139
        $waitingPeriod = (int)$input->getOption('waitingPeriod');
140
141
        /** @var Repository $repository */
142
        $repository = GeneralUtility::makeInstance($repositoryName);
143
144
        $io->section('Reindex all events');
145
        // Index all events to start on a clean slate
146
        $this->indexerService->reindexAll();
147
148
        // repository name -> model name -> table name
149
        $objectType = ClassNamingUtility::translateRepositoryNameToModelName($repositoryName);
150
        $tableName = $this->dataMapper->getDataMap($objectType)->getTableName();
151
152
        $io->text('Tablename ' . $tableName);
153
154
        if (self::MODUS_HIDDEN === $modus
155
            && !isset($GLOBALS['TCA'][$tableName]['ctrl']['enablecolumns']['disabled'])
156
        ) {
157
            $io->error('Cannot hide events due to missing hidden/disabled field.');
158
159
            return 3;
160
        }
161
162
        $io->section('Find outdated events');
163
        // events uid, to be precise
164
        $events = $this->findOutdatedEvents($tableName, $waitingPeriod);
165
166
        $io->text('Found ' . \count($events) . ' Events ready to process.');
167
168
        if (0 === \count($events) || true === $input->getOption('dry-run')) {
169
            return 0;
170
        }
171
172
        // climb through the events and hide/delete them
173
        foreach ($events as $event) {
174
            $uid = (int)$event['foreign_uid'];
175
176
            /** @var AbstractEntity $model */
177
            $model = $repository->findByUid($uid);
178
179
            $this->processEvent($repository, $model, $modus);
180
        }
181
        $io->text('Events processed.');
182
183
        $this->persistenceManager->persistAll();
184
185
        $io->section('Reindex all events');
186
        // after all this deleting ... reindex!
187
        $this->indexerService->reindexAll();
188
189
        return 0;
190
    }
191
192
    /**
193
     * Process the found Event and delete or hide it.
194
     *
195
     * @param EventRepository $repository
196
     * @param Event           $model
197
     * @param string          $modus
198
     */
199
    protected function processEvent(Repository $repository, AbstractEntity $model, string $modus)
200
    {
201
        // define the function for the delete-modus.
202
        $delete = function ($repository, $model) {
203
            $repository->remove($model);
204
        };
205
206
        // define the function for the hide-modus.
207
        $hide = function ($repository, $model) {
208
            $model->setHidden(true);
209
            $repository->update($model);
210
        };
211
212
        if (self::MODUS_DELETED === $modus) {
213
            $function = $delete;
214
        } else {
215
            $function = $hide;
216
        }
217
218
        $event = new CleanupEvent($modus, $repository, $model, $function);
219
        $this->eventDispatcher->dispatch($event);
0 ignored issues
show
Documentation introduced by
$event is of type object<HDNET\Calendarize\Event\CleanupEvent>, but the function expects a object<Psr\EventDispatcher\object>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
220
221
        $myFunction = $event->getFunction();
222
        $myFunction($repository, $model);
223
    }
224
225
    /**
226
     * Find outdated events.
227
     *
228
     * @param string $tableName
229
     * @param int    $waitingPeriod
230
     *
231
     * @return array
232
     */
233
    protected function findOutdatedEvents(string $tableName, int $waitingPeriod): array
234
    {
235
        // calculate the waiting time
236
        $interval = 'P' . $waitingPeriod . 'D';
237
        $now = DateTimeUtility::getNow();
238
        $now->sub(new \DateInterval($interval));
239
240
        // search for outdated events
241
        $table = IndexerService::TABLE_NAME;
242
243
        $q = HelperUtility::getDatabaseConnection($table)->createQueryBuilder();
244
        $q->getRestrictions()
245
            ->removeAll()
246
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
247
            ->add(GeneralUtility::makeInstance(HiddenRestriction::class));
248
249
        $q->select('foreign_uid')
250
            ->addSelectLiteral(
251
                $q->expr()->max('end_date', 'max_end_date')
252
            )
253
            ->from($table)
254
            ->where($q->expr()->eq('foreign_table', $q->createNamedParameter($tableName)))
255
            ->groupBy('foreign_uid')
256
            ->having(
257
                $q->expr()->lt('max_end_date', $q->createNamedParameter($now->format('Y-m-d')))
258
            );
259
260
        return $q->execute()->fetchAll();
261
    }
262
}
263