Completed
Pull Request — master (#88)
by Vladimir
02:11
created

FrontMatterDocument::afterReadContents()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 55

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7.0084

Importance

Changes 0
Metric Value
dl 0
loc 55
ccs 17
cts 18
cp 0.9444
rs 8.0484
c 0
b 0
f 0
cc 7
nc 8
nop 1
crap 7.0084

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * @copyright 2018 Vladimir Jimenez
5
 * @license   https://github.com/stakx-io/stakx/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\stakx\Document;
9
10
use allejo\stakx\Exception\FileAwareException;
11
use allejo\stakx\Exception\InvalidSyntaxException;
12
use allejo\stakx\FrontMatter\Exception\YamlVariableUndefinedException;
13
use allejo\stakx\FrontMatter\FrontMatterParser;
14
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
15
use Symfony\Component\Yaml\Exception\ParseException;
16
use Symfony\Component\Yaml\Yaml;
17
18
abstract class FrontMatterDocument extends ReadableDocument implements \IteratorAggregate, \ArrayAccess
19
{
20
    const TEMPLATE = "---\n%s\n---\n\n%s";
21
22
    /** @var array Functions that are white listed and can be called from templates. */
23
    public static $whiteListedFunctions = [
24
        'getPermalink', 'getRedirects', 'getTargetFile', 'getContent',
25
        'getFilename', 'getBasename', 'getExtension', 'isDraft',
26
    ];
27
28
    /** @var array FrontMatter keys that will be defined internally and cannot be overridden by users. */
29
    protected $specialFrontMatter = [
30
        'filePath' => null,
31
    ];
32
33
    /** @var bool Whether or not the body content has been evaluated yet. */
34
    protected $bodyContentEvaluated = false;
35
36
    /** @var FrontMatterParser */
37
    protected $frontMatterParser;
38
39
    /** @var array The raw FrontMatter that has not been evaluated. */
40
    protected $rawFrontMatter = [];
41
42
    /** @var array|null FrontMatter that is read from user documents. */
43
    protected $frontMatter = null;
44
45
    /** @var int The number of lines that Twig template errors should offset. */
46
    protected $lineOffset = 0;
47
48
    ///
49
    // Getter functions
50
    ///
51
52
    /**
53
     * {@inheritdoc}
54
     */
55 19
    public function getIterator()
56
    {
57 19
        return new \ArrayIterator($this->frontMatter);
58
    }
59
60
    /**
61
     * Get the number of lines that are taken up by FrontMatter and whitespace.
62
     *
63
     * @return int
64
     */
65
    public function getLineOffset()
66
    {
67
        return $this->lineOffset;
68
    }
69
70
    /**
71
     * Get whether or not this document is a draft.
72
     *
73
     * @return bool
74
     */
75 2
    public function isDraft()
76
    {
77 2
        return isset($this->frontMatter['draft']) && $this->frontMatter['draft'] === true;
78
    }
79
80
    ///
81
    // FrontMatter functionality
82
    ///
83
84
    /**
85
     * {@inheritdoc}
86
     */
87 118
    protected function beforeReadContents()
88
    {
89 118
        if (!$this->file->exists())
90
        {
91 1
            throw new FileNotFoundException(null, 0, null, $this->file->getAbsolutePath());
92
        }
93
94
        // If the "Last Modified" time is equal to what we have on record, then there's no need to read the file again
95 118
        if ($this->metadata['last_modified'] === $this->file->getMTime())
96
        {
97
            return false;
98
        }
99
100 118
        $this->metadata['last_modified'] = $this->file->getMTime();
101
102 118
        return true;
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108 118
    protected function readContents($readNecessary)
109
    {
110 118
        if (!$readNecessary)
111
        {
112
            return [];
113
        }
114
115 118
        $fileStructure = [];
116 118
        $rawFileContents = $this->file->getContents();
117 118
        preg_match('/---\R(.*?\R)?---(\s+)(.*)/s', $rawFileContents, $fileStructure);
118
119 118
        if (count($fileStructure) != 4)
120
        {
121 9
            throw new InvalidSyntaxException('Invalid FrontMatter file', 0, null, $this->getRelativeFilePath());
122
        }
123
124 109
        if (empty(trim($fileStructure[3])))
125
        {
126 1
            throw new InvalidSyntaxException('FrontMatter files must have a body to render', 0, null, $this->getRelativeFilePath());
127
        }
128
129 108
        return $fileStructure;
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135 108
    protected function afterReadContents($fileStructure)
136
    {
137
        // The file wasn't modified since our last read, so we can exit out quickly
138 108
        if (empty($fileStructure))
139
        {
140
            return;
141
        }
142
143
        /*
144
         * $fileStructure[1] is the YAML
145
         * $fileStructure[2] is the amount of new lines after the closing `---` and the beginning of content
146
         * $fileStructure[3] is the body of the document
147
         */
148
149
        // The hard coded 1 is the offset used to count the new line used after the first `---` that is not caught in the regex
150 108
        $this->lineOffset = substr_count($fileStructure[1], "\n") + substr_count($fileStructure[2], "\n") + 1;
151
152
        //
153
        // Update the FM of the document, if necessary
154
        //
155
156 108
        $fmHash = md5($fileStructure[1]);
157
158 108
        if ($this->metadata['fm_hash'] !== $fmHash)
159
        {
160 108
            $this->metadata['fm_hash'] = $fmHash;
161
162 108
            if (!empty(trim($fileStructure[1])))
163
            {
164 92
                $this->rawFrontMatter = Yaml::parse($fileStructure[1], Yaml::PARSE_DATETIME);
165
166 92
                if (!empty($this->rawFrontMatter) && !is_array($this->rawFrontMatter))
167
                {
168 92
                    throw new ParseException('The evaluated FrontMatter should be an array');
169
                }
170
            }
171
            else
172
            {
173 19
                $this->rawFrontMatter = [];
174
            }
175
        }
176
177
        //
178
        // Update the body of the document, if necessary
179
        //
180
181 107
        $bodyHash = md5($fileStructure[3]);
182
183 107
        if ($this->metadata['body_sum'] !== $bodyHash)
184
        {
185 107
            $this->metadata['body_sum'] = $bodyHash;
186 107
            $this->bodyContent = $fileStructure[3];
187 107
            $this->bodyContentEvaluated = false;
188
        }
189 107
    }
190
191
    /**
192
     * Get the FrontMatter without evaluating its variables or special functionality.
193
     *
194
     * @return array
195
     */
196 18
    final public function getRawFrontMatter()
197
    {
198 18
        return $this->rawFrontMatter;
199
    }
200
201
    /**
202
     * Get the FrontMatter for this document.
203
     *
204
     * @param bool $evaluateYaml whether or not to evaluate any variables
205
     *
206
     * @return array
207
     */
208 14
    final public function getFrontMatter($evaluateYaml = true)
0 ignored issues
show
Unused Code introduced by
The parameter $evaluateYaml is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
209
    {
210 14
        if ($this->frontMatter === null)
211
        {
212
            throw new \LogicException('FrontMatter has not been evaluated yet, be sure FrontMatterDocument::evaluateFrontMatter() is called before.');
213
        }
214
215 14
        return $this->frontMatter;
216
    }
217
218
    /**
219
     * {@inheritdoc}
220
     */
221 92
    final public function evaluateFrontMatter(array $variables = [], array $complexVariables = [])
222
    {
223 92
        $this->frontMatter = array_merge($this->rawFrontMatter, $variables);
224 92
        $this->evaluateYaml($this->frontMatter, $complexVariables);
225 91
    }
226
227
    /**
228
     * Returns true when the evaluated Front Matter has expanded values embedded.
229
     *
230
     * @return bool
231
     */
232 14
    final public function hasExpandedFrontMatter()
233
    {
234 14
        return $this->frontMatterParser !== null && $this->frontMatterParser->hasExpansion();
235
    }
236
237
    /**
238
     * Evaluate an array of data for FrontMatter variables. This function will modify the array in place.
239
     *
240
     * @param array $yaml An array of data containing FrontMatter variables
241
     *
242
     * @see $specialFrontMatter
243
     *
244
     * @throws YamlVariableUndefinedException A FrontMatter variable used does not exist
245
     */
246 92
    private function evaluateYaml(array &$yaml, array $complexVariables = [])
247
    {
248
        try
249
        {
250
            // The second parameter for this parser must match the $specialFrontMatter structure
251 92
            $this->frontMatterParser = new FrontMatterParser($yaml, [
252 92
                'basename' => $this->getBasename(),
253 92
                'filename' => $this->getFilename(),
254 92
                'filePath' => $this->getRelativeFilePath(),
255
            ]);
256 92
            $this->frontMatterParser->addComplexVariables($complexVariables);
257 92
            $this->frontMatterParser->parse();
258
        }
259 1
        catch (\Exception $e)
260
        {
261 1
            throw FileAwareException::castException($e, $this->getRelativeFilePath());
262
        }
263 91
    }
264
265
    ///
266
    // ArrayAccess Implementation
267
    ///
268
269
    /**
270
     * {@inheritdoc}
271
     */
272
    public function offsetSet($offset, $value)
273
    {
274
        throw new \LogicException('FrontMatter is read-only.');
275
    }
276
277
    /**
278
     * {@inheritdoc}
279
     */
280 47
    public function offsetExists($offset)
281
    {
282 47
        if (isset($this->frontMatter[$offset]) || isset($this->specialFrontMatter[$offset]))
283
        {
284 37
            return true;
285
        }
286
287 27
        $fxnCall = 'get' . ucfirst($offset);
288
289 27
        return method_exists($this, $fxnCall) && in_array($fxnCall, static::$whiteListedFunctions);
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295
    public function offsetUnset($offset)
296
    {
297
        throw new \LogicException('FrontMatter is read-only.');
298
    }
299
300
    /**
301
     * {@inheritdoc}
302
     */
303 48
    public function offsetGet($offset)
304
    {
305 48
        if (isset($this->specialFrontMatter[$offset]))
306
        {
307
            return $this->specialFrontMatter[$offset];
308
        }
309
310 48
        $fxnCall = 'get' . ucfirst($offset);
311
312 48 View Code Duplication
        if (in_array($fxnCall, self::$whiteListedFunctions) && method_exists($this, $fxnCall))
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
313
        {
314 25
            return call_user_func_array([$this, $fxnCall], []);
315
        }
316
317 41
        if (isset($this->frontMatter[$offset]))
318
        {
319 40
            return $this->frontMatter[$offset];
320
        }
321
322 2
        return null;
323
    }
324
}
325