Test Failed
Pull Request — master (#4)
by
unknown
01:25
created

SyncTimings::retrieveProjectsForWorkspace()   C

Complexity

Conditions 13
Paths 35

Size

Total Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 58
rs 6.6166
c 0
b 0
f 0
cc 13
nc 35
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
namespace Syncer\Command;
4
5
use Syncer\Dto\InvoiceNinja\Task;
6
use Syncer\Dto\Toggl\TimeEntry;
7
use Syncer\Dto\InvoiceNinja\Client as InvoiceNinjaClientDto;
8
use Syncer\Dto\InvoiceNinja\Project as InvoiceNinjaProject;
9
use Syncer\InvoiceNinja\InvoiceNinjaClient;
10
use Syncer\Toggl\ReportsClient;
11
use Syncer\Toggl\TogglClient;
12
13
use Symfony\Component\Console\Command\Command;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Output\OutputInterface;
16
use Symfony\Component\Console\Style\SymfonyStyle;
17
18
/**
19
 * Class SyncTimings
20
 * @package Syncer\Command
21
 *
22
 * @author Matthieu Calie <[email protected]>
23
 */
24
class SyncTimings extends Command
25
{
26
    /**
27
     * @var SymfonyStyle
28
     */
29
    private $io;
30
31
    /**
32
     * @var TogglClient
33
     */
34
    private $togglClient;
35
36
    /**
37
     * @var ReportsClient
38
     */
39
    private $reportsClient;
40
41
    /**
42
     * @var InvoiceNinjaClient
43
     */
44
    private $invoiceNinjaClient;
45
46
    /**
47
     * @var array
48
     */
49
    private $clients;
50
51
    /**
52
     * @var array
53
     */
54
    private $projects;
55
56
    /**
57
     * @var string
58
     */
59
    private $storageDir;
60
61
    /**
62
     * @var string
63
     */
64
    private $storageFileName;
65
66
    /**
67
     * @var bool
68
     */
69
    private $useProjectsAsClients;
70
71
    /**
72
     * @var int
73
     */
74
    private $sinceDaysAgo;
75
76
    /**
77
     * SyncTimings constructor.
78
     *
79
     * @param TogglClient $togglClient
80
     * @param ReportsClient $reportsClient
81
     * @param InvoiceNinjaClient $invoiceNinjaClient
82
     * @param array $clients
83
     * @param array $projects
84
     */
85
    public function __construct(
86
        TogglClient $togglClient,
87
        ReportsClient $reportsClient,
88
        InvoiceNinjaClient $invoiceNinjaClient,
89
        ?array $clients,
90
        ?array $projects,
91
        string $storageDir,
92
        string $storageFileName,
93
        ?bool $useProjectsAsClients,
94
        ?int $sinceDaysAgo
95
    ) {
96
        $this->togglClient = $togglClient;
97
        $this->reportsClient = $reportsClient;
98
        $this->invoiceNinjaClient = $invoiceNinjaClient;
99
        $this->clients = $clients ?: [];
100
        $this->projects = $projects ?: [];
101
        $this->storageDir = $storageDir;
102
        $this->storageFileName = $storageFileName;
103
        $this->useProjectsAsClients = $useProjectsAsClients ?: false;
104
        $this->sinceDaysAgo = $sinceDaysAgo ?: 1;
105
106
        parent::__construct();
107
    }
108
109
    /**
110
     * Configure the command
111
     */
112
    protected function configure()
113
    {
114
        $this
115
            ->setName('sync:timings')
116
            ->setDescription('Syncs timings from toggl to invoiceninja')
117
        ;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    protected function execute(InputInterface $input, OutputInterface $output)
124
    {
125
        $this->io = new SymfonyStyle($input, $output);
126
        $workspaces = $this->togglClient->getWorkspaces();
127
128
        if (!is_array($workspaces) || count($workspaces) === 0) {
129
            $this->io->error('No workspaces to sync.');
130
131
            return;
132
        }
133
134
        $sentTimeEntries = $this->retrieveSentTimeEntries();
135
136
        foreach ($workspaces as $workspace) {
137
            $detailedReport = $this->reportsClient->getDetailedReport($workspace->getId(), $this->sinceDaysAgo);
138
            $workspaceClients = array_merge($this->clients, $this->retrieveClientsForWorkspace($workspace->getId(), $this->clients));
139
            $workspaceProjects = array_merge($this->projects, $this->retrieveProjectsForWorkspace($workspace->getId(), $workspaceClients, $this->projects));
140
141
            foreach($detailedReport->getData() as $timeEntry) {
142
                $timeEntrySent = false;
0 ignored issues
show
Unused Code introduced by
$timeEntrySent is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
143
                $timeEntryClient = $timeEntry->getClient();
144
                $timeEntryProject = $timeEntry->getProject();
145
146
                if (in_array($timeEntry->getId(), $sentTimeEntries)) {
147
                    continue;
148
                }
149
150
                // Since all Toggl time entries require there to be a project before there can be a client,
151
                // a project is required, but a client is not.
152
                if (!isset($timeEntryProject)) {
153
                    $this->io->warning('No project set for TimeEntry (' . $timeEntry->getDescription() . ')');
154
                } else if (!isset($timeEntryClient)) {
155
                    if ($this->useProjectsAsClients) {
156
                        $timeEntryClient = $timeEntryProject;
157
                        $workspaceClients[$timeEntryProject] = $this->getInvoiceNinjaClientIdForProject($timeEntryProject);
158
                    } else {
159
                        $timeEntryProject = NULL;
160
                        $this->io->warning('No client set for TimeEntry (' . $timeEntry->getDescription() . ')');
161
                        $this->io->warning("To allow using projects as clients enable 'use_projects_as_clients' in parameters config file");
162
                    }
163
                }
164
165
                if (isset($timeEntryProject)) {
166
                    $this->logTask($timeEntry, $workspaceClients, $timeEntryClient, $workspaceProjects, $timeEntryProject);
167
168
                    $sentTimeEntries[] = $timeEntry->getId();
169
                    $timeEntrySent = true;
170
                } else {
171
                    $this->logTask($timeEntry);
172
173
                    $sentTimeEntries[] = $timeEntry->getId();
174
                    $timeEntrySent = true;
175
                }
176
177
                if ($timeEntrySent) {
178
                    $this->io->success('TimeEntry ('. $timeEntry->getDescription() . ') sent to InvoiceNinja');
179
                }
180
            }
181
        }
182
183
        $this->storeSentTimeEntries($sentTimeEntries);
184
    }
185
186
    /**
187
     * @param TimeEntry $entry
188
     * @param array $clients
189
     * @param string $clientKey
190
     *
191
     * @return void
192
     */
193
    private function logTask(TimeEntry $entry, array $clients = NULL, string $clientKey = NULL, array $projects = NULL, string $projectKey = NULL)
194
    {
195
        $task = new Task();
196
197
        $task->setDescription($this->buildTaskDescription($entry));
198
        $task->setTimeLog($this->buildTimeLog($entry));
199
        if (isset($clients) && isset($clientKey)) {
200
            $task->setClientId($clients[$clientKey]);
201
        }
202
203
        if (isset($projects) && isset($projectKey)) {
204
            $task->setProjectId($projects[$projectKey]);
205
        }
206
207
        $this->invoiceNinjaClient->saveNewTask($task);
208
    }
209
210
    /**
211
     * @param TimeEntry $entry
212
     *
213
     * @return string
214
     */
215
    private function buildTaskDescription(TimeEntry $entry): string
216
    {
217
        $description = '';
218
219
        if ($entry->getProject()) {
220
            $description .= $entry->getProject() . ': ';
221
        }
222
223
        $description .= $entry->getDescription();
224
225
        return $description;
226
    }
227
228
    /**
229
     * @param TimeEntry $entry
230
     *
231
     * @return string
232
     */
233
    private function buildTimeLog(TimeEntry $entry): string
234
    {
235
        $timeLog = [[
236
            $entry->getStart()->getTimestamp(),
237
            $entry->getEnd()->getTimestamp(),
238
        ]];
239
240
        return \GuzzleHttp\json_encode($timeLog);
241
    }
242
243
    /**
244
     * Retrieve clients from Toggl and match them with their corresponding InvoiceNinja client,
245
     * or create a new InvoiceNinja client for the Toggl client.
246
     * 
247
     * @param  int    $workspaceId
248
     * @param  array  $workspaceClients
249
     *
250
     * @return array
251
     */
252
    private function retrieveClientsForWorkspace(int $workspaceId, array $workspaceClients): array
253
    {
254
        $togglClients = $this->togglClient->getClientsForWorkspace($workspaceId);
255
        $invoiceNinjaClients = $this->invoiceNinjaClient->getClients();
256
257
        $clients = [];
258
259
        foreach ($togglClients as $togglClient) {
0 ignored issues
show
Bug introduced by
The expression $togglClients of type object|array|integer|double|string|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
260
            if (!isset($workspaceClients[$togglClient->getName()])) {
261
                $found = false;
262
                foreach ($invoiceNinjaClients as $invoiceNinjaClient) {
263
                    if ($invoiceNinjaClient->getIsDeleted() == false && strcasecmp($togglClient->getName(), $invoiceNinjaClient->getName()) == 0) {
264
                        $clients[$invoiceNinjaClient->getName()] = $invoiceNinjaClient->getId();
265
                        $found = true;
266
                    }
267
                }
268
                if (!$found) {
269
                    $client = new InvoiceNinjaClientDto();
270
271
                    $client->setName($togglClient->getName());
272
273
                    $clients[$togglClient->getName()] = $this->invoiceNinjaClient->saveNewClient($client)->getId();
274
275
                    $this->io->success('Client ('. $togglClient->getName() . ') created in InvoiceNinja');
276
                }
277
            }
278
        }
279
280
        return $clients;
281
    }
282
283
    /**
284
     * Retrieve projects from Toggl and match them with their corresponding InvoiceNinja project,
285
     * or create a new InvoiceNinja project for the Toggl project.
286
     * 
287
     * @param  int    $workspaceId
288
     * @param  array  $workspaceClients
289
     * @param  array  $workspaceProjects
290
     * 
291
     * @return array
292
     */
293
    private function retrieveProjectsForWorkspace(int $workspaceId, array &$workspaceClients, array $workspaceProjects): array
294
    {
295
        $togglProjects = $this->togglClient->getProjectsForWorkspace($workspaceId);
296
        $togglClients = $this->togglClient->getClientsForWorkspace($workspaceId);
297
        $invoiceNinjaProjects = $this->invoiceNinjaClient->getProjects();
298
299
        $projects = [];
300
301
        foreach ($togglProjects as $togglProject) {
0 ignored issues
show
Bug introduced by
The expression $togglProjects of type object|array|integer|double|string|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
302
            if (!isset($workspaceProjects[$togglProject->getName()])) {
303
                $found = false;
304
                foreach ($invoiceNinjaProjects as $invoiceNinjaProject) {
305
                    if ($invoiceNinjaProject->getIsDeleted() == false && strcasecmp($togglProject->getName(), $invoiceNinjaProject->getName()) == 0) {
306
                        $projects[$invoiceNinjaProject->getName()] = $invoiceNinjaProject->getId();
307
                        $found = true;
308
                    }
309
                }
310
                if (!$found) {
311
                    $clientPresent = true;
312
313
                    $project = new InvoiceNinjaProject();
314
315
                    $project->setName($togglProject->getName());
316
317
                    if ($togglProject->getCid() !== NULL) {
318
                        foreach ($togglClients as $togglClient) {
0 ignored issues
show
Bug introduced by
The expression $togglClients of type object|array|integer|double|string|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
319
                            if ($togglClient->getWid() == $workspaceId && $togglClient->getId() == $togglProject->getCid()) {
320
                                $project->setClientId($workspaceClients[$togglClient->getName()]);
321
                            }
322
                        }
323
                    } else if ($this->useProjectsAsClients) {
324
                        $client = new InvoiceNinjaClientDto();
325
326
                        $client->setName($togglProject->getName());
327
328
                        $workspaceClients[$togglProject->getName()] = $this->invoiceNinjaClient->saveNewClient($client)->getId();
329
330
                        $this->io->success('Project ('. $togglProject->getName() . ') created as Client in InvoiceNinja');
331
332
                        $project->setClientId($workspaceClients[$togglProject->getName()]);
333
                    } else {
334
                        $clientPresent = false;
335
336
                        $this->io->error('Client not provided for Project ('. $togglProject->getName() . ') in Toggl');
337
                        $this->io->warning("To allow using projects as clients enable 'use_projects_as_clients' in parameters config file");
338
                    }
339
340
                    if ($clientPresent) {
341
                        $projects[$togglProject->getName()] = $this->invoiceNinjaClient->saveNewProject($project)->getId();
342
343
                        $this->io->success('Project ('. $togglProject->getName() . ') created in InvoiceNinja');
344
                    }
345
                }
346
            }
347
        }
348
349
        return $projects;
350
    }
351
352
    /**
353
     * Retrieve the Invoice Ninja client id given the name of a project the client is assigned to.
354
     * @param  string $projectName
355
     * @return string
356
     */
357
    private function getInvoiceNinjaClientIdForProject(string $projectName): ?int
358
    {
359
        $invoiceNinjaProjects  = $this->invoiceNinjaClient->getProjects();
360
        $invoiceNinjaClientId = NULL;
361
362
        foreach ($invoiceNinjaProjects as $invoiceNinjaProject) {
363
            if ($invoiceNinjaProject->getName() == $projectName) {
364
                $invoiceNinjaClientId = $invoiceNinjaProject->getClientId();
365
            }
366
        }
367
368
        return $invoiceNinjaClientId;
369
    }
370
371
    /**
372
     * Retrieve log of past sent time entries to prevent sending the same time entries over again.
373
     * 
374
     * @return array
375
     */
376
    private function retrieveSentTimeEntries(): array
377
    {
378
        if (!file_exists($this->storageDir)) {
379
            mkdir($this->storageDir, 0777, true);
380
        }
381
382
        if (!file_exists($this->storageDir . $this->storageFileName)) {
383
            touch($this->storageDir . $this->storageFileName);
384
        }
385
        
386
        $sentTimeEntries = unserialize(file_get_contents($this->storageDir . $this->storageFileName));
387
388
        if (!is_array($sentTimeEntries)) {
389
            $sentTimeEntries = [];
390
        }
391
392
        return $sentTimeEntries;
393
    }
394
395
    /**
396
     * Store newly sent time entries into time entry log.
397
     *
398
     * @return void
399
     */
400
    private function storeSentTimeEntries($sentTimeEntries)
401
    {
402
        file_put_contents($this->storageDir . $this->storageFileName, serialize($sentTimeEntries));
403
    }
404
}
405