Completed
Push — master ( a06a95...a1d542 )
by dan
15s
created

Uploader.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace IrishDan\ResponsiveImageBundle;
4
5
use Symfony\Component\HttpFoundation\File\Exception\FileException;
6
use Symfony\Component\HttpFoundation\File\UploadedFile;
7
8
/**
9
 * Class Uploader
10
 *
11
 * @package ResponsiveImageBundle
12
 */
13
class Uploader
14
{
15
    /**
16
     * @var array
17
     */
18
    public $allowedTypes = [
19
        'jpg',
20
        'jpeg',
21
        'png',
22
    ];
23
    /**
24
     * @var FileManager
25
     */
26
    private $fileSystem;
27
    /**
28
     * @var
29
     */
30
    private $file;
31
    /**
32
     * @var
33
     */
34
    private $uploadOk = false;
35
36
    /**
37
     * Uploader constructor.
38
     *
39
     * @param FileManager $system
40
     */
41
    public function __construct(FileManager $system)
42
    {
43
        $this->fileSystem = $system;
44
    }
45
46
    /**
47
     * Sets file.
48
     *
49
     * @param UploadedFile $file
50
     */
51
    public function setFile(UploadedFile $file = null)
52
    {
53
        $this->file = $file;
54
    }
55
56
    /**
57
     * Get file.
58
     *
59
     * @return UploadedFile
60
     */
61
    public function getFile()
62
    {
63
        return $this->file;
64
    }
65
66
    /**
67
     * Sanitizes and cleans up filename
68
     *
69
     * @param $str
70
     * @return mixed
71
     */
72
    private function createFilename($str)
73
    {
74
        // Sanitize and transliterate
75
        $str = strtolower($str);
76
        $str = strip_tags($str);
77
        $safeName = preg_replace('/[^a-z0-9-_\.]/', '', $str);
78
79
        // Create unique filename.
80
        $i = 1;
81
        while (!$this->isUniqueFilename($safeName)) {
82
            $nameArray = explode('.', $safeName);
83
            $safeName = $nameArray[0] . '-' . $i . '.' . $nameArray[1];
84
            $i++;
85
        }
86
87
        return $safeName;
88
    }
89
90
    /**
91
     * Convert MB/K/G to bytesize
92
     *
93
     * @param $uploadMaxSize
94
     * @return int
95
     */
96
    public function mToBytes($uploadMaxSize)
97
    {
98
        $uploadMaxSize = trim($uploadMaxSize);
99
        $last = strtolower($uploadMaxSize[strlen($uploadMaxSize) - 1]);
100
101
        switch ($last) {
102
            case 'g':
103
                $uploadMaxSize *= 1024 * 1000 * 1000;
104
                break;
105
106
            case 'm':
107
                $uploadMaxSize *= 1024 * 1000;
108
                break;
109
110
            case 'k':
111
                $uploadMaxSize *= 1024;
112
                break;
113
        }
114
115
        return $uploadMaxSize;
116
    }
117
118
    /**
119
     * Checks to see if a file name is unique in the storage directory.
120
     *
121
     * @param $name
122
     * @return bool
123
     */
124
    private function isUniqueFilename($name)
125
    {
126
        $storageDirectory = $this->fileSystem->getStorageDirectory();
127
        $filePath = $storageDirectory . $name;
128
        if ($this->fileSystem->directoryExists($filePath)) {
129
            return false;
130
        } else {
131
            return true;
132
        }
133
    }
134
135
    /**
136
     * Tests if mime type is allowed.
137
     *
138
     * @return bool
139
     */
140
    public function isAllowedType()
141
    {
142
        $extension = $this->getFile()->guessExtension();
143
        if (in_array($extension, $this->allowedTypes)) {
144
            return true;
145
        } else {
146
            return false;
147
        }
148
    }
149
150
    /**
151
     * After uploading, this function checks if the image is valid and if so moves it to an appropriate storage
152
     * location.
153
     *
154
     * @param ResponsiveImageInterface $image .
155
     * @return ResponsiveImageInterface
156
     */
157
    public function upload(ResponsiveImageInterface $image)
158
    {
159
        // The file property can be empty if the field is not required.
160
        if (null === $image->getFile()) {
161
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by IrishDan\ResponsiveImageBundle\Uploader::upload of type IrishDan\ResponsiveImage...esponsiveImageInterface.

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...
162
        }
163
164
        $this->setFile($image->getFile());
165
        $messages = [];
166
        $this->uploadOk = true;
167
168
        // Get max file upload in bytes.
169
        $uploadMaxSize = ini_get('upload_max_filesize');
170
        $uploadMaxSize = $this->mToBytes($uploadMaxSize);
171
172
        if (!$this->file instanceof UploadedFile && !empty($image->getFile()->getError())) {
173
            $messages[] = 'Uploaded file should be an instance of \'UploadedFile\'';
174
            $this->uploadOk = false;
175
        } elseif ($this->file->getSize() > $uploadMaxSize) {
176
            $messages[] = sprintf('%s: File size cannot be larger than %s', $this->file->getSize(), $uploadMaxSize);
177
            $this->uploadOk = false;
178
        } elseif (!$this->isAllowedType()) {
179
            $messages[] = 'File type is not allowed';
180
            $this->uploadOk = false;
181
        } else {
182
            // Sanitize it at least to avoid any security issues.
183
            $fileName = $this->file->getClientOriginalName();
184
            $newFileName = $this->createFilename($fileName);
185
186
            // Move takes the target directory and then the target filename to move to.
187
            $storageDirectory = $this->fileSystem->getStorageDirectory('original');
188
            $this->file->move(
189
                $storageDirectory,
190
                $newFileName
191
            );
192
193
            // Set the path property to the filename where you've saved the file.
194
            $image->setPath($newFileName);
195
196
            // Set the image dimensions.
197
            $imageData = getimagesize($storageDirectory . $newFileName);
198
            $image->setWidth($imageData[0]);
199
            $image->setHeight($imageData[1]);
200
201
            // Clean up the file property as you won't need it anymore.
202
            $this->file = null;
203
            $image->setFile(null);
204
        }
205
206
        if ($this->uploadOk) {
207
            return $image;
208
        } else {
209
            throw new FileException($messages[0]);
210
        }
211
    }
212
}
213