vendor/symfony/form/Extension/Core/Type/FileType.php line 25

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Form\Extension\Core\Type;
  11. use Symfony\Component\Form\AbstractType;
  12. use Symfony\Component\Form\FileUploadError;
  13. use Symfony\Component\Form\FormBuilderInterface;
  14. use Symfony\Component\Form\FormEvent;
  15. use Symfony\Component\Form\FormEvents;
  16. use Symfony\Component\Form\FormInterface;
  17. use Symfony\Component\Form\FormView;
  18. use Symfony\Component\OptionsResolver\Options;
  19. use Symfony\Component\OptionsResolver\OptionsResolver;
  20. use Symfony\Contracts\Translation\TranslatorInterface;
  21. class FileType extends AbstractType
  22. {
  23.     public const KIB_BYTES 1024;
  24.     public const MIB_BYTES 1048576;
  25.     private const SUFFIXES = [
  26.         => 'bytes',
  27.         self::KIB_BYTES => 'KiB',
  28.         self::MIB_BYTES => 'MiB',
  29.     ];
  30.     private $translator;
  31.     public function __construct(TranslatorInterface $translator null)
  32.     {
  33.         $this->translator $translator;
  34.     }
  35.     /**
  36.      * {@inheritdoc}
  37.      */
  38.     public function buildForm(FormBuilderInterface $builder, array $options)
  39.     {
  40.         // Ensure that submitted data is always an uploaded file or an array of some
  41.         $builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) use ($options) {
  42.             $form $event->getForm();
  43.             $requestHandler $form->getConfig()->getRequestHandler();
  44.             if ($options['multiple']) {
  45.                 $data = [];
  46.                 $files $event->getData();
  47.                 if (!\is_array($files)) {
  48.                     $files = [];
  49.                 }
  50.                 foreach ($files as $file) {
  51.                     if ($requestHandler->isFileUpload($file)) {
  52.                         $data[] = $file;
  53.                         if (method_exists($requestHandler'getUploadFileError') && null !== $errorCode $requestHandler->getUploadFileError($file)) {
  54.                             $form->addError($this->getFileUploadError($errorCode));
  55.                         }
  56.                     }
  57.                 }
  58.                 // Since the array is never considered empty in the view data format
  59.                 // on submission, we need to evaluate the configured empty data here
  60.                 if ([] === $data) {
  61.                     $emptyData $form->getConfig()->getEmptyData();
  62.                     $data $emptyData instanceof \Closure $emptyData($form$data) : $emptyData;
  63.                 }
  64.                 $event->setData($data);
  65.             } elseif ($requestHandler->isFileUpload($event->getData()) && method_exists($requestHandler'getUploadFileError') && null !== $errorCode $requestHandler->getUploadFileError($event->getData())) {
  66.                 $form->addError($this->getFileUploadError($errorCode));
  67.             } elseif (!$requestHandler->isFileUpload($event->getData())) {
  68.                 $event->setData(null);
  69.             }
  70.         });
  71.     }
  72.     /**
  73.      * {@inheritdoc}
  74.      */
  75.     public function buildView(FormView $viewFormInterface $form, array $options)
  76.     {
  77.         if ($options['multiple']) {
  78.             $view->vars['full_name'] .= '[]';
  79.             $view->vars['attr']['multiple'] = 'multiple';
  80.         }
  81.         $view->vars array_replace($view->vars, [
  82.             'type' => 'file',
  83.             'value' => '',
  84.         ]);
  85.     }
  86.     /**
  87.      * {@inheritdoc}
  88.      */
  89.     public function finishView(FormView $viewFormInterface $form, array $options)
  90.     {
  91.         $view->vars['multipart'] = true;
  92.     }
  93.     /**
  94.      * {@inheritdoc}
  95.      */
  96.     public function configureOptions(OptionsResolver $resolver)
  97.     {
  98.         $dataClass null;
  99.         if (class_exists(\Symfony\Component\HttpFoundation\File\File::class)) {
  100.             $dataClass = function (Options $options) {
  101.                 return $options['multiple'] ? null 'Symfony\Component\HttpFoundation\File\File';
  102.             };
  103.         }
  104.         $emptyData = function (Options $options) {
  105.             return $options['multiple'] ? [] : null;
  106.         };
  107.         $resolver->setDefaults([
  108.             'compound' => false,
  109.             'data_class' => $dataClass,
  110.             'empty_data' => $emptyData,
  111.             'multiple' => false,
  112.             'allow_file_upload' => true,
  113.             'invalid_message' => function (Options $options$previousValue) {
  114.                 return ($options['legacy_error_messages'] ?? true)
  115.                     ? $previousValue
  116.                     'Please select a valid file.';
  117.             },
  118.         ]);
  119.     }
  120.     /**
  121.      * {@inheritdoc}
  122.      */
  123.     public function getBlockPrefix()
  124.     {
  125.         return 'file';
  126.     }
  127.     private function getFileUploadError(int $errorCode)
  128.     {
  129.         $messageParameters = [];
  130.         if (\UPLOAD_ERR_INI_SIZE === $errorCode) {
  131.             [$limitAsString$suffix] = $this->factorizeSizes(0self::getMaxFilesize());
  132.             $messageTemplate 'The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.';
  133.             $messageParameters = [
  134.                 '{{ limit }}' => $limitAsString,
  135.                 '{{ suffix }}' => $suffix,
  136.             ];
  137.         } elseif (\UPLOAD_ERR_FORM_SIZE === $errorCode) {
  138.             $messageTemplate 'The file is too large.';
  139.         } else {
  140.             $messageTemplate 'The file could not be uploaded.';
  141.         }
  142.         if (null !== $this->translator) {
  143.             $message $this->translator->trans($messageTemplate$messageParameters'validators');
  144.         } else {
  145.             $message strtr($messageTemplate$messageParameters);
  146.         }
  147.         return new FileUploadError($message$messageTemplate$messageParameters);
  148.     }
  149.     /**
  150.      * Returns the maximum size of an uploaded file as configured in php.ini.
  151.      *
  152.      * This method should be kept in sync with Symfony\Component\HttpFoundation\File\UploadedFile::getMaxFilesize().
  153.      *
  154.      * @return int|float The maximum size of an uploaded file in bytes (returns float if size > PHP_INT_MAX)
  155.      */
  156.     private static function getMaxFilesize()
  157.     {
  158.         $iniMax strtolower(ini_get('upload_max_filesize'));
  159.         if ('' === $iniMax) {
  160.             return \PHP_INT_MAX;
  161.         }
  162.         $max ltrim($iniMax'+');
  163.         if (str_starts_with($max'0x')) {
  164.             $max = \intval($max16);
  165.         } elseif (str_starts_with($max'0')) {
  166.             $max = \intval($max8);
  167.         } else {
  168.             $max = (int) $max;
  169.         }
  170.         switch (substr($iniMax, -1)) {
  171.             case 't'$max *= 1024;
  172.             case 'g'$max *= 1024;
  173.             case 'm'$max *= 1024;
  174.             case 'k'$max *= 1024;
  175.         }
  176.         return $max;
  177.     }
  178.     /**
  179.      * Converts the limit to the smallest possible number
  180.      * (i.e. try "MB", then "kB", then "bytes").
  181.      *
  182.      * This method should be kept in sync with Symfony\Component\Validator\Constraints\FileValidator::factorizeSizes().
  183.      *
  184.      * @param int|float $limit
  185.      */
  186.     private function factorizeSizes(int $size$limit)
  187.     {
  188.         $coef self::MIB_BYTES;
  189.         $coefFactor self::KIB_BYTES;
  190.         $limitAsString = (string) ($limit $coef);
  191.         // Restrict the limit to 2 decimals (without rounding! we
  192.         // need the precise value)
  193.         while (self::moreDecimalsThan($limitAsString2)) {
  194.             $coef /= $coefFactor;
  195.             $limitAsString = (string) ($limit $coef);
  196.         }
  197.         // Convert size to the same measure, but round to 2 decimals
  198.         $sizeAsString = (string) round($size $coef2);
  199.         // If the size and limit produce the same string output
  200.         // (due to rounding), reduce the coefficient
  201.         while ($sizeAsString === $limitAsString) {
  202.             $coef /= $coefFactor;
  203.             $limitAsString = (string) ($limit $coef);
  204.             $sizeAsString = (string) round($size $coef2);
  205.         }
  206.         return [$limitAsStringself::SUFFIXES[$coef]];
  207.     }
  208.     /**
  209.      * This method should be kept in sync with Symfony\Component\Validator\Constraints\FileValidator::moreDecimalsThan().
  210.      */
  211.     private static function moreDecimalsThan(string $doubleint $numberOfDecimals): bool
  212.     {
  213.         return \strlen($double) > \strlen(round($double$numberOfDecimals));
  214.     }
  215. }