Complex classes like Version 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 Version, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 18 | class Version implements JsonSerializable  | 
            ||
| 19 | { | 
            ||
| 20 | /** @var int */  | 
            ||
| 21 | protected $major;  | 
            ||
| 22 | |||
| 23 | /** @var int */  | 
            ||
| 24 | protected $minor;  | 
            ||
| 25 | |||
| 26 | /** @var int */  | 
            ||
| 27 | protected $patch;  | 
            ||
| 28 | |||
| 29 | /** @var PreRelease */  | 
            ||
| 30 | protected $preRelease;  | 
            ||
| 31 | |||
| 32 | /** @var Build */  | 
            ||
| 33 | protected $build;  | 
            ||
| 34 | |||
| 35 | /** @var ComparatorInterface */  | 
            ||
| 36 | private static $comparator;  | 
            ||
| 37 | |||
| 38 | 85 | protected function __construct(int $major, int $minor, int $patch, PreRelease $preRelease, Build $build)  | 
            |
| 39 |     { | 
            ||
| 40 | 85 |         $this->validateNumber('major', $major); | 
            |
| 41 | 84 |         $this->validateNumber('minor', $minor); | 
            |
| 42 | 83 |         $this->validateNumber('patch', $patch); | 
            |
| 43 | |||
| 44 | 82 | $this->major = $major;  | 
            |
| 45 | 82 | $this->minor = $minor;  | 
            |
| 46 | 82 | $this->patch = $patch;  | 
            |
| 47 | 82 | $this->preRelease = $preRelease;  | 
            |
| 48 | 82 | $this->build = $build;  | 
            |
| 49 | 82 | }  | 
            |
| 50 | |||
| 51 | 85 | protected function validateNumber(string $name, int $value) : void  | 
            |
| 52 |     { | 
            ||
| 53 | 85 |         if ($value < 0) { | 
            |
| 54 | 3 | throw InvalidVersionException::forNumber($name, $value);  | 
            |
| 55 | }  | 
            ||
| 56 | 84 | }  | 
            |
| 57 | |||
| 58 | 85 | public static function fromParts(int $major, int $minor = 0, int $patch = 0, PreRelease $preRelease = null, Build $build = null) : Version  | 
            |
| 59 |     { | 
            ||
| 60 | 85 | return new static($major, $minor, $patch, $preRelease ?? new NoPreRelease(), $build ?? new NoBuild());  | 
            |
| 61 | }  | 
            ||
| 62 | |||
| 63 | /**  | 
            ||
| 64 | * @param string $versionString  | 
            ||
| 65 | *  | 
            ||
| 66 | * @throws InvalidVersionStringException  | 
            ||
| 67 | *  | 
            ||
| 68 | * @return Version  | 
            ||
| 69 | */  | 
            ||
| 70 | 81 | public static function fromString(string $versionString) : Version  | 
            |
| 71 |     { | 
            ||
| 72 | 81 | if (!preg_match(  | 
            |
| 73 | '#^'  | 
            ||
| 74 | . '(v|release\-)?'  | 
            ||
| 75 |             . '(?P<core>(?:[0-9]|[1-9][0-9]+)(?:\.(?:[0-9]|[1-9][0-9]+)){2})' | 
            ||
| 76 | . '(?:\-(?P<preRelease>[0-9A-Za-z\-\.]+))?'  | 
            ||
| 77 | . '(?:\+(?P<build>[0-9A-Za-z\-\.]+))?'  | 
            ||
| 78 | 81 | . '$#',  | 
            |
| 79 | 81 | $versionString,  | 
            |
| 80 | 81 | $parts  | 
            |
| 81 |         )) { | 
            ||
| 82 | 5 | throw InvalidVersionStringException::forVersionString($versionString);  | 
            |
| 83 | }  | 
            ||
| 84 | |||
| 85 | 77 |         [$major, $minor, $patch] = explode('.', $parts['core']); | 
            |
| 86 | 77 | $preRelease = !empty($parts['preRelease']) ? PreRelease::fromIdentifiersString($parts['preRelease']) : new NoPreRelease();  | 
            |
| 87 | 77 | $build = !empty($parts['build']) ? Build::fromIdentifiersString($parts['build']) : new NoBuild();  | 
            |
| 88 | |||
| 89 | 77 | return static::fromParts((int) $major, (int) $minor, (int) $patch, $preRelease, $build);  | 
            |
| 90 | }  | 
            ||
| 91 | |||
| 92 | 61 | public function getMajor() : int  | 
            |
| 93 |     { | 
            ||
| 94 | 61 | return $this->major;  | 
            |
| 95 | }  | 
            ||
| 96 | |||
| 97 | 56 | public function getMinor() : int  | 
            |
| 98 |     { | 
            ||
| 99 | 56 | return $this->minor;  | 
            |
| 100 | }  | 
            ||
| 101 | |||
| 102 | 52 | public function getPatch() : int  | 
            |
| 103 |     { | 
            ||
| 104 | 52 | return $this->patch;  | 
            |
| 105 | }  | 
            ||
| 106 | |||
| 107 | 48 | public function getPreRelease() : PreRelease  | 
            |
| 108 |     { | 
            ||
| 109 | 48 | return $this->preRelease;  | 
            |
| 110 | }  | 
            ||
| 111 | |||
| 112 | 28 | public function getBuild() : Build  | 
            |
| 113 |     { | 
            ||
| 114 | 28 | return $this->build;  | 
            |
| 115 | }  | 
            ||
| 116 | |||
| 117 | /**  | 
            ||
| 118 | * @param Version|string $version  | 
            ||
| 119 | * @return bool  | 
            ||
| 120 | */  | 
            ||
| 121 | 6 | public function isEqualTo($version) : bool  | 
            |
| 122 |     { | 
            ||
| 123 | 6 | return $this->compareTo($version) === 0;  | 
            |
| 124 | }  | 
            ||
| 125 | |||
| 126 | /**  | 
            ||
| 127 | * @param Version|string $version  | 
            ||
| 128 | * @return bool  | 
            ||
| 129 | */  | 
            ||
| 130 | 1 | public function isNotEqualTo($version) : bool  | 
            |
| 131 |     { | 
            ||
| 132 | 1 | return !$this->isEqualTo($version);  | 
            |
| 133 | }  | 
            ||
| 134 | |||
| 135 | /**  | 
            ||
| 136 | * @param Version|string $version  | 
            ||
| 137 | * @return bool  | 
            ||
| 138 | */  | 
            ||
| 139 | 3 | public function isGreaterThan($version) : bool  | 
            |
| 140 |     { | 
            ||
| 141 | 3 | return $this->compareTo($version) > 0;  | 
            |
| 142 | }  | 
            ||
| 143 | |||
| 144 | /**  | 
            ||
| 145 | * @param Version|string $version  | 
            ||
| 146 | * @return bool  | 
            ||
| 147 | */  | 
            ||
| 148 | 6 | public function isGreaterOrEqualTo($version) : bool  | 
            |
| 149 |     { | 
            ||
| 150 | 6 | return $this->compareTo($version) >= 0;  | 
            |
| 151 | }  | 
            ||
| 152 | |||
| 153 | /**  | 
            ||
| 154 | * @param Version|string $version  | 
            ||
| 155 | * @return bool  | 
            ||
| 156 | */  | 
            ||
| 157 | 3 | public function isLessThan($version) : bool  | 
            |
| 158 |     { | 
            ||
| 159 | 3 | return $this->compareTo($version) < 0;  | 
            |
| 160 | }  | 
            ||
| 161 | |||
| 162 | /**  | 
            ||
| 163 | * @param Version|string $version  | 
            ||
| 164 | * @return bool  | 
            ||
| 165 | */  | 
            ||
| 166 | 2 | public function isLessOrEqualTo($version) : bool  | 
            |
| 167 |     { | 
            ||
| 168 | 2 | return $this->compareTo($version) <= 0;  | 
            |
| 169 | }  | 
            ||
| 170 | |||
| 171 | /**  | 
            ||
| 172 | * @param Version|string $version  | 
            ||
| 173 | * @return int (1 if $this > $version, -1 if $this < $version, 0 if equal)  | 
            ||
| 174 | */  | 
            ||
| 175 | 26 | public function compareTo($version) : int  | 
            |
| 176 |     { | 
            ||
| 177 | 26 |         if (is_string($version)) { | 
            |
| 178 | 7 | $version = static::fromString($version);  | 
            |
| 179 | }  | 
            ||
| 180 | |||
| 181 | 26 | return $this->getComparator()->compare($this, $version);  | 
            |
| 182 | }  | 
            ||
| 183 | |||
| 184 | 2 | public function isMajorRelease() : bool  | 
            |
| 185 |     { | 
            ||
| 186 | 2 | return $this->major > 0 && $this->minor === 0 && $this->patch === 0;  | 
            |
| 187 | }  | 
            ||
| 188 | |||
| 189 | 1 | public function isMinorRelease() : bool  | 
            |
| 190 |     { | 
            ||
| 191 | 1 | return $this->minor > 0 && $this->patch === 0;  | 
            |
| 192 | }  | 
            ||
| 193 | |||
| 194 | 1 | public function isPatchRelease() : bool  | 
            |
| 195 |     { | 
            ||
| 196 | 1 | return $this->patch > 0;  | 
            |
| 197 | }  | 
            ||
| 198 | |||
| 199 | 39 | public function isPreRelease() : bool  | 
            |
| 200 |     { | 
            ||
| 201 | 39 | return !$this->preRelease->isEmpty();  | 
            |
| 202 | }  | 
            ||
| 203 | |||
| 204 | /**  | 
            ||
| 205 | * @deprecated Use hasBuild() instead  | 
            ||
| 206 | * @return bool  | 
            ||
| 207 | */  | 
            ||
| 208 | 1 | public function isBuild() : bool  | 
            |
| 209 |     { | 
            ||
| 210 | 1 | return !$this->build->isEmpty();  | 
            |
| 211 | }  | 
            ||
| 212 | |||
| 213 | 16 | public function hasBuild() : bool  | 
            |
| 214 |     { | 
            ||
| 215 | 16 | return !$this->build->isEmpty();  | 
            |
| 216 | }  | 
            ||
| 217 | |||
| 218 | 1 | public function incrementMajor() : Version  | 
            |
| 219 |     { | 
            ||
| 220 | 1 | return static::fromParts($this->major + 1, 0, 0, new NoPreRelease(), new NoBuild());  | 
            |
| 221 | }  | 
            ||
| 222 | |||
| 223 | 2 | public function incrementMinor() : Version  | 
            |
| 224 |     { | 
            ||
| 225 | 2 | return static::fromParts($this->major, $this->minor + 1, 0, new NoPreRelease(), new NoBuild());  | 
            |
| 226 | }  | 
            ||
| 227 | |||
| 228 | 1 | public function incrementPatch() : Version  | 
            |
| 229 |     { | 
            ||
| 230 | 1 | return static::fromParts($this->major, $this->minor, $this->patch + 1, new NoPreRelease(), new NoBuild());  | 
            |
| 231 | }  | 
            ||
| 232 | |||
| 233 | /**  | 
            ||
| 234 | * @param PreRelease|string $preRelease  | 
            ||
| 235 | * @return Version  | 
            ||
| 236 | */  | 
            ||
| 237 | 2 | public function withPreRelease($preRelease) : Version  | 
            |
| 238 |     { | 
            ||
| 239 | 2 |         if (is_string($preRelease)) { | 
            |
| 240 | 2 | $preRelease = PreRelease::fromIdentifiersString($preRelease);  | 
            |
| 241 | }  | 
            ||
| 242 | |||
| 243 | 2 | return static::fromParts($this->major, $this->minor, $this->patch, $preRelease, new NoBuild());  | 
            |
| 244 | }  | 
            ||
| 245 | |||
| 246 | /**  | 
            ||
| 247 | * @param Build|string $build  | 
            ||
| 248 | * @return Version  | 
            ||
| 249 | */  | 
            ||
| 250 | 1 | public function withBuild($build) : Version  | 
            |
| 251 |     { | 
            ||
| 252 | 1 |         if (is_string($build)) { | 
            |
| 253 | 1 | $build = Build::fromIdentifiersString($build);  | 
            |
| 254 | }  | 
            ||
| 255 | |||
| 256 | 1 | return static::fromParts($this->major, $this->minor, $this->patch, $this->preRelease, $build);  | 
            |
| 257 | }  | 
            ||
| 258 | |||
| 259 | 4 | public function matches(ConstraintInterface $constraint) : bool  | 
            |
| 260 |     { | 
            ||
| 261 | 4 | return $constraint->assert($this);  | 
            |
| 262 | }  | 
            ||
| 263 | |||
| 264 | 15 | public function getVersionString() : string  | 
            |
| 265 |     { | 
            ||
| 266 | return  | 
            ||
| 267 | 15 | $this->major  | 
            |
| 268 | 15 | . '.' . $this->minor  | 
            |
| 269 | 15 | . '.' . $this->patch  | 
            |
| 270 | 15 | . ($this->isPreRelease() ? '-' . $this->preRelease : '')  | 
            |
| 271 | 15 | . ($this->hasBuild() ? '+' . $this->build : '')  | 
            |
| 272 | ;  | 
            ||
| 273 | }  | 
            ||
| 274 | |||
| 275 | 11 | public function __toString() : string  | 
            |
| 276 |     { | 
            ||
| 277 | 11 | return $this->getVersionString();  | 
            |
| 278 | }  | 
            ||
| 279 | |||
| 280 | 4 | public function jsonSerialize() : string  | 
            |
| 281 |     { | 
            ||
| 282 | 4 | return $this->getVersionString();  | 
            |
| 283 | }  | 
            ||
| 284 | |||
| 285 | 4 | public function toArray() : array  | 
            |
| 286 |     { | 
            ||
| 287 | return [  | 
            ||
| 288 | 4 | 'major' => $this->major,  | 
            |
| 289 | 4 | 'minor' => $this->minor,  | 
            |
| 290 | 4 | 'patch' => $this->patch,  | 
            |
| 291 | 4 | 'preRelease' => $this->preRelease->getIdentifiers(),  | 
            |
| 292 | 4 | 'build' => $this->build->getIdentifiers(),  | 
            |
| 293 | ];  | 
            ||
| 294 | }  | 
            ||
| 295 | |||
| 296 | 1 | public static function setComparator(?ComparatorInterface $comparator) : void  | 
            |
| 297 |     { | 
            ||
| 298 | 1 | self::$comparator = $comparator;  | 
            |
| 299 | 1 | }  | 
            |
| 300 | |||
| 301 | 26 | protected function getComparator() : ComparatorInterface  | 
            |
| 309 | }  | 
            ||
| 310 | 
This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.