Completed
Push — master ( 252970...4a8c07 )
by Faiz
02:03
created

SurahCommand::askSurah()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 8
ccs 0
cts 7
cp 0
rs 9.4285
cc 1
eloc 5
nc 1
nop 3
crap 2
1
<?php
2
3
namespace FaizShukri\Quran\Commands;
4
5
use FaizShukri\Quran\Quran;
6
use FaizShukri\Quran\Supports\Levenshtein;
7
use FaizShukri\Quran\Exceptions\SurahInvalid;
8
use Symfony\Component\Console\Helper\Table;
9
use Symfony\Component\Console\Helper\TableCell;
10
use Symfony\Component\Console\Command\Command;
11
use Symfony\Component\Console\Input\InputArgument;
12
use Symfony\Component\Console\Input\InputInterface;
13
use Symfony\Component\Console\Output\OutputInterface;
14
use Symfony\Component\Console\Question\ChoiceQuestion;
15
16
class SurahCommand extends Command
17
{
18
    private $quran;
19
20
    public function __construct()
21
    {
22
        parent::__construct();
23
        $this->quran = new Quran();
24
    }
25
26
    protected function configure()
27
    {
28
        $this
29
            ->setName('surah')
30
            ->setDescription('Retrieve surah')
31
            ->addArgument(
32
                'surah',
33
                InputArgument::OPTIONAL,
34
                'Specify surah'
35
            )
36
            ->addArgument(
37
                'ayah',
38
                InputArgument::OPTIONAL,
39
                'Specify ayah'
40
            )
41
            ->addArgument(
42
                'translation',
43
                InputArgument::OPTIONAL,
44
                'Specify ayah'
45
            )
46
            ->addUsage('2')
47
            ->addUsage('2 3')
48
            ->addUsage('2 3,5 en')
49
            ->addUsage('2 3,5-6 ar,en')
50
        ;
51
    }
52
53
    protected function execute(InputInterface $input, OutputInterface $output)
54
    {
55
        $surah = $input->getArgument('surah');
56
        $ayah = $input->getArgument('ayah');
57
        $translation = $input->getArgument('translation');
58
59
        $surah = $this->confirmSurahNo($surah, $input, $output);
60
61
        if ($ayah) {
62
            if ($translation) {
63
                $this->quran->translation($translation);
64
            }
65
66
            $ayah = $this->quran->get($surah.':'.$ayah);
67
            $output->writeln($this->parseResult($ayah));
68
        } elseif ($surah) {
69
            $this->chapter($output, $surah);
70
        } else {
71
            $this->chapters($output);
72
        }
73
    }
74
75
    private function chapter($output, $verse)
76
    {
77
        $surah = $this->quran->getSource()->surah($verse);
78
79
        $table = new Table($output);
80
        $table
81
            ->setHeaders([
82
                [new TableCell('Surah '.$surah['tname'], array('colspan' => 2))],
83
            ])
84
            ->setRows([
85
                ['Index',  $surah['index']],
86
                ['Name',  $surah['tname']],
87
                ['Name (ar)',  $surah['name']],
88
                ['Meaning',  $surah['ename']],
89
                ['No. Ayah',  $surah['ayas']],
90
                ['Start',  $surah['start']],
91
                ['Type',  $surah['type']],
92
                ['Order',  $surah['order']],
93
                ['Rukus',  $surah['rukus']],
94
            ])
95
            ->setStyle('borderless')
96
        ;
97
        $table->render();
98
    }
99
100
    private function chapters($output)
101
    {
102
        $surah = $this->quran->getSource()->chapters();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface FaizShukri\Quran\Reposit...\Source\SourceInterface as the method chapters() does only exist in the following implementations of said interface: FaizShukri\Quran\Repositories\Source\XMLRepository.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
103
        $surah = array_map(function ($sura) { return "$sura->index. $sura->tname"; }, $surah);
104
        $surah = $this->array_chunk_vertical($surah, 4);
105
106
        $table = new Table($output);
107
        $table
108
            ->setHeaders([
109
                [new TableCell('All surah', array('colspan' => 4))],
110
            ])
111
            ->setRows($surah)
112
        ;
113
        $table->render();
114
    }
115
116
    private function array_chunk_vertical($data, $columns)
117
    {
118
        $n = count($data);
119
        $per_column = floor($n / $columns);
120
        $rest = $n % $columns;
121
122
        // The map
123
        $per_columns = array();
124
        for ($i = 0; $i < $columns; ++$i) {
125
            $per_columns[$i] = $per_column + ($i < $rest ? 1 : 0);
126
        }
127
128
        $tabular = array();
129
        foreach ($per_columns as $rows) {
130
            for ($i = 0; $i < $rows; ++$i) {
131
                $tabular[$i][ ] = array_shift($data);
132
            }
133
        }
134
135
        return $tabular;
136
    }
137
138
    /**
139
     * @param int|string|null $surah
140
     * @param InputInterface  $input
141
     * @param OutputInterface $output
142
     *
143
     * @return int|null
144
     *
145
     * @throws SurahInvalid
146
     */
147
    private function confirmSurahNo($surah, $input, $output)
148
    {
149
        if ($surah && !is_numeric($surah)) {
150
            $surah_list = $this->quran->surah();
151
            $surah_list_array = array_map(function ($surah) { return $surah->tname; }, (array) $surah_list);
152
            $closest_surah = $this->closestSurah($surah, $surah_list_array);
153
154
            if (sizeof($closest_surah) == 1) {
155
                $surah = $closest_surah[0];
156
            } elseif (sizeof($closest_surah) > 1) {
157
                $surah = $this->askSurah($closest_surah, $input, $output);
158
            } else {
159
                throw new SurahInvalid();
160
            }
161
            $surah = array_search($surah, $surah_list_array);
162
        }
163
164
        return ($surah === null) ? null : intval($surah);
165
    }
166
167
    /**
168
     * @param string $surah
169
     * @param array  $options
170
     *
171
     * @return array
172
     */
173
    private function closestSurah($surah, $options)
174
    {
175
        $l = new Levenshtein();
176
177
        return $l->closest($surah, $options);
178
    }
179
180
    /**
181
     * @param array           $options
182
     * @param InputInterface  $input
183
     * @param OutputInterface $output
184
     *
185
     * @return mixed
186
     */
187
    private function askSurah($options, $input, $output)
188
    {
189
        $helper = $this->getHelper('question');
190
        $question = new ChoiceQuestion('No surah found. Did you mean one of the following?', $options, 0);
191
        $question->setErrorMessage('Surah %s is invalid.');
192
193
        return $helper->ask($input, $output, $question);
194
    }
195
196
    private function parseResult($args)
197
    {
198
        // Just a single ayah is return. No need to parse anything.
199
        if (is_string($args)) {
200
            return $args."\n";
201
        }
202
203
        // Multiple ayah/one surah or multiple surah/one ayah. Not both.
204
        if (is_string(current($args))) {
205
            return $this->buildAyah($args);
206
        }
207
208
        // Both multiple ayah and multiple surah.
209
        $count = 0;
210
        $result = "\n";
211
212
        foreach ($args as $translation => $aya) {
213
            $result .= strtoupper($translation)."\n".str_repeat('=', strlen($translation) + 2)."\n\n";
214
            $result .= $this->buildAyah($aya);
215
216
            ++$count;
217
            if ($count < sizeof($args)) {
218
                $result .= "\n\n";
219
            }
220
        }
221
222
        return $result;
223
    }
224
225
    private function buildAyah($ayah)
226
    {
227
        $result = '';
228
        foreach ($ayah as $num => $aya) {
229
            $result .= '[ '.strtoupper($num)." ]\t".$aya."\n";
230
        }
231
232
        return $result;
233
    }
234
}
235