• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nepada / file-upload-control / 4300204206

pending completion
4300204206

push

github

GitHub
Update shipmonk/phpstan-rules requirement from 2.2.0 to 2.3.1

641 of 710 relevant lines covered (90.28%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.74
/src/FileUploadControl/Storage/FileSystemStorage.php
1
<?php
2
declare(strict_types = 1);
3

4
namespace Nepada\FileUploadControl\Storage;
5

6
use Nepada\FileUploadControl\Storage\Metadata\FileUploadMetadata;
7
use Nepada\FileUploadControl\Storage\Metadata\FileUploadMetadataAlreadyExistsException;
8
use Nepada\FileUploadControl\Storage\Metadata\FileUploadMetadataNotFoundException;
9
use Nepada\FileUploadControl\Storage\Metadata\MetadataJournal;
10
use Nepada\FileUploadControl\Utils\FileSystem;
11
use Nette;
12
use Nette\Http\FileUpload;
13

14
final class FileSystemStorage implements Storage
15
{
16

17
    use Nette\SmartObject;
18

19
    private MetadataJournal $metadataJournal;
20

21
    private FileSystem $fileSystem;
22

23
    private string $directory;
24

25
    public function __construct(MetadataJournal $metadataJournal, FileSystem $fileSystem, string $directory)
1✔
26
    {
27
        $this->metadataJournal = $metadataJournal;
1✔
28
        $this->fileSystem = $fileSystem;
1✔
29
        if (! $this->fileSystem->directoryExists($directory)) {
1✔
30
            throw new \InvalidArgumentException("Directory '$directory' does not exist.");
×
31
        }
32
        if (! $this->fileSystem->isWritable($directory)) {
1✔
33
            throw new \InvalidArgumentException("Directory '$directory' is not writable.");
×
34
        }
35
        $this->directory = $directory;
1✔
36
    }
1✔
37

38
    /**
39
     * @return FileUploadItem[]
40
     */
41
    public function list(): array
42
    {
43
        $items = [];
1✔
44
        foreach ($this->metadataJournal->list() as $id) {
1✔
45
            try {
46
                $fileUploadItem = $this->load($id);
1✔
47
            } catch (FileUploadNotFoundException $exception) {
1✔
48
                continue;
1✔
49
            }
50
            $items[] = $fileUploadItem;
1✔
51
        }
52
        return $items;
1✔
53
    }
54

55
    /**
56
     * @param FileUploadId $id
57
     * @return FileUploadItem
58
     * @throws FileUploadNotFoundException
59
     */
60
    public function load(FileUploadId $id): FileUploadItem
1✔
61
    {
62
        try {
63
            $metadata = $this->metadataJournal->load($id);
1✔
64
        } catch (FileUploadMetadataNotFoundException $exception) {
1✔
65
            throw new FileUploadNotFoundException($exception->getMessage(), 0, $exception);
1✔
66
        }
67

68
        $file = $this->getFilePath($id);
1✔
69
        if (! $this->fileSystem->fileExists($file)) {
1✔
70
            throw FileUploadNotFoundException::withId($id);
1✔
71
        }
72

73
        $error = $this->fileSystem->fileSize($file) === $metadata->getSize() ? UPLOAD_ERR_OK : UPLOAD_ERR_PARTIAL;
1✔
74

75
        $fileUpload = new FileUpload([
1✔
76
            'name' => $metadata->getName(),
1✔
77
            'size' => $metadata->getSize(),
1✔
78
            'tmp_name' => $file,
1✔
79
            'error' => $error,
1✔
80
        ]);
81
        return new FileUploadItem($id, $fileUpload);
1✔
82
    }
83

84
    public function delete(FileUploadId $id): void
1✔
85
    {
86
        $this->metadataJournal->delete($id);
1✔
87
        $this->fileSystem->delete($this->getFilePath($id));
1✔
88
    }
1✔
89

90
    /**
91
     * @param FileUploadChunk $fileUploadChunk
92
     * @return FileUploadItem
93
     * @throws UnableToSaveFileUploadException
94
     */
95
    public function save(FileUploadChunk $fileUploadChunk): FileUploadItem
1✔
96
    {
97
        if (! $fileUploadChunk->getFileUpload()->isOk()) {
1✔
98
            throw UnableToSaveFileUploadException::withUploadError();
1✔
99
        }
100

101
        if ($fileUploadChunk->getContentRange()->containsFirstByte()) {
1✔
102
            $id = $this->saveNewUpload($fileUploadChunk);
1✔
103
        } else {
104
            $id = $this->resumeExistingUpload($fileUploadChunk);
1✔
105
        }
106

107
        try {
108
            return $this->load($id);
1✔
109
        } catch (FileUploadNotFoundException $exception) {
×
110
            throw new UnableToSaveFileUploadException($exception->getMessage(), 0, $exception);
×
111
        }
112
    }
113

114
    public function destroy(): void
115
    {
116
        $this->metadataJournal->destroy();
1✔
117
        $this->fileSystem->delete($this->directory);
1✔
118
    }
1✔
119

120
    private function getFilePath(FileUploadId $id): string
1✔
121
    {
122
        return $this->directory . DIRECTORY_SEPARATOR . $id->toString();
1✔
123
    }
124

125
    /**
126
     * @param FileUploadChunk $fileUploadChunk
127
     * @return FileUploadId
128
     * @throws UnableToSaveFileUploadException
129
     */
130
    private function saveNewUpload(FileUploadChunk $fileUploadChunk): FileUploadId
1✔
131
    {
132
        $metadata = FileUploadMetadata::fromFileUploadChunk($fileUploadChunk);
1✔
133
        $id = $metadata->createFileUploadId();
1✔
134
        try {
135
            $this->metadataJournal->save($id, $metadata);
1✔
136
        } catch (FileUploadMetadataAlreadyExistsException $exception) {
1✔
137
            throw UnableToSaveFileUploadException::withConflict($id);
1✔
138
        }
139

140
        $file = $this->getFilePath($id);
1✔
141
        $contents = $fileUploadChunk->getFileUpload()->getContents();
1✔
142
        assert(is_string($contents));
1✔
143
        $this->fileSystem->write($file, $contents);
1✔
144

145
        return $id;
1✔
146
    }
147

148
    /**
149
     * @param FileUploadChunk $fileUploadChunk
150
     * @return FileUploadId
151
     * @throws UnableToSaveFileUploadException
152
     */
153
    private function resumeExistingUpload(FileUploadChunk $fileUploadChunk): FileUploadId
1✔
154
    {
155
        $metadata = FileUploadMetadata::fromFileUploadChunk($fileUploadChunk);
1✔
156
        $id = $metadata->createFileUploadId();
1✔
157
        try {
158
            $storedMetadata = $this->metadataJournal->load($id);
1✔
159
            if (! $metadata->equals($storedMetadata)) {
1✔
160
                throw UnableToSaveFileUploadException::withConflict($id);
1✔
161
            }
162
        } catch (FileUploadMetadataNotFoundException $exception) {
1✔
163
            throw UnableToSaveFileUploadException::withFailedChunk($id, 'failed to load metadata');
1✔
164
        }
165

166
        $file = $this->getFilePath($id);
1✔
167
        if (! $this->fileSystem->fileExists($file)) {
1✔
168
            throw UnableToSaveFileUploadException::withFailedChunk($id, 'missing previously uploaded file part');
1✔
169
        }
170
        if ($this->fileSystem->fileSize($file) !== $fileUploadChunk->getContentRange()->getStart()) {
1✔
171
            throw UnableToSaveFileUploadException::withFailedChunk($id, 'previously uploaded file part size does not match given content-range value');
1✔
172
        }
173

174
        $contents = $fileUploadChunk->getFileUpload()->getContents();
1✔
175
        assert(is_string($contents));
1✔
176
        $this->fileSystem->append($file, $contents);
1✔
177

178
        return $id;
1✔
179
    }
180

181
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc