Completed
Push — master ( b05943...b0fd4f )
by Joachim
03:37
created

ReportWriter::getReportConfiguration()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3
c 1
b 0
f 0
dl 0
loc 5
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Setono\SyliusStockMovementPlugin\Writer;
6
7
use InvalidArgumentException;
8
use League\Flysystem\FilesystemInterface;
9
use RuntimeException;
10
use Safe\Exceptions\FilesystemException;
11
use Safe\Exceptions\OutcontrolException;
12
use Safe\Exceptions\StringsException;
13
use function Safe\fclose;
14
use function Safe\fopen;
15
use function Safe\fwrite;
16
use function Safe\ob_end_clean;
17
use function Safe\sprintf;
18
use function Safe\unlink;
19
use Setono\SyliusStockMovementPlugin\Exception\BlockNotPresentException;
20
use Setono\SyliusStockMovementPlugin\Model\ReportConfigurationInterface;
21
use Setono\SyliusStockMovementPlugin\Model\ReportInterface;
22
use Setono\SyliusStockMovementPlugin\Resolver\ReportPathResolverInterface;
23
use Throwable;
24
use Twig\Environment;
25
use Twig\Error\LoaderError;
26
use Twig\Error\RuntimeError;
27
use Twig\Error\SyntaxError;
28
use Twig\TemplateWrapper;
29
30
class ReportWriter implements ReportWriterInterface
31
{
32
    /** @var Environment */
33
    private $twig;
34
35
    /** @var FilesystemInterface */
36
    private $filesystem;
37
38
    /** @var ReportPathResolverInterface */
39
    private $reportPathResolver;
40
41
    public function __construct(Environment $twig, FilesystemInterface $filesystem, ReportPathResolverInterface $reportPathResolver)
42
    {
43
        $this->filesystem = $filesystem;
44
        $this->twig = $twig;
45
        $this->reportPathResolver = $reportPathResolver;
46
    }
47
48
    /**
49
     * @throws FilesystemException
50
     * @throws OutcontrolException
51
     * @throws StringsException
52
     * @throws Throwable
53
     * @throws LoaderError
54
     * @throws RuntimeError
55
     * @throws SyntaxError
56
     */
57
    public function write(ReportInterface $report): string
58
    {
59
        $key = $this->reportPathResolver->resolve($report);
60
        if ($this->filesystem->has($key)) {
61
            return $key;
62
        }
63
64
        $reportConfiguration = $this->getReportConfiguration($report);
65
66
        $template = $this->twig->load($reportConfiguration->getTemplate());
67
68
        $this->validateTemplate($template, (string) $reportConfiguration->getTemplate());
69
70
        $path = $this->generateTempPath();
71
72
        $fp = fopen($path, 'w+b'); // needs to be w+ since we use the same stream later to read from
73
74
        ob_start(static function ($buffer) use ($fp) {
75
            fwrite($fp, $buffer);
76
        }, 1024);
77
78
        $template->displayBlock('body', [
79
            'stockMovements' => $report->getStockMovements(),
80
        ]);
81
82
        ob_end_clean();
83
84
        $res = $this->filesystem->writeStream($key, $fp);
85
86
        try {
87
            // tries to close the file pointer although it may already have been closed by flysystem
88
            fclose($fp);
89
90
            unlink($path);
91
        } catch (FilesystemException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
92
        }
93
94
        if (false === $res) {
95
            throw new RuntimeException(sprintf('An error occurred when trying to write the report %s', $key));
96
        }
97
98
        return $key;
99
    }
100
101
    /**
102
     * @throws StringsException
103
     */
104
    private function getReportConfiguration(ReportInterface $report): ReportConfigurationInterface
105
    {
106
        $reportConfiguration = $report->getReportConfiguration();
107
        if (null === $reportConfiguration) {
108
            throw new InvalidArgumentException(sprintf('No report configuration associated with report %s', $report->getId()));
109
        }
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 107 is false. This is incompatible with the type-hinted return Setono\SyliusStockMoveme...tConfigurationInterface. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
110
    }
111
112
    /**
113
     * @throws StringsException
114
     */
115
    private function validateTemplate(TemplateWrapper $template, string $templateName): void
116
    {
117
        $definedBlocks = $template->getBlockNames();
118
        foreach (['extension', 'body'] as $block) {
119
            if (!in_array($block, $definedBlocks, true)) {
120
                throw new BlockNotPresentException($block, $definedBlocks, $templateName);
121
            }
122
        }
123
    }
124
125
    private function generateTempPath(): string
126
    {
127
        do {
128
            $path = sys_get_temp_dir() . '/' . uniqid('stock-movement-report-', true);
129
        } while (file_exists($path));
130
131
        return $path;
132
    }
133
}
134