vendor/gedmo/doctrine-extensions/src/Uploadable/UploadableListener.php line 559

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the Doctrine Behavioral Extensions package.
  4.  * (c) Gediminas Morkevicius <gediminas.morkevicius@gmail.com> http://www.gediminasm.org
  5.  * For the full copyright and license information, please view the LICENSE
  6.  * file that was distributed with this source code.
  7.  */
  8. namespace Gedmo\Uploadable;
  9. use Doctrine\Common\EventArgs;
  10. use Doctrine\DBAL\Types\Type;
  11. use Doctrine\Persistence\Event\LoadClassMetadataEventArgs;
  12. use Doctrine\Persistence\Mapping\ClassMetadata;
  13. use Doctrine\Persistence\NotifyPropertyChanged;
  14. use Gedmo\Exception\UploadableCantWriteException;
  15. use Gedmo\Exception\UploadableCouldntGuessMimeTypeException;
  16. use Gedmo\Exception\UploadableExtensionException;
  17. use Gedmo\Exception\UploadableFileAlreadyExistsException;
  18. use Gedmo\Exception\UploadableFormSizeException;
  19. use Gedmo\Exception\UploadableIniSizeException;
  20. use Gedmo\Exception\UploadableInvalidMimeTypeException;
  21. use Gedmo\Exception\UploadableMaxSizeException;
  22. use Gedmo\Exception\UploadableNoFileException;
  23. use Gedmo\Exception\UploadableNoPathDefinedException;
  24. use Gedmo\Exception\UploadableNoTmpDirException;
  25. use Gedmo\Exception\UploadablePartialException;
  26. use Gedmo\Exception\UploadableUploadException;
  27. use Gedmo\Mapping\Event\AdapterInterface;
  28. use Gedmo\Mapping\MappedEventSubscriber;
  29. use Gedmo\Uploadable\Event\UploadablePostFileProcessEventArgs;
  30. use Gedmo\Uploadable\Event\UploadablePreFileProcessEventArgs;
  31. use Gedmo\Uploadable\FileInfo\FileInfoArray;
  32. use Gedmo\Uploadable\FileInfo\FileInfoInterface;
  33. use Gedmo\Uploadable\Mapping\Validator;
  34. use Gedmo\Uploadable\MimeType\MimeTypeGuesser;
  35. use Gedmo\Uploadable\MimeType\MimeTypeGuesserInterface;
  36. /**
  37.  * Uploadable listener
  38.  *
  39.  * @author Gustavo Falco <comfortablynumb84@gmail.com>
  40.  * @author Gediminas Morkevicius <gediminas.morkevicius@gmail.com>
  41.  */
  42. class UploadableListener extends MappedEventSubscriber
  43. {
  44.     public const ACTION_INSERT 'INSERT';
  45.     public const ACTION_UPDATE 'UPDATE';
  46.     /**
  47.      * Default path to move files in
  48.      *
  49.      * @var string
  50.      */
  51.     private $defaultPath;
  52.     /**
  53.      * Mime type guesser
  54.      *
  55.      * @var MimeTypeGuesserInterface
  56.      */
  57.     private $mimeTypeGuesser;
  58.     /**
  59.      * Default FileInfoInterface class
  60.      *
  61.      * @var string
  62.      */
  63.     private $defaultFileInfoClass FileInfoArray::class;
  64.     /**
  65.      * Array of files to remove on postFlush
  66.      *
  67.      * @var array
  68.      */
  69.     private $pendingFileRemovals = [];
  70.     /**
  71.      * Array of FileInfoInterface objects. The index is the hash of the entity owner
  72.      * of the FileInfoInterface object.
  73.      *
  74.      * @var array
  75.      */
  76.     private $fileInfoObjects = [];
  77.     public function __construct(MimeTypeGuesserInterface $mimeTypeGuesser null)
  78.     {
  79.         parent::__construct();
  80.         $this->mimeTypeGuesser $mimeTypeGuesser $mimeTypeGuesser : new MimeTypeGuesser();
  81.     }
  82.     /**
  83.      * @return string[]
  84.      */
  85.     public function getSubscribedEvents()
  86.     {
  87.         return [
  88.             'loadClassMetadata',
  89.             'preFlush',
  90.             'onFlush',
  91.             'postFlush',
  92.         ];
  93.     }
  94.     /**
  95.      * This event is needed in special cases where the entity needs to be updated, but it only has the
  96.      * file field modified. Since we can't mark an entity as "dirty" in the "addEntityFileInfo" method,
  97.      * doctrine thinks the entity has no changes, which produces that the "onFlush" event gets never called.
  98.      * Here we mark the entity as dirty, so the "onFlush" event gets called, and the file is processed.
  99.      *
  100.      * @return void
  101.      */
  102.     public function preFlush(EventArgs $args)
  103.     {
  104.         if (empty($this->fileInfoObjects)) {
  105.             // Nothing to do
  106.             return;
  107.         }
  108.         $ea $this->getEventAdapter($args);
  109.         $om $ea->getObjectManager();
  110.         $uow $om->getUnitOfWork();
  111.         $first reset($this->fileInfoObjects);
  112.         $meta $om->getClassMetadata(get_class($first['entity']));
  113.         $config $this->getConfiguration($om$meta->getName());
  114.         foreach ($this->fileInfoObjects as $info) {
  115.             $entity $info['entity'];
  116.             // If the entity is in the identity map, it means it will be updated. We need to force the
  117.             // "dirty check" here by "modifying" the path. We are actually setting the same value, but
  118.             // this will mark the entity as dirty, and the "onFlush" event will be fired, even if there's
  119.             // no other change in the entity's fields apart from the file itself.
  120.             if ($uow->isInIdentityMap($entity)) {
  121.                 if ($config['filePathField']) {
  122.                     $path $this->getFilePathFieldValue($meta$config$entity);
  123.                     $uow->propertyChanged($entity$config['filePathField'], $path$path);
  124.                 } else {
  125.                     $fileName $this->getFileNameFieldValue($meta$config$entity);
  126.                     $uow->propertyChanged($entity$config['fileNameField'], $fileName$fileName);
  127.                 }
  128.                 $uow->scheduleForUpdate($entity);
  129.             }
  130.         }
  131.     }
  132.     /**
  133.      * Handle file-uploading depending on the action
  134.      * being done with objects
  135.      *
  136.      * @return void
  137.      */
  138.     public function onFlush(EventArgs $args)
  139.     {
  140.         $ea $this->getEventAdapter($args);
  141.         $om $ea->getObjectManager();
  142.         $uow $om->getUnitOfWork();
  143.         // Do we need to upload files?
  144.         foreach ($this->fileInfoObjects as $info) {
  145.             $entity $info['entity'];
  146.             $scheduledForInsert $uow->isScheduledForInsert($entity);
  147.             $scheduledForUpdate $uow->isScheduledForUpdate($entity);
  148.             $action = ($scheduledForInsert || $scheduledForUpdate) ?
  149.                 ($scheduledForInsert self::ACTION_INSERT self::ACTION_UPDATE) :
  150.                 false;
  151.             if ($action) {
  152.                 $this->processFile($ea$entity$action);
  153.             }
  154.         }
  155.         // Do we need to remove any files?
  156.         foreach ($ea->getScheduledObjectDeletions($uow) as $object) {
  157.             $meta $om->getClassMetadata(get_class($object));
  158.             if ($config $this->getConfiguration($om$meta->getName())) {
  159.                 if (isset($config['uploadable']) && $config['uploadable']) {
  160.                     $this->addFileRemoval($meta$config$object);
  161.                 }
  162.             }
  163.         }
  164.     }
  165.     /**
  166.      * Handle removal of files
  167.      *
  168.      * @return void
  169.      */
  170.     public function postFlush(EventArgs $args)
  171.     {
  172.         if (!empty($this->pendingFileRemovals)) {
  173.             foreach ($this->pendingFileRemovals as $file) {
  174.                 $this->removeFile($file);
  175.             }
  176.             $this->pendingFileRemovals = [];
  177.         }
  178.         $this->fileInfoObjects = [];
  179.     }
  180.     /**
  181.      * If it's a Uploadable object, verify if the file was uploaded.
  182.      * If that's the case, process it.
  183.      *
  184.      * @param object $object
  185.      * @param string $action
  186.      *
  187.      * @throws \Gedmo\Exception\UploadableNoPathDefinedException
  188.      * @throws \Gedmo\Exception\UploadableCouldntGuessMimeTypeException
  189.      * @throws \Gedmo\Exception\UploadableMaxSizeException
  190.      * @throws \Gedmo\Exception\UploadableInvalidMimeTypeException
  191.      *
  192.      * @return void
  193.      */
  194.     public function processFile(AdapterInterface $ea$object$action)
  195.     {
  196.         $oid spl_object_id($object);
  197.         $om $ea->getObjectManager();
  198.         $uow $om->getUnitOfWork();
  199.         $meta $om->getClassMetadata(get_class($object));
  200.         $config $this->getConfiguration($om$meta->getName());
  201.         if (!$config || !isset($config['uploadable']) || !$config['uploadable']) {
  202.             // Nothing to do
  203.             return;
  204.         }
  205.         $refl $meta->getReflectionClass();
  206.         $fileInfo $this->fileInfoObjects[$oid]['fileInfo'];
  207.         $evm $om->getEventManager();
  208.         if ($evm->hasListeners(Events::uploadablePreFileProcess)) {
  209.             $evm->dispatchEvent(Events::uploadablePreFileProcess, new UploadablePreFileProcessEventArgs(
  210.                 $this,
  211.                 $om,
  212.                 $config,
  213.                 $fileInfo,
  214.                 $object,
  215.                 $action
  216.             ));
  217.         }
  218.         // Validations
  219.         if ($config['maxSize'] > && $fileInfo->getSize() > $config['maxSize']) {
  220.             $msg 'File "%s" exceeds the maximum allowed size of %d bytes. File size: %d bytes';
  221.             throw new UploadableMaxSizeException(sprintf($msg$fileInfo->getName(), $config['maxSize'], $fileInfo->getSize()));
  222.         }
  223.         $mime $this->mimeTypeGuesser->guess($fileInfo->getTmpName());
  224.         if (!$mime) {
  225.             throw new UploadableCouldntGuessMimeTypeException(sprintf('Couldn\'t guess mime type for file "%s".'$fileInfo->getName()));
  226.         }
  227.         if ($config['allowedTypes'] || $config['disallowedTypes']) {
  228.             $ok $config['allowedTypes'] ? false true;
  229.             $mimes $config['allowedTypes'] ? $config['allowedTypes'] : $config['disallowedTypes'];
  230.             foreach ($mimes as $m) {
  231.                 if ($mime === $m) {
  232.                     $ok $config['allowedTypes'] ? true false;
  233.                     break;
  234.                 }
  235.             }
  236.             if (!$ok) {
  237.                 throw new UploadableInvalidMimeTypeException(sprintf('Invalid mime type "%s" for file "%s".'$mime$fileInfo->getName()));
  238.             }
  239.         }
  240.         $path $this->getPath($meta$config$object);
  241.         if (self::ACTION_UPDATE === $action) {
  242.             // First we add the original file to the pendingFileRemovals array
  243.             $this->addFileRemoval($meta$config$object);
  244.         }
  245.         // We generate the filename based on configuration
  246.         $generatorNamespace 'Gedmo\Uploadable\FilenameGenerator';
  247.         switch ($config['filenameGenerator']) {
  248.             case Validator::FILENAME_GENERATOR_ALPHANUMERIC:
  249.                 $generatorClass $generatorNamespace.'\FilenameGeneratorAlphanumeric';
  250.                 break;
  251.             case Validator::FILENAME_GENERATOR_SHA1:
  252.                 $generatorClass $generatorNamespace.'\FilenameGeneratorSha1';
  253.                 break;
  254.             case Validator::FILENAME_GENERATOR_NONE:
  255.                 $generatorClass false;
  256.                 break;
  257.             default:
  258.                 $generatorClass $config['filenameGenerator'];
  259.         }
  260.         $info $this->moveFile($fileInfo$path$generatorClass$config['allowOverwrite'], $config['appendNumber'], $object);
  261.         // We override the mime type with the guessed one
  262.         $info['fileMimeType'] = $mime;
  263.         if ('' !== $config['callback']) {
  264.             $callbackMethod $refl->getMethod($config['callback']);
  265.             $callbackMethod->setAccessible(true);
  266.             $callbackMethod->invokeArgs($object, [$info]);
  267.         }
  268.         if ($config['filePathField']) {
  269.             $this->updateField($object$uow$ea$meta$config['filePathField'], $info['filePath']);
  270.         }
  271.         if ($config['fileNameField']) {
  272.             $this->updateField($object$uow$ea$meta$config['fileNameField'], $info['fileName']);
  273.         }
  274.         if ($config['fileMimeTypeField']) {
  275.             $this->updateField($object$uow$ea$meta$config['fileMimeTypeField'], $info['fileMimeType']);
  276.         }
  277.         if ($config['fileSizeField']) {
  278.             $typeOfSizeField Type::getType($meta->getTypeOfField($config['fileSizeField']));
  279.             $value $typeOfSizeField->convertToPHPValue(
  280.                 $info['fileSize'],
  281.                 $om->getConnection()->getDatabasePlatform()
  282.             );
  283.             $this->updateField($object$uow$ea$meta$config['fileSizeField'], $value);
  284.         }
  285.         $ea->recomputeSingleObjectChangeSet($uow$meta$object);
  286.         if ($evm->hasListeners(Events::uploadablePostFileProcess)) {
  287.             $evm->dispatchEvent(Events::uploadablePostFileProcess, new UploadablePostFileProcessEventArgs(
  288.                 $this,
  289.                 $om,
  290.                 $config,
  291.                 $fileInfo,
  292.                 $object,
  293.                 $action
  294.             ));
  295.         }
  296.         unset($this->fileInfoObjects[$oid]);
  297.     }
  298.     /**
  299.      * Simple wrapper for the function "unlink" to ease testing
  300.      *
  301.      * @param string $filePath
  302.      *
  303.      * @return bool
  304.      */
  305.     public function removeFile($filePath)
  306.     {
  307.         if (is_file($filePath)) {
  308.             return @unlink($filePath);
  309.         }
  310.         return false;
  311.     }
  312.     /**
  313.      * Moves the file to the specified path
  314.      *
  315.      * @param string      $path
  316.      * @param string|bool $filenameGeneratorClass
  317.      * @param bool        $overwrite
  318.      * @param bool        $appendNumber
  319.      * @param object      $object
  320.      *
  321.      * @return array
  322.      *
  323.      * @throws \Gedmo\Exception\UploadableUploadException
  324.      * @throws \Gedmo\Exception\UploadableNoFileException
  325.      * @throws \Gedmo\Exception\UploadableExtensionException
  326.      * @throws \Gedmo\Exception\UploadableIniSizeException
  327.      * @throws \Gedmo\Exception\UploadableFormSizeException
  328.      * @throws \Gedmo\Exception\UploadableFileAlreadyExistsException
  329.      * @throws \Gedmo\Exception\UploadablePartialException
  330.      * @throws \Gedmo\Exception\UploadableNoTmpDirException
  331.      * @throws \Gedmo\Exception\UploadableCantWriteException
  332.      *
  333.      * @phpstan-param class-string|false $filenameGeneratorClass
  334.      */
  335.     public function moveFile(FileInfoInterface $fileInfo$path$filenameGeneratorClass false$overwrite false$appendNumber false$object null)
  336.     {
  337.         if ($fileInfo->getError() > 0) {
  338.             switch ($fileInfo->getError()) {
  339.                 case 1:
  340.                     $msg 'Size of uploaded file "%s" exceeds limit imposed by directive "upload_max_filesize" in php.ini';
  341.                     throw new UploadableIniSizeException(sprintf($msg$fileInfo->getName()));
  342.                 case 2:
  343.                     $msg 'Size of uploaded file "%s" exceeds limit imposed by option MAX_FILE_SIZE in your form.';
  344.                     throw new UploadableFormSizeException(sprintf($msg$fileInfo->getName()));
  345.                 case 3:
  346.                     $msg 'File "%s" was partially uploaded.';
  347.                     throw new UploadablePartialException(sprintf($msg$fileInfo->getName()));
  348.                 case 4:
  349.                     $msg 'No file was uploaded!';
  350.                     throw new UploadableNoFileException($msg);
  351.                 case 6:
  352.                     $msg 'Upload failed. Temp dir is missing.';
  353.                     throw new UploadableNoTmpDirException($msg);
  354.                 case 7:
  355.                     $msg 'File "%s" couldn\'t be uploaded because directory is not writable.';
  356.                     throw new UploadableCantWriteException(sprintf($msg$fileInfo->getName()));
  357.                 case 8:
  358.                     $msg 'A PHP Extension stopped the uploaded for some reason.';
  359.                     throw new UploadableExtensionException($msg);
  360.                 default:
  361.                     throw new UploadableUploadException(sprintf('There was an unknown problem while uploading file "%s"'$fileInfo->getName()));
  362.             }
  363.         }
  364.         $info = [
  365.             'fileName' => '',
  366.             'fileExtension' => '',
  367.             'fileWithoutExt' => '',
  368.             'origFileName' => '',
  369.             'filePath' => '',
  370.             'fileMimeType' => $fileInfo->getType(),
  371.             'fileSize' => $fileInfo->getSize(),
  372.         ];
  373.         $info['fileName'] = basename($fileInfo->getName());
  374.         $info['filePath'] = $path.'/'.$info['fileName'];
  375.         $hasExtension strrpos($info['fileName'], '.');
  376.         if ($hasExtension) {
  377.             $info['fileExtension'] = substr($info['filePath'], strrpos($info['filePath'], '.'));
  378.             $info['fileWithoutExt'] = substr($info['filePath'], 0strrpos($info['filePath'], '.'));
  379.         } else {
  380.             $info['fileWithoutExt'] = $info['fileName'];
  381.         }
  382.         // Save the original filename for later use
  383.         $info['origFileName'] = $info['fileName'];
  384.         // Now we generate the filename using the configured class
  385.         if (false !== $filenameGeneratorClass) {
  386.             $filename $filenameGeneratorClass::generate(
  387.                 str_replace($path.'/'''$info['fileWithoutExt']),
  388.                 $info['fileExtension'],
  389.                 $object
  390.             );
  391.             $info['filePath'] = str_replace(
  392.                 '/'.$info['fileName'],
  393.                 '/'.$filename,
  394.                 $info['filePath']
  395.             );
  396.             $info['fileName'] = $filename;
  397.             if ($pos strrpos($info['filePath'], '.')) {
  398.                 // ignores positions like "./file" at 0 see #915
  399.                 $info['fileWithoutExt'] = substr($info['filePath'], 0$pos);
  400.             } else {
  401.                 $info['fileWithoutExt'] = $info['filePath'];
  402.             }
  403.         }
  404.         if (is_file($info['filePath'])) {
  405.             if ($overwrite) {
  406.                 $this->cancelFileRemoval($info['filePath']);
  407.                 $this->removeFile($info['filePath']);
  408.             } elseif ($appendNumber) {
  409.                 $counter 1;
  410.                 $info['filePath'] = $info['fileWithoutExt'].'-'.$counter.$info['fileExtension'];
  411.                 do {
  412.                     $info['filePath'] = $info['fileWithoutExt'].'-'.(++$counter).$info['fileExtension'];
  413.                 } while (is_file($info['filePath']));
  414.             } else {
  415.                 throw new UploadableFileAlreadyExistsException(sprintf('File "%s" already exists!'$info['filePath']));
  416.             }
  417.         }
  418.         if (!$this->doMoveFile($fileInfo->getTmpName(), $info['filePath'], $fileInfo->isUploadedFile())) {
  419.             throw new UploadableUploadException(sprintf('File "%s" was not uploaded, or there was a problem moving it to the location "%s".'$fileInfo->getName(), $path));
  420.         }
  421.         return $info;
  422.     }
  423.     /**
  424.      * Simple wrapper method used to move the file. If it's an uploaded file
  425.      * it will use the "move_uploaded_file method. If it's not, it will
  426.      * simple move it
  427.      *
  428.      * @param string $source         Source file
  429.      * @param string $dest           Destination file
  430.      * @param bool   $isUploadedFile Whether this is an uploaded file?
  431.      *
  432.      * @return bool
  433.      */
  434.     public function doMoveFile($source$dest$isUploadedFile true)
  435.     {
  436.         return $isUploadedFile ? @move_uploaded_file($source$dest) : @copy($source$dest);
  437.     }
  438.     /**
  439.      * Maps additional metadata
  440.      *
  441.      * @param LoadClassMetadataEventArgs $eventArgs
  442.      *
  443.      * @return void
  444.      */
  445.     public function loadClassMetadata(EventArgs $eventArgs)
  446.     {
  447.         $ea $this->getEventAdapter($eventArgs);
  448.         $this->loadMetadataForObjectClass($ea->getObjectManager(), $eventArgs->getClassMetadata());
  449.     }
  450.     /**
  451.      * Sets the default path
  452.      *
  453.      * @param string $path
  454.      *
  455.      * @return void
  456.      */
  457.     public function setDefaultPath($path)
  458.     {
  459.         $this->defaultPath $path;
  460.     }
  461.     /**
  462.      * Returns default path
  463.      *
  464.      * @return string|null
  465.      */
  466.     public function getDefaultPath()
  467.     {
  468.         return $this->defaultPath;
  469.     }
  470.     /**
  471.      * Sets file info default class
  472.      *
  473.      * @param string $defaultFileInfoClass
  474.      *
  475.      * @return void
  476.      */
  477.     public function setDefaultFileInfoClass($defaultFileInfoClass)
  478.     {
  479.         if (!is_string($defaultFileInfoClass) || !class_exists($defaultFileInfoClass) ||
  480.             !is_subclass_of($defaultFileInfoClassFileInfoInterface::class)
  481.         ) {
  482.             throw new \Gedmo\Exception\InvalidArgumentException(sprintf('Default FileInfo class must be a valid class, and it must implement "%s".'FileInfoInterface::class));
  483.         }
  484.         $this->defaultFileInfoClass $defaultFileInfoClass;
  485.     }
  486.     /**
  487.      * Returns file info default class
  488.      *
  489.      * @return string
  490.      */
  491.     public function getDefaultFileInfoClass()
  492.     {
  493.         return $this->defaultFileInfoClass;
  494.     }
  495.     /**
  496.      * Adds a FileInfoInterface object for the given entity
  497.      *
  498.      * @param object                  $entity
  499.      * @param array|FileInfoInterface $fileInfo
  500.      *
  501.      * @throws \RuntimeException
  502.      *
  503.      * @return void
  504.      */
  505.     public function addEntityFileInfo($entity$fileInfo)
  506.     {
  507.         $fileInfoClass $this->getDefaultFileInfoClass();
  508.         $fileInfo is_array($fileInfo) ? new $fileInfoClass($fileInfo) : $fileInfo;
  509.         if (!$fileInfo instanceof FileInfoInterface) {
  510.             $msg 'You must pass an instance of FileInfoInterface or a valid array for entity of class "%s".';
  511.             throw new \RuntimeException(sprintf($msgget_class($entity)));
  512.         }
  513.         $this->fileInfoObjects[spl_object_id($entity)] = [
  514.             'entity' => $entity,
  515.             'fileInfo' => $fileInfo,
  516.         ];
  517.     }
  518.     /**
  519.      * @param object $entity
  520.      *
  521.      * @return FileInfoInterface
  522.      */
  523.     public function getEntityFileInfo($entity)
  524.     {
  525.         $oid spl_object_id($entity);
  526.         if (!isset($this->fileInfoObjects[$oid])) {
  527.             throw new \RuntimeException(sprintf('There\'s no FileInfoInterface object for entity of class "%s".'get_class($entity)));
  528.         }
  529.         return $this->fileInfoObjects[$oid]['fileInfo'];
  530.     }
  531.     /**
  532.      * @return void
  533.      */
  534.     public function setMimeTypeGuesser(MimeTypeGuesserInterface $mimeTypeGuesser)
  535.     {
  536.         $this->mimeTypeGuesser $mimeTypeGuesser;
  537.     }
  538.     /**
  539.      * @return \Gedmo\Uploadable\MimeType\MimeTypeGuesserInterface
  540.      */
  541.     public function getMimeTypeGuesser()
  542.     {
  543.         return $this->mimeTypeGuesser;
  544.     }
  545.     /**
  546.      * @param object $object Entity
  547.      *
  548.      * @return string
  549.      *
  550.      * @throws UploadableNoPathDefinedException
  551.      */
  552.     protected function getPath(ClassMetadata $meta, array $config$object)
  553.     {
  554.         $path $config['path'];
  555.         if ('' === $path) {
  556.             $defaultPath $this->getDefaultPath();
  557.             if ('' !== $config['pathMethod']) {
  558.                 $getPathMethod \Closure::bind(function (string $pathMethod, ?string $defaultPath): string {
  559.                     return $this->{$pathMethod}($defaultPath);
  560.                 }, $object$meta->getReflectionClass()->getName());
  561.                 $path $getPathMethod($config['pathMethod'], $defaultPath);
  562.             } elseif (null !== $defaultPath) {
  563.                 $path $defaultPath;
  564.             } else {
  565.                 $msg 'You have to define the path to save files either in the listener, or in the class "%s"';
  566.                 throw new UploadableNoPathDefinedException(sprintf($msg$meta->getName()));
  567.             }
  568.         }
  569.         Validator::validatePath($path);
  570.         $path rtrim($path'\/');
  571.         return $path;
  572.     }
  573.     /**
  574.      * @param ClassMetadata $meta
  575.      * @param array         $config
  576.      * @param object        $object Entity
  577.      *
  578.      * @return void
  579.      */
  580.     protected function addFileRemoval($meta$config$object)
  581.     {
  582.         if ($config['filePathField']) {
  583.             $this->pendingFileRemovals[] = $this->getFilePathFieldValue($meta$config$object);
  584.         } else {
  585.             $path $this->getPath($meta$config$object);
  586.             $fileName $this->getFileNameFieldValue($meta$config$object);
  587.             $this->pendingFileRemovals[] = $path.DIRECTORY_SEPARATOR.$fileName;
  588.         }
  589.     }
  590.     /**
  591.      * @param string $filePath
  592.      *
  593.      * @return void
  594.      */
  595.     protected function cancelFileRemoval($filePath)
  596.     {
  597.         $k array_search($filePath$this->pendingFileRemovalstrue);
  598.         if (false !== $k) {
  599.             unset($this->pendingFileRemovals[$k]);
  600.         }
  601.     }
  602.     /**
  603.      * Returns value of the entity's property
  604.      *
  605.      * @param string $propertyName
  606.      * @param object $object
  607.      *
  608.      * @return mixed
  609.      */
  610.     protected function getPropertyValueFromObject(ClassMetadata $meta$propertyName$object)
  611.     {
  612.         $getFilePath \Closure::bind(function (string $propertyName) {
  613.             return $this->{$propertyName};
  614.         }, $object$meta->getReflectionClass()->getName());
  615.         return $getFilePath($propertyName);
  616.     }
  617.     /**
  618.      * Returns the path of the entity's file
  619.      *
  620.      * @param object $object
  621.      *
  622.      * @return string
  623.      */
  624.     protected function getFilePathFieldValue(ClassMetadata $meta, array $config$object)
  625.     {
  626.         return $this->getPropertyValueFromObject($meta$config['filePathField'], $object);
  627.     }
  628.     /**
  629.      * Returns the name of the entity's file
  630.      *
  631.      * @param object $object
  632.      *
  633.      * @return string
  634.      */
  635.     protected function getFileNameFieldValue(ClassMetadata $meta, array $config$object)
  636.     {
  637.         return $this->getPropertyValueFromObject($meta$config['fileNameField'], $object);
  638.     }
  639.     protected function getNamespace()
  640.     {
  641.         return __NAMESPACE__;
  642.     }
  643.     /**
  644.      * @param object $object
  645.      * @param object $uow
  646.      * @param string $field
  647.      * @param mixed  $value
  648.      * @param bool   $notifyPropertyChanged
  649.      *
  650.      * @return void
  651.      */
  652.     protected function updateField($object$uowAdapterInterface $eaClassMetadata $meta$field$value$notifyPropertyChanged true)
  653.     {
  654.         $property $meta->getReflectionProperty($field);
  655.         $oldValue $property->getValue($object);
  656.         $property->setValue($object$value);
  657.         if ($notifyPropertyChanged && $object instanceof NotifyPropertyChanged) {
  658.             $uow $ea->getObjectManager()->getUnitOfWork();
  659.             $uow->propertyChanged($object$field$oldValue$value);
  660.         }
  661.     }
  662. }