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:
| 1 | <?php declare(strict_types=1); | ||
| 385 | class Logger | ||
| 386 | { | ||
| 387 | const EMERGENCY = 'emergency'; | ||
| 388 | const ALERT = 'alert'; | ||
| 389 | const CRITICAL = 'critical'; | ||
| 390 | const ERROR = 'error'; | ||
| 391 | const WARNING = 'warning'; | ||
| 392 | const NOTICE = 'notice'; | ||
| 393 | const INFO = 'info'; | ||
| 394 | const DEBUG = 'debug'; | ||
| 395 | |||
| 396 | const BLACK = 'black'; | ||
| 397 | const DARK_GRAY = 'dark_gray'; | ||
| 398 | const BLUE = 'blue'; | ||
| 399 | const LIGHT_BLUE = 'light_blue'; | ||
| 400 | const GREEN = 'green'; | ||
| 401 | const LIGHT_GREEN = 'light_green'; | ||
| 402 | const CYAN = 'cyan'; | ||
| 403 | const LIGHT_CYAN = 'light_cyan'; | ||
| 404 | const RED = 'red'; | ||
| 405 | const LIGHT_RED = 'light_red'; | ||
| 406 | const PURPLE = 'purple'; | ||
| 407 | const LIGHT_PURPLE = 'light_purple'; | ||
| 408 | const BROWN = 'brown'; | ||
| 409 | const YELLOW = 'yellow'; | ||
| 410 | const MAGENTA = 'magenta'; | ||
| 411 | const LIGHT_GRAY = 'light_gray'; | ||
| 412 | const WHITE = 'white'; | ||
| 413 | const DEFAULT = 'default'; | ||
| 414 | const BOLD = 'bold'; | ||
| 415 | |||
| 416 | /** @var resource $resource The file handle */ | ||
| 417 | protected $resource = null; | ||
| 418 | |||
| 419 | /** @var string $level */ | ||
| 420 | protected $level = self::INFO; | ||
| 421 | |||
| 422 | /** @var bool $closeLocally */ | ||
| 423 | protected $closeLocally = false; | ||
| 424 | |||
| 425 | /** @var bool */ | ||
| 426 | protected $addDate = true; | ||
| 427 | |||
| 428 | /** @var string */ | ||
| 429 | protected $separator = ' | '; | ||
| 430 | |||
| 431 | /** @var \Closure */ | ||
| 432 | protected $formatter = null; | ||
| 433 | |||
| 434 | /** @var string */ | ||
| 435 | protected $lastLogEntry = ''; | ||
| 436 | |||
| 437 | /** @var bool|null */ | ||
| 438 | protected $gzipFile = null; | ||
| 439 | |||
| 440 | /** @var bool */ | ||
| 441 | protected $useLocking = false; | ||
| 442 | |||
| 443 | /** | ||
| 444 | * @var array $logLevels List of supported levels | ||
| 445 | */ | ||
| 446 | static protected $logLevels = [ | ||
| 447 | self::EMERGENCY => [1, self::WHITE, self::RED, self::DEFAULT, 'EMERG'], | ||
| 448 | self::ALERT => [2, self::WHITE, self::YELLOW, self::DEFAULT, 'ALERT'], | ||
| 449 | self::CRITICAL => [3, self::RED, self::DEFAULT, self::BOLD , 'CRIT'], | ||
| 450 | self::ERROR => [4, self::RED, self::DEFAULT, self::DEFAULT, 'ERROR'], | ||
| 451 | self::WARNING => [5, self::YELLOW, self::DEFAULT, self::DEFAULT, 'WARN'], | ||
| 452 | self::NOTICE => [6, self::CYAN, self::DEFAULT, self::DEFAULT, 'NOTE'], | ||
| 453 | self::INFO => [7, self::GREEN, self::DEFAULT, self::DEFAULT, 'INFO'], | ||
| 454 | self::DEBUG => [8, self::LIGHT_GRAY, self::DEFAULT, self::DEFAULT, 'DEBUG'], | ||
| 455 | ]; | ||
| 456 | |||
| 457 | /** | ||
| 458 | * @var array | ||
| 459 | */ | ||
| 460 | static protected $colours = [ | ||
| 461 | 'fore' => [ | ||
| 462 | self::BLACK => '0;30', | ||
| 463 | self::DARK_GRAY => '1;30', | ||
| 464 | self::BLUE => '0;34', | ||
| 465 | self::LIGHT_BLUE => '1;34', | ||
| 466 | self::GREEN => '0;32', | ||
| 467 | self::LIGHT_GREEN => '1;32', | ||
| 468 | self::CYAN => '0;36', | ||
| 469 | self::LIGHT_CYAN => '1;36', | ||
| 470 | self::RED => '0;31', | ||
| 471 | self::LIGHT_RED => '1;31', | ||
| 472 | self::PURPLE => '0;35', | ||
| 473 | self::LIGHT_PURPLE => '1;35', | ||
| 474 | self::BROWN => '0;33', | ||
| 475 | self::YELLOW => '1;33', | ||
| 476 | self::MAGENTA => '0;35', | ||
| 477 | self::LIGHT_GRAY => '0;37', | ||
| 478 | self::WHITE => '1;37', | ||
| 479 | ], | ||
| 480 | 'back' => [ | ||
| 481 | self::DEFAULT => '49', | ||
| 482 | self::BLACK => '40', | ||
| 483 | self::RED => '41', | ||
| 484 | self::GREEN => '42', | ||
| 485 | self::YELLOW => '43', | ||
| 486 | self::BLUE => '44', | ||
| 487 | self::MAGENTA => '45', | ||
| 488 | self::CYAN => '46', | ||
| 489 | self::LIGHT_GRAY => '47', | ||
| 490 | ], | ||
| 491 | self::BOLD => [], | ||
| 492 | ]; | ||
| 493 | |||
| 494 | /** | ||
| 495 | * @param mixed $resource | ||
| 496 | * @param string $level | ||
| 497 | * @param bool $useLocking | ||
| 498 | * @param bool $gzipFile | ||
| 499 | * @param bool $addDate | ||
| 500 | */ | ||
| 501 | public function __construct($resource=STDOUT, string $level=self::INFO, bool $useLocking=false, bool $gzipFile=false, bool $addDate=true) | ||
| 509 | |||
| 510 | /** | ||
| 511 | * System is unusable. | ||
| 512 | * | ||
| 513 | * @param string $message | ||
| 514 | * @param array $context | ||
| 515 | */ | ||
| 516 | public function emergency(string $message, array $context=[]) | ||
| 520 | |||
| 521 | /** | ||
| 522 | * Action must be taken immediately. | ||
| 523 | * | ||
| 524 | * Example: Entire website down, database unavailable, etc. This should | ||
| 525 | * trigger the SMS alerts and wake you up. | ||
| 526 | * | ||
| 527 | * @param string $message | ||
| 528 | * @param array $context | ||
| 529 | */ | ||
| 530 | public function alert(string $message, array $context=[]) | ||
| 534 | |||
| 535 | /** | ||
| 536 | * Critical conditions. | ||
| 537 | * | ||
| 538 | * Example: Application component unavailable, unexpected exception. | ||
| 539 | * | ||
| 540 | * @param string $message | ||
| 541 | * @param array $context | ||
| 542 | */ | ||
| 543 | public function critical(string $message, array $context=[]) | ||
| 547 | |||
| 548 | /** | ||
| 549 | * Runtime errors that do not require immediate action but should typically | ||
| 550 | * be logged and monitored. | ||
| 551 | * | ||
| 552 | * @param string $message | ||
| 553 | * @param array $context | ||
| 554 | */ | ||
| 555 | public function error(string $message, array $context=[]) | ||
| 559 | |||
| 560 | /** | ||
| 561 | * Exceptional occurrences that are not errors. | ||
| 562 | * | ||
| 563 | * Example: Use of deprecated APIs, poor use of an API, undesirable things | ||
| 564 | * that are not necessarily wrong. | ||
| 565 | * | ||
| 566 | * @param string $message | ||
| 567 | * @param array $context | ||
| 568 | */ | ||
| 569 | public function warning(string $message, array $context=[]) | ||
| 573 | |||
| 574 | /** | ||
| 575 | * Normal but significant events. | ||
| 576 | * | ||
| 577 | * @param string $message | ||
| 578 | * @param array $context | ||
| 579 | */ | ||
| 580 | public function notice(string $message, array $context=[]) | ||
| 584 | |||
| 585 | /** | ||
| 586 | * Interesting events. | ||
| 587 | * | ||
| 588 | * Example: User logs in, SQL logs. | ||
| 589 | * | ||
| 590 | * @param string $message | ||
| 591 | * @param array $context | ||
| 592 | */ | ||
| 593 | public function info(string $message, array $context=[]) | ||
| 597 | |||
| 598 | /** | ||
| 599 | * Detailed debug information. | ||
| 600 | * | ||
| 601 | * @param string $message | ||
| 602 | * @param array $context | ||
| 603 | */ | ||
| 604 | public function debug(string $message, array $context=[]) | ||
| 608 | |||
| 609 | /** | ||
| 610 | * @param $resource | ||
| 611 | * @return Logger | ||
| 612 | */ | ||
| 613 | public function setLogFile($resource) : Logger | ||
| 619 | |||
| 620 | /** | ||
| 621 | * @param string $string | ||
| 622 | * @param string $foregroundColor | ||
| 623 | * @param string $backgroundColor | ||
| 624 | * @param bool $bold | ||
| 625 | * @return string | ||
| 626 | */ | ||
| 627 | public static function addColour(string $string, string $foregroundColor='', string $backgroundColor='', bool $bold=false) : string | ||
| 647 | |||
| 648 | /** | ||
| 649 | * @param string $string | ||
| 650 | * @param string $foregroundColor | ||
| 651 | * @param string $backgroundColor | ||
| 652 | * @param bool $bold | ||
| 653 | * @return string | ||
| 654 | */ | ||
| 655 | public function colourize(string $string, string $foregroundColor='', string $backgroundColor='', bool $bold=false) : string | ||
| 659 | |||
| 660 | /** | ||
| 661 | * @param string $level Ignore logging attempts at a level less the $level | ||
| 662 | * @return Logger | ||
| 663 | */ | ||
| 664 | public function setLogLevel(string $level) : Logger | ||
| 674 | |||
| 675 | /** | ||
| 676 | * @return Logger | ||
| 677 | */ | ||
| 678 | public function lock() : Logger | ||
| 684 | |||
| 685 | /** | ||
| 686 | * @return Logger | ||
| 687 | */ | ||
| 688 | public function gzipped() : Logger | ||
| 694 | |||
| 695 | /** | ||
| 696 | * @param callable $fnFormatter | ||
| 697 | * | ||
| 698 | * @return Logger | ||
| 699 | */ | ||
| 700 | public function formatter(callable $fnFormatter) : Logger | ||
| 706 | |||
| 707 | /** | ||
| 708 | * Log messages to resource | ||
| 709 | * | ||
| 710 | * @param mixed $level The level of the log message | ||
| 711 | * @param string|object $message If an object is passed it must implement __toString() | ||
| 712 | * @param array $context Placeholders to be substituted in the message | ||
| 713 | */ | ||
| 714 | public function log($level, $message, array $context=[]) | ||
| 734 | |||
| 735 | /** | ||
| 736 | * @param string $style | ||
| 737 | * @param string $message | ||
| 738 | * @return string | ||
| 739 | */ | ||
| 740 | public static function style(string $style, string $message) : string | ||
| 748 | |||
| 749 | /** | ||
| 750 | * @param string $level | ||
| 751 | * @param string $message | ||
| 752 | * @param array $context | ||
| 753 | * @return string | ||
| 754 | */ | ||
| 755 | protected function formatMessage(string $level, string $message, array $context=[]) : string | ||
| 766 | |||
| 767 | /** | ||
| 768 | * Write the content to the stream | ||
| 769 | * | ||
| 770 | * @param string $content | ||
| 771 | */ | ||
| 772 | public function write(string $content) | ||
| 785 | |||
| 786 | /** | ||
| 787 | * @return mixed|resource | ||
| 788 | * @throws \Exception | ||
| 789 | */ | ||
| 790 | protected function getResource() | ||
| 806 | |||
| 807 | /** | ||
| 808 | * @return string | ||
| 809 | */ | ||
| 810 | public function getLastLogEntry() : string | ||
| 814 | |||
| 815 | /** | ||
| 816 | * @return resource | ||
| 817 | */ | ||
| 818 | protected function openResource() | ||
| 827 | |||
| 828 | public function __destruct() | ||
| 835 | } | ||
| 836 | 
Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.