Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like InstallCommand 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 InstallCommand, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | class InstallCommand extends AbstractMagentoCommand |
||
33 | { |
||
34 | /** |
||
35 | * @deprecated since since 1.97.22; Use constant from Exec-Utility instead |
||
36 | * @see Exec::CODE_CLEAN_EXIT |
||
37 | */ |
||
38 | const EXEC_STATUS_OK = 0; |
||
39 | |||
40 | const DEFAULT_SESSION_PATH = 'var/session'; |
||
41 | |||
42 | const MAGENTO_INSTALL_SCRIPT_PATH = 'install.php'; |
||
43 | |||
44 | /** |
||
45 | * @var array |
||
46 | */ |
||
47 | protected $config; |
||
48 | |||
49 | /** |
||
50 | * @var array |
||
51 | */ |
||
52 | protected $_argv; |
||
53 | |||
54 | /** |
||
55 | * @var array |
||
56 | */ |
||
57 | protected $commandConfig; |
||
58 | |||
59 | /** |
||
60 | * @var \Closure |
||
61 | */ |
||
62 | protected $notEmptyCallback; |
||
63 | |||
64 | protected function configure() |
||
65 | { |
||
66 | $this |
||
67 | ->setName('install') |
||
68 | ->addOption('magentoVersion', null, InputOption::VALUE_OPTIONAL, 'Magento version') |
||
69 | ->addOption( |
||
70 | 'magentoVersionByName', |
||
71 | null, |
||
72 | InputOption::VALUE_OPTIONAL, |
||
73 | 'Magento version name instead of order number' |
||
74 | ) |
||
75 | ->addOption('installationFolder', null, InputOption::VALUE_OPTIONAL, 'Installation folder') |
||
76 | ->addOption('dbHost', null, InputOption::VALUE_OPTIONAL, 'Database host') |
||
77 | ->addOption('dbUser', null, InputOption::VALUE_OPTIONAL, 'Database user') |
||
78 | ->addOption('dbPass', null, InputOption::VALUE_OPTIONAL, 'Database password') |
||
79 | ->addOption('dbName', null, InputOption::VALUE_OPTIONAL, 'Database name') |
||
80 | ->addOption('dbPort', null, InputOption::VALUE_OPTIONAL, 'Database port', 3306) |
||
81 | ->addOption('dbPrefix', null, InputOption::VALUE_OPTIONAL, 'Table prefix', '') |
||
82 | ->addOption('installSampleData', null, InputOption::VALUE_OPTIONAL, 'Install sample data') |
||
83 | ->addOption( |
||
84 | 'useDefaultConfigParams', |
||
85 | null, |
||
86 | InputOption::VALUE_OPTIONAL, |
||
87 | 'Use default installation parameters defined in the yaml file' |
||
88 | )->addOption('baseUrl', null, InputOption::VALUE_OPTIONAL, 'Installation base url') |
||
89 | ->addOption( |
||
90 | 'replaceHtaccessFile', |
||
91 | null, |
||
92 | InputOption::VALUE_OPTIONAL, |
||
93 | 'Generate htaccess file (for non vhost environment)' |
||
94 | )->addOption( |
||
95 | 'noDownload', |
||
96 | null, |
||
97 | InputOption::VALUE_NONE, |
||
98 | 'If set skips download step. Used when installationFolder is already a Magento installation that has ' . |
||
99 | 'to be installed on the given database.' |
||
100 | ) |
||
101 | ->addOption( |
||
102 | 'only-download', |
||
103 | null, |
||
104 | InputOption::VALUE_NONE, |
||
105 | 'Downloads (and extracts) source code' |
||
106 | ) |
||
107 | ->addOption( |
||
108 | 'forceUseDb', |
||
109 | null, |
||
110 | InputOption::VALUE_NONE, |
||
111 | 'If --noDownload passed, force to use given database if it already exists.' |
||
112 | )->setDescription('Install magento'); |
||
113 | |||
114 | $help = <<<HELP |
||
115 | * Download Magento by a list of git repos and zip files (mageplus, |
||
116 | magelte, official community packages). |
||
117 | * Try to create database if it does not exist. |
||
118 | * Installs Magento sample data if available (since version 1.2.0). |
||
119 | * Starts Magento installer |
||
120 | * Sets rewrite base in .htaccess file |
||
121 | |||
122 | Example of an unattended Magento CE 1.7.0.2 installation: |
||
123 | |||
124 | $ n98-magerun.phar install --dbHost="localhost" --dbUser="mydbuser" \ |
||
125 | --dbPass="mysecret" --dbName="magentodb" --installSampleData=yes \ |
||
126 | --useDefaultConfigParams=yes \ |
||
127 | --magentoVersionByName="magento-ce-1.7.0.2" \ |
||
128 | --installationFolder="magento" --baseUrl="http://magento.localdomain/" |
||
129 | |||
130 | Additionally, with --noDownload option you can install Magento working |
||
131 | copy already stored in --installationFolder on the given database. |
||
132 | |||
133 | See it in action: http://youtu.be/WU-CbJ86eQc |
||
134 | |||
135 | HELP; |
||
136 | $this->setHelp($help); |
||
137 | |||
138 | $this->notEmptyCallback = function ($input) { |
||
139 | if (empty($input)) { |
||
140 | throw new InvalidArgumentException('Please enter a value'); |
||
141 | } |
||
142 | |||
143 | return $input; |
||
144 | }; |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * @return bool |
||
149 | */ |
||
150 | public function isEnabled() |
||
151 | { |
||
152 | return Exec::allowed(); |
||
153 | } |
||
154 | |||
155 | /** |
||
156 | * @param InputInterface $input |
||
157 | * @param OutputInterface $output |
||
158 | * @throws RuntimeException |
||
159 | * @return int|null|void |
||
160 | */ |
||
161 | protected function execute(InputInterface $input, OutputInterface $output) |
||
162 | { |
||
163 | $this->commandConfig = $this->getCommandConfig(); |
||
164 | $this->writeSection($output, 'Magento Installation'); |
||
165 | |||
166 | $this->precheckPhp(); |
||
167 | |||
168 | if (!$input->getOption('noDownload')) { |
||
169 | $this->selectMagentoVersion($input, $output); |
||
170 | } |
||
171 | |||
172 | $this->chooseInstallationFolder($input, $output); |
||
173 | |||
174 | if (!$input->getOption('noDownload')) { |
||
175 | $result = $this->downloadMagento($input, $output); |
||
176 | |||
177 | if ($result === false) { |
||
178 | return 1; |
||
179 | } |
||
180 | } |
||
181 | |||
182 | if ($input->getOption('only-download')) { |
||
183 | return 0; |
||
184 | } |
||
185 | |||
186 | $this->createDatabase($input, $output); |
||
187 | |||
188 | if (!$input->getOption('noDownload')) { |
||
189 | $this->installSampleData($input, $output); |
||
190 | } |
||
191 | |||
192 | $this->removeEmptyFolders(); |
||
193 | $this->setDirectoryPermissions($output); |
||
194 | $this->installMagento($input, $output, $this->config['installationFolder']); |
||
|
|||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Check PHP environment agains minimal required settings modules |
||
199 | */ |
||
200 | protected function precheckPhp() |
||
201 | { |
||
202 | $extensions = $this->commandConfig['installation']['pre-check']['php']['extensions']; |
||
203 | $missingExtensions = array(); |
||
204 | foreach ($extensions as $extension) { |
||
205 | if (!extension_loaded($extension)) { |
||
206 | $missingExtensions[] = $extension; |
||
207 | } |
||
208 | } |
||
209 | |||
210 | if (count($missingExtensions) > 0) { |
||
211 | throw new RuntimeException( |
||
212 | 'The following PHP extensions are required to start installation: ' . implode(',', $missingExtensions) |
||
213 | ); |
||
214 | } |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * @param InputInterface $input |
||
219 | * @param OutputInterface $output |
||
220 | * |
||
221 | * @throws InvalidArgumentException |
||
222 | */ |
||
223 | protected function selectMagentoVersion(InputInterface $input, OutputInterface $output) |
||
224 | { |
||
225 | if ($input->getOption('magentoVersion') == null && $input->getOption('magentoVersionByName') == null) { |
||
226 | $question = array(); |
||
227 | foreach ($this->commandConfig['magento-packages'] as $key => $package) { |
||
228 | $question[] = '<comment>' . str_pad('[' . ($key + 1) . ']', 4, ' ') . '</comment> ' . |
||
229 | $package['name'] . "\n"; |
||
230 | } |
||
231 | $question[] = "<question>Choose a magento version:</question> "; |
||
232 | |||
233 | $commandConfig = $this->commandConfig; |
||
234 | |||
235 | $type = $this->getHelper('dialog')->askAndValidate( |
||
236 | $output, |
||
237 | $question, |
||
238 | function ($typeInput) use ($commandConfig) { |
||
239 | if (!in_array($typeInput, range(1, count($commandConfig['magento-packages'])))) { |
||
240 | throw new InvalidArgumentException('Invalid type'); |
||
241 | } |
||
242 | |||
243 | return $typeInput; |
||
244 | } |
||
245 | ); |
||
246 | } else { |
||
247 | $type = null; |
||
248 | |||
249 | if ($input->getOption('magentoVersion')) { |
||
250 | $type = $input->getOption('magentoVersion'); |
||
251 | if ($type !== (string) (int) $type) { |
||
252 | $type = $this->getPackageNumberByName($type); |
||
253 | } |
||
254 | } elseif ($input->getOption('magentoVersionByName')) { |
||
255 | $type = $this->getPackageNumberByName($input->getOption('magentoVersionByName')); |
||
256 | } |
||
257 | |||
258 | if ($type == null) { |
||
259 | throw new InvalidArgumentException('Unable to locate Magento version'); |
||
260 | } |
||
261 | } |
||
262 | |||
263 | $magentoPackages = $this->commandConfig['magento-packages']; |
||
264 | |||
265 | $index = $type - 1; |
||
266 | if (!isset($magentoPackages[$index])) { |
||
267 | throw new InvalidArgumentException( |
||
268 | sprintf( |
||
269 | 'Invalid Magento package number %s, must be from 1 to %d.', |
||
270 | var_export($type, true), |
||
271 | count($magentoPackages) |
||
272 | ) |
||
273 | ); |
||
274 | } |
||
275 | |||
276 | $this->config['magentoVersionData'] = $magentoPackages[$index]; |
||
277 | } |
||
278 | |||
279 | /** |
||
280 | * @param $name |
||
281 | * |
||
282 | * @return int 1 or greater as the one-based package number, null on failure to resolve the name |
||
283 | */ |
||
284 | private function getPackageNumberByName($name) |
||
285 | { |
||
286 | // directly filter integer strings |
||
287 | if ($name === (string) (int) $name) { |
||
288 | return (int) $name; |
||
289 | } |
||
290 | |||
291 | $magentoPackages = $this->commandConfig['magento-packages']; |
||
292 | |||
293 | foreach ($magentoPackages as $key => $package) { |
||
294 | if ($package['name'] === $name) { |
||
295 | return $key + 1; |
||
296 | } |
||
297 | } |
||
298 | |||
299 | return; |
||
300 | } |
||
301 | |||
302 | /** |
||
303 | * @param InputInterface $input |
||
304 | * @param OutputInterface $output |
||
305 | * @return bool |
||
306 | */ |
||
307 | public function downloadMagento(InputInterface $input, OutputInterface $output) |
||
352 | |||
353 | /** |
||
354 | * construct a folder to where magerun will download the source to, cache git/hg repositories under COMPOSER_HOME |
||
355 | * |
||
356 | * @param Composer $composer |
||
357 | * @param CompletePackage $package |
||
358 | * @param $installationFolder |
||
359 | * |
||
360 | * @return string |
||
361 | */ |
||
362 | protected function getTargetFolderByType(Composer $composer, CompletePackage $package, $installationFolder) |
||
363 | { |
||
364 | $type = $package->getSourceType(); |
||
365 | if ($this->isSourceTypeRepository($type)) { |
||
366 | $targetPath = sprintf( |
||
367 | '%s/%s/%s/%s', |
||
368 | $composer->getConfig()->get('cache-dir'), |
||
369 | '_n98_magerun_download', |
||
370 | $type, |
||
371 | preg_replace('{[^a-z0-9.]}i', '-', $package->getSourceUrl()) |
||
372 | ); |
||
373 | } else { |
||
374 | $targetPath = sprintf( |
||
375 | '%s/%s', |
||
376 | $installationFolder, |
||
377 | '_n98_magerun_download' |
||
378 | ); |
||
379 | } |
||
380 | |||
381 | return $targetPath; |
||
382 | } |
||
383 | |||
384 | /** |
||
385 | * @param string $magentoFolder |
||
386 | */ |
||
387 | protected function patchMagentoInstallerForPHP54($magentoFolder) |
||
397 | |||
398 | /** |
||
399 | * @param InputInterface $input |
||
400 | * @param OutputInterface $output |
||
401 | * |
||
402 | * @throws InvalidArgumentException |
||
403 | */ |
||
404 | protected function createDatabase(InputInterface $input, OutputInterface $output) |
||
496 | |||
497 | /** |
||
498 | * @param OutputInterface $output |
||
499 | * @param InputInterface $input |
||
500 | * |
||
501 | * @return bool|PDO |
||
502 | */ |
||
503 | protected function validateDatabaseSettings(OutputInterface $output, InputInterface $input) |
||
533 | |||
534 | /** |
||
535 | * @param InputInterface $input |
||
536 | * @param OutputInterface $output |
||
537 | */ |
||
538 | protected function installSampleData(InputInterface $input, OutputInterface $output) |
||
631 | |||
632 | protected function _fixComposerExtractionBug() |
||
647 | |||
648 | /** |
||
649 | * Remove empty composer extraction folder |
||
650 | */ |
||
651 | protected function removeEmptyFolders() |
||
662 | |||
663 | /** |
||
664 | * @param InputInterface $input |
||
665 | * @param OutputInterface $output |
||
666 | * |
||
667 | * @return array |
||
668 | * @throws InvalidArgumentException parameter mismatch (e.g. base-url components like hostname) |
||
669 | * @throws RuntimeException |
||
670 | */ |
||
671 | protected function installMagento(InputInterface $input, OutputInterface $output) |
||
888 | |||
889 | /** |
||
890 | * Check if we have a magento 2 or 1 installation and return path to install.php |
||
891 | * |
||
892 | * @return string |
||
893 | */ |
||
894 | protected function getInstallScriptPath() |
||
904 | |||
905 | /** |
||
906 | * @param string $baseUrl |
||
907 | */ |
||
908 | protected function replaceHtaccessFile($baseUrl) |
||
918 | |||
919 | /** |
||
920 | * @param OutputInterface $output |
||
921 | */ |
||
922 | protected function setDirectoryPermissions($output) |
||
923 | { |
||
924 | try { |
||
925 | $varFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var'; |
||
926 | if (!is_dir($varFolder)) { |
||
927 | @mkdir($varFolder); |
||
928 | } |
||
929 | @chmod($varFolder, 0777); |
||
930 | |||
931 | $varCacheFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'var/cache'; |
||
932 | if (!is_dir($varCacheFolder)) { |
||
933 | @mkdir($varCacheFolder); |
||
934 | } |
||
935 | @chmod($varCacheFolder, 0777); |
||
936 | |||
937 | $mediaFolder = $this->config['installationFolder'] . DIRECTORY_SEPARATOR . 'media'; |
||
938 | if (!is_dir($mediaFolder)) { |
||
939 | @mkdir($mediaFolder); |
||
940 | } |
||
941 | @chmod($mediaFolder, 0777); |
||
942 | |||
943 | $finder = Finder::create(); |
||
944 | $finder->directories() |
||
945 | ->ignoreUnreadableDirs(true) |
||
946 | ->in(array($varFolder, $mediaFolder)); |
||
947 | foreach ($finder as $dir) { |
||
948 | @chmod($dir->getRealpath(), 0777); |
||
949 | } |
||
950 | } catch (Exception $e) { |
||
951 | $output->writeln('<error>' . $e->getMessage() . '</error>'); |
||
952 | } |
||
953 | } |
||
954 | |||
955 | /** |
||
956 | * @return array |
||
957 | */ |
||
958 | public function getCliArguments() |
||
959 | { |
||
960 | if ($this->_argv === null) { |
||
961 | $this->_argv = $_SERVER['argv']; |
||
962 | } |
||
963 | |||
964 | return $this->_argv; |
||
965 | } |
||
966 | |||
967 | /** |
||
968 | * @param array $args |
||
969 | */ |
||
970 | public function setCliArguments($args) |
||
971 | { |
||
972 | $this->_argv = $args; |
||
973 | } |
||
974 | |||
975 | /** |
||
976 | * Invoke Magento PHP install script shell/install.php |
||
977 | * |
||
978 | * @param OutputInterface $output |
||
979 | * @param string $installationFolder folder where magento is installed in, must exists setup script in |
||
980 | * @param array $argv |
||
981 | * @return void |
||
982 | */ |
||
983 | private function runInstallScriptCommand(OutputInterface $output, $installationFolder, array $argv) |
||
1020 | } |
||
1021 |
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.