| Total Complexity | 71 | 
| Total Lines | 518 | 
| Duplicated Lines | 0 % | 
| Changes | 2 | ||
| Bugs | 2 | Features | 0 | 
Complex classes like Page 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.
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 Page, and based on these observations, apply Extract Interface, too.
| 1 | <?php  | 
            ||
| 24 | class Page extends Item  | 
            ||
| 25 | { | 
            ||
| 26 | const SLUGIFY_PATTERN = '/(^\/|[^._a-z0-9\/]|-)+/'; // should be '/^\/|[^_a-z0-9\/]+/'  | 
            ||
| 27 | |||
| 28 | /** @var bool True if page is not created from a Markdown file. */  | 
            ||
| 29 | protected $virtual;  | 
            ||
| 30 | |||
| 31 | /** @var SplFileInfo */  | 
            ||
| 32 | protected $file;  | 
            ||
| 33 | |||
| 34 | /** @var string Homepage, Page, Section, etc. */  | 
            ||
| 35 | protected $type;  | 
            ||
| 36 | |||
| 37 | /** @var string */  | 
            ||
| 38 | protected $folder;  | 
            ||
| 39 | |||
| 40 | /** @var string */  | 
            ||
| 41 | protected $slug;  | 
            ||
| 42 | |||
| 43 | /** @var string folder + slug. */  | 
            ||
| 44 | protected $path;  | 
            ||
| 45 | |||
| 46 | /** @var string */  | 
            ||
| 47 | protected $section;  | 
            ||
| 48 | |||
| 49 | /** @var string */  | 
            ||
| 50 | protected $frontmatter;  | 
            ||
| 51 | |||
| 52 | /** @var string Body before conversion. */  | 
            ||
| 53 | protected $body;  | 
            ||
| 54 | |||
| 55 | /** @var array Front matter before conversion. */  | 
            ||
| 56 | protected $fmVariables = [];  | 
            ||
| 57 | |||
| 58 | /** @var string Body after Markdown conversion. */  | 
            ||
| 59 | protected $html;  | 
            ||
| 60 | |||
| 61 | /** @var Slugify */  | 
            ||
| 62 | private static $slugifier;  | 
            ||
| 63 | |||
| 64 | public function __construct(string $id)  | 
            ||
| 65 |     { | 
            ||
| 66 | parent::__construct($id);  | 
            ||
| 67 | $this->setVirtual(true);  | 
            ||
| 68 | $this->setType(Type::PAGE);  | 
            ||
| 69 | // default variables  | 
            ||
| 70 | $this->setVariables([  | 
            ||
| 71 | 'title' => 'Page Title',  | 
            ||
| 72 | 'date' => new \DateTime(),  | 
            ||
| 73 | 'updated' => new \DateTime(),  | 
            ||
| 74 | 'weight' => null,  | 
            ||
| 75 | 'filepath' => null,  | 
            ||
| 76 | 'published' => true,  | 
            ||
| 77 | 'content_template' => 'page.content.twig',  | 
            ||
| 78 | ]);  | 
            ||
| 79 | }  | 
            ||
| 80 | |||
| 81 | /**  | 
            ||
| 82 | * Turns a path (string) into a slug (URI).  | 
            ||
| 83 | */  | 
            ||
| 84 | public static function slugify(string $path): string  | 
            ||
| 85 |     { | 
            ||
| 86 |         if (!self::$slugifier instanceof Slugify) { | 
            ||
| 87 | self::$slugifier = Slugify::create(['regexp' => self::SLUGIFY_PATTERN]);  | 
            ||
| 88 | }  | 
            ||
| 89 | |||
| 90 | return self::$slugifier->slugify($path);  | 
            ||
| 91 | }  | 
            ||
| 92 | |||
| 93 | /**  | 
            ||
| 94 | * Creates the ID from the file path.  | 
            ||
| 95 | */  | 
            ||
| 96 | public static function createId(SplFileInfo $file): string  | 
            ||
| 97 |     { | 
            ||
| 98 | $relativepath = self::slugify(str_replace(DIRECTORY_SEPARATOR, '/', $file->getRelativePath()));  | 
            ||
| 99 |         $basename = self::slugify(PrefixSuffix::subPrefix($file->getBasename('.'.$file->getExtension()))); | 
            ||
| 100 | // case of "README" -> index  | 
            ||
| 101 |         $basename = (string) str_ireplace('readme', 'index', $basename); | 
            ||
| 102 | // case of section's index: "section/index" -> "section"  | 
            ||
| 103 |         if (!empty($relativepath) && PrefixSuffix::sub($basename) == 'index') { | 
            ||
| 104 | // case of a localized section  | 
            ||
| 105 |             if (PrefixSuffix::hasSuffix($basename)) { | 
            ||
| 106 | return $relativepath.'.'.PrefixSuffix::getSuffix($basename);  | 
            ||
| 107 | }  | 
            ||
| 108 | |||
| 109 | return $relativepath;  | 
            ||
| 110 | }  | 
            ||
| 111 | |||
| 112 | return trim(Util::joinPath($relativepath, $basename), '/');  | 
            ||
| 113 | }  | 
            ||
| 114 | |||
| 115 | /**  | 
            ||
| 116 | * Returns the ID of a page without language suffix.  | 
            ||
| 117 | */  | 
            ||
| 118 | public function getIdWithoutLang(): string  | 
            ||
| 119 |     { | 
            ||
| 120 | return PrefixSuffix::sub($this->getId());  | 
            ||
| 121 | }  | 
            ||
| 122 | |||
| 123 | /**  | 
            ||
| 124 | * Set file.  | 
            ||
| 125 | */  | 
            ||
| 126 | public function setFile(SplFileInfo $file): self  | 
            ||
| 127 |     { | 
            ||
| 128 | $this->setVirtual(false);  | 
            ||
| 129 | $this->file = $file;  | 
            ||
| 130 | |||
| 131 | /*  | 
            ||
| 132 | * File path components  | 
            ||
| 133 | */  | 
            ||
| 134 | $fileRelativePath = str_replace(DIRECTORY_SEPARATOR, '/', $this->file->getRelativePath());  | 
            ||
| 135 | $fileExtension = $this->file->getExtension();  | 
            ||
| 136 |         $fileName = $this->file->getBasename('.'.$fileExtension); | 
            ||
| 137 | // case of "README" -> "index"  | 
            ||
| 138 |         $fileName = (string) str_ireplace('readme', 'index', $fileName); | 
            ||
| 139 | // case of "index" = home page  | 
            ||
| 140 |         if (empty($this->file->getRelativePath()) && PrefixSuffix::sub($fileName) == 'index') { | 
            ||
| 141 | $this->setType(Type::HOMEPAGE);  | 
            ||
| 142 | }  | 
            ||
| 143 | /*  | 
            ||
| 144 | * Set protected variables  | 
            ||
| 145 | */  | 
            ||
| 146 | $this->setFolder($fileRelativePath); // ie: "blog"  | 
            ||
| 147 | $this->setSlug($fileName); // ie: "post-1"  | 
            ||
| 148 | $this->setPath($this->getFolder().'/'.$this->getSlug()); // ie: "blog/post-1"  | 
            ||
| 149 | /*  | 
            ||
| 150 | * Set default variables  | 
            ||
| 151 | */  | 
            ||
| 152 | $this->setVariables([  | 
            ||
| 153 | 'title' => PrefixSuffix::sub($fileName),  | 
            ||
| 154 | 'date' => (new \DateTime())->setTimestamp($this->file->getCTime()),  | 
            ||
| 155 | 'updated' => (new \DateTime())->setTimestamp($this->file->getMTime()),  | 
            ||
| 156 | 'filepath' => $this->file->getRelativePathname(),  | 
            ||
| 157 | ]);  | 
            ||
| 158 | /*  | 
            ||
| 159 | * Set specific variables  | 
            ||
| 160 | */  | 
            ||
| 161 | // is file has a prefix?  | 
            ||
| 162 |         if (PrefixSuffix::hasPrefix($fileName)) { | 
            ||
| 163 | $prefix = PrefixSuffix::getPrefix($fileName);  | 
            ||
| 164 |             if ($prefix !== null) { | 
            ||
| 165 | // prefix is a valid date?  | 
            ||
| 166 |                 if (Util\Date::isDateValid($prefix)) { | 
            ||
| 167 |                     $this->setVariable('date', (string) $prefix); | 
            ||
| 168 |                 } else { | 
            ||
| 169 | // prefix is an integer: used for sorting  | 
            ||
| 170 |                     $this->setVariable('weight', (int) $prefix); | 
            ||
| 171 | }  | 
            ||
| 172 | }  | 
            ||
| 173 | }  | 
            ||
| 174 | // is file has a language suffix?  | 
            ||
| 175 |         if (PrefixSuffix::hasSuffix($fileName)) { | 
            ||
| 176 |             $this->setVariable('language', PrefixSuffix::getSuffix($fileName)); | 
            ||
| 177 | }  | 
            ||
| 178 | // set reference between translations  | 
            ||
| 179 |         $this->setVariable('langref', $this->getPath()); | 
            ||
| 180 | |||
| 181 | return $this;  | 
            ||
| 182 | }  | 
            ||
| 183 | |||
| 184 | /**  | 
            ||
| 185 | * Returns file real path.  | 
            ||
| 186 | */  | 
            ||
| 187 | public function getFilePath(): ?string  | 
            ||
| 188 |     { | 
            ||
| 189 | return $this->file->getRealPath() === false ? null : $this->file->getRealPath();  | 
            ||
| 190 | }  | 
            ||
| 191 | |||
| 192 | /**  | 
            ||
| 193 | * Parse file content.  | 
            ||
| 194 | */  | 
            ||
| 195 | public function parse(): self  | 
            ||
| 196 |     { | 
            ||
| 197 | $parser = new Parser($this->file);  | 
            ||
| 198 | $parsed = $parser->parse();  | 
            ||
| 199 | $this->frontmatter = $parsed->getFrontmatter();  | 
            ||
| 200 | $this->body = $parsed->getBody();  | 
            ||
| 201 | |||
| 202 | return $this;  | 
            ||
| 203 | }  | 
            ||
| 204 | |||
| 205 | /**  | 
            ||
| 206 | * Get frontmatter.  | 
            ||
| 207 | */  | 
            ||
| 208 | public function getFrontmatter(): ?string  | 
            ||
| 209 |     { | 
            ||
| 210 | return $this->frontmatter;  | 
            ||
| 211 | }  | 
            ||
| 212 | |||
| 213 | /**  | 
            ||
| 214 | * Get body as raw.  | 
            ||
| 215 | */  | 
            ||
| 216 | public function getBody(): ?string  | 
            ||
| 217 |     { | 
            ||
| 218 | return $this->body;  | 
            ||
| 219 | }  | 
            ||
| 220 | |||
| 221 | /**  | 
            ||
| 222 | * Set virtual status.  | 
            ||
| 223 | */  | 
            ||
| 224 | public function setVirtual(bool $virtual): self  | 
            ||
| 225 |     { | 
            ||
| 226 | $this->virtual = $virtual;  | 
            ||
| 227 | |||
| 228 | return $this;  | 
            ||
| 229 | }  | 
            ||
| 230 | |||
| 231 | /**  | 
            ||
| 232 | * Is current page is virtual?  | 
            ||
| 233 | */  | 
            ||
| 234 | public function isVirtual(): bool  | 
            ||
| 235 |     { | 
            ||
| 236 | return $this->virtual;  | 
            ||
| 237 | }  | 
            ||
| 238 | |||
| 239 | /**  | 
            ||
| 240 | * Set page type.  | 
            ||
| 241 | */  | 
            ||
| 242 | public function setType(string $type): self  | 
            ||
| 243 |     { | 
            ||
| 244 | $this->type = new Type($type);  | 
            ||
| 245 | |||
| 246 | return $this;  | 
            ||
| 247 | }  | 
            ||
| 248 | |||
| 249 | /**  | 
            ||
| 250 | * Get page type.  | 
            ||
| 251 | */  | 
            ||
| 252 | public function getType(): string  | 
            ||
| 255 | }  | 
            ||
| 256 | |||
| 257 | /**  | 
            ||
| 258 | * Set path without slug.  | 
            ||
| 259 | */  | 
            ||
| 260 | public function setFolder(string $folder): self  | 
            ||
| 261 |     { | 
            ||
| 262 | $this->folder = self::slugify($folder);  | 
            ||
| 263 | |||
| 264 | return $this;  | 
            ||
| 265 | }  | 
            ||
| 266 | |||
| 267 | /**  | 
            ||
| 268 | * Get path without slug.  | 
            ||
| 269 | */  | 
            ||
| 270 | public function getFolder(): ?string  | 
            ||
| 273 | }  | 
            ||
| 274 | |||
| 275 | /**  | 
            ||
| 276 | * Set slug.  | 
            ||
| 277 | */  | 
            ||
| 278 | public function setSlug(string $slug): self  | 
            ||
| 279 |     { | 
            ||
| 280 |         if (!$this->slug) { | 
            ||
| 281 | $slug = self::slugify(PrefixSuffix::sub($slug));  | 
            ||
| 282 | }  | 
            ||
| 283 | // force slug and update path  | 
            ||
| 284 |         if ($this->slug && $this->slug != $slug) { | 
            ||
| 285 | $this->setPath($this->getFolder().'/'.$slug);  | 
            ||
| 286 | }  | 
            ||
| 287 | $this->slug = $slug;  | 
            ||
| 288 | |||
| 289 | return $this;  | 
            ||
| 290 | }  | 
            ||
| 291 | |||
| 292 | /**  | 
            ||
| 293 | * Get slug.  | 
            ||
| 294 | */  | 
            ||
| 295 | public function getSlug(): string  | 
            ||
| 296 |     { | 
            ||
| 297 | return $this->slug;  | 
            ||
| 298 | }  | 
            ||
| 299 | |||
| 300 | /**  | 
            ||
| 301 | * Set path.  | 
            ||
| 302 | */  | 
            ||
| 303 | public function setPath(string $path): self  | 
            ||
| 304 |     { | 
            ||
| 305 | $path = self::slugify(PrefixSuffix::sub($path));  | 
            ||
| 306 | |||
| 307 | // case of homepage  | 
            ||
| 308 |         if ($path == 'index') { | 
            ||
| 309 | $this->path = '';  | 
            ||
| 310 | |||
| 311 | return $this;  | 
            ||
| 312 | }  | 
            ||
| 313 | |||
| 314 | // case of custom sections' index (ie: content/section/index.md)  | 
            ||
| 315 |         if (substr($path, -6) == '/index') { | 
            ||
| 316 | $path = substr($path, 0, strlen($path) - 6);  | 
            ||
| 317 | }  | 
            ||
| 318 | $this->path = $path;  | 
            ||
| 319 | |||
| 320 | // case of root pages  | 
            ||
| 321 | $lastslash = strrpos($this->path, '/');  | 
            ||
| 322 |         if ($lastslash === false) { | 
            ||
| 323 | $this->slug = $this->path;  | 
            ||
| 324 | |||
| 325 | return $this;  | 
            ||
| 326 | }  | 
            ||
| 327 | |||
| 328 |         if (!$this->virtual && $this->getSection() === null) { | 
            ||
| 329 |             $this->section = explode('/', $this->path)[0]; | 
            ||
| 330 | }  | 
            ||
| 331 | $this->folder = substr($this->path, 0, $lastslash);  | 
            ||
| 332 | $this->slug = substr($this->path, -(strlen($this->path) - $lastslash - 1));  | 
            ||
| 333 | |||
| 334 | return $this;  | 
            ||
| 335 | }  | 
            ||
| 336 | |||
| 337 | /**  | 
            ||
| 338 | * Get path.  | 
            ||
| 339 | */  | 
            ||
| 340 | public function getPath(): ?string  | 
            ||
| 343 | }  | 
            ||
| 344 | |||
| 345 | /**  | 
            ||
| 346 | * @see getPath()  | 
            ||
| 347 | */  | 
            ||
| 348 | public function getPathname(): ?string  | 
            ||
| 349 |     { | 
            ||
| 350 | return $this->getPath();  | 
            ||
| 351 | }  | 
            ||
| 352 | |||
| 353 | /**  | 
            ||
| 354 | * Set section.  | 
            ||
| 355 | */  | 
            ||
| 356 | public function setSection(string $section): self  | 
            ||
| 357 |     { | 
            ||
| 358 | $this->section = $section;  | 
            ||
| 359 | |||
| 360 | return $this;  | 
            ||
| 361 | }  | 
            ||
| 362 | |||
| 363 | /**  | 
            ||
| 364 | * Get section.  | 
            ||
| 365 | */  | 
            ||
| 366 | public function getSection(): ?string  | 
            ||
| 367 |     { | 
            ||
| 368 | return !empty($this->section) ? $this->section : null;  | 
            ||
| 369 | }  | 
            ||
| 370 | |||
| 371 | /**  | 
            ||
| 372 | * Set body as HTML.  | 
            ||
| 373 | */  | 
            ||
| 374 | public function setBodyHtml(string $html): self  | 
            ||
| 379 | }  | 
            ||
| 380 | |||
| 381 | /**  | 
            ||
| 382 | * Get body as HTML.  | 
            ||
| 383 | */  | 
            ||
| 384 | public function getBodyHtml(): ?string  | 
            ||
| 387 | }  | 
            ||
| 388 | |||
| 389 | /**  | 
            ||
| 390 | * @see getBodyHtml()  | 
            ||
| 391 | */  | 
            ||
| 392 | public function getContent(): ?string  | 
            ||
| 393 |     { | 
            ||
| 394 | return $this->getBodyHtml();  | 
            ||
| 395 | }  | 
            ||
| 396 | |||
| 397 | /*  | 
            ||
| 398 | * Helpers to set and get variables.  | 
            ||
| 399 | */  | 
            ||
| 400 | |||
| 401 | /**  | 
            ||
| 402 | * Set an array as variables.  | 
            ||
| 403 | *  | 
            ||
| 404 | * @throws RuntimeException  | 
            ||
| 405 | */  | 
            ||
| 406 | public function setVariables(array $variables): self  | 
            ||
| 413 | }  | 
            ||
| 414 | |||
| 415 | /**  | 
            ||
| 416 | * Get all variables.  | 
            ||
| 417 | */  | 
            ||
| 418 | public function getVariables(): array  | 
            ||
| 419 |     { | 
            ||
| 420 | return $this->properties;  | 
            ||
| 421 | }  | 
            ||
| 422 | |||
| 423 | /**  | 
            ||
| 424 | * Set a variable.  | 
            ||
| 425 | *  | 
            ||
| 426 | * @param string $name  | 
            ||
| 427 | * @param mixed $value  | 
            ||
| 428 | *  | 
            ||
| 429 | * @throws RuntimeException  | 
            ||
| 430 | */  | 
            ||
| 431 | public function setVariable(string $name, $value): self  | 
            ||
| 432 |     { | 
            ||
| 433 |         if (is_bool($value)) { | 
            ||
| 434 | $value = $value ?: 0;  | 
            ||
| 435 | }  | 
            ||
| 436 |         switch ($name) { | 
            ||
| 437 | /**  | 
            ||
| 438 | * date: 2012-10-08.  | 
            ||
| 439 | */  | 
            ||
| 440 | case 'date':  | 
            ||
| 441 |                 try { | 
            ||
| 442 | $date = Util\Date::dateToDatetime($value);  | 
            ||
| 443 |                 } catch (\Exception $e) { | 
            ||
| 444 |                     throw new RuntimeException(\sprintf('Expected date format for "date" in "%s" must be "YYYY-MM-DD" instead of "%s"', $this->getId(), (string) $value)); | 
            ||
| 445 | }  | 
            ||
| 446 |                 $this->offsetSet('date', $date); | 
            ||
| 447 | break;  | 
            ||
| 448 | /**  | 
            ||
| 449 | * schedule:  | 
            ||
| 450 | * publish: 2012-10-08  | 
            ||
| 451 | * expiry: 2012-10-09.  | 
            ||
| 452 | */  | 
            ||
| 453 | case 'schedule':  | 
            ||
| 454 |                 $this->offsetSet('published', false); | 
            ||
| 455 |                 if (is_array($value)) { | 
            ||
| 456 |                     if (array_key_exists('publish', $value) && Util\Date::dateToDatetime($value['publish']) <= Util\Date::dateToDatetime('now')) { | 
            ||
| 457 |                         $this->offsetSet('published', true); | 
            ||
| 458 | }  | 
            ||
| 459 |                     if (array_key_exists('expiry', $value) && Util\Date::dateToDatetime($value['expiry']) >= Util\Date::dateToDatetime('now')) { | 
            ||
| 460 |                         $this->offsetSet('published', true); | 
            ||
| 461 | }  | 
            ||
| 462 | }  | 
            ||
| 463 | break;  | 
            ||
| 464 | /**  | 
            ||
| 465 | * draft: true.  | 
            ||
| 466 | */  | 
            ||
| 467 | case 'draft':  | 
            ||
| 468 |                 if ($value === true) { | 
            ||
| 469 |                     $this->offsetSet('published', false); | 
            ||
| 470 | }  | 
            ||
| 471 | break;  | 
            ||
| 472 | /**  | 
            ||
| 473 | * path: about/about  | 
            ||
| 474 | * slug: about.  | 
            ||
| 475 | */  | 
            ||
| 476 | case 'path':  | 
            ||
| 477 | case 'slug':  | 
            ||
| 478 | $slugify = self::slugify((string) $value);  | 
            ||
| 479 |                 if ($value != $slugify) { | 
            ||
| 480 |                     throw new RuntimeException(\sprintf('"%s" variable should be "%s" (not "%s") in "%s"', $name, $slugify, (string) $value, $this->getId())); | 
            ||
| 481 | }  | 
            ||
| 482 | /** @see setPath() */  | 
            ||
| 483 | /** @see setSlug() */  | 
            ||
| 484 | $method = 'set'.\ucfirst($name);  | 
            ||
| 485 | $this->$method($value);  | 
            ||
| 486 | break;  | 
            ||
| 487 | default:  | 
            ||
| 488 | $this->offsetSet($name, $value);  | 
            ||
| 489 | }  | 
            ||
| 490 | |||
| 491 | return $this;  | 
            ||
| 492 | }  | 
            ||
| 493 | |||
| 494 | /**  | 
            ||
| 495 | * Is variable exists?  | 
            ||
| 496 | */  | 
            ||
| 497 | public function hasVariable(string $name): bool  | 
            ||
| 498 |     { | 
            ||
| 499 | return $this->offsetExists($name);  | 
            ||
| 500 | }  | 
            ||
| 501 | |||
| 502 | /**  | 
            ||
| 503 | * Get a variable.  | 
            ||
| 504 | *  | 
            ||
| 505 | * @return mixed|null  | 
            ||
| 506 | */  | 
            ||
| 507 | public function getVariable(string $name)  | 
            ||
| 508 |     { | 
            ||
| 509 |         if ($this->offsetExists($name)) { | 
            ||
| 510 | return $this->offsetGet($name);  | 
            ||
| 511 | }  | 
            ||
| 512 | }  | 
            ||
| 513 | |||
| 514 | /**  | 
            ||
| 515 | * Unset a variable.  | 
            ||
| 516 | */  | 
            ||
| 517 | public function unVariable(string $name): self  | 
            ||
| 518 |     { | 
            ||
| 519 |         if ($this->offsetExists($name)) { | 
            ||
| 520 | $this->offsetUnset($name);  | 
            ||
| 521 | }  | 
            ||
| 522 | |||
| 523 | return $this;  | 
            ||
| 524 | }  | 
            ||
| 525 | |||
| 526 | /**  | 
            ||
| 527 | * Set front matter (only) variables.  | 
            ||
| 528 | */  | 
            ||
| 529 | public function setFmVariables(array $variables): self  | 
            ||
| 530 |     { | 
            ||
| 531 | $this->fmVariables = $variables;  | 
            ||
| 532 | |||
| 533 | return $this;  | 
            ||
| 534 | }  | 
            ||
| 535 | |||
| 536 | /**  | 
            ||
| 537 | * Get front matter variables.  | 
            ||
| 538 | */  | 
            ||
| 539 | public function getFmVariables(): array  | 
            ||
| 542 | }  | 
            ||
| 543 | }  | 
            ||
| 544 |