Completed
Push — master ( af52bc...6c4269 )
by André
25:16
created

IOService   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 309
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 309
rs 8.3206
wmc 51
lcom 1
cbo 12

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A setPrefix() 0 4 1
B newBinaryCreateStructFromUploadedFile() 0 22 6
B newBinaryCreateStructFromLocalFile() 0 25 6
D createBinaryFile() 0 35 9
A deleteBinaryFile() 0 14 2
A loadBinaryFile() 0 16 3
A loadBinaryFileByUri() 0 8 1
A getFileInputStream() 0 8 1
A getFileContents() 0 8 1
A getInternalPath() 0 4 1
A getExternalPath() 0 4 1
A getUri() 0 4 1
A getMimeType() 0 4 1
A exists() 0 4 1
A buildSPIBinaryFileCreateStructObject() 0 12 1
A buildDomainBinaryFileObject() 0 12 2
A getPrefixedUri() 0 9 2
A removeUriPrefix() 0 12 3
A checkBinaryFileId() 0 6 3
A deleteDirectory() 0 6 1
A isAbsolutePath() 0 4 3

How to fix   Complexity   

Complex Class

Complex classes like IOService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use IOService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of the eZ Publish Kernel package.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\IO;
10
11
use Exception;
12
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException;
13
use eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue;
14
use eZ\Publish\Core\IO\Exception\BinaryFileNotFoundException;
15
use eZ\Publish\Core\IO\Exception\InvalidBinaryFileIdException;
16
use eZ\Publish\Core\IO\Exception\IOException;
17
use eZ\Publish\Core\IO\Values\BinaryFile;
18
use eZ\Publish\Core\IO\Values\BinaryFileCreateStruct;
19
use eZ\Publish\SPI\IO\BinaryFile as SPIBinaryFile;
20
use eZ\Publish\SPI\IO\BinaryFileCreateStruct as SPIBinaryFileCreateStruct;
21
use eZ\Publish\SPI\IO\MimeTypeDetector;
22
23
/**
24
 * The io service for managing binary files.
25
 */
26
class IOService implements IOServiceInterface
27
{
28
    /** @var \eZ\Publish\Core\IO\IOBinarydataHandler */
29
    protected $binarydataHandler;
30
31
    /** @var \eZ\Publish\Core\IO\IOMetadataHandler */
32
    protected $metadataHandler;
33
34
    /** @var \eZ\Publish\SPI\IO\MimeTypeDetector */
35
    protected $mimeTypeDetector;
36
37
    public function __construct(
38
        IOMetadataHandler $metadataHandler,
39
        IOBinarydataHandler $binarydataHandler,
40
        MimeTypeDetector $mimeTypeDetector,
41
        array $settings = array()
42
    ) {
43
        $this->metadataHandler = $metadataHandler;
44
        $this->binarydataHandler = $binarydataHandler;
45
        $this->mimeTypeDetector = $mimeTypeDetector;
46
        $this->settings = $settings;
0 ignored issues
show
Bug introduced by
The property settings does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
47
    }
48
49
    public function setPrefix($prefix)
50
    {
51
        $this->settings['prefix'] = $prefix;
52
    }
53
54
    public function newBinaryCreateStructFromUploadedFile(array $uploadedFile)
55
    {
56
        if (!is_string($uploadedFile['tmp_name']) || empty($uploadedFile['tmp_name'])) {
57
            throw new InvalidArgumentException('uploadedFile', "uploadedFile['tmp_name'] does not exist or has invalid value");
58
        }
59
60
        if (!is_uploaded_file($uploadedFile['tmp_name']) || !is_readable($uploadedFile['tmp_name'])) {
61
            throw new InvalidArgumentException('uploadedFile', 'file was not uploaded or is unreadable');
62
        }
63
64
        $fileHandle = fopen($uploadedFile['tmp_name'], 'rb');
65
        if ($fileHandle === false) {
66
            throw new InvalidArgumentException('uploadedFile', 'failed to get file resource');
67
        }
68
69
        $binaryCreateStruct = new BinaryFileCreateStruct();
70
        $binaryCreateStruct->size = $uploadedFile['size'];
71
        $binaryCreateStruct->inputStream = $fileHandle;
72
        $binaryCreateStruct->mimeType = $uploadedFile['type'];
73
74
        return $binaryCreateStruct;
75
    }
76
77
    public function newBinaryCreateStructFromLocalFile($localFile)
78
    {
79
        if (empty($localFile) || !is_string($localFile)) {
80
            throw new InvalidArgumentException('localFile', 'localFile has an invalid value');
81
        }
82
83
        if (!is_file($localFile) || !is_readable($localFile)) {
84
            throw new InvalidArgumentException('localFile', "file does not exist or is unreadable: {$localFile}");
85
        }
86
87
        $fileHandle = fopen($localFile, 'rb');
88
        if ($fileHandle === false) {
89
            throw new InvalidArgumentException('localFile', 'failed to get file resource');
90
        }
91
92
        $binaryCreateStruct = new BinaryFileCreateStruct(
93
            array(
94
                'size' => filesize($localFile),
95
                'inputStream' => $fileHandle,
96
                'mimeType' => $this->mimeTypeDetector->getFromPath($localFile),
97
            )
98
        );
99
100
        return $binaryCreateStruct;
101
    }
102
103
    public function createBinaryFile(BinaryFileCreateStruct $binaryFileCreateStruct)
104
    {
105
        if (empty($binaryFileCreateStruct->id) || !is_string($binaryFileCreateStruct->id)) {
106
            throw new InvalidArgumentValue('id', $binaryFileCreateStruct->id, 'BinaryFileCreateStruct');
107
        }
108
109
        if (!is_int($binaryFileCreateStruct->size) || $binaryFileCreateStruct->size < 0) {
110
            throw new InvalidArgumentValue('size', $binaryFileCreateStruct->size, 'BinaryFileCreateStruct');
111
        }
112
113
        if (!is_resource($binaryFileCreateStruct->inputStream)) {
114
            throw new InvalidArgumentValue('inputStream', 'property is not a file resource', 'BinaryFileCreateStruct');
115
        }
116
117
        if (!isset($binaryFileCreateStruct->mimeType)) {
118
            $buffer = fread($binaryFileCreateStruct->inputStream, $binaryFileCreateStruct->size);
119
            $binaryFileCreateStruct->mimeType = $this->mimeTypeDetector->getFromBuffer($buffer);
120
            unset($buffer);
121
        }
122
123
        $spiBinaryCreateStruct = $this->buildSPIBinaryFileCreateStructObject($binaryFileCreateStruct);
124
125
        try {
126
            $this->binarydataHandler->create($spiBinaryCreateStruct);
127
        } catch (Exception $e) {
128
            throw new IOException('An error occured creating binarydata', $e);
129
        }
130
131
        $spiBinaryFile = $this->metadataHandler->create($spiBinaryCreateStruct);
132
        if (!isset($spiBinaryFile->uri)) {
133
            $spiBinaryFile->uri = $this->binarydataHandler->getUri($spiBinaryFile->id);
134
        }
135
136
        return $this->buildDomainBinaryFileObject($spiBinaryFile);
137
    }
138
139
    public function deleteBinaryFile(BinaryFile $binaryFile)
140
    {
141
        $this->checkBinaryFileId($binaryFile->id);
142
        $spiUri = $this->getPrefixedUri($binaryFile->id);
143
144
        try {
145
            $this->metadataHandler->delete($spiUri);
146
        } catch (BinaryFileNotFoundException $e) {
147
            $this->binarydataHandler->delete($spiUri);
148
            throw $e;
149
        }
150
151
        $this->binarydataHandler->delete($spiUri);
152
    }
153
154
    public function loadBinaryFile($binaryFileId)
155
    {
156
        $this->checkBinaryFileId($binaryFileId);
157
158
        // @todo An absolute path can in no case be loaded, but throwing an exception is too much (why ?)
159
        if ($this->isAbsolutePath($binaryFileId)) {
160
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface eZ\Publish\Core\IO\IOSer...terface::loadBinaryFile of type eZ\Publish\Core\IO\Values\BinaryFile.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
161
        }
162
163
        $spiBinaryFile = $this->metadataHandler->load($this->getPrefixedUri($binaryFileId));
164
        if (!isset($spiBinaryFile->uri)) {
165
            $spiBinaryFile->uri = $this->binarydataHandler->getUri($spiBinaryFile->id);
166
        }
167
168
        return $this->buildDomainBinaryFileObject($spiBinaryFile);
169
    }
170
171
    public function loadBinaryFileByUri($binaryFileUri)
172
    {
173
        return $this->loadBinaryFile(
174
            $this->removeUriPrefix(
175
                $this->binarydataHandler->getIdFromUri($binaryFileUri)
176
            )
177
        );
178
    }
179
180
    public function getFileInputStream(BinaryFile $binaryFile)
181
    {
182
        $this->checkBinaryFileId($binaryFile->id);
183
184
        return $this->binarydataHandler->getResource(
185
            $this->getPrefixedUri($binaryFile->id)
186
        );
187
    }
188
189
    public function getFileContents(BinaryFile $binaryFile)
190
    {
191
        $this->checkBinaryFileId($binaryFile->id);
192
193
        return $this->binarydataHandler->getContents(
194
            $this->getPrefixedUri($binaryFile->id)
195
        );
196
    }
197
198
    public function getInternalPath($binaryFileId)
199
    {
200
        return $this->binarydataHandler->getUri($this->getPrefixedUri($binaryFileId));
201
    }
202
203
    public function getExternalPath($internalId)
204
    {
205
        return $this->loadBinaryFileByUri($internalId)->id;
206
    }
207
208
    public function getUri($binaryFileId)
209
    {
210
        return $this->binarydataHandler->getUri($binaryFileId);
211
    }
212
213
    public function getMimeType($binaryFileId)
214
    {
215
        return $this->metadataHandler->getMimeType($this->getPrefixedUri($binaryFileId));
216
    }
217
218
    public function exists($binaryFileId)
219
    {
220
        return $this->metadataHandler->exists($this->getPrefixedUri($binaryFileId));
221
    }
222
223
    /**
224
     * Generates SPI BinaryFileCreateStruct object from provided API BinaryFileCreateStruct object.
225
     *
226
     * @param \eZ\Publish\Core\IO\Values\BinaryFileCreateStruct $binaryFileCreateStruct
227
     *
228
     * @return \eZ\Publish\SPI\IO\BinaryFileCreateStruct
229
     */
230
    protected function buildSPIBinaryFileCreateStructObject(BinaryFileCreateStruct $binaryFileCreateStruct)
231
    {
232
        $spiBinaryCreateStruct = new SPIBinaryFileCreateStruct();
233
234
        $spiBinaryCreateStruct->id = $this->getPrefixedUri($binaryFileCreateStruct->id);
235
        $spiBinaryCreateStruct->size = $binaryFileCreateStruct->size;
236
        $spiBinaryCreateStruct->setInputStream($binaryFileCreateStruct->inputStream);
237
        $spiBinaryCreateStruct->mimeType = $binaryFileCreateStruct->mimeType;
238
        $spiBinaryCreateStruct->mtime = time();
0 ignored issues
show
Documentation Bug introduced by
It seems like time() of type integer is incompatible with the declared type object<DateTime> of property $mtime.

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...
239
240
        return $spiBinaryCreateStruct;
241
    }
242
243
    /**
244
     * Generates API BinaryFile object from provided SPI BinaryFile object.
245
     *
246
     * @param \eZ\Publish\SPI\IO\BinaryFile $spiBinaryFile
247
     *
248
     * @return \eZ\Publish\Core\IO\Values\BinaryFile
249
     */
250
    protected function buildDomainBinaryFileObject(SPIBinaryFile $spiBinaryFile)
251
    {
252
        return new BinaryFile(
253
            array(
254
                'size' => (int)$spiBinaryFile->size,
255
                'mtime' => $spiBinaryFile->mtime,
256
                'id' => $this->removeUriPrefix($spiBinaryFile->id),
257
                'uri' => $spiBinaryFile->uri,
258
                'mimeType' => $spiBinaryFile->mimeType ?: $this->metadataHandler->getMimeType($spiBinaryFile->id),
0 ignored issues
show
Deprecated Code introduced by
The property eZ\Publish\SPI\IO\BinaryFile::$mimeType has been deprecated with message: Since 5.3.3, use IO\Handler::getMimeType()

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
259
            )
260
        );
261
    }
262
263
    /**
264
     * Returns $uri prefixed with what is configured in the service.
265
     *
266
     * @param string $binaryFileId
267
     *
268
     * @return string
269
     */
270
    protected function getPrefixedUri($binaryFileId)
271
    {
272
        $prefix = '';
273
        if (isset($this->settings['prefix'])) {
274
            $prefix = $this->settings['prefix'] . '/';
275
        }
276
277
        return $prefix . $binaryFileId;
278
    }
279
280
    /**
281
     * @param mixed $spiBinaryFileId
282
     *
283
     * @return string
284
     *
285
     * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException
286
     */
287
    protected function removeUriPrefix($spiBinaryFileId)
288
    {
289
        if (!isset($this->settings['prefix'])) {
290
            return $spiBinaryFileId;
291
        }
292
293
        if (strpos($spiBinaryFileId, $this->settings['prefix'] . '/') !== 0) {
294
            throw new InvalidArgumentException('$id', "Prefix {$this->settings['prefix']} not found in {$spiBinaryFileId}");
295
        }
296
297
        return substr($spiBinaryFileId, strlen($this->settings['prefix']) + 1);
298
    }
299
300
    /**
301
     * @param string $binaryFileId
302
     *
303
     * @throws InvalidBinaryFileIdException If the id is invalid
304
     */
305
    protected function checkBinaryFileId($binaryFileId)
306
    {
307
        if (empty($binaryFileId) || !is_string($binaryFileId)) {
308
            throw new InvalidBinaryFileIdException($binaryFileId);
309
        }
310
    }
311
312
    /**
313
     * Deletes a directory.
314
     *
315
     * @param string $path
316
     */
317
    public function deleteDirectory($path)
318
    {
319
        $prefixedUri = $this->getPrefixedUri($path);
320
        $this->metadataHandler->deleteDirectory($prefixedUri);
321
        $this->binarydataHandler->deleteDirectory($prefixedUri);
322
    }
323
324
    /**
325
     * Check if path is absolute, in terms of http or disk (incl if it contains driver letter on Win).
326
     *
327
     * @param string $path
328
     * @return bool
329
     */
330
    protected function isAbsolutePath($path)
331
    {
332
        return $path[0] === '/' || (PHP_OS === 'WINNT' && $path[1] === ':');
333
    }
334
}
335