Passed
Pull Request — master (#790)
by Guilherme
06:37 queued 02:01
created

ConvertSubjectIdentifiersCommand::execute()   C

Complexity

Conditions 8
Paths 49

Size

Total Lines 74
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 48
nc 49
nop 2
dl 0
loc 74
rs 6.2894
c 0
b 0
f 0

How to fix   Long Method   

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
2
/**
3
 * This file is part of the login-cidadao project or it's bundles.
4
 *
5
 * (c) Guilherme Donato <guilhermednt on github>
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 LoginCidadao\OpenIDBundle\Command;
12
13
use Doctrine\ORM\EntityManagerInterface;
14
use Doctrine\ORM\NonUniqueResultException;
15
use Doctrine\ORM\NoResultException;
16
use LoginCidadao\CoreBundle\Entity\Authorization;
17
use LoginCidadao\CoreBundle\Entity\AuthorizationRepository;
18
use LoginCidadao\OAuthBundle\Model\ClientInterface;
19
use LoginCidadao\OpenIDBundle\Entity\ClientMetadata;
20
use LoginCidadao\OpenIDBundle\Manager\ClientManager;
21
use LoginCidadao\OpenIDBundle\Service\SubjectIdentifierService;
22
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
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
use Symfony\Component\Console\Style\SymfonyStyle;
28
29
/**
30
 * @codeCoverageIgnore Ignoring command code. Ideally it should be refactored into a testable Service
31
 */
32
class ConvertSubjectIdentifiersCommand extends ContainerAwareCommand
33
{
34
    /** @var EntityManagerInterface */
35
    private $em;
36
37
    /** @var array */
38
    private $clientMetadata = [];
39
40
    protected function configure()
41
    {
42
        $this
43
            ->setName('lc:oidc:convert-subject-identifiers')
44
            ->setDescription("Convert Subject Identifiers, from public to pairwise.")
45
            ->addArgument('client', InputArgument::REQUIRED, "Client's ID")
46
            ->addArgument('file', InputArgument::REQUIRED, "File where to save the conversion result")
47
            ->addOption('dry-run', null, InputOption::VALUE_NONE,
48
                'Prevent changes from being persisted on the database');
49
    }
50
51
    /**
52
     * @param InputInterface $input
53
     * @param OutputInterface $output
54
     * @return int|null|void
55
     */
56
    protected function execute(InputInterface $input, OutputInterface $output)
57
    {
58
        $dryRyn = $input->getOption('dry-run');
59
        $io = new SymfonyStyle($input, $output);
60
61
        $io->title('Convert Public Subject Identifiers to Pairwise');
62
63
        $fp = $this->checkAndOpenFile($io, $input);
64
        if ($fp === false) {
65
            return;
66
        }
67
68
        $this->em = $this->getContainer()->get('doctrine.orm.entity_manager');
69
        $client = $this->getClient($input->getArgument('client'));
70
71
        $count = $this->checkCount($io, $client);
72
        if ($count === null) {
73
            fclose($fp);
74
75
            return;
76
        }
77
78
        $publicSubs = $this->getPublicSubQuery($client)
79
            ->getQuery()->iterate();
80
81
        $io->section("Generating Sector Identifiers...");
82
83
        /** @var SubjectIdentifierService $subIdService */
84
        $subIdService = $this->getContainer()->get('oidc.subject_identifier.service');
85
86
        $metadata = $this->getClientMetadata($client);
87
        $metadata->setSubjectType('pairwise');
88
89
        $io->progressStart($count);
90
        $this->em->beginTransaction();
91
        $done = 0;
92
        try {
93
            foreach ($publicSubs as $row) {
94
                /** @var Authorization $auth */
95
                $auth = $row[0];
96
                $person = $auth->getPerson();
97
98
                $sub = $subIdService->convertSubjectIdentifier($person, $metadata);
99
100
                $row = [$person->getId(), $sub->getSubjectIdentifier()];
101
                if (false === fputcsv($fp, $row)) {
102
                    throw new \RuntimeException('Error writing CSV to file! Aborting!');
103
                }
104
105
                $this->em->persist($sub);
106
                if ($done++ % 50 === 0) {
107
                    $this->em->flush();
108
                    $this->em->clear();
109
                }
110
                $io->progressAdvance();
111
            }
112
            $io->progressFinish();
113
114
            if ($dryRyn) {
115
                $this->em->rollback();
116
                $io->note("Dry Run: no changes were persisted.");
117
            } else {
118
                $this->em->commit();
119
            }
120
            $this->em->flush();
121
            $this->em->clear();
122
123
            $io->success("Done! {$done} Authorizations updated!");
124
        } catch (\Exception $e) {
125
            $io->error($e->getMessage());
126
            $this->em->rollback();
127
            $this->em->clear();
128
        }
129
        fclose($fp);
130
    }
131
132
    /**
133
     * @return AuthorizationRepository
134
     */
135
    private function getAuthorizationRepository()
136
    {
137
        /** @var AuthorizationRepository $repo */
138
        $repo = $this->em->getRepository('LoginCidadaoCoreBundle:Authorization');
139
140
        return $repo;
141
    }
142
143
    /**
144
     * @param ClientInterface $client
145
     * @return ClientMetadata
146
     */
147
    private function getClientMetadata(ClientInterface $client)
148
    {
149
        $id = $client->getId();
150
        if (array_key_exists($id, $this->clientMetadata)) {
151
            $metadata = $this->clientMetadata[$id];
152
        } else {
153
            $metadata = $client->getMetadata();
154
            $this->clientMetadata[$id] = $metadata;
155
        }
156
157
        return $metadata;
158
    }
159
160
    /**
161
     * @param $clientId
162
     * @return ClientInterface
163
     */
164
    private function getClient($clientId)
165
    {
166
        /** @var ClientManager $clientManager */
167
        $clientManager = $this->getContainer()->get('lc.client_manager');
168
169
        $client = $clientManager->getClientById($clientId);
170
        if (!$client instanceof ClientInterface) {
171
            // TODO: use appropriate exception
172
            throw new \RuntimeException('Client not found');
173
        }
174
175
        return $client;
176
    }
177
178
    /**
179
     * @param ClientInterface $client
180
     * @return \Doctrine\ORM\QueryBuilder
181
     */
182
    private function getPublicSubQuery(ClientInterface $client)
183
    {
184
        $repo = $this->getAuthorizationRepository();
185
186
        return $repo->createQueryBuilder('a')
187
            ->innerJoin('LoginCidadaoCoreBundle:Person', 'p', 'WITH', 'a.person = p')
188
            ->leftJoin(
189
                'LoginCidadaoOpenIDBundle:SubjectIdentifier',
190
                's',
191
                'WITH',
192
                's.person = a.person AND s.client = a.client'
193
            )
194
            ->where('CAST(p.id AS text) = s.subjectIdentifier')
195
            ->andWhere('a.client = :client')
196
            ->setParameter('client', $client);
197
    }
198
199
    /**
200
     * @param SymfonyStyle $io
201
     * @param ClientInterface $client
202
     * @return int|null
203
     */
204
    private function checkCount(SymfonyStyle $io, ClientInterface $client)
205
    {
206
        try {
207
            $count = $this->getPublicSubQuery($client)
208
                ->select('COUNT(a)')
209
                ->getQuery()
210
                ->getSingleScalarResult();
211
        } catch (NoResultException $e) {
212
            $count = 0;
213
        } catch (NonUniqueResultException $e) {
214
            $io->error("Error counting public subject identifiers: {$e->getMessage()}");
215
216
            return null;
217
        }
218
219
        if ($count === 0) {
220
            $io->success("No changes needed. You're all set!");
221
222
            return null;
223
        }
224
225
        return $count;
226
    }
227
228
    /**
229
     * @param SymfonyStyle $io
230
     * @param InputInterface $input
231
     * @return false|resource file pointer on success or false on error
232
     */
233
    private function checkAndOpenFile(SymfonyStyle $io, InputInterface $input)
234
    {
235
        $file = $input->getArgument('file');
236
        if (file_exists($file) && !$io->confirm("File '{$file}' already exists. Override?", false)) {
237
            $io->comment('Aborted');
238
239
            return false;
240
        }
241
        if (false === $fp = fopen($file, 'w')) {
242
            $io->error("Error opening file '{$file}'...");
243
        }
244
245
        return $fp;
246
    }
247
}
248