Passed
Branch master (267be1)
by Eugene
03:10
created

SaveResourceMiddlewareAbstract::save()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 31
ccs 0
cts 19
cp 0
rs 8.439
cc 6
eloc 17
nc 16
nop 2
crap 42
1
<?php
2
namespace Staticus\Resources\Middlewares;
3
4
use League\Flysystem\FilesystemInterface;
5
use Psr\Http\Message\UploadedFileInterface;
6
use Staticus\Exceptions\WrongRequestException;
7
use Staticus\Diactoros\Response\FileUploadedResponse;
8
use Staticus\Resources\Commands\BackupResourceCommand;
9
use Staticus\Resources\Commands\CopyResourceCommand;
10
use Staticus\Resources\Commands\DestroyEqualResourceCommand;
11
use Staticus\Resources\File\ResourceDO;
12
use Staticus\Middlewares\MiddlewareAbstract;
13
use Staticus\Diactoros\Response\FileContentResponse;
14
use Staticus\Resources\Exceptions\SaveResourceErrorException;
15
use Staticus\Exceptions\WrongResponseException;
16
use Staticus\Resources\ResourceDOInterface;
17
use Zend\Diactoros\Response\EmptyResponse;
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
use Zend\Diactoros\Stream;
21
22
abstract class SaveResourceMiddlewareAbstract extends MiddlewareAbstract
23
{
24
    protected $resourceDO;
25
26
    /**
27
     * Another type for nice IDE autocomplete in child classes
28
     * @var FileContentResponse
29
     */
30
    protected $response;
31
    /**
32
     * @var FilesystemInterface
33
     */
34
    protected $filesystem;
35
36
    public function __construct(ResourceDOInterface $resourceDO, FilesystemInterface $filesystem)
37
    {
38
        $this->resourceDO = $resourceDO;
39
        $this->filesystem = $filesystem;
40
    }
41
42
    /**
43
     * @param ServerRequestInterface $request
44
     * @param ResponseInterface $response
45
     * @param callable|null $next
46
     * @return EmptyResponse
47
     * @throws \Exception
48
     */
49
    public function __invoke(
50
        ServerRequestInterface $request,
51
        ResponseInterface $response,
52
        callable $next = null
53
    )
54
    {
55
        parent::__invoke($request, $response, $next);
56
        if (
57
            $response instanceof FileContentResponse
58
            || $response instanceof FileUploadedResponse
59
        ) {
60
            $resourceDO = $this->resourceDO;
61
            $filePath = $resourceDO->getFilePath();
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
62
            if (empty($filePath)) {
63
                throw new WrongResponseException('Empty file path. File can\'t be saved.');
64
            }
65
            $resourceStream = $response->getResource();
66
            if (is_resource($resourceStream)) {
67
                $this->save($resourceDO, $resourceStream);
68
            } else {
69
                $body = $response->getContent();
70
                if (!$body) {
71
                    throw new WrongResponseException('Empty body for generated file. Request: ' . $resourceDO->getName());
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
72
                }
73
                $this->save($resourceDO, $body);
74
            }
75
            $this->copyFileToDefaults($resourceDO);
76
            $this->response = new EmptyResponse($response->getStatusCode(), [
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Zend\Diactoros\Resp...urceDO->getMimeType())) of type object<Zend\Diactoros\Response\EmptyResponse> is incompatible with the declared type object<Staticus\Diactoro...se\FileContentResponse> of property $response.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
77
                'Content-Type' => $this->resourceDO->getMimeType(),
78
            ]);
79
        }
80
81
        return $this->next();
82
    }
83
84
    /**
85
     * @param $filePath
86
     * @param $content
87
     */
88
    protected function writeFile($filePath, $content)
89
    {
90
        if (is_resource($content)) {
91
            $result = $this->filesystem->putStream($filePath, $content);
92
        } else {
93
            $result = $this->filesystem->put($filePath, $content);
94
        }
95
        if (!$result) {
96
            throw new SaveResourceErrorException('File cannot be written to the path ' . $filePath);
97
        }
98
    }
99
100
    protected function uploadFile(UploadedFileInterface $content, $mime, $filePath)
101
    {
102
        $uri = $content->getStream()->getMetadata('uri');
103
        if (!$uri) {
104
            throw new SaveResourceErrorException('Unknown error: can\'t get uploaded file uri');
105
        }
106
        $uploadedMime = $this->filesystem->getMimetype($uri);
107
        if ($mime !== $uploadedMime) {
108
            /**
109
             * Try to remove unnecessary file because UploadFile object can be emulated
110
             * @see \Staticus\Middlewares\ActionPostAbstract::download
111
             */
112
            $this->filesystem->delete($uri);
113
            throw new WrongRequestException('Bad request: incorrect mime-type of the uploaded file');
114
        }
115
        $content->moveTo($filePath);
116
    }
117
118
    protected function copyResource(ResourceDOInterface $originResourceDO, ResourceDOInterface $newResourceDO)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
119
    {
120
        $command = new CopyResourceCommand($originResourceDO, $newResourceDO, $this->filesystem);
121
122
        return $command();
123
    }
124
125
    /**
126
     * @param $directory
127
     * @throws SaveResourceErrorException
128
     * @see \Staticus\Resources\Middlewares\Image\ImagePostProcessingMiddlewareAbstract::createDirectory
129
     */
130
    protected function createDirectory($directory)
131
    {
132
        if (!$this->filesystem->createDir($directory)) {
133
            throw new SaveResourceErrorException('Can\'t create a directory: ' . $directory);
134
        }
135
    }
136
137
    protected function copyFileToDefaults(ResourceDOInterface $resourceDO)
138
    {
139 View Code Duplication
        if (ResourceDO::DEFAULT_VARIANT !== $resourceDO->getVariant()) {
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...
140
            $defaultDO = clone $resourceDO;
141
            $defaultDO->setVariant();
142
            $defaultDO->setVersion();
143
            $this->copyResource($resourceDO, $defaultDO);
144
        }
145
        if (ResourceDO::DEFAULT_VERSION !== $resourceDO->getVersion()) {
146
            $defaultDO = clone $resourceDO;
147
            $defaultDO->setVersion();
148
            $this->copyResource($resourceDO, $defaultDO);
149
        }
150
    }
151
152
    /**
153
     * @param ResourceDOInterface $resourceDO
154
     * @param string|resource|Stream $content
155
     * @return ResourceDOInterface
156
     * @throws \RuntimeException if the upload was not successful.
157
     * @throws \InvalidArgumentException if the $path specified is invalid.
158
     * @throws \RuntimeException on any error during the move operation, or on
159
     */
160
    protected function save(ResourceDOInterface $resourceDO, $content)
161
    {
162
        $backupResourceVerDO = null;
163
        $filePath = $resourceDO->getFilePath();
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 12 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
164
        $this->createDirectory(dirname($filePath));
165
        // backups don't needs if this is a 'new creation' command
166
        if ($resourceDO->isRecreate()) {
167
            $backupResourceVerDO = $this->backup($resourceDO);
168
        }
169
        if ($content instanceof UploadedFileInterface) {
170
            $this->uploadFile($content, $resourceDO->getMimeType(), $filePath);
171
        } else {
172
            $this->writeFile($filePath, $content);
173
        }
174
175
        $responseDO = $resourceDO;
176
        if ($backupResourceVerDO instanceof ResourceDOInterface
177
            && $backupResourceVerDO->getVersion() !== ResourceDOInterface::DEFAULT_VERSION) {
178
            // If the newly created file is the same as the previous version, remove backup immediately
179
            $responseDO = $this->destroyEqual($resourceDO, $backupResourceVerDO);
180
        }
181
        if ($responseDO === $resourceDO) {
182
183
            // cleanup postprocessing cache folders
184
            // - if it is a new file creation (remove possible garbage after other operations)
185
            // - or if the basic file is replaced and not equal to the previous version
186
            $this->afterSave($resourceDO);
187
        }
188
189
        return $resourceDO;
190
    }
191
    abstract protected function afterSave(ResourceDOInterface $resourceDO);
0 ignored issues
show
Documentation introduced by
For interfaces and abstract methods it is generally a good practice to add a @return annotation even if it is just @return void or @return null, so that implementors know what to do in the overridden method.

For interface and abstract methods, it is impossible to infer the return type from the immediate code. In these cases, it is generally advisible to explicitly annotate these methods with a @return doc comment to communicate to implementors of these methods what they are expected to return.

Loading history...
192
193
    protected function backup(ResourceDOInterface $resourceDO)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
194
    {
195
        $command = new BackupResourceCommand($resourceDO, $this->filesystem);
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 13 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
196
        $backupResourceVerDO = $command();
197
198
        return $backupResourceVerDO;
199
    }
200
201
    /**
202
     * @param ResourceDOInterface $resourceDO
203
     * @param ResourceDOInterface $backupResourceVerDO
204
     * @return mixed
205
     */
206
    protected function destroyEqual(ResourceDOInterface $resourceDO, ResourceDOInterface $backupResourceVerDO)
207
    {
208
        $command = new DestroyEqualResourceCommand($resourceDO, $backupResourceVerDO, $this->filesystem);
1 ignored issue
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 4 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
209
        $responseDO = $command();
210
211
        return $responseDO;
212
    }
213
}
214