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

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