ICanBoogie/HTTP v2.4.0
  • Namespace
  • Class

Namespaces

  • ICanBoogie
    • Exception
    • HTTP
      • Dispatcher
      • Headers
      • Request

Classes

  • ICanBoogie\Exception\RescueEvent
  • ICanBoogie\HTTP\CallableDispatcher
  • ICanBoogie\HTTP\Dispatcher
  • ICanBoogie\HTTP\Dispatcher\BeforeDispatchEvent
  • ICanBoogie\HTTP\Dispatcher\DispatchEvent
  • ICanBoogie\HTTP\File
  • ICanBoogie\HTTP\FileInfo
  • ICanBoogie\HTTP\FileList
  • ICanBoogie\HTTP\Headers
  • ICanBoogie\HTTP\Headers\CacheControl
  • ICanBoogie\HTTP\Headers\ContentDisposition
  • ICanBoogie\HTTP\Headers\ContentType
  • ICanBoogie\HTTP\Headers\Date
  • ICanBoogie\HTTP\Headers\Header
  • ICanBoogie\HTTP\Headers\HeaderParameter
  • ICanBoogie\HTTP\Helpers
  • ICanBoogie\HTTP\RedirectResponse
  • ICanBoogie\HTTP\Request
  • ICanBoogie\HTTP\Request\Context
  • ICanBoogie\HTTP\Response
  • ICanBoogie\HTTP\Status
  • ICanBoogie\HTTP\WeightedDispatcher

Interfaces

  • ICanBoogie\HTTP\DispatcherInterface
  • ICanBoogie\HTTP\Exception

Exceptions

  • ICanBoogie\HTTP\DispatcherNotDefined
  • ICanBoogie\HTTP\ForceRedirect
  • ICanBoogie\HTTP\MethodNotSupported
  • ICanBoogie\HTTP\NotFound
  • ICanBoogie\HTTP\ServiceUnavailable
  • ICanBoogie\HTTP\StatusCodeNotValid
  1 <?php
  2 
  3 /*
  4  * This file is part of the ICanBoogie package.
  5  *
  6  * (c) Olivier Laviale <olivier.laviale@gmail.com>
  7  *
  8  * For the full copyright and license information, please view the LICENSE
  9  * file that was distributed with this source code.
 10  */
 11 
 12 namespace ICanBoogie\HTTP;
 13 
 14 use ICanBoogie\Accessor\AccessorTrait;
 15 use ICanBoogie\ToArray;
 16 
 17 /**
 18  * Representation of a POST file.
 19  *
 20  * @property-read string $name Name of the file.
 21  * @property-read string $type MIME type of the file.
 22  * @property-read string $size Size of the file.
 23  * @property-read string $error Error code, one of `UPLOAD_ERR_*`.
 24  * @property-read string $error_message A formatted message representing the error.
 25  * @property-read string $pathname Pathname of the file.
 26  * @property-read string $extension The extension of the file. If any, the dot is included e.g.
 27  * ".zip".
 28  * @property-read string $unsuffixed_name The name of the file without its extension.
 29  * @property-read bool $is_uploaded `true` if the file is uploaded, `false` otherwise.
 30  * @property-read bool $is_valid `true` if the file is valid, `false` otherwise.
 31  * See: {@link is_valid()}.
 32  */
 33 class File implements ToArray
 34 {
 35     use AccessorTrait;
 36 
 37     const MOVE_OVERWRITE = true;
 38     const MOVE_NO_OVERWRITE = false;
 39 
 40     /**
 41      * Creates a {@link File} instance.
 42      *
 43      * @param array|string $properties_or_name An array of properties or a file identifier.
 44      *
 45      * @return File
 46      */
 47     static public function from($properties_or_name)
 48     {
 49         $properties = [];
 50 
 51         if (is_string($properties_or_name))
 52         {
 53             $properties = isset($_FILES[$properties_or_name])
 54             ? $_FILES[$properties_or_name]
 55             : [ 'name' => basename($properties_or_name) ];
 56         }
 57         else if (is_array($properties_or_name))
 58         {
 59             $properties = $properties_or_name;
 60         }
 61 
 62         $properties = self::filter_initial_properties($properties);
 63 
 64         return new static($properties);
 65     }
 66 
 67     /**
 68      * Keeps only initial properties.
 69      *
 70      * @param array $properties
 71      *
 72      * @return array
 73      */
 74     static private function filter_initial_properties(array $properties)
 75     {
 76         static $initial_properties = [ 'name', 'type', 'size', 'tmp_name', 'error', 'pathname' ];
 77 
 78         return array_intersect_key($properties, array_combine($initial_properties, $initial_properties));
 79     }
 80 
 81     /**
 82      * Format a string.
 83      *
 84      * @param string $format The format of the string.
 85      * @param array $args The arguments.
 86      * @param array $options Some options.
 87      *
 88      * @return \ICanBoogie\FormattedString|\ICanBoogie\I18n\FormattedString|string
 89      */
 90     static private function format($format, array $args = [], array $options = [])
 91     {
 92         if (class_exists('ICanBoogie\I18n\FormattedString', true))
 93         {
 94             return new \ICanBoogie\I18n\FormattedString($format, $args, $options); // @codeCoverageIgnore
 95         }
 96 
 97         if (class_exists('ICanBoogie\FormattedString', true))
 98         {
 99             return new \ICanBoogie\FormattedString($format, $args, $options);
100         }
101 
102         return \ICanBoogie\format($format, $args); // @codeCoverageIgnore
103     }
104 
105     /*
106      * Instance
107      */
108 
109     /**
110      * Name of the file.
111      *
112      * @var string
113      */
114     protected $name;
115 
116     /**
117      * Returns the name of the file.
118      *
119      * @return string
120      */
121     protected function get_name()
122     {
123         return $this->name;
124     }
125 
126     /**
127      * Returns the name of the file, without its extension.
128      *
129      * @return string
130      */
131     protected function get_unsuffixed_name()
132     {
133         return $this->name ? basename($this->name, $this->extension) : null;
134     }
135 
136     protected $type;
137 
138     /**
139      * Returns the type of the file.
140      *
141      * If the {@link $type} property was not defined during construct, the type
142      * is guessed from the name or the pathname of the file.
143      *
144      * @return string|null The MIME type of the file, or `null` if it cannot be determined.
145      */
146     protected function get_type()
147     {
148         if (!empty($this->type))
149         {
150             return $this->type;
151         }
152 
153         if (!$this->pathname && !$this->tmp_name)
154         {
155             return null;
156         }
157 
158         return FileInfo::resolve_type($this->pathname ?: $this->tmp_name);
159     }
160 
161     protected $size;
162 
163     /**
164      * Returns the size of the file.
165      *
166      * If the {@link $size} property was not defined during construct, the size
167      * is guessed using the pathname of the file. If the pathname is not available the method
168      * returns `null`.
169      *
170      * @return int|false The size of the file or `false` if it cannot be determined.
171      */
172     protected function get_size()
173     {
174         if (!empty($this->size))
175         {
176             return $this->size;
177         }
178 
179         if ($this->pathname)
180         {
181             return filesize($this->pathname);
182         }
183 
184         return null;
185     }
186 
187     protected $tmp_name;
188 
189     protected $error;
190 
191     /**
192      * Whether the file is valid.
193      *
194      * A file is considered valid if it has no error code, if it has a size,
195      * if it has either a temporary name or a pathname and that the file actually exists.
196      *
197      * @return boolean `true` if the file is valid, `false` otherwise.
198      */
199     protected function get_is_valid()
200     {
201         return !$this->error
202         && $this->size
203         && ($this->tmp_name || ($this->pathname && file_exists($this->pathname)));
204     }
205 
206     protected $pathname;
207 
208     /**
209      * Returns the pathname of the file.
210      *
211      * **Note:** If the {@link $pathname} property is empty, the {@link $tmp_name} property
212      * is returned.
213      *
214      * @return string
215      */
216     protected function get_pathname()
217     {
218         return $this->pathname ?: $this->tmp_name;
219     }
220 
221     protected function __construct(array $properties)
222     {
223         foreach ($properties as $property => $value)
224         {
225             $this->$property = $value;
226         }
227 
228         if (!$this->name && $this->pathname)
229         {
230             $this->name = basename($this->pathname);
231         }
232 
233         if (empty($this->type))
234         {
235             unset($this->type);
236         }
237 
238         if (empty($this->size))
239         {
240             unset($this->size);
241         }
242     }
243 
244     /**
245      * Returns an array representation of the instance.
246      *
247      * The following properties are exported:
248      *
249      * - {@link $name}
250      * - {@link $unsuffixed_name}
251      * - {@link $extension}
252      * - {@link $type}
253      * - {@link $size}
254      * - {@link $pathname}
255      * - {@link $error}
256      * - {@link $error_message}
257      *
258      * @return array
259      */
260     public function to_array()
261     {
262         $error_message = $this->error_message;
263 
264         if ($error_message !== null)
265         {
266             $error_message = (string) $error_message;
267         }
268 
269         return [
270 
271             'name' => $this->name,
272             'unsuffixed_name' => $this->unsuffixed_name,
273             'extension' => $this->extension,
274             'type' => $this->type,
275             'size' => $this->size,
276             'pathname' => $this->pathname,
277             'error' => $this->error,
278             'error_message' => $error_message
279 
280         ];
281     }
282 
283     /**
284      * Returns the error code.
285      *
286      * @return string
287      */
288     protected function get_error()
289     {
290         return $this->error;
291     }
292 
293     /**
294      * Returns the message associated with the error.
295      *
296      * @return \ICanBoogie\I18n\FormattedString|\ICanBoogie\FormattedString|string|null
297      */
298     protected function get_error_message()
299     {
300         switch ($this->error)
301         {
302             case UPLOAD_ERR_OK:
303 
304                 return null;
305 
306             case UPLOAD_ERR_INI_SIZE:
307 
308                 return $this->format("Maximum file size is :size Mb", [ ':size' => (int) ini_get('upload_max_filesize') ]);
309 
310             case UPLOAD_ERR_FORM_SIZE:
311 
312                 return $this->format("Maximum file size is :size Mb", [ ':size' => 'MAX_FILE_SIZE' ]);
313 
314             case UPLOAD_ERR_PARTIAL:
315 
316                 return $this->format("The uploaded file was only partially uploaded.");
317 
318             case UPLOAD_ERR_NO_FILE:
319 
320                 return $this->format("No file was uploaded.");
321 
322             case UPLOAD_ERR_NO_TMP_DIR:
323 
324                 return $this->format("Missing a temporary folder.");
325 
326             case UPLOAD_ERR_CANT_WRITE:
327 
328                 return $this->format("Failed to write file to disk.");
329 
330             case UPLOAD_ERR_EXTENSION:
331 
332                 return $this->format("A PHP extension stopped the file upload.");
333 
334             default:
335 
336                 return $this->format("An error has occurred.");
337         }
338     }
339 
340     /**
341      * Returns the extension of the file, if any.
342      *
343      * **Note:** The extension includes the dot e.g. ".zip". The extension is always in lower case.
344      *
345      * @return string|null
346      */
347     protected function get_extension()
348     {
349         $extension = pathinfo($this->name, PATHINFO_EXTENSION);
350 
351         if (!$extension)
352         {
353             return null;
354         }
355 
356         return '.' . strtolower($extension);
357     }
358 
359     /**
360      * Checks if a file is uploaded.
361      *
362      * @return boolean `true` if the file is uploaded, `false` otherwise.
363      */
364     protected function get_is_uploaded()
365     {
366         return $this->tmp_name && is_uploaded_file($this->tmp_name);
367     }
368 
369     /**
370      * Checks if the file matches a MIME class, a MIME type, or a file extension.
371      *
372      * @param string|array $type The type can be a MIME class (e.g. "image"),
373      * a MIME type (e.g. "image/png"), or an extensions (e.g. ".zip"). An array can be used to
374      * check if a file matches multiple type e.g. `[ "image", ".mp3" ]`, which matches any type
375      * of image or files with the ".mp3" extension.
376      *
377      * @return bool `true` if the file matches (or `$type` is empty), `false` otherwise.
378      */
379     public function match($type)
380     {
381         if (!$type)
382         {
383             return true;
384         }
385 
386         if (is_array($type))
387         {
388             return $this->match_multiple($type);
389         }
390 
391         if ($type{0} === '.')
392         {
393             return $type === $this->extension;
394         }
395 
396         if (strpos($type, '/') === false)
397         {
398             return (bool) preg_match('#^' . preg_quote($type) . '/#', $this->type);
399         }
400 
401         return $type === $this->type;
402     }
403 
404     /**
405      * Checks if the file matches one of the types in the list.
406      *
407      * @param array $type_list
408      *
409      * @return bool `true` if the file matches, `false` otherwise.
410      */
411     private function match_multiple(array $type_list)
412     {
413         foreach ($type_list as $type)
414         {
415             if ($this->match($type))
416             {
417                 return true;
418             }
419         }
420 
421         return false;
422     }
423 
424     /**
425      * Moves the file.
426      *
427      * @param string $destination Pathname to the destination file.
428      * @param bool $overwrite Use {@link MOVE_OVERWRITE} to delete the destination before the file
429      * is moved. Defaults to {@link MOVE_NO_OVERWRITE}.
430      *
431      * @throws \Exception if the file failed to be moved.
432      */
433     public function move($destination, $overwrite = self::MOVE_NO_OVERWRITE)
434     {
435         if (file_exists($destination))
436         {
437             if (!$overwrite)
438             {
439                 throw new \Exception("The destination file already exists: $destination.");
440             }
441 
442             unlink($destination);
443         }
444 
445         if ($this->pathname)
446         {
447             if (!rename($this->pathname, $destination))
448             {
449                 throw new \Exception("Unable to move file to destination: $destination.");  // @codeCoverageIgnore
450             }
451         }
452         else
453         {
454             if (!move_uploaded_file($this->tmp_name, $destination))
455             {
456                 throw new \Exception("Unable to move file to destination: $destination.");
457             }
458         }
459 
460         $this->pathname = $destination;
461     }
462 }
463 
ICanBoogie/HTTP v2.4.0 API documentation generated by ApiGen