Completed
Push — master ( 2319eb...3c4d84 )
by Vladimir
02:22
created

PHPUnit_Stakx_TestCase::buildFrontMatterTemplate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 2
1
<?php
2
3
/**
4
 * @copyright 2017 Vladimir Jimenez
5
 * @license   https://github.com/allejo/stakx/blob/master/LICENSE.md MIT
6
 */
7
8
namespace allejo\stakx\Test;
9
10
use allejo\stakx\Command\BuildableCommand;
11
use allejo\stakx\Configuration;
12
use allejo\stakx\Core\StakxLogger;
13
use allejo\stakx\Document\FrontMatterDocument;
14
use allejo\stakx\Filesystem\File;
15
use allejo\stakx\Filesystem\FilesystemLoader as fs;
16
use allejo\stakx\Manager\CollectionManager;
17
use allejo\stakx\Manager\DataManager;
18
use allejo\stakx\Service;
19
use allejo\stakx\System\Filesystem;
20
use allejo\stakx\Filesystem\Folder;
21
use org\bovigo\vfs\vfsStream;
22
use org\bovigo\vfs\vfsStreamDirectory;
23
use org\bovigo\vfs\vfsStreamFile;
24
use Psr\Log\LoggerInterface;
25
use Symfony\Component\Console\Output\ConsoleOutput;
26
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
27
use Symfony\Component\Yaml\Yaml;
28
29
abstract class PHPUnit_Stakx_TestCase extends \PHPUnit_Framework_TestCase
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
30
{
31
    const FM_OBJ_TEMPLATE = "---\n%s\n---\n\n%s";
32
33
    /** @var string */
34
    protected $assetFolder;
35
    /** @var vfsStreamFile */
36
    protected $dummyFile;
37
    /** @var vfsStreamDirectory */
38
    protected $rootDir;
39
    /** @var Filesystem */
40
    protected $fs;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fs. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
41
42
    public function setUp()
43
    {
44
        $this->dummyFile = vfsStream::newFile('stakx.html.twig');
45
        $this->rootDir = vfsStream::setup();
46
        $this->fs = new Filesystem();
47
48
        Service::setParameter(BuildableCommand::USE_DRAFTS, false);
49
        Service::setParameter(BuildableCommand::WATCHING, false);
50
        Service::setParameter(BuildableCommand::SAFE_MODE, false);
51
        Service::setParameter(BuildableCommand::BUILD_PROFILE, false);
52
        Service::setParameter(Configuration::HIGHLIGHTER_ENABLED, true);
53
        Service::setParameter('build.preserveCase', false);
54
55
        // Inspect the VFS as an array
56
        // vfsStream::inspect(new vfsStreamStructureVisitor())->getStructure();
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
57
    }
58
59
    public function tearDown()
60
    {
61
        if ($this->assetFolder !== null)
62
        {
63
            $this->fs->remove($this->assetFolder);
64
        }
65
    }
66
67
    ///
68
    // Assertion Functions
69
    ///
70
71
    /**
72
     * @param string $needle
73
     * @param string $haystack
74
     * @param string $message
75
     */
76
    protected function assertStringContains($needle, $haystack, $message = '')
77
    {
78
        $this->assertNotFalse(strpos($haystack, $needle), $message);
79
    }
80
81
    /**
82
     * @param string $fileContent
83
     * @param string $filePath
84
     * @param string $message
85
     */
86
    protected function assertFileContains($fileContent, $filePath, $message = '')
87
    {
88
        (substr($filePath, -1, 1) == '/') && $filePath .= 'index.html';
89
90
        $contents = file_get_contents($filePath);
91
92
        $this->assertStringContains($fileContent, $contents, $message);
93
    }
94
95
    ///
96
    // Filesystem Functions
97
    ///
98
99
    /**
100
     * Create a temporary folder where temporary file writes will be made to.
101
     *
102
     * @param string $folderName
103
     */
104
    protected function createAssetFolder($folderName)
105
    {
106
        $this->assetFolder = fs::getRelativePath(fs::appendPath(__DIR__, $folderName));
107
108
        fs::mkdir($this->assetFolder);
109
    }
110
111
    /**
112
     * Write a file to the asset folder.
113
     *
114
     * This file will be written to the actual filesystem and not the virtual filesystem. This file will be deleted at
115
     * each tearDown().
116
     *
117
     * @param string $fileName
118
     * @param string $content
119
     *
120
     * @return string Path to the temporary file; relative to the project's root
121
     */
122
    protected function createPhysicalFile($fileName, $content)
123
    {
124
        $folder = new Folder($this->assetFolder);
125
        $folder->writeFile($fileName, $content);
126
127
        return fs::appendPath($this->assetFolder, $fileName);
128
    }
129
130
    /**
131
     * Write a file to the virtual filesystem.
132
     *
133
     * This file will be deleted at each tearDown().
134
     *
135
     * @param string $filename
136
     * @param string $content
137
     *
138
     * @return string The URL of the file on the virtual filesystem.
139
     */
140
    protected function createVirtualFile($filename, $content)
141
    {
142
        $file = vfsStream::newFile($filename);
143
        $file
144
            ->setContent($content)
145
            ->at($this->rootDir)
146
        ;
147
148
        return $file->url();
149
    }
150
151
    /**
152
     * Create an object of a given type.
153
     *
154
     * This will create a virtual file and then create an object of the specified type for the created file.
155
     *
156
     * @param string $classType
157
     * @param string $filename
158
     * @param string $content
159
     *
160
     * @return object An instance of $classType
161
     */
162
    protected function createDocumentOfType($classType, $filename, $content)
163
    {
164
        $file = $this->createVirtualFile($filename, $content);
165
166
        return new $classType(new File($file));
167
    }
168
169
    /**
170
     * Create an object of a given type following the Front Matter format.
171
     *
172
     * @param string $classType
173
     * @param string $filename
0 ignored issues
show
Documentation introduced by
Should the type for parameter $filename not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
174
     * @param array  $frontMatter
175
     * @param string $content
176
     *
177
     * @return object An instance of $classType
178
     */
179
    protected function createFrontMatterDocumentOfType($classType, $filename = null, $frontMatter = [], $content = 'Body Text')
180
    {
181
        $body = $this->buildFrontMatterTemplate($frontMatter, $content);
182
183
        if (!$filename)
0 ignored issues
show
Bug Best Practice introduced by
The expression $filename of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
184
        {
185
            $filename = hash('sha256', uniqid(mt_rand(), true), false);
0 ignored issues
show
Coding Style introduced by
Consider using a different name than the parameter $filename. This often makes code more readable.
Loading history...
186
        }
187
188
        return $this->createDocumentOfType($classType, $filename, $body);
189
    }
190
191
    /**
192
     * Create multiple virtual files from a given array of information.
193
     *
194
     * ```php
195
     * $elements = [
196
     *     [
197
     *         'filename' => '<string>',
198
     *         'frontmatter' => [],
199
     *         'body' => '<string>',
200
     *     ],
201
     * ];
202
     * ```
203
     *
204
     * @param string $classType
205
     * @param array  $elements
206
     *
207
     * @return array
208
     */
209
    protected function createMultipleFrontMatterDocumentsOfType($classType, $elements)
210
    {
211
        $results = [];
212
213
        foreach ($elements as $element)
214
        {
215
            $filename = (isset($element['filename'])) ? $element['filename'] : null;
216
            $frontMatter = (!isset($element['frontmatter']) || empty($element['frontmatter'])) ? [] : $element['frontmatter'];
217
            $body = (isset($element['body'])) ? $element['body'] : 'Body Text';
218
219
            /** @var FrontMatterDocument $item */
220
            $item = $this->createFrontMatterDocumentOfType($classType, $filename, $frontMatter, $body);
221
            $item->evaluateFrontMatter();
222
223
            $results[] = $item;
224
        }
225
226
        return $results;
227
    }
228
229
    /**
230
     * Create a File object from a given path.
231
     *
232
     * @deprecated
233
     *
234
     * @param  string $filePath
235
     *
236
     * @return File
237
     */
238
    protected function createFileObjectFromPath($filePath)
239
    {
240
        return (new File($filePath));
241
    }
242
243
    ///
244
    // Mock Objects
245
    ///
246
247
    /**
248
     * @return CollectionManager|\PHPUnit_Framework_MockObject_MockObject
249
     */
250
    protected function getMockCollectionManager()
251
    {
252
        return $this->getMockBuilder(CollectionManager::class)
253
            ->disableOriginalConstructor()
254
            ->getMock()
255
        ;
256
    }
257
258
    /**
259
     * @return DataManager|\PHPUnit_Framework_MockObject_MockObject
260
     */
261
    protected function getMockDataManager()
262
    {
263
        return $this->getMockBuilder(DataManager::class)
264
            ->disableOriginalConstructor()
265
            ->getMock()
266
        ;
267
    }
268
269
    /**
270
     * Get a mock EventDispatcher.
271
     *
272
     * @return EventDispatcherInterface
273
     */
274
    protected function getMockEventDistpatcher()
275
    {
276
        return $this->getMock(EventDispatcherInterface::class);
277
    }
278
279
    /**
280
     * Get a mock logger.
281
     *
282
     * @return LoggerInterface
283
     */
284
    protected function getMockLogger()
285
    {
286
        return $this->getMock(LoggerInterface::class);
287
    }
288
289
    /**
290
     * Get a real logger instance that will save output to the console.
291
     *
292
     * @return StakxLogger
293
     */
294
    protected function getReadableLogger()
295
    {
296
        stream_filter_register('intercept', StreamInterceptor::class);
297
        $stakxLogger = new StakxLogger(new ConsoleOutput());
298
        stream_filter_append($stakxLogger->getOutputInterface()->getStream(), 'intercept');
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method getStream() does only exist in the following implementations of said interface: Symfony\Component\Console\Output\ConsoleOutput, Symfony\Component\Console\Output\StreamOutput.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
299
300
        return $stakxLogger;
301
    }
302
303
    ///
304
    // Utility Functions
305
    ///
306
307
    /**
308
     * Get the directory of the unit tests.
309
     *
310
     * @return string
311
     */
312
    protected function getTestRoot()
313
    {
314
        return __DIR__;
315
    }
316
317
    /**
318
     * Generate a FrontMatter-ready syntax to be used as a file's content.
319
     *
320
     * @param array  $frontMatter
321
     * @param string $body
322
     *
323
     * @return string
324
     */
325
    private function buildFrontMatterTemplate(array $frontMatter = array(), $body = 'Body text')
326
    {
327
        $fm = (empty($frontMatter)) ? '' : Yaml::dump($frontMatter, 2);
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $fm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
328
329
        return sprintf(self::FM_OBJ_TEMPLATE, $fm, $body);
330
    }
331
332
    ///
333
    // Misc Functions
334
    ///
335
336
    protected function bookCollectionProvider($jailed = false)
337
    {
338
        $cm = new CollectionManager(
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $cm. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
339
            $this->getMock(Configuration::class),
340
            $this->getMockEventDistpatcher(),
341
            $this->getMockLogger()
342
        );
343
        $cm->parseCollections(array(
344
            array(
345
                'name' => 'books',
346
                'folder' => 'tests/allejo/stakx/Test/assets/MyBookCollection/',
347
            ),
348
        ));
349
350
        return (!$jailed) ? $cm->getCollections() : $cm->getJailedCollections();
351
    }
352
}
353