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