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 WebController 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 WebController, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 18 | class WebController extends Controller |
||
| 19 | { |
||
| 20 | /** |
||
| 21 | * Provides access to the templating engine. |
||
| 22 | * @property object $twig the twig templating engine. |
||
| 23 | */ |
||
| 24 | public $twig; |
||
| 25 | |||
| 26 | public $base_href; |
||
| 27 | |||
| 28 | /** |
||
| 29 | * Constructor for the WebController. |
||
| 30 | * @param Model $model |
||
| 31 | */ |
||
| 32 | public function __construct($model) |
||
| 123 | |||
| 124 | private function guessBaseHref() |
||
| 142 | |||
| 143 | /** |
||
| 144 | * Guess the language of the user. Return a language string that is one |
||
| 145 | * of the supported languages defined in the $LANGUAGES setting, e.g. "fi". |
||
| 146 | * @param string $vocab_id identifier for the vocabulary eg. 'yso'. |
||
| 147 | * @return string returns the language choice as a numeric string value |
||
| 148 | */ |
||
| 149 | public function guessLanguage($vocab_id = null) |
||
| 179 | |||
| 180 | /** |
||
| 181 | * Loads and renders the view containing all the vocabularies. |
||
| 182 | * @param Request $request |
||
| 183 | */ |
||
| 184 | public function invokeVocabularies($request) |
||
| 185 | { |
||
| 186 | // set language parameters for gettext |
||
| 187 | $this->setLanguageProperties($request->getLang()); |
||
| 188 | // load template |
||
| 189 | $template = $this->twig->loadTemplate('light.twig'); |
||
| 190 | // set template variables |
||
| 191 | $categoryLabel = $this->model->getClassificationLabel($request->getLang()); |
||
| 192 | $sortedVocabs = $this->model->getVocabularyList(false, true); |
||
| 193 | $langList = $this->model->getLanguages($request->getLang()); |
||
| 194 | |||
| 195 | // render template |
||
| 196 | echo $template->render( |
||
| 197 | array( |
||
| 198 | 'sorted_vocabs' => $sortedVocabs, |
||
| 199 | 'category_label' => $categoryLabel, |
||
| 200 | 'languages' => $this->languages, |
||
| 201 | 'lang_list' => $langList, |
||
| 202 | 'request' => $request, |
||
| 203 | )); |
||
| 204 | } |
||
| 205 | |||
| 206 | /** |
||
| 207 | * Invokes the concept page of a single concept in a specific vocabulary. |
||
| 208 | */ |
||
| 209 | public function invokeVocabularyConcept($request) |
||
| 210 | { |
||
| 211 | $lang = $request->getLang(); |
||
| 212 | $this->setLanguageProperties($lang); |
||
| 213 | $vocab = $request->getVocab(); |
||
| 214 | |||
| 215 | $langcodes = $vocab->getConfig()->getShowLangCodes(); |
||
| 216 | $uri = $vocab->getConceptURI($request->getUri()); // make sure it's a full URI |
||
| 217 | |||
| 218 | $results = $vocab->getConceptInfo($uri, $request->getContentLang()); |
||
| 219 | $template = (in_array('skos:Concept', $results[0]->getType())) ? $this->twig->loadTemplate('concept-info.twig') : $this->twig->loadTemplate('group-contents.twig'); |
||
| 220 | |||
| 221 | $crumbs = $vocab->getBreadCrumbs($request->getContentLang(), $uri); |
||
| 222 | echo $template->render(array( |
||
| 223 | 'search_results' => $results, |
||
| 224 | 'vocab' => $vocab, |
||
| 225 | 'languages' => $this->languages, |
||
| 226 | 'explicit_langcodes' => $langcodes, |
||
| 227 | 'bread_crumbs' => $crumbs['breadcrumbs'], |
||
| 228 | 'combined' => $crumbs['combined'], |
||
| 229 | 'request' => $request) |
||
| 230 | ); |
||
| 231 | } |
||
| 232 | |||
| 233 | /** |
||
| 234 | * Invokes the feedback page with information of the users current vocabulary. |
||
| 235 | */ |
||
| 236 | public function invokeFeedbackForm($request) |
||
| 237 | { |
||
| 238 | $template = $this->twig->loadTemplate('feedback.twig'); |
||
| 239 | $this->setLanguageProperties($request->getLang()); |
||
| 240 | $vocabList = $this->model->getVocabularyList(false); |
||
| 241 | $vocab = $request->getVocab(); |
||
| 242 | |||
| 243 | $feedback_sent = false; |
||
| 244 | $feedback_msg = null; |
||
| 245 | if (filter_input(INPUT_POST, 'message', FILTER_SANITIZE_STRING)) { |
||
| 246 | $feedback_sent = true; |
||
| 247 | $feedback_msg = filter_input(INPUT_POST, 'message', FILTER_SANITIZE_STRING); |
||
| 248 | } |
||
| 249 | $feedback_name = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING); |
||
| 250 | $feedback_email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_STRING); |
||
| 251 | $feedback_vocab = filter_input(INPUT_POST, 'vocab', FILTER_SANITIZE_STRING); |
||
| 252 | $feedback_vocab_email = ($vocab !== null) ? $vocab->getConfig()->getFeedbackRecipient() : null; |
||
| 253 | |||
| 254 | // if the hidden field has been set a value we have found a spam bot |
||
| 255 | // and we do not actually send the message. |
||
| 256 | if ($feedback_sent && filter_input(INPUT_POST, 'trap', FILTER_SANITIZE_STRING) === '') { |
||
| 257 | $this->sendFeedback($feedback_msg, $feedback_name, $feedback_email, $feedback_vocab, $feedback_vocab_email); |
||
| 258 | } |
||
| 259 | |||
| 260 | echo $template->render( |
||
| 261 | array( |
||
| 262 | 'languages' => $this->languages, |
||
| 263 | 'vocab' => $vocab, |
||
| 264 | 'vocabList' => $vocabList, |
||
| 265 | 'feedback_sent' => $feedback_sent, |
||
| 266 | 'request' => $request, |
||
| 267 | )); |
||
| 268 | } |
||
| 269 | |||
| 270 | /** |
||
| 271 | * Sends the user entered message through the php's mailer. |
||
| 272 | * @param string $message only required parameter is the actual message. |
||
| 273 | * @param string $fromName senders own name. |
||
| 274 | * @param string $fromEmail senders email adress. |
||
| 275 | * @param string $fromVocab which vocabulary is the feedback related to. |
||
| 276 | */ |
||
| 277 | public function sendFeedback($message, $fromName = null, $fromEmail = null, $fromVocab = null, $toMail = null) |
||
| 278 | { |
||
| 279 | $toAddress = ($toMail) ? $toMail : $this->model->getConfig()->getFeedbackAddress(); |
||
| 280 | if ($fromVocab !== null) { |
||
| 281 | $message = 'Feedback from vocab: ' . strtoupper($fromVocab) . "<br />" . $message; |
||
| 282 | } |
||
| 283 | |||
| 284 | $subject = SERVICE_NAME . " feedback"; |
||
| 285 | $headers = "MIME-Version: 1.0″ . '\r\n"; |
||
| 286 | $headers .= "Content-type: text/html; charset=UTF-8" . "\r\n"; |
||
| 287 | if ($toMail) { |
||
| 288 | $headers .= "Cc: " . $this->model->getConfig()->getFeedbackAddress() . "\r\n"; |
||
| 289 | } |
||
| 290 | |||
| 291 | $headers .= "From: $fromName <$fromEmail>" . "\r\n" . 'X-Mailer: PHP/' . phpversion(); |
||
| 292 | $envelopeSender = FEEDBACK_ENVELOPE_SENDER; |
||
| 293 | $params = empty($envelopeSender) ? '' : "-f $envelopeSender"; |
||
| 294 | |||
| 295 | // adding some information about the user for debugging purposes. |
||
| 296 | $agent = (filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_STRING)) ? filter_input(INPUT_SERVER, 'HTTP_USER_AGENT', FILTER_SANITIZE_STRING) : ''; |
||
| 297 | $referer = (filter_input(INPUT_SERVER, 'HTTP_REFERER', FILTER_SANITIZE_STRING)) ? filter_input(INPUT_SERVER, 'HTTP_REFERER', FILTER_SANITIZE_STRING) : ''; |
||
| 298 | $ipAddress = (filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_SANITIZE_STRING)) ? filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_SANITIZE_STRING) : ''; |
||
| 299 | $timestamp = date(DATE_RFC2822); |
||
| 300 | |||
| 301 | $message = $message . "<br /><br /> Debugging information:" |
||
| 302 | . "<br />Timestamp: " . $timestamp |
||
| 303 | . "<br />User agent: " . $agent |
||
| 304 | . "<br />IP address: " . $ipAddress |
||
| 305 | . "<br />Referer: " . $referer; |
||
| 306 | |||
| 307 | try { |
||
| 308 | mail($toAddress, $subject, $message, $headers, $params); |
||
| 309 | } catch (Exception $e) { |
||
| 310 | header("HTTP/1.0 404 Not Found"); |
||
| 311 | $template = $this->twig->loadTemplate('error-page.twig'); |
||
| 312 | if ($this->model->getConfig()->getLogCaughtExceptions()) { |
||
| 313 | error_log('Caught exception: ' . $e->getMessage()); |
||
| 314 | } |
||
| 315 | |||
| 316 | echo $template->render( |
||
| 317 | array( |
||
| 318 | 'languages' => $this->languages, |
||
| 319 | )); |
||
| 320 | |||
| 321 | return; |
||
| 322 | } |
||
| 323 | } |
||
| 324 | |||
| 325 | /** |
||
| 326 | * Invokes the about page for the Skosmos service. |
||
| 327 | */ |
||
| 328 | View Code Duplication | public function invokeAboutPage($request) |
|
| 329 | { |
||
| 330 | $template = $this->twig->loadTemplate('about.twig'); |
||
| 331 | $this->setLanguageProperties($request->getLang()); |
||
| 332 | $url = $request->getServerConstant('HTTP_HOST'); |
||
| 333 | $version = $this->model->getVersion(); |
||
| 334 | |||
| 335 | echo $template->render( |
||
| 336 | array( |
||
| 337 | 'languages' => $this->languages, |
||
| 338 | 'version' => $version, |
||
| 339 | 'server_instance' => $url, |
||
| 340 | 'request' => $request, |
||
| 341 | )); |
||
| 342 | } |
||
| 343 | |||
| 344 | /** |
||
| 345 | * Invokes the search for concepts in all the availible ontologies. |
||
| 346 | */ |
||
| 347 | public function invokeGlobalSearch($request) |
||
| 348 | { |
||
| 349 | $lang = $request->getLang(); |
||
| 350 | $template = $this->twig->loadTemplate('vocab-search-listing.twig'); |
||
| 351 | $this->setLanguageProperties($lang); |
||
| 352 | |||
| 353 | $parameters = new ConceptSearchParameters($request, $this->model->getConfig()); |
||
| 354 | |||
| 355 | $vocabs = $request->getQueryParam('vocabs'); # optional |
||
| 356 | // convert to vocids array to support multi-vocabulary search |
||
| 357 | $vocids = ($vocabs !== null && $vocabs !== '') ? explode(' ', $vocabs) : null; |
||
| 358 | $vocabObjects = array(); |
||
| 359 | if ($vocids) { |
||
| 360 | foreach($vocids as $vocid) { |
||
| 361 | $vocabObjects[] = $this->model->getVocabulary($vocid); |
||
| 362 | } |
||
| 363 | } |
||
| 364 | $parameters->setVocabularies($vocabObjects); |
||
| 365 | |||
| 366 | try { |
||
| 367 | $count_and_results = $this->model->searchConceptsAndInfo($parameters); |
||
| 368 | } catch (Exception $e) { |
||
| 369 | header("HTTP/1.0 404 Not Found"); |
||
| 370 | if ($this->model->getConfig()->getLogCaughtExceptions()) { |
||
| 371 | error_log('Caught exception: ' . $e->getMessage()); |
||
| 372 | } |
||
| 373 | $this->invokeGenericErrorPage($request, $e->getMessage()); |
||
| 374 | return; |
||
| 375 | } |
||
| 376 | $counts = $count_and_results['count']; |
||
| 377 | $search_results = $count_and_results['results']; |
||
| 378 | $vocabList = $this->model->getVocabularyList(); |
||
| 379 | $sortedVocabs = $this->model->getVocabularyList(false, true); |
||
| 380 | $langList = $this->model->getLanguages($lang); |
||
| 381 | |||
| 382 | echo $template->render( |
||
| 383 | array( |
||
| 384 | 'search_count' => $counts, |
||
| 385 | 'languages' => $this->languages, |
||
| 386 | 'search_results' => $search_results, |
||
| 387 | 'rest' => $parameters->getOffset()>0, |
||
| 388 | 'global_search' => true, |
||
| 389 | 'term' => $request->getQueryParam('q'), |
||
| 390 | 'lang_list' => $langList, |
||
| 391 | 'vocabs' => str_replace(' ', '+', $vocabs), |
||
| 392 | 'vocab_list' => $vocabList, |
||
| 393 | 'sorted_vocabs' => $sortedVocabs, |
||
| 394 | 'request' => $request, |
||
| 395 | )); |
||
| 396 | } |
||
| 397 | |||
| 398 | /** |
||
| 399 | * Invokes the search for a single vocabs concepts. |
||
| 400 | */ |
||
| 401 | public function invokeVocabularySearch($request) |
||
| 402 | { |
||
| 403 | $template = $this->twig->loadTemplate('vocab-search-listing.twig'); |
||
| 404 | $this->setLanguageProperties($request->getLang()); |
||
| 405 | $vocab = $request->getVocab(); |
||
| 406 | try { |
||
| 407 | $vocab_types = $this->model->getTypes($request->getVocabid(), $request->getLang()); |
||
| 408 | } catch (Exception $e) { |
||
| 409 | header("HTTP/1.0 404 Not Found"); |
||
| 410 | if ($this->model->getConfig()->getLogCaughtExceptions()) { |
||
| 411 | error_log('Caught exception: ' . $e->getMessage()); |
||
| 412 | } |
||
| 413 | |||
| 414 | echo $template->render( |
||
| 415 | array( |
||
| 416 | 'languages' => $this->languages, |
||
| 417 | )); |
||
| 418 | |||
| 419 | return; |
||
| 420 | } |
||
| 421 | |||
| 422 | $langcodes = $vocab->getConfig()->getShowLangCodes(); |
||
| 423 | $parameters = new ConceptSearchParameters($request, $this->model->getConfig()); |
||
| 424 | |||
| 425 | try { |
||
| 426 | $count_and_results = $this->model->searchConceptsAndInfo($parameters); |
||
| 427 | $counts = $count_and_results['count']; |
||
| 428 | $search_results = $count_and_results['results']; |
||
| 429 | } catch (Exception $e) { |
||
| 430 | header("HTTP/1.0 404 Not Found"); |
||
| 431 | if ($this->model->getConfig()->getLogCaughtExceptions()) { |
||
| 432 | error_log('Caught exception: ' . $e->getMessage()); |
||
| 433 | } |
||
| 434 | |||
| 435 | echo $template->render( |
||
| 436 | array( |
||
| 437 | 'languages' => $this->languages, |
||
| 438 | 'vocab' => $vocab, |
||
| 439 | 'term' => $request->getQueryParam('q'), |
||
| 440 | )); |
||
| 441 | return; |
||
| 442 | } |
||
| 443 | echo $template->render( |
||
| 444 | array( |
||
| 445 | 'languages' => $this->languages, |
||
| 446 | 'vocab' => $vocab, |
||
| 447 | 'search_results' => $search_results, |
||
| 448 | 'search_count' => $counts, |
||
| 449 | 'rest' => $parameters->getOffset()>0, |
||
| 450 | 'limit_parent' => $parameters->getParentLimit(), |
||
| 451 | 'limit_type' => $request->getQueryParam('type') ? explode('+', $request->getQueryParam('type')) : null, |
||
| 452 | 'limit_group' => $parameters->getGroupLimit(), |
||
| 453 | 'limit_scheme' => $request->getQueryParam('scheme') ? explode('+', $request->getQueryParam('scheme')) : null, |
||
| 454 | 'group_index' => $vocab->listConceptGroups($request->getContentLang()), |
||
| 455 | 'parameters' => $parameters, |
||
| 456 | 'term' => $request->getQueryParam('q'), |
||
| 457 | 'types' => $vocab_types, |
||
| 458 | 'explicit_langcodes' => $langcodes, |
||
| 459 | 'request' => $request, |
||
| 460 | )); |
||
| 461 | } |
||
| 462 | |||
| 463 | /** |
||
| 464 | * Invokes the alphabetical listing for a specific vocabulary. |
||
| 465 | */ |
||
| 466 | public function invokeAlphabeticalIndex($request) |
||
| 503 | |||
| 504 | /** |
||
| 505 | * Invokes the vocabulary group index page template. |
||
| 506 | * @param boolean $stats set to true to get vocabulary statistics visible. |
||
| 507 | */ |
||
| 508 | View Code Duplication | public function invokeGroupIndex($request, $stats = false) |
|
| 523 | |||
| 524 | /** |
||
| 525 | * Loads and renders the view containing a specific vocabulary. |
||
| 526 | */ |
||
| 527 | public function invokeVocabularyHome($request) |
||
| 528 | { |
||
| 529 | $lang = $request->getLang(); |
||
| 530 | // set language parameters for gettext |
||
| 552 | |||
| 553 | /** |
||
| 554 | * Invokes a very generic errorpage. |
||
| 555 | */ |
||
| 556 | public function invokeGenericErrorPage($request, $message = null) |
||
| 569 | |||
| 570 | /** |
||
| 571 | * Loads and renders the view containing a list of recent changes in the vocabulary. |
||
| 572 | * @param Request $request |
||
| 573 | */ |
||
| 574 | public function invokeChangeList($request, $prop='dc:created') |
||
| 593 | |||
| 594 | } |
||
| 595 |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.