1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.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
12namespace Symfony\Component\HttpFoundation\File;
13
14use Symfony\Component\HttpFoundation\File\Exception\FileException;
15use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException;
16use Symfony\Component\Mime\MimeTypes;
17
18/**
19 * A file in the file system.
20 *
21 * @author Bernhard Schussek <bschussek@gmail.com>
22 */
23class File extends \SplFileInfo
24{
25    /**
26     * Constructs a new file from the given path.
27     *
28     * @param string $path      The path to the file
29     * @param bool   $checkPath Whether to check the path or not
30     *
31     * @throws FileNotFoundException If the given path is not a file
32     */
33    public function __construct(string $path, bool $checkPath = true)
34    {
35        if ($checkPath && !is_file($path)) {
36            throw new FileNotFoundException($path);
37        }
38
39        parent::__construct($path);
40    }
41
42    /**
43     * Returns the extension based on the mime type.
44     *
45     * If the mime type is unknown, returns null.
46     *
47     * This method uses the mime type as guessed by getMimeType()
48     * to guess the file extension.
49     *
50     * @return string|null The guessed extension or null if it cannot be guessed
51     *
52     * @see MimeTypes
53     * @see getMimeType()
54     */
55    public function guessExtension()
56    {
57        return MimeTypes::getDefault()->getExtensions($this->getMimeType())[0] ?? null;
58    }
59
60    /**
61     * Returns the mime type of the file.
62     *
63     * The mime type is guessed using a MimeTypeGuesserInterface instance,
64     * which uses finfo_file() then the "file" system binary,
65     * depending on which of those are available.
66     *
67     * @return string|null The guessed mime type (e.g. "application/pdf")
68     *
69     * @see MimeTypes
70     */
71    public function getMimeType()
72    {
73        return MimeTypes::getDefault()->guessMimeType($this->getPathname());
74    }
75
76    /**
77     * Moves the file to a new location.
78     *
79     * @return self A File object representing the new file
80     *
81     * @throws FileException if the target file could not be created
82     */
83    public function move(string $directory, string $name = null)
84    {
85        $target = $this->getTargetFile($directory, $name);
86
87        set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
88        $renamed = rename($this->getPathname(), $target);
89        restore_error_handler();
90        if (!$renamed) {
91            throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s).', $this->getPathname(), $target, strip_tags($error)));
92        }
93
94        @chmod($target, 0666 & ~umask());
95
96        return $target;
97    }
98
99    /**
100     * @return self
101     */
102    protected function getTargetFile(string $directory, string $name = null)
103    {
104        if (!is_dir($directory)) {
105            if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) {
106                throw new FileException(sprintf('Unable to create the "%s" directory.', $directory));
107            }
108        } elseif (!is_writable($directory)) {
109            throw new FileException(sprintf('Unable to write in the "%s" directory.', $directory));
110        }
111
112        $target = rtrim($directory, '/\\').\DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name));
113
114        return new self($target, false);
115    }
116
117    /**
118     * Returns locale independent base name of the given path.
119     *
120     * @return string
121     */
122    protected function getName(string $name)
123    {
124        $originalName = str_replace('\\', '/', $name);
125        $pos = strrpos($originalName, '/');
126        $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1);
127
128        return $originalName;
129    }
130}
131