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 UpdateTimestampsToUTCCommand 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 UpdateTimestampsToUTCCommand, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
25 | class UpdateTimestampsToUTCCommand extends Command |
||
26 | { |
||
27 | const MAX_TIMESTAMP_VALUE = 2147483647; |
||
28 | const DEFAULT_ITERATION_COUNT = 100; |
||
29 | const MODES = [ |
||
30 | 'date' => ['ezdate'], |
||
31 | 'datetime' => ['ezdatetime'], |
||
32 | 'all' => ['ezdate', 'ezdatetime'], |
||
33 | ]; |
||
34 | |||
35 | /** @var int */ |
||
36 | protected $done = 0; |
||
37 | |||
38 | /** @var string */ |
||
39 | protected $timezone; |
||
40 | |||
41 | /** @var string */ |
||
42 | private $mode; |
||
43 | |||
44 | /** @var string */ |
||
45 | private $from; |
||
46 | |||
47 | /** @var string */ |
||
48 | private $to; |
||
49 | |||
50 | /** @var \Doctrine\DBAL\Connection */ |
||
51 | private $connection; |
||
52 | |||
53 | /** @var string */ |
||
54 | private $phpPath; |
||
55 | |||
56 | /** @var bool */ |
||
57 | private $dryRun; |
||
58 | |||
59 | public function __construct(Connection $connection) |
||
60 | { |
||
61 | $this->connection = $connection; |
||
62 | parent::__construct(); |
||
63 | } |
||
64 | |||
65 | protected function configure() |
||
66 | { |
||
67 | $this |
||
68 | ->setName('ezplatform:timestamps:to-utc') |
||
69 | ->setDescription('Updates ezdate and ezdatetime timestamps to UTC') |
||
70 | ->addArgument( |
||
71 | 'timezone', |
||
72 | InputArgument::OPTIONAL, |
||
73 | 'Original timestamp\'s TimeZone', |
||
74 | null |
||
75 | ) |
||
76 | ->addOption( |
||
77 | 'dry-run', |
||
78 | null, |
||
79 | InputOption::VALUE_NONE, |
||
80 | 'Execute a dry run' |
||
81 | ) |
||
82 | ->addOption( |
||
83 | 'mode', |
||
84 | null, |
||
85 | InputOption::VALUE_REQUIRED, |
||
86 | 'Select conversion scope: date, datetime, all', |
||
87 | 'all' |
||
88 | ) |
||
89 | ->addOption( |
||
90 | 'from', |
||
91 | null, |
||
92 | InputOption::VALUE_REQUIRED, |
||
93 | 'Only versions AFTER this date will be converted', |
||
94 | null |
||
95 | ) |
||
96 | ->addOption( |
||
97 | 'to', |
||
98 | null, |
||
99 | InputOption::VALUE_REQUIRED, |
||
100 | 'Only versions BEFORE this date will be converted', |
||
101 | null |
||
102 | ) |
||
103 | ->addOption( |
||
104 | 'offset', |
||
105 | null, |
||
106 | InputArgument::OPTIONAL, |
||
107 | 'Offset for updating records', |
||
108 | 0 |
||
109 | ) |
||
110 | ->addOption( |
||
111 | 'iteration-count', |
||
112 | null, |
||
113 | InputArgument::OPTIONAL, |
||
114 | 'Limit how many records get updated by a single process', |
||
115 | self::DEFAULT_ITERATION_COUNT |
||
116 | ) |
||
117 | ->setHelp( |
||
118 | <<<'EOT' |
||
119 | The command <info>%command.name%</info> updates field |
||
120 | data_int in configured Legacy Storage database for a given Field Type. |
||
121 | |||
122 | This is to be used either when migrating from eZ Publish to eZ Platform |
||
123 | (when using platform backend instead of legacy), or when upgrading legacy |
||
124 | to v2019.03 which has been adapted to use UTC. |
||
125 | |||
126 | <warning>The database should not be modified while the script is being executed. |
||
127 | |||
128 | You are advised to create a backup or execute a dry run before |
||
129 | proceeding with the actual update.</warning> |
||
130 | |||
131 | <warning>This command should only be ran ONCE.</warning> |
||
132 | |||
133 | Since this script can potentially run for a very long time, to avoid memory |
||
134 | exhaustion run it in production environment using <info>--env=prod</info> switch. |
||
135 | EOT |
||
136 | ); |
||
137 | } |
||
138 | |||
139 | /** |
||
140 | * @param \Symfony\Component\Console\Input\InputInterface $input |
||
141 | * @param \Symfony\Component\Console\Output\OutputInterface $output |
||
142 | */ |
||
143 | protected function execute(InputInterface $input, OutputInterface $output) |
||
259 | |||
260 | /** |
||
261 | * @param int $offset |
||
262 | * @param int $limit |
||
263 | * @param \Symfony\Component\Console\Output\OutputInterface $output |
||
264 | */ |
||
265 | protected function processTimestamps($offset, $limit, $output) |
||
284 | |||
285 | /** |
||
286 | * @param int $offset |
||
287 | * @param int $limit |
||
288 | * |
||
289 | * @return array |
||
290 | */ |
||
291 | protected function getTimestampBasedFields($offset, $limit) |
||
325 | |||
326 | /** |
||
327 | * Counts affected timestamp based fields using captured "mode", "from" and "to" command options. |
||
328 | * |
||
329 | * @return int |
||
330 | */ |
||
331 | protected function countTimestampBasedFields() |
||
363 | |||
364 | /** |
||
365 | * @param int $timestamp |
||
366 | * @return int |
||
367 | */ |
||
368 | protected function convertToUtcTimestamp($timestamp) |
||
379 | |||
380 | /** |
||
381 | * @param string $dateTimeString |
||
382 | * @param OutputInterface $output |
||
383 | * @return bool |
||
384 | */ |
||
385 | protected function validateDateTimeString($dateTimeString, OutputInterface $output) |
||
397 | |||
398 | /** |
||
399 | * @param string $timezone |
||
400 | * @param OutputInterface $output |
||
401 | * @return string |
||
402 | */ |
||
403 | protected function validateTimezone($timezone, OutputInterface $output) |
||
429 | |||
430 | /** |
||
431 | * Return configured progress bar helper. |
||
432 | * |
||
433 | * @param int $maxSteps |
||
434 | * @param \Symfony\Component\Console\Output\OutputInterface $output |
||
435 | * |
||
436 | * @return \Symfony\Component\Console\Helper\ProgressBar |
||
437 | */ |
||
438 | protected function getProgressBar($maxSteps, OutputInterface $output) |
||
447 | |||
448 | /** |
||
449 | * @param int $contentAttributeId |
||
450 | * @param int $contentAttributeVersion |
||
451 | * @param int $newTimestamp |
||
452 | */ |
||
453 | protected function updateTimestampToUTC( |
||
470 | |||
471 | /** |
||
472 | * @return string |
||
473 | */ |
||
474 | View Code Duplication | private function getPhpPath() |
|
489 | |||
490 | /** |
||
491 | * @param $dateString string |
||
492 | * @throws \Exception |
||
493 | * @return int |
||
494 | */ |
||
495 | private function dateStringToTimestamp($dateString) |
||
501 | } |
||
502 |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.