Failed Conditions
Push — master ( 029e81...ce5fbf )
by Luca
06:29
created

Exporter::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 18
ccs 9
cts 9
cp 1
rs 10
cc 1
nc 1
nop 8
crap 1

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Application\Service\Exporter;
6
7
use Application\DBAL\Types\ExportFormatType;
8
use Application\Model\Export;
9
use Application\Model\User;
10
use Application\Repository\CardRepository;
11
use Application\Repository\ExportRepository;
12
use Application\Service\MessageQueuer;
13
use Ecodev\Felix\Api\Exception;
14
use Ecodev\Felix\Service\Mailer;
15
use InvalidArgumentException;
16
use Throwable;
17
18
class Exporter
19
{
20
    private ExportRepository $exportRepository;
21
22
    private CardRepository $cardRepository;
23
24
    private MessageQueuer $messageQueuer;
25
26
    private Mailer $mailer;
27
28
    private Writer $zip;
29
30
    private Writer $pptx;
31
32
    private Writer $csv;
33
34
    private string $phpPath;
35
36 1
    public function __construct(
37
        ExportRepository $exportRepository,
38
        CardRepository $cardRepository,
39
        MessageQueuer $messageQueuer,
40
        Mailer $mailer,
41
        Writer $zip,
42
        Writer $pptx,
43
        Writer $csv,
44
        string $phpPath
45
    ) {
46 1
        $this->exportRepository = $exportRepository;
47 1
        $this->cardRepository = $cardRepository;
48 1
        $this->messageQueuer = $messageQueuer;
49 1
        $this->mailer = $mailer;
50 1
        $this->zip = $zip;
51 1
        $this->pptx = $pptx;
52 1
        $this->csv = $csv;
53 1
        $this->phpPath = $phpPath;
54 1
    }
55
56
    /**
57
     * Export asynchronously in a separate process.
58
     *
59
     * This should be the preferred way to do big export
60
     */
61
    public function exportAsync(Export $export): void
62
    {
63
        $args = [
64
            realpath('bin/export.php'),
65
            $export->getId(),
66
        ];
67
68
        $escapedArgs = array_map('escapeshellarg', $args);
69
70
        // Forward SERVER_NAME to CLI command
71
        $env = 'SERVER_NAME=' . escapeshellarg(getenv('SERVER_NAME'));
72
73
        $cmd = $env . ' ' . escapeshellcmd($this->phpPath) . ' ' . implode(' ', $escapedArgs) . ' > /dev/null 2>&1 &';
74
        exec($cmd);
75
    }
76
77
    /**
78
     * Export immediately and return the v $export object
79
     *
80
     * Because this method will indirectly clear the EntityManager any existing object
81
     * before calling this method will become invalid and must be re-fetched from DB.
82
     */
83 2
    public function export(Export $export): Export
84
    {
85 2
        $writer = $this->getWriter($export);
86 2
        $title = $export->getSite() . '-' . $export->getId();
87
88
        // Poor man's security by using hard-to-guess suffix
89 2
        $suffix = bin2hex(random_bytes(5));
90
91 2
        $filename = $title . '-' . $suffix . '.' . $writer->getExtension();
92
93 2
        $export->markAsInProgress($filename);
94 2
        _em()->flush();
95
96
        try {
97 2
            $writer->initialize($export, $title);
98 2
            $this->writeCards($writer, $export);
99 2
            $writer->finalize();
100
101 2
            $reloadExport = $this->reloadExport($export);
102 2
            $reloadExport->markAsDone();
103
        } catch (Throwable $throwable) {
104
            $reloadExport = $this->reloadExport($export);
105
            $reloadExport->markAsErrored($throwable);
106
107
            throw $throwable;
108 2
        } finally {
109 2
            _em()->flush();
110
        }
111
112 2
        return $reloadExport;
113
    }
114
115 2
    private function reloadExport(Export $export): Export
116
    {
117 2
        User::reloadCurrentUser();
118
119 2
        return $this->exportRepository->findOneById($export->getId());
0 ignored issues
show
Bug introduced by
The method findOneById() does not exist on Application\Repository\ExportRepository. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

119
        return $this->exportRepository->/** @scrutinizer ignore-call */ findOneById($export->getId());
Loading history...
120
    }
121
122
    /**
123
     * Export immediately and send an email to the export creator.
124
     *
125
     * This must only be used via CLI, because export might be very long (several minutes)
126
     * and the email is sent synchronously
127
     */
128
    public function exportAndSendMessage(int $id): void
129
    {
130
        /** @var null|Export $export */
131
        $export = $this->exportRepository->findOneById($id);
132
        if (!$export) {
133
            throw new InvalidArgumentException('Could not find export with ID: ' . $id);
134
        }
135
136
        $export = $this->export($export);
137
138
        $user = $export->getCreator();
139
        $message = $this->messageQueuer->queueExportDone($user, $export);
0 ignored issues
show
Bug introduced by
It seems like $user can also be of type null; however, parameter $user of Application\Service\Mess...euer::queueExportDone() does only seem to accept Application\Model\User, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

139
        $message = $this->messageQueuer->queueExportDone(/** @scrutinizer ignore-type */ $user, $export);
Loading history...
140
141
        $this->mailer->sendMessage($message);
142
    }
143
144 2
    private function getWriter(Export $export): Writer
145
    {
146 2
        switch ($export->getFormat()) {
147
            case ExportFormatType::ZIP:
148 2
                return $this->zip;
149
            case ExportFormatType::PPTX:
150 1
                return $this->pptx;
151
            case ExportFormatType::CSV:
152 1
                return $this->csv;
153
            default:
154
                throw new Exception('Invalid export format:' . $export->getFormat());
155
        }
156
    }
157
158
    /**
159
     * Write all cards in small batches to avoid exploding PHP memory
160
     */
161 2
    private function writeCards(Writer $writer, Export $export): void
162
    {
163 2
        $totalRecordsProcessed = 0;
164 2
        while (true) {
165 2
            $cards = $this->cardRepository->getExportCards($export, $totalRecordsProcessed);
166
167
            // If nothing to process anymore, stop the whole thing
168 2
            if (!$cards) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cards of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
169 2
                break;
170
            }
171
172 2
            foreach ($cards as $card) {
173 2
                $writer->write($card);
174
175 2
                ++$totalRecordsProcessed;
176
            }
177
178 2
            _em()->flush();
179 2
            _em()->clear();
180
        }
181 2
    }
182
}
183