Passed
Push — main ( a115e2...ed9b12 )
by Marc
04:17 queued 13s
created

ModelFactory   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 139
Duplicated Lines 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 59
c 8
b 0
f 0
dl 0
loc 139
rs 10
wmc 19

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getContentDateAndTimestamp() 0 9 2
A restoreNewlines() 0 2 1
A getContentObject() 0 9 2
A createEmptyContentObject() 0 9 1
A getSummary() 0 8 2
A __construct() 0 4 1
A loadDataFile() 0 11 4
A getCategoryObject() 0 5 2
A createContentObject() 0 21 4
1
<?php declare(strict_types=1);
2
namespace html_go\model;
3
4
use DateTimeInterface;
5
use InvalidArgumentException;
6
use html_go\exceptions\InternalException;
7
use html_go\indexing\IndexManager;
8
use html_go\markdown\MarkdownParser;
9
10
/**
11
 * Responsible for creating <code>Content</code> objects ready to be used in templates.
12
 * @author Marc L. Veary
13
 * @since 1.0
14
 */
15
final class ModelFactory extends AdminModelFactory
16
{
17
    private MarkdownParser $parser;
18
    private IndexManager $manager;
19
20
    /**
21
     * ModelFactory constructor.
22
     * @param Config $config
23
     * @param MarkdownParser $parser Implementation of the
24
     * <code>MarkdownParser</code> interface.
25
     */
26
    public function __construct(Config $config, MarkdownParser $parser, IndexManager $manager) {
27
        parent::__construct($config);
28
        $this->parser = $parser;
29
        $this->manager = $manager;
30
    }
31
32
    /**
33
     * Create a content object (stdClass) from an index object (stdClass).
34
     * @param \stdClass $indexElement As obtained from the <code>IndexManager</code>
35
     * @return \stdClass
36
     */
37
    public function createContentObject(\stdClass $indexElement): \stdClass {
38
        $contentObject = $this->getContentObject($indexElement);
39
        $contentObject->key = $indexElement->key;
40
        $contentObject->list = [];
41
        $contentObject->site = $this->getSiteObject();
42
43
        if (!empty($indexElement->category)) {
44
            $contentObject->category = $this->getCategoryObject($indexElement->category);
45
        }
46
        if (isset($indexElement->tags)) {
47
            $contentObject->tags = $indexElement->tags;
48
        }
49
        $dt = $this->getContentDateAndTimestamp($indexElement);
50
        $contentObject->date = $dt[0];
51
        $contentObject->timestamp = $dt[1];
52
53
        if (empty($contentObject->summary)) {
54
            $contentObject->summary = $this->getSummary($contentObject->body);
55
        }
56
57
        return $contentObject;
58
    }
59
60
    private function getContentObject(\stdClass $indexElement): \stdClass {
61
        if ($indexElement->section === TAG_SECTION) {
62
            $contentObject = $this->createEmptyContentObject($indexElement);
63
            $contentObject->title = \substr($indexElement->key, \strlen(TAG_SECTION) + 1);
64
        } else {
65
            $contentObject = $this->loadDataFile($indexElement);
66
            $contentObject->body = $this->restoreNewlines($contentObject->body);
67
        }
68
        return $contentObject;
69
    }
70
71
    /**
72
     * @param \stdClass $indexElement
73
     * @return array<string>
74
     */
75
    private function getContentDateAndTimestamp(\stdClass $indexElement): array {
76
        if (empty($indexElement->timestamp) === false) {
77
            $dt = new \DateTime($indexElement->timestamp);
78
            $date = $dt->format($this->config->getString(Config::KEY_POST_DATE_FMT));
79
            $timestamp = $dt->format(DateTimeInterface::W3C);
80
        } else {
81
            $date = $timestamp = EMPTY_VALUE;
82
        }
83
        return [$date, $timestamp];
84
    }
85
86
    private function getCategoryObject(string $slug): \stdClass {
87
        if ($this->manager->elementExists($slug) === false) {
88
            throw new \UnexpectedValueException("Element does not exist [$slug]"); // @codeCoverageIgnore
89
        }
90
        return $this->loadDataFile($this->manager->getElementFromSlugIndex($slug));
91
    }
92
93
    /**
94
     * This loads the content's associated file and merges it with the index element.
95
     * @param \stdClass $indexElement
96
     * @throws InvalidArgumentException
97
     * @throws InternalException
98
     * @return \stdClass
99
     */
100
    private function loadDataFile(\stdClass $indexElement): \stdClass {
101
        if (empty($indexElement->path)) {
102
            throw new InvalidArgumentException("Object does not have 'path' property "./** @scrutinizer ignore-type */print_r($indexElement, true)); // @codeCoverageIgnore
103
        }
104
        if (($data = \file_get_contents($indexElement->path)) === false) {
105
            throw new InternalException("file_get_contents() failed opening [$indexElement->path]"); // @codeCoverageIgnore
106
        }
107
        if (($fileData = \json_decode($data, true)) === null) {
108
            throw new InternalException("json_decode returned null decoding [$fileData] from [$indexElement->path]"); // @codeCoverageIgnore
109
        }
110
        return (object)\array_merge((array)$indexElement, $fileData);
111
    }
112
113
    /**
114
     * This is used for tags only as tags don't have an associated file on the
115
     * filesystem.
116
     * @param \stdClass $indexElement
117
     * @return \stdClass
118
     */
119
    private function createEmptyContentObject(\stdClass $indexElement): \stdClass {
120
        $obj = new \stdClass();
121
        $obj->key = EMPTY_VALUE;
122
        $obj->section = TAG_SECTION;
123
        $obj->body = EMPTY_VALUE;
124
        $obj->title = EMPTY_VALUE;
125
        $obj->description = EMPTY_VALUE;
126
        $obj->timestamp = EMPTY_VALUE;
127
        return (object)\array_merge((array)$indexElement, (array)$obj);
128
    }
129
130
    /**
131
     * Returns the summary for the content object. If '<!--more-->' is used within
132
     * the body, then this is removed once the summary is obtained.
133
     * @param string $body the body. The '<!--more--> removed if found thus passed by reference.
134
     * @return string The summary text. If no summary is defined, returns an empty string
135
     */
136
    private function getSummary(string &$body): string {
137
        $pos = \strpos($body, SUMMARY_MARKER);
138
        if ($pos !== false) {
139
            $summary = \substr($body, 0, $pos);
140
            $body = \str_replace(SUMMARY_MARKER, '', $body);
141
            return $summary;
142
        }
143
        return '';
144
    }
145
146
    /**
147
     * Newlines must be encoded for PHP functions. So we use '<nl>' to for '\n'.
148
     * This method replaces '<nl>' with '\n'.
149
     * @param string $text
150
     * @return string
151
     */
152
    private function restoreNewlines(string $text): string {
153
        return \str_replace(NEWLINE_MARKER, '\n', $text);
154
    }
155
}
156