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

SaveResourceMiddlewareAbstract::__invoke()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 34
Code Lines 24

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 34
ccs 0
cts 22
cp 0
rs 8.439
cc 6
eloc 24
nc 5
nop 3
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();
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());
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)
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();
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);
192
193
    protected function backup(ResourceDOInterface $resourceDO)
194
    {
195
        $command = new BackupResourceCommand($resourceDO, $this->filesystem);
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);
209
        $responseDO = $command();
210
211
        return $responseDO;
212
    }
213
}
214