Complex classes like LogfileCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use LogfileCommand, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | class LogfileCommand extends Command |
||
33 | { |
||
34 | /** |
||
35 | * @var array |
||
36 | */ |
||
37 | private $undefinedClients = []; |
||
38 | |||
39 | /** |
||
40 | * @var array |
||
41 | */ |
||
42 | private $uas = []; |
||
43 | |||
44 | /** |
||
45 | * @var array |
||
46 | */ |
||
47 | private $uasWithType = []; |
||
48 | |||
49 | /** |
||
50 | * @var int |
||
51 | */ |
||
52 | private $countOk = 0; |
||
53 | |||
54 | /** |
||
55 | * @var int |
||
56 | */ |
||
57 | private $countNok = 0; |
||
58 | |||
59 | /** |
||
60 | * @var int |
||
61 | */ |
||
62 | private $totalCount = 0; |
||
63 | |||
64 | /** |
||
65 | * @var string |
||
66 | */ |
||
67 | private $defaultCacheFolder; |
||
68 | |||
69 | public function __construct(string $defaultCacheFolder) |
||
70 | { |
||
71 | $this->defaultCacheFolder = $defaultCacheFolder; |
||
72 | |||
73 | parent::__construct(); |
||
74 | } |
||
75 | |||
76 | 1 | protected function configure() : void |
|
77 | { |
||
78 | $this |
||
79 | 1 | ->setName('browscap:log') |
|
80 | 1 | ->setDescription('Parses the supplied webserver log file.') |
|
81 | 1 | ->addArgument( |
|
82 | 1 | 'output', |
|
83 | 1 | InputArgument::REQUIRED, |
|
84 | 1 | 'Path to output log file', |
|
85 | 1 | null |
|
86 | ) |
||
87 | 1 | ->addOption( |
|
88 | 1 | 'log-file', |
|
89 | 1 | 'f', |
|
90 | 1 | InputOption::VALUE_REQUIRED, |
|
91 | 1 | 'Path to a webserver log file' |
|
92 | ) |
||
93 | 1 | ->addOption( |
|
94 | 1 | 'log-dir', |
|
95 | 1 | 'd', |
|
96 | 1 | InputOption::VALUE_REQUIRED, |
|
97 | 1 | 'Path to webserver log directory' |
|
98 | ) |
||
99 | 1 | ->addOption( |
|
100 | 1 | 'include', |
|
101 | 1 | 'i', |
|
102 | 1 | InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, |
|
103 | 1 | 'Include glob expressions for log files in the log directory', |
|
104 | 1 | ['*.log', '*.log*.gz', '*.log*.bz2'] |
|
105 | ) |
||
106 | 1 | ->addOption( |
|
107 | 1 | 'exclude', |
|
108 | 1 | 'e', |
|
109 | 1 | InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, |
|
110 | 1 | 'Exclude glob expressions for log files in the log directory', |
|
111 | 1 | ['*error*'] |
|
112 | ) |
||
113 | 1 | ->addOption( |
|
114 | 1 | 'cache', |
|
115 | 1 | 'c', |
|
116 | 1 | InputOption::VALUE_OPTIONAL, |
|
117 | 1 | 'Where the cache files are located', |
|
118 | 1 | $this->defaultCacheFolder |
|
119 | ); |
||
120 | 1 | } |
|
121 | |||
122 | protected function execute(InputInterface $input, OutputInterface $output) : void |
||
123 | { |
||
124 | if (! $input->getOption('log-file') && ! $input->getOption('log-dir')) { |
||
125 | throw InvalidArgumentException::oneOfCommandArguments('log-file', 'log-dir'); |
||
126 | } |
||
127 | |||
128 | $logger = LoggerHelper::createDefaultLogger($output); |
||
129 | |||
130 | $fileCache = new FilesystemCache($input->getOption('cache')); |
||
131 | $cache = new SimpleCacheAdapter($fileCache); |
||
132 | |||
133 | $browscap = new Browscap($cache, $logger); |
||
134 | $collection = ReaderFactory::factory(); |
||
135 | $fs = new Filesystem(); |
||
136 | |||
137 | /** @var $file \Symfony\Component\Finder\SplFileInfo */ |
||
138 | foreach ($this->getFiles($input) as $file) { |
||
139 | $this->uas = []; |
||
140 | $path = $this->getPath($file); |
||
141 | |||
142 | $this->countOk = 0; |
||
143 | $this->countNok = 0; |
||
144 | |||
145 | $logger->info('Analyzing file "' . $file->getPathname() . '"'); |
||
146 | |||
147 | $lines = file($path); |
||
148 | |||
149 | if (empty($lines)) { |
||
150 | $logger->info('Skipping empty file "' . $file->getPathname() . '"'); |
||
151 | continue; |
||
152 | } |
||
153 | |||
154 | $this->totalCount = count($lines); |
||
155 | |||
156 | foreach ($lines as $line) { |
||
157 | $this->handleLine( |
||
158 | $output, |
||
159 | $collection, |
||
160 | $browscap, |
||
161 | $line |
||
162 | ); |
||
163 | } |
||
164 | |||
165 | $this->outputProgress($output, '', true); |
||
166 | |||
167 | arsort($this->uas, SORT_NUMERIC); |
||
168 | |||
169 | try { |
||
170 | $fs->dumpFile( |
||
171 | $input->getArgument('output') . '/output.txt', |
||
172 | implode(PHP_EOL, array_unique($this->undefinedClients)) |
||
173 | ); |
||
174 | } catch (IOException $e) { |
||
175 | // do nothing |
||
176 | } |
||
177 | |||
178 | try { |
||
179 | $fs->dumpFile( |
||
180 | $input->getArgument('output') . '/output-with-amount.txt', |
||
181 | $this->createAmountContent() |
||
182 | ); |
||
183 | } catch (IOException $e) { |
||
184 | // do nothing |
||
185 | } |
||
186 | |||
187 | try { |
||
188 | $fs->dumpFile( |
||
189 | $input->getArgument('output') . '/output-with-amount-and-type.txt', |
||
190 | $this->createAmountTypeContent() |
||
191 | ); |
||
192 | } catch (IOException $e) { |
||
193 | // do nothing |
||
194 | } |
||
195 | } |
||
196 | |||
197 | $outputFile = $input->getArgument('output') . '/output.txt'; |
||
198 | |||
199 | try { |
||
200 | $fs->dumpFile( |
||
201 | $outputFile, |
||
202 | implode(PHP_EOL, array_unique($this->undefinedClients)) |
||
203 | ); |
||
204 | } catch (IOException $e) { |
||
205 | throw new \UnexpectedValueException('writing to file "' . $outputFile . '" failed', 0, $e); |
||
206 | } |
||
207 | |||
208 | try { |
||
209 | $fs->dumpFile( |
||
210 | $input->getArgument('output') . '/output-with-amount.txt', |
||
211 | $this->createAmountContent() |
||
212 | ); |
||
213 | } catch (IOException $e) { |
||
214 | // do nothing |
||
215 | } |
||
216 | |||
217 | try { |
||
218 | $fs->dumpFile( |
||
219 | $input->getArgument('output') . '/output-with-amount-and-type.txt', |
||
220 | $this->createAmountTypeContent() |
||
221 | ); |
||
222 | } catch (IOException $e) { |
||
223 | // do nothing |
||
224 | } |
||
225 | } |
||
226 | |||
227 | private function createAmountContent() : string |
||
251 | |||
252 | private function createAmountTypeContent() : string |
||
253 | { |
||
254 | $content = ''; |
||
255 | $types = ['B', 'T', 'P', 'D', 'N', 'U']; |
||
256 | |||
257 | foreach ($types as $type) { |
||
258 | if (! isset($this->uasWithType[$type])) { |
||
259 | continue; |
||
260 | } |
||
261 | |||
262 | arsort($this->uasWithType[$type], SORT_NUMERIC); |
||
263 | |||
264 | foreach ($this->uasWithType[$type] as $agentOfLine => $count) { |
||
265 | $content .= "$type\t$count\t$agentOfLine\n"; |
||
266 | } |
||
267 | } |
||
268 | |||
269 | return $content; |
||
270 | } |
||
271 | |||
272 | private function handleLine( |
||
338 | |||
339 | private function outputProgress(OutputInterface $output, string $result, bool $end = false) : void |
||
340 | { |
||
341 | if (($this->totalCount % 70) === 0 || $end) { |
||
342 | $formatString = ' %' . strlen($this->countOk) . 'd OK, %' . strlen($this->countNok) . 'd NOK, Summary %' |
||
343 | . strlen($this->totalCount) . 'd'; |
||
344 | |||
345 | if ($end) { |
||
346 | $result = str_pad($result, 70 - ($this->totalCount % 70), ' ', STR_PAD_RIGHT); |
||
347 | } |
||
348 | |||
349 | $endString = sprintf($formatString, $this->countOk, $this->countNok, $this->totalCount); |
||
350 | |||
351 | $output->writeln($result . $endString); |
||
352 | |||
353 | return; |
||
354 | } |
||
355 | |||
356 | $output->write($result); |
||
357 | } |
||
358 | |||
359 | private function getResult(\stdClass $result) : string |
||
387 | |||
388 | private function getFiles(InputInterface $input) : Finder |
||
389 | { |
||
390 | $finder = Finder::create(); |
||
391 | |||
392 | if ($input->getOption('log-file')) { |
||
408 | |||
409 | private function getPath(SplFileInfo $file) : string |
||
425 | } |
||
426 |