Problem/Motivation

Part of #3221796: [META] Modernise file upload logic. Replace file_managed_file_save_upload(), file_save_upload() and _file_save_upload_from_form with new services to separate the Form API and user-facing messages from API-level services.

Steps to reproduce

N/A

Proposed resolution

  • file_managed_file_save_upload() -> \Drupal::service(ManagedFileElementHelper::class)->managedFileSaveUpload()
  • _file_save_upload_from_form() -> \Drupal::service(ManagedFileElementHelper::class)->saveFileUploads()
  • file_save_upload() -> \Drupal::service(FormFileUploader::class)->saveFormUploadedFiles()

Remaining tasks

Review
Decide if we should remove the DI for Messenger and Renderer

User interface changes

N/A

API changes

See Proposed Resolution

Data model changes

N/A

Release notes snippet

N/A

Issue fork drupal-3375423

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

kim.pepper created an issue. See original summary.

kim.pepper’s picture

Issue summary: View changes
StatusFileSize
new1.1 KB
new1003 bytes
new337.63 KB
new300.7 KB
andypost’s picture

Thank you fur schemas!

larowlan’s picture

This feels like a good first step and is compatible with #3221796: [META] Modernise file upload logic

kim.pepper’s picture

Status: Active » Needs review

This deprecates:

  • file_managed_file_save_upload()
  • _file_save_upload_from_form()
  • and file_save_upload()

and replaces them with a few new services.

\Drupal\file\Upload\FileElementHelper::saveFileUploads(array $element, FormStateInterface $formState) replaces file_managed_file_save_upload($element, FormStateInterface $form_state) and _file_save_upload_from_form(). The intention is to separate all the form api and user-facing messaging into this service.

\Drupal\file\Upload\FormFileUploadHandler::saveFileUploads(string $uploadKey, array $validators, callable $errorHandler, ?string $destination = 'temporary://', string $replace = FileSystemInterface::EXISTS_RENAME) replaces file_save_upload($form_field_name, $validators = [], $destination = FALSE, $delta = NULL, $replace = FileSystemInterface::EXISTS_RENAME).

callable $errorHandler that accept three arguments:

  • \Drupal\file\Upload\UploadedFileInterface $uploadedFile
  • string $destination
  • \Exception $e

We then have a \Drupal\file\Upload\MessageCollectingErrorCallback which we pass to collect any errors and allows us to remove a load of the messy message deleting, and re-adding that we had in _file_save_upload_from_form()

\Drupal\file\Upload\FormUploadedFileRetriever removes the duplicated code of retrieving the uploaded files from the request, and is injected into FileElementHelper and FormFileUploadHandler.

Lastly, in order to not have to add a message for each file rename in our API-level code, instead we dispatch a \Drupal\file\Upload\FileUploadedEvent and added a MessengerFileUploadedSubscriber to add a status message if a file was renamed.

kim.pepper’s picture

I'm confused by the phpstan failure.

First it says its comparing against 10.2.x where the MR is for 11.x. Then its showing an error about a variable $file_upload which I couldn't find anywhere in file.module.

Running PHPStan on *all* files.
 ------ -------------------------------------------------------------------- 
  Line   core/modules/file/file.module                                       
 ------ -------------------------------------------------------------------- 
         Ignored error pattern #^Variable \$file_upload in empty\(\) always  
         exists and is not falsy\.$# in path                                 
         /var/www/html/core/modules/file/file.module was not matched in      
         reported errors

Edit:
Running ./core/scripts/dev/commit-code-check.sh --branch 11.x locally produces no errors.

kim.pepper’s picture

kim.pepper’s picture

Looks like I have an issue somewhere with setting the form errors and them being double-escaped:

One or more files could not be uploaded.<ul><li>The file is <em class="placeholder">1.25 MB</em> exceeding the maximum file size of <em class="placeholder">1 MB</em>.</li></ul>
smustgrave’s picture

Status: Needs review » Needs work

Seems to have some test failures.

@larowlan as a committer do you agree with the task being done?

kim.pepper’s picture

Issue summary: View changes
Issue tags: +Needs issue summary update, +Needs tests

Updating title and remaining tasks:

  • Add tests for new services
  • Add legacy tests for deprecated functions
  • Update mermain diagrams with current architecture
  • Update issue summary
kim.pepper’s picture

Title: Deprecate file_managed_file_save_upload and _file_save_upload_from_form and replace with a service » Deprecate file_managed_file_save_upload(), file_save_upload() and _file_save_upload_from_form() and replace with a service
kim.pepper’s picture

The image test fail is due to changes in the messages being displayed:

Old:

The specified file image-test.jpg could not be uploaded.

    The image is too small. The minimum dimensions are 50x50 pixels and the image size is 40x20 pixels.

New:

One or more files could not be uploaded.

    The image is too small. The minimum dimensions are 50x50 pixels and the image size is 40x20 pixels.
kim.pepper’s picture

Status: Needs work » Needs review

Finally, tests passing! 😅

kim.pepper’s picture

Issue summary: View changes
StatusFileSize
new1.27 KB
new360.36 KB

Updated the mermaid diagram to match the current MR.

kim.pepper’s picture

Updated IS and CR

kim.pepper’s picture

Issue summary: View changes
joachim’s picture

One or more files could not be uploaded.

The image is too small. The minimum dimensions are 50x50 pixels and the image size is 40x20 pixels.

This no longer tells you the filename of the problem file. If you uploaded several, how are you supposed to know which one to fix?

kim.pepper’s picture

I fixed that. There is no difference between HEAD and this MR now as to what is output.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

kim.pepper’s picture

Status: Needs work » Needs review

Rebase with 11.x

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

kim.pepper’s picture

Status: Needs work » Needs review

Merge with 11.x

kim.pepper’s picture

kim.pepper’s picture

Title: Deprecate file_managed_file_save_upload(), file_save_upload() and _file_save_upload_from_form() and replace with a service » [PP-1] Deprecate file_managed_file_save_upload(), file_save_upload() and _file_save_upload_from_form() and replace with a service
larowlan’s picture

Status: Postponed » Needs work

Blocker is in

larowlan’s picture

Title: [PP-1] Deprecate file_managed_file_save_upload(), file_save_upload() and _file_save_upload_from_form() and replace with a service » Deprecate file_managed_file_save_upload(), file_save_upload() and _file_save_upload_from_form() and replace with a service
kim.pepper’s picture

Going to try and avoid the whole 'message collector' business, and just return a new FileResults (plural) object that has a ConstraintViolationList. Might be a nice utility to write violations to Drupal::addMessage() or something like that.

larowlan’s picture

Removing tag until #28 is done

kim.pepper’s picture

Status: Needs work » Needs review

Finally tests back to green. ✅ Ready for reviews again.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: -Needs tests +Needs Review Queue Initiative

Hiding files from the bot.

Removing tests tag as coverage appears to be there for the deprecations here https://git.drupalcode.org/issue/drupal-3375423/-/jobs/1646261

Did leave a few comments on the MR.

andypost’s picture

btw deprecations should be updated to 11.0.0 for removal in 12.0.0

kim.pepper’s picture

According to @larowlan we should be deprecating in 10.4.0 for removal in 12.0.0. https://drupal.slack.com/archives/C1BMUQ9U6/p1716324332447859?thread_ts=...

kim.pepper’s picture

Issue summary: View changes
kim.pepper’s picture

Status: Needs work » Needs review

Updated all deprecation versions to drupal:10.4.0 and is removed from drupal:12.0.0

andypost’s picture

Status: Needs review » Reviewed & tested by the community

I find it ready now

larowlan’s picture

Status: Reviewed & tested by the community » Needs review

Couple of questions on the MR, nice work!

kim.pepper’s picture

Issue summary: View changes

Updated IS

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Re-reviewing and appears all feedback has been addressed.

CR is straight forward too.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

This change feels like it is doing more that it should. It is added typehints which mean we have to consider multiple angles of BC. It is changing logic deep in security focussed code. This should not be necessary to achieve the aims of the issue.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new90 bytes

The Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

kim.pepper’s picture

This got stalled most likely because it was trying to do too much.

I'm starting to split things off into smaller issues starting with #3555115: Add a UploadedFilesExtractor and remove duplicate code

nicxvan’s picture

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

kim.pepper’s picture

Title: Deprecate file_managed_file_save_upload(), file_save_upload() and _file_save_upload_from_form() and replace with a service » [META] Deprecate file_managed_file_save_upload(), file_save_upload() and _file_save_upload_from_form() and replace with a service

Propose converting this to a meta issue under #3221796: [META] Modernise file upload logic, since it stalled from combining too much scope. #3555115: Add a UploadedFilesExtractor and remove duplicate code already replaces FormUploadedFileRetriever with UploadedFilesExtractor.

Suggested child issues, in dependency order:

- Request-scoped memory cache for file uploads (no dependencies).
- FileUploadResult::getErrors() for FileExistsException and FileException; handler stops throwing them (no dependencies).
- New multi-file upload service returning array (depends on the two above).
- Deprecate file_save_upload() with a BC shim (depends on the new service).
- Migrate the ManagedFile widget and remove _file_save_upload_from_form() (depends on the new service).
- Migrate ThemeSettingsForm, ImportForm, and navigation SettingsForm (depends on the new service).
- Test cleanup: rewrite file_test, re-home SaveUploadFormTest assertions, convert Functional to Kernel tests where possible (depends on all above).

This replaces the FileElementHelper/FormFileUploadHandler design with smaller, independently reviewable issues.

I'll go ahead an start creating these sub-issues.

kim.pepper’s picture

Flagging the IS needs updating with the new sub-task approach

nicxvan changed the visibility of the branch main to hidden.

nicxvan changed the visibility of the branch 3375423-deprecate-file-save-upload to hidden.

nicxvan changed the visibility of the branch 3375423-uploadedfile-form-helper to hidden.

kim.pepper’s picture

Title: [META] Deprecate file_managed_file_save_upload(), file_save_upload() and _file_save_upload_from_form() and replace with a service » Deprecate file_managed_file_save_upload(), file_save_upload() and _file_save_upload_from_form() and replace with a service
Status: Needs work » Needs review

Since @nicxvan worked on this PR overnight, I've done some cleanup. We can postpone the sub-issues I created if this approach is viable.

  • Fixed the build by changing the deprecated MemoryCacheInterface to BackendCacheInterface
  • Changed the cache ID to be scoped per form field name (file:uploads:<field_name>).
  • Renamed FileElementHelper to ManagedFileElementHelper to avoid confusion between the form element \Drupal\file\Element\ManagedFile and the render element \Drupal\Core\Render\Element\File, and updated all references.
kim.pepper’s picture

StatusFileSize
new153.98 KB

Here's a before/after diagram.

mermaid diagram

nicxvan’s picture

Issue summary: View changes
nicxvan’s picture

Issue summary: View changes

I reviewed the changes @kim.pepper made and they all look good.

This could use a review, but one important note is this contains both:
#3616645: Form #value_callback does not support CallableResolver-style callables
#3616663: ElementInfoManager overwrites a form element's own #value_callback declaration

This is technically postponed on those two, but let's get this reviewed on top of those.

kim.pepper’s picture

I reviewed this code (it has been a while) and I think we should remove as much user facing code from FormFileUploader and let calling code handle Messenger calls. This resulted in a new result value object and bringing in #3619417: Return FileExistsException and FileException as data on FileUploadResult into this MR.

  • Incorporated #3619417 into this issue.
  • Added FileUploadResult::addError()/getErrors()/hasErrors().
  • FileUploadHandler::handleFileUpload() now returns FileExistsException and FileException as data instead of throwing them.
  • FormFileUploader no longer depends on Messenger or the Renderer; it returns errors as data on the result object.
nicxvan’s picture

I reviewed those changes, I think it's out of scope, had a short discussion in slack and pushed the MR with that change to #3619417: Return FileExistsException and FileException as data on FileUploadResult
I then reverted it here.

I think the open question is do we remove the renderer and messenger from DI since we're going to do the follow up quickly.

I think we should since we don't want those to be part of the api long term.

nicxvan’s picture

Issue summary: View changes

Did a closer review, the issues mentioned in the IS are not prerequisites.

#3616645: Form #value_callback does not support CallableResolver-style callables
#3616663: ElementInfoManager overwrites a form element's own #value_callback declaration

I just rebased to pull out the prerequisite commits and the exceptions.

I think the only remaining question is whether we need to remove DI for messenger and renderer.

kim.pepper’s picture

Status: Needs review » Reviewed & tested by the community

I have reviewed the changes that @nicxvan has been making, and (one minor nitpick remaining) I think this is ready. RTBC+1

kim.pepper’s picture

Note for committers: the API is pretty much a 1-to-1 conversion from the procedural code. @alexpott highlighted (somewhere?) the security risk of rewriting the whole API in one go, so I think we need to make these changes incrementally. Having said that, I'm keen to keep pushing forward on cleaning up remaining issues (e.g. the Messenger calls in what should be a pure API) in follow ups.

nicxvan’s picture

Status: Reviewed & tested by the community » Needs review

Broke some tests, let me fix that.

I've reviewed all of the changes that @kim.pepper made and I agree this is pretty much ready, but the last commit could use a review again.

nicxvan’s picture

Status: Needs review » Needs work

Still a couple more.

kim.pepper’s picture

Status: Needs work » Reviewed & tested by the community

The last couple of commits we just updating the default $destination param handling and a minor typehint change `int|null` -> `?int`. Looks good, so back to RTBC

nicxvan’s picture

Thanks, yeah I had to change a couple of places to handle that change.

nicxvan’s picture

I've addressed all of the feedback again. I also checked if we could remove the autowire, we can't yet.

  • amateescu committed 72844a3c on main
    task: #3375423 Deprecate file_managed_file_save_upload(),...
amateescu’s picture

Version: main » 11.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)
Issue tags: -Needs issue summary update

Committed 72844a3 and pushed to main. Thanks!

Needs a quick rebase on 11.x.

amateescu’s picture

Status: Patch (to be ported) » Fixed

Did the backport myself and committed e842449 and pushed to 11.x.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • amateescu committed e8424495 on 11.x
    task: #3375423 Deprecate file_managed_file_save_upload(),...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.