Problem/Motivation
Currently there are a number of low-level file operations in file.inc. These are difficult to test, and have hidden and often circular dependencies. They should be moved to a service and cleaned up.
Proposed resolution
Move low-level file operations to the file_system service.
Remaining tasks
None
User interface changes
None.
API changes
Old functions in file.inc are deprecated, and all usages converted to using methods in the file_system service (\Drupal\Core\File\FileSystemInterface)
Data model changes
None.
Release notes snippet
See change record https://www.drupal.org/node/3006851
Original Summary
Problem/Motivation
file_unmanaged_copy() has an @todo saying
@todo Replace drupal_set_message() calls with exceptions instead.
added a long time ago by #517814: File API Stream Wrapper Conversion
Proposed resolution
Introduces a new service, file_handler.unmanaged
, and an accompanying interface. This service has methods for file operations, e.g. copy()
, which does exactly what file_unmanaged_copy()
does, except it throws exceptions. file_unmanaged_copy()
becomes a deprecated wrapper around this method, and if it throws any exceptions, they will be caught and logged, and the function will return FALSE.
In other words, as far as calling code is concerned, file_unmanaged_copy()
will behave exactly the same way as it did before. Perfect backwards compatibility. Bam!
Remaining tasks
Refactor allfile_unmanaged_*
functions into thefile_handler.unmanaged
handler service.- Create
file_handler.managed
service that has the same public API and deprecate old managed file API. - Deprecate all remaining functions in
core/included/file.inc
in favor of filesystem service:file_munge_filename(), file_create_filename(), file_destination()
, etc.
User interface changes
None.
API changes
- Create new hander services with accompanying interfaces and exceptions.
- Deprecate all
file_unmanaged_*()
functions and file_prepare_directory(), file_destination() and file_create_filename().
Related Issues
- @todo was introduced by #517814: File API Stream Wrapper Conversion
Release notes snippet
Many low level procedural functions to handle unmanaged files have moved to the file.system
service. The file_unmanaged_*()
functions and file_prepare_directory(), file_destination() and file_create_filename() have been deprecated.
Comment | File | Size | Author |
---|---|---|---|
#200 | 2244513-200.patch | 215.79 KB | alexpott |
#200 | 192-200-interdiff.txt | 5.08 KB | alexpott |
#192 | 2244513-file-192.patch | 215.86 KB | kim.pepper |
#186 | 2244513-file-186.patch | 215.91 KB | kim.pepper |
#186 | 2244513-file-186-interdiff.txt | 2.39 KB | kim.pepper |
Comments
Comment #1
andrei.dincu CreditAttribution: andrei.dincu commentedReplace drupal_set_message with throw new Exception.
Comment #3
andrei.dincu CreditAttribution: andrei.dincu commentedFixed tests that generated exceptions in #1.
Comment #4
Chadwick Wood CreditAttribution: Chadwick Wood commentedThis is my first patch review, so pardon if I do anything wrong! It looks like putting the Exception throw before the call(s) to watchdog is going to prevent the logging of this error that should happen, right? I think it would be better to put the Exception throw above
return FALSE;
That also brings up a minor issue that the returning of FALSE there doesn't make sense anyway, if an exception will be thrown before returning FALSE can even happen.
Comment #5
Chadwick Wood CreditAttribution: Chadwick Wood commentedActually, looking further into this, there are multiple places in this function where drupal_set_message would still need to be replaced with an Exception being thrown (basically, everywhere FALSE is currently returned). The patch only addresses the first error case.
Also, Exception messages should not be translated, so the use of t() here should be removed. See this issue: #2055851: Remove translation of exception messages
Comment #6
Chadwick Wood CreditAttribution: Chadwick Wood commentedComment #7
Chadwick Wood CreditAttribution: Chadwick Wood commentedComment #8
YesCT CreditAttribution: YesCT commentedjust re-wording a bit.
Comment #9
andrei.dincu CreditAttribution: andrei.dincu commentedReplace drupal_set_message() with exceptions in file_unmanaged_copy()
Replace t() with String::format() in message texts.
Alter tests.
Comment #11
andrei.dincu CreditAttribution: andrei.dincu commented9: 2244513-9.patch queued for re-testing.
Comment #13
sunLet's use explicit Exception classes per condition + a base class that can be caught; e.g.:
Comment #14
phenaproximaI might be wrong about this, but I don't think we're using format() in exceptions anymore -- IIRC, we just use sprintf(). (Also, the String class is long gone to support PHP 7. :)
Comment #15
phenaproximaMarking for re-roll due to usages of the String class.
Comment #16
phenaproximaComment #17
phenaproximaThis patch totally refactors file_unmanaged_copy(), turning it into a backwards-compatible wrapper around a new file_handler.unmanaged service. This service provides a copy() method which will throw exceptions (based on a new FileException class) when various error conditions occur.
Still needs doc comments and tests.
Comment #18
phenaproximaComment #19
phenaproximaThis issue wants to be its own parent?! Kinky, but no.
Comment #20
tim.plunkettRemember, methods like drupal_realpath() and drupal_dirname() are already deprecated wrappers around the file_system service, so this class should have that injected and use it instead.
Comment #21
phenaproximaGood point. Fixed in this patch.
Comment #23
jibranReroll.
Comment #26
phenaproximaRerolled again. Still needs tests...but I'm determined to get this into 8.3.x. It's long overdue.
Comment #27
phenaproximaI make test fun times!
Comment #29
phenaproximaD'oh. Like an idiot, caught the wrong exception in modified file_unmanaged_copy(). THIS patch oughta pass everything.
Comment #30
larowlannit: missing docblock
Can we replace these with injected services? If not, a follow up to to do so would be good.
if we're silently swallowing errors, shouldn't we have an else here?
nit: i think there's supposed to be a space here. And needs a docblock
Comment #31
chx CreditAttribution: chx at Smartsheet commentedThat wouldn't be acceptable but it does no such thing:
Comment #32
phenaproximaPoint. IS updated.
Comment #33
phenaproximaComment #34
20th CreditAttribution: 20th commentedI have tried to address @larowlan's feedback from #30. Uploading patch for testing.
FILE_EXISTS_*
constants from interface to make sure that they do not end up having different values by accident.UnmanagedFileHandler
.NonExistentFileException
into easier to readFileNotExistsException
.sprintf()
instead of variable interpolation to format exception messages.Comment #35
20th CreditAttribution: 20th commentedThe
file_unmanaged_*
API consists of 6 functions, as well as several other helpers that are shared between managed and unmanaged API. It does not make sense to refactor only one of them into a service and keep the rest of thefile.inc
untouched.In #2229865: [meta] Modernize File/StreamWrapper API @larowlan suggested to use League\Flysystem to replace Drupal file management code but this suggestion did not receive any support. Personally, I think that this change would be too large for a minor release, but D8 would still benefit from a File API based on services.
Comment #36
20th CreditAttribution: 20th commentedFirst of all, deprecate internal
file_unmanaged_prepare()
in the similar manner.Comment #37
phenaproximaThanks for the great work continuing this patch. I've wanted this to get in since Drupal 8.0.0, but I keep getting sidetracked with other things.
I think we should stick with self::FILE_EXISTS_RENAME, since UnmanagedFileHandlerInterface extends FileHandlerInterface by design.
Should read "Prepares the destination for a file copy or move operation."
Drupal core rarely if ever uses private anything, so this should read "will be converted into a protected method".
These should all document the circumstances under which each exception type is thrown.
Comment #38
20th CreditAttribution: 20th commented@phenaproxima
I will try to work on this patch as much as I can and your feedback is very appreciated!!
I agree with your comments in #37, uploading updated patch for testing. I will move
file_unmanaged_move
next.Comment #39
20th CreditAttribution: 20th commentedComment #41
20th CreditAttribution: 20th commentedWith this version of patch, all 6
file_unmanaged_*
function are moved intoUnmanagedFileHandler
service.Changes from previous patch:
UnmanagedFileHandler
: delete(), deleteRecursive(), move(), saveData().FileException
fromRuntimeException
because that is what SPL file classes throw.FileWriteException
,NotRegularFileException
.Old unmanaged and managed file APIs have very parallel structure
file_delete() vs. file_unmanaged_delete()
,file_copy() vs. file_unmanaged_copy()
, etc. I think it would be nice to have two services in corefile_handler.unmanaged
andfile_handler.managed
that have the same set of public methods and can be used interchangeably.Comment #42
20th CreditAttribution: 20th commentedComment #45
alexpottI think we also need to consider file_save_upload(), file_save_data() and file_save_htaccess(). It'd be great if the IS could list all the methods to be refactored into a service and what the new service and method name is. Also it'd be great if the initial refactor concentrated on the interface and service names rather than any internal refactoring. As I said in #2482783-62: File upload errors not set or shown correctly
Comment #46
alexpottSo let's start with a list of the non deprecated methods in file.inc and file.module that are related to this.
file.inc
Unmanaged file service?
Separate issue to move to 'file_system' service or a 'file_url/i' service
Separate issue to move to 'file_system'
Not sure
file.module
Managed file service - or stuff on the entity?
Weird hooks - the hook is file_validate but these have no module identifier - I would have expected file_file_validate_name_length() for example...
Not sure
Separate issue to deprecate in favour of entity methods
Comment #47
phenaproximaGlancing at this function, I think this should be made into a method of file entities.
FileInterface::getContentHeaders()
.I'm not sure if this would constitute a BC break, though. Maybe if we added it to the File entity class, but not FileInterface, and updated the function to accept only a File, rather than FileInterface, we could have a @todo to move
File::getContentHeaders()
to FileInterface in D9.Comment #48
BerdirSome random thoughts:
* I think we should avoid putting a lot of logic on the file entity class, so better make a service. But we could have methods that call that service, just like $entity->save() calls the storage handler?
* validate stuff isn't a hook, it's just callbacks that you specify, with arguments/configuration. Maybe we could use validation constraints for this?
* file_create_url() has an issue already: #2669074: Convert file_create_url() & file_url_transform_relative() to service, deprecate it
* Partially unrelated but I'm not sure why we have file_upload_max_size() and use it to validate the filesize. If it's really bigger than the allowed size ,then it possibly doesn't even get to that point. Due to generic entity validation, this can also fail on files that were uploaded to S3 or so which is super weird. Will open a dedicated issue.
* file_managed_file_submit() should be a static method on \Drupal\file\Element\ManagedFile, but \Drupal\image\Plugin\Field\FieldWidget\ImageWidget::validateRequiredFields() is a fun example where switching could possibly break something if someone else relies on that too.
* file_get_content_headers() sounds like a good match for a method on File, yes.
Comment #49
20th CreditAttribution: 20th as a volunteer and commentedPersonally, I think that File entity class is a perfect place to put refactored version of the current "managed file API".
If you think about it, all these three methods do a similar thing: create a record in the DB to reflect state of a file on disk, the difference is in how they process input arguments.
As of the other functions of the "managed file API"
Behind the scenes, they all will be using a couple of services but a public-facing API can be added to File entity directly.
I don't know if it will help the current discussion, but I've described the problems I see with the file API in the parent meta-issue #2229865: [meta] Modernize File/StreamWrapper API in #12.
Comment #50
BerdirThat's *exactly* what I said?
Also, $file->delete() already exists, so we can immediately deprecate those two methods.
Pretty sure we want to make this a meta/plan issue, so those delete functions would be an easy standalone API.
Comment #51
20th CreditAttribution: 20th as a volunteer and commented@Berdir
I wasn't trying to argue with what you said. On the contrary, just adding my two cents.
@alexpott
Thanks for the suggestion on where to move next. I really needed some feedback on this patch. Perhaps, #2229865: [meta] Modernize File/StreamWrapper API would be a better place to create a list of new services. There is one there already but it is almost three years out of date.
A lot of work has already been started. As @Berdir said, there is #2669074: Convert file_create_url() & file_url_transform_relative() to service, deprecate it for
file_create_url()
, there is also #2620304: htaccess functions should be a service opened by @phenaproxima, and other issues as well.Comment #52
dmsmidtNo updates from @phenaproxima, removing assignment.
Comment #55
Mile23Coming here from #2800267: Turn simpletest_clean*() functions into a helper class where we still have
file_unmanaged_delete_recursive()
as a dependency.Comment #56
anavarreComment #57
larowlanAdding file name to the issue title to help with issue searching
Comment #58
kim.pepperReroll of #41
Comment #60
kim.pepperA lot of the files point to core/includes/file.inc:846 which calls \Drupal::logger('file')->error($e->getMessage());
Do we need to initialise watchdog or something?
Comment #61
catchGiven #2821423: Dealing with unexpected file deletion due to incorrect file usage and #2835840: Track media usage and present it to the site builder (in the media library, media view, on media deletion confirmation, etc.) I think we should be completely rewriting this functionality (i.e. not using a counter) rather than converting it to OOP. Going to move this to 'needs more info'.
Comment #62
Berdir@catch: This is about the (unmanaged) low-level file_unmanaged_* API functions mostly, which have nothing to do with file usage which sets several API levels above it.
I don't think it needs to be postponed on that discussion.
Comment #63
catchDoh, probably I should have read the issue properly..
Comment #64
andypost@kim.pepper it points that a lot of exceptions trown, maybe better to rework usage somehow?
Comment #65
andypostAs example we have a lot of external url generation for files #2669074-37: Convert file_create_url() & file_url_transform_relative() to service, deprecate it and usage of (un)managed api still high in core as well, so both changes require followups to replace usage and rework, then we can discover why exception rate is so high.
not sure that all failed tests require dblog module enabled
Comment #67
kim.pepperReroll of #58
Comment #69
kim.pepperLooks like throwing an exception if the file could not deleted was something introduced here, which is throwing lots of exceptions in tests.
I've removed it to see what other fails we are getting.
Comment #71
Mile23Heads-up that #2987538: Notice: Constant FILE_EXISTS_RENAME already defined might require changing a test or two.
Or we could change
FileCopyTest
to use the newFILE_*
constants here.Comment #72
kim.pepperThanks Mile23. I'll try fixing some of these tests first.
Comment #73
andypostafter change record it should be added to all deprecated functions
Comment #74
kim.pepperAdded draft change record https://www.drupal.org/node/3006851
Comment #75
andypostLooks good, now only use of CR left
Comment #76
phenaproximaTagging for framework manager sign-off, since we're modifying Drupal core's API here.
Comment #77
kim.pepperAdded @see for CR and fixed codesniff issues in \Drupal\Core\File\UnmanagedFileHandlerInterface
Comment #78
kim.pepperAlso \Drupal\Core\File\UnmanagedFileHandler::prepare() is not in \Drupal\Core\File\UnmanagedFileHandlerInterface
This was marked as @internal on file_unmanaged_prepare() and its calling \Drupal::service('file_handler.unmanaged')->prepare() internally. Is this intentional?
Comment #79
andypostCoding standards still reports 7 additions
I think this doc block should not change especially about params because this is API still
Should point to #2620304: htaccess functions should be a service
Comment #80
andypostComment #81
kim.pepperAddresses feedback in #79
Also found an issue with it not checking the realpath.
Comment #82
larowlanThis looks good, a couple of observations,
We also need deprecation tests and trigger_error calls?
this needs to have a trigger_error with a link to a CR in the deprecation message, applies for all of these?
shouldn't UnmanagedFileHandler::copy handle the logging for us so there is parity with the existing function?
I think this is better (leave it up to the caller) but I think we have to give consumers like for like.
i think we retain these, even though its deprecated
unmanaged?
should we call this prepareDestination for clarity?
Comment #83
kim.pepper@larowlan thanks for the review!
Comment #84
kim.pepperRe #82.2 nevermind. I think I'll just move the logging as you suggested.
Comment #85
kim.pepperRe #82
Comment #87
kim.pepperThis replaces all usages of the deprecated unmanaged file functions. Lets see what testbot thinks.
Comment #89
kim.pepperFixed an unusual function name concatenation occurrence, as well as uncovered a bug in 2 tests which thought they were loading translation files but weren't.
Comment #91
kim.pepperWe're throwing exceptions now, so we need to expect them in tests. I also updated the changelog example to show you need to handle exceptions.
Comment #93
kim.pepperAdded some more expectExceptions to tests.
Comment #94
larowlancan we inject this?
same here?
c/p? should be 8.7.x
We should deprecate it straight away if that's the case?
Comment #95
kim.pepperRe #94
Comment #96
acbramley CreditAttribution: acbramley at PreviousNext commented1 and 2 from #94 addressed.
Tiny nit from 3:
Comment still talks about being marked internal but has been set as deprecated.
Comment #97
kim.pepperThanks @acbramley! Fixed.
Comment #98
acbramley CreditAttribution: acbramley at PreviousNext commentedThanks @kim.pepper! All feedback from #94 has been addressed.
Comment #99
alexpottThis code now doesn't make much sense because
saveData
is never going to return FALSE. It's going to throw an exception. The change to make these utility functions throw exception instead of return FALSE is always tricky to implement because these functions are so low level. Essentially every usage now needs to be wrapped with a try / catch to be sure of BC. For me that means we shouldn't do that in this issue because it makes a lot of work and is change on top of a refactor. That said this is one of the times when you get to do this so it is a good time to consider it. With these though because they are so low level and used by so many other utility like places I'm not sure it is worth it and I think we need more discussion on issue as to the advantages of throwing exceptions.Comment #100
kim.pepperComment #101
BerdirI do feel bad about my comment after so much work was put into this issue, the first part might realtively easy to adress, not so sure about the follow-up thought.
Surprised to see that the name hasn't been discussed more, at least not as far as I see. I think there are 3 different things that I don't like about it :p
* . as a separator seems strange here, if we use that then usually as a separator for a subsystem or so, e.g. entity. or language. (although we're also very inconsistent with this stuff, e.g. entity_type.manager. But file_handler isn't a component or anything. So if we have a dot, then file.something? But we also have file_system above, so we could just as well do file_something.
* The concept of an "unmanaged" API was introduced when we added file entities in 7.x and needed API functions that were lower-level than that. E.g. compared to file_move(), file_copy(). Without namespaces or anything, we had to come up with something to deal with that overlap. I think we can do better today and figure out a name for these low level file API that's better than "unmanaged".
* handler: What exactly is this handling? This doesn't tell me anything about what this service does, especially compared to the already existing file_system service. Would it maybe be an option to add these file_system to the existing FileSystem service?
--
Looking at this made me think again about the sad state of our low-level file API. I had a look at the git history and the only real change in there in the last 2+ years is a bugfix to the fairly new and actually not file system related file_url_transform_relative().
Is this really a part of Drupal that we want to and can maintain ourself?
Is there a good reason we can't use https://symfony.com/doc/3.4/components/filesystem.html, for example? Or http://flysystem.thephpleague.com/, which seems to be very actively maintained and might allow us to also remove/simplify a ton of custom code around stream wrappers as well.
Comment #102
kim.pepperThanks @Berdir! I think this is very useful feedback and worthy of discussion.
I'd be happy to consider leveraging existing filesystem components such as symfony filesystem or flysystem. I'm not sure how much work, or framework manager buy in there would be for this kind of change.
I think we'd need to be sure of that before doing any more work.
Comment #103
alexpottI think @Berdir raises an excellent point. It would be good to have some exploration of leveraging others work. One thing I think that will cause problems with adopting another framework is where the Drupal application bleeds into the low-level system. For example logging and configuration.
Another thing that @Berdir's comment makes me think is that we already have a file system service - why are we not adding these methods to that and then sometime in the future try and refactor that to leverage Symfony or PHPLeague's code? Hah, It looks like I suggested this split in #46. I think that that is what we are still missing - a discussion about the overall API and where everything belongs. Doing things piecemeal looks like we're ending up with a hodgepodge of services. I think I've over-egged the importance of the word
unmanaged
- this made more sense when the functions are all global but once they're on a file system service is more obvious they are low level and do not concern entities. If we consolidate these method on the file system service then it'll be easier to potentially move to another framework's code because we'll have a better idea of all the functionality we need.Comment #104
kim.pepper#103 Makes sense to me. I can give this a go later this week. Happy if anyone else wants to jump in in the meantime.
Comment #105
BerdirYeah, or as I said in #101: "Would it maybe be an option to add these file_system to the existing FileSystem service?" :)
+1 to moving these methods there and then later on evaluate if we can re-use symfony or something else there internally. Needs work for that, then.
I also added a comment to #2669074: Convert file_create_url() & file_url_transform_relative() to service, deprecate it about all the file_url related functions mentioned in #46.
Comment #106
kim.pepperOK. Moved everything over to FileSystem service, and deleted the UnmanagedFile service.
Discussed whether we should be adding exceptions here and agreed "I think now is the only time when we *can* add exceptions".
I expect there to be a few fails with this patch, but sharing anyway. :-)
Comment #107
BerdirSo this function is categorized as a file url related thing in #46 but I think the name is misleading. it looks like it's just a wrapper around several other functions. one is already a method on this service, another is a deprecated function on this service and the third is a pretty simple preg_replace function that is used in quite a lot of places.
Considering that, I would move this , including the functions it calls, into the file system service as well and deprecate the leftovers too.
I guess that makes sense as a separate issue, together with file_prepare_directory(), file_destination and that group, which #46 already suggests to do so in a separate issue.
While it will be a large service with a lot of methods, I think these things belong together.
no need for both then, I suppose we can remove the @return, will need quite a lot of test updates that are currently doing assertTrue()
search & replace thing, @see should refer to the interface, not this :)
leftover @var class + variable name
I guess we should confirm that this direction makes sense before going further but I like how this will clean this up, the code below is now not needed anymore.
I guess a $fille_handler => $file_system search & replace should take care of a lot of these.
Comment #109
andypostComment #110
kim.pepperI haven't been able to work out that test fail yet, but took care of the rest of the feedback in #107
Comment #112
kim.pepperFix some test fails.
Comment #113
kim.pepperFileSystemInterface::prepareDirectory() needs $directory to be passed by reference.
Comment #114
BerdirHm, this part was actually useful and might make sense to keep? Do we need a try/catch with manual fail here then?
these are now > 80 characters, given that it is deprecated, we can maybe remove the method reference? Technically, these new methods are not actually using this constants anymore anyway ;)
Comment #117
kim.pepperLooks like I missed importing FileSystemInterface for some of the find/replace of constants.
Re: #114
Comment #119
kim.pepperMore missing imports 🤦♂️
Comment #121
kim.pepper..and more
Comment #123
kim.pepper...and more.
Comment #125
BerdirThis might do the trick. The kernel test was failing because it defaulted to rename, so the self-copy didn't actually self-copy, added a separate method to test both versions.
And the warnings happened due to a missing @ inside prepareDestination() that is suppressing the warning. Somehow that got lost in the conversion. Kinda weird to do that like that, but it's been like thatbefore.
The level of nesting that's going on in those file system calls is pretty mindblowing and "fun" to debug as we call into the LocalStream implementation, which then goes through the deprecated functions back into the file_system service where the real action finally happens. We should create an issue to add @trigger_error()'s to the already deprecated functions in file.inc and convert them.
Comment #126
BerdirCross-posting my comment from the file_create_url() issue in regards to those uri functions that we also need to move somewhere.
This is definitely already big enough, so lets do a follow-up issue for these, with these we are getting close to having the whole file deprecated. Looking at what's left now..
* Another is file_destination(), that is actually small enough to possibly handle in this issue, I'm only seeing ~4 non-test calls to that. Could be challenging one to name, though. getDestinationFilename()?
* file_create_filename() is except in tests only used inside file_destination() in core at least. could also be a target for making protected in 9.x as you could always call it through the other method?
* file_munge_filename() is a pretty weird one, maybe we should have something like a file upload service, with functions related to handling uploads, which this clearly is. (That said, drupal_move_uploaded_file() was already moved to the file_system service, so we could also just move those to that as well)
* Same for file_unmunge_filename(), which is actually used only once in a test.
* file_upload_max_size(), another upload related thingy.
* the file status constant, that should be a separate issue to move it to FileInterface.
* Then we have file_delete() and file_delete_multiple(), which should be trivial deprecations, both are unused in core, same issue as th one above?
* file_directory_temp() is kinda weird and scary as it also updates configuration on the fly. not quite sure why that's even an "API", as temporary:// should have the same effect, except maybe in super-early installer or something?
* file_scan_directory() I think should be separate, there's an issue to use the symfony finder component, or it should be something in the extension subsystem?
Comment #127
kim.pepperThanks @Berdir. I created a number of follow up issues:
Re: #125 created #3021651: Add trigger_error to deprecated functions in file.inc in convert them
Re #126 created
Comment #128
kim.pepperRe #126:
Moved to file_system service and converted usages.
Comment #129
kim.pepperUpdated IS and change record.
Comment #131
kim.pepperMissed a usage.
I'm a bit concerned file_system will become a dumping ground for anything related to files. I'd like to keep it simple and restrict it to low-level file operations. Anything higher level, like file upload functions, uri functions etc. should be separate.
Comment #132
andypostI think `FILE_` prefix should be removed from new constants because they already in FileSystemInsteface
Comment #133
kim.pepperFixes for #132
Comment #134
kim.pepperFixes deprecation notices and converts a number of deprecated constants to the new ones.
Comment #135
BerdirUnsure about that. for create directory and modify permissions, it works I guess, but the exists constants seem a bit strange now IMHO.
Afaik all constants are about directories, so *maybe* we could include that in the name somehow?
DIRECTORY_CREATE
DIRECTORY_MODIFY_PERMISSIONS
DIRECTORY_EXITsS_...?
Alternatively RENAME_EXISTING? But then are no longer grouped together.
Just thinking out loud, see also comments below.
so what exactly does happen now with ERROR? Is it really still an error, isn't it an exception, and therefore should be called _EXCEPTION?
ok, so the exists constants are not only for directories, so directory definitely doesn't make sense here.
Also, this one seems double the fun:
a) as mentioned, ERROR actually does an exception now.
b) except this function is actually an existing one and we should not suddenly throw exceptions in it, which means we need to catch it and do what it it did before.
on error vs exception, one option might be to use separate values for error and exception and deprecate error without a direct replacement, then we could also support that in file_copy() if someone does want an exception. But hopefully these functions will also move somewhere.
Comment #136
BerdirExtensive review of the whole patch, focused specifically on error handling, consistent DI as well as the test coverage, which showed some inconsistencies with return values.
I believe we can do the change from return value to exception but there are various calls to update still. Hopefully once this review is addressed, it should be RTBC-able.
one call to \Drupal::service().
also prepareDirectory() does not yet throw an exceptions but does use return true/false. Should we change that too? Counting 50 usages to it, so that would be a ton of changes :-/
still called fileHandler here.
Not injected yet.
still uses the old constant.
repeating from previous comment, for completeness, I think file_copy() and file_move() should do a try/catch and return FALSE.
Pretty awkward comment already and getting worse, half code/half english and also references the deprecated constant (looks like these were not replaced yet, seeing 50 cases just for REPLACE. separate issue maybe as this is huge already?).
Maybe just something like this:
// If the destination is FALSE then there is an existing file and $replace is set to return an error, so we need to bail.
Or something like that.
the comment is a lie now, this will fail with an exception, so we need a try/catch. Maybe we can then also simplify the comment.
this should already have $this->fileSystem injecteed.
constant updated only in code but not comment, but as mentioned, I would suggest a follow-up to update them all.
Also this is checking for a boolean return value still, we need to try/catch and throw the http exception.
restoring the behavior of file_copy() means we can then also completely revert the changes to this test and likely others, making the patch a tiny bit smaller again ;)
file_save_data() is another candidate for a try/catch...
should we inject this?
try/catch
try/catch to make sure this can't somehow fail and break the uninstall?
same.
same.
inject?
try/catch?
this has it injected already.
FileCopy too (there are 3+ calls further down)
try/catch.
I'd say actually running tests can fail with an exception, likely won't get this far it wouldn't be writable anyway.
unrelated: the @todo points to an issue that was fixed in 2015 :)
should reference the interface instead of \Drupal::service()
Also > 80 characters, maybe rewrite to avoid the exact function call and put a @see or so?
try/catch?
inject? Was surprised it wasn't already, there are some calls to \Drupal::service() already as well as other deprecated file system methods.
another catch? this is already in a try/catch, so should be easy to add.
interesting that this still passes, looks like delete() actually still returns TRUE, despite not having a documented return value.
another such case above. and the deleteRecursive() tests will also be affected when changing this.
the assertFalse() is bogus now, the asserTrue() means lost test coverage, we make sure that we still don't delete the directory.
the assertFalse() below is dead code.
another reference to \Drupal::service() and > 80 characters.
Comment #137
kim.pepperThanks @Berdir. I agree with the general approach:
I've addressed most of your feedback, although there were a few hairy ones which I've outlined below.
Comment #139
kim.pepperFixed a leftover issue playing around with FileStorage.
Comment #141
kim.pepperLooks like I accidentally reverted changes in install.core.inc
Comment #143
BerdirThanks for all the work.
Not sure if something needs to be done about #135 now, the error vs exception constant for example is a fun one. The constants names might be OK otherwise, feedback from someone else would be great.
3. Maybe add it behind a method or so to limit the size of this issue? could still be mocked then if we'd want to.
7. I see, you added a try/catch now, then the comment remains the same, yes.
23. Maybe (yet another) separate issue about this? I'm not sure if we can simply remove the @todo or if there's still something that should be improved and didn't happen in that other issue.
strange change.
looks like we have nested try/catch here now. could be the existing with an additional catch statement.
A bit unrelated but in general +1 on the logging change, the existing code can't be parsed by potx.
do these methods really still have a return value?
this does a separate return, like I expected above.
now we're not actually asserting the exception. maybe do a fail after the call to delete(), which shouldn't be executed?
I was also wondering about the new test class you added, seems like quite a few fail-cases are covered in existing tests, so not quite sure how much we really need those new tests?
Comment #144
kim.pepperI can't seem to find the change that introduced these test fails!
Re #136
Re: #143
Re the test class, I think it was added waaaaay back. Happy to move these somewhere else, or remove completely?
Comment #146
BerdirLooks like the argument order here changed? I guess some left-over from trying to inject the file system service..
Also not yet sure about the approach we use there right now for using the using the file system service ( I know I suggested that). But it doesn't seem to put us in a a good way to move forward. Maybe just assign to a property in the constructor, with a follow-up to allow to inject it?
Comment #147
kim.pepperThanks @Berdir! Was scratching my head with that one. :-)
I've created a follow-up for FileStorage #3023222: Inject FileSystem into Config\FileStorage and added a todo in this patch.
Comment #149
kim.pepperYikes!
Comment #150
kim.pepperComment #151
kim.pepperLooks like calling \Drupal::service('file_system') in the constructor of FileStorage is the cause of the circular reference. I think we should punt that to #2940135: Use file_system service to \Drupal\Core\Config\FileStorage
It also highlights that file_system should probably have zero dependencies. We should try and remove logger and config.factory as dependencies if we can.
Comment #153
voleger#149 same issue happened here #3021434-11: Properly deprecate drupal_unlink()
I think #3021434: Properly deprecate drupal_unlink() should be postponed untill dependency of file_system will be resolved.
Comment #154
Anonymous (not verified) CreditAttribution: Anonymous commentedgoing to try fixing the failing tests.
Comment #155
Anonymous (not verified) CreditAttribution: Anonymous commentedfound the first fail.
Comment #157
Anonymous (not verified) CreditAttribution: Anonymous commentedfixed the last remaining fail. hopefully.
Comment #158
Anonymous (not verified) CreditAttribution: Anonymous commentedforgot status change to needs review.
Comment #159
BerdirSee #143, specifically 3. Those methods *should* not have a return value anymore according to their docblock but they kind of still do. So we shouldn't do return of them but probably just a return TRUE if no exception is thrown, instead of return $destination.
Comment #160
Anonymous (not verified) CreditAttribution: Anonymous commentedComment #161
Wim Leers@Berdir pointed me to this issue and asked me to review it.
I was frightened at first because of the patch size. But that turns out to be quite alright :) In the end, this is surprisingly simple, just very big!
Great work! 👏
👍 This file becomes a redirection graveyard. Everything is deprecated. Every call is redirected to a call on the
file_system
service.🐛 Nit: this name seems so generic it doesn't convey meaning?
🐛 Nit: and it's inconsistent.
Now this is a clear name: file operations.
Perhaps it should be called theFileOperations(Interface)
/file_operations
service?(We've renamed services before, we can redirect old calls automatically. I do realize this is a LOT of extra work though, so we better all be strongly in favor of this name before we even start doing it.)That wouldn't work for everything, for example
FileOperations::validScheme()
doesn't make sense;FileSystem::validScheme()
then makes more sense.Let's just update the inconsistent bits of documentation to all say
The file system service.
, like most parts of the patch already do (and like core already does!).👍 I wondered:
Oh because we now throw exceptions in case of an error, instead of returning FALSE. This is evident when you look at for example the updated
file_unmanaged_copy()
infile.inc
.Great! Explicit failures instead of requiring the caller to remember to do error handling. That's a step in the right direction. 👌
🤔
Why do we need to wrap some of this in a try/catch, but not
::prepareDirectory()
?Why does
FileSystem::prepareDirectory()
suppress errors instead of throwing exceptions? It does this, for example:It's odd that this appears to be the sole exception (pun not intended) to the pattern of using exceptions everywhere.
👍 Nice, this will make any future refactoring easier too :)
👍 #136.23 confirmed that this was indeed fixed a long time ago.
👍
👍 This is a new test to explicitly test every one of the exceptions that can be thrown.
We already test this implicitly in our test coverage, this ensures we explicitly don't regress.
👍 Explicit deprecation tests.
Comment #162
Berdir1. There are 4+ more issues to get there but yeah, that is the goal :)
2. - 4. Yes, as discussed, just make it consistent here. We're actually considering to move the stream wrapper related methods/functions to the stream wrapper instead, then this might make more sense then, but definitely for later. The symfony component is also named FileSystem.
6. Suppressing errors with @ is ugly but for now necessary, there are tests that file if some of these are missing. Maybe we can improve that later with doing explicit checks instead. But exception vs return value is a good question, especially combined with the flags. What I never quite understood is why prepareDirectory() doesn't create the directory by default, and if not having that set, is the directory not existing an exception state then? it's more like a does-this-directory-exist-check than preparing it.. but it would likely be an exception if you ask for it to be created and it's not possible. There are 50 usages, so would be a lot of work to change.
And I just found this interesting usage in prepareDestination():
So this indeed explicitly uses it as an is-writable-directory check and so at least that case is definitely not an exception-y situation. And when we need a boolean return value in some cases, I think we should for now just keep it for everything?
Comment #163
Wim LeersThat would then make it consistent, which is good.
But it seems that exceptions are actually better in general, since they force a developer to consider the edge cases instead of silently failing. So perhaps it’s better to stick to what this patch is doing currently.
On the other hand, it sure would make the transition easier if we don’t introduce exceptions.
There is no war best choice here, just trade-offs.
I think this is already a big step forward. It’s a huge patch. So let’s pick the path of least resistance?
Comment #164
Anonymous (not verified) CreditAttribution: Anonymous commentedthanks for the reviews. i'll try to address them this weekend.
Comment #165
kim.pepperRe-roll of #157 before addressing the feedback.
Comment #166
kim.pepperThis patch fixes #161 2 & 3. Seems that most of the discussions were resolved between @Berdir and @Wim Leers above.
Re: #159 the docblocks still say they return the destination. This still makes sense to me as $destination can be renamed if the file exists. Thoughts?
I also posted an issue in the Ideas project about switching to a 3rd-party lib eventually #3029088: Replace file system component with a 3rd party library. Please post your thoughts in there. :-)
Comment #167
BerdirYes, the return value feedback was a misunderstanding, I mixed up the methods.
$file_system? I think the everything-consistent rule for using camel case isn't going to work on an existing class.. Most examples below are $file_system.
Adding a completely new constructor is tricky for BC, I did find one subclass of that in flysystem, but it doesn't have a constructor atm: http://grep.xnddx.ru/node/24785993.
The only thing we could do is a $this->getFileSystem() that would fall back to \Drupal::service() and then do a @trigger_error(), but I think that's not worth it for this case.. might be for a commonly subclassed class.
same with camelCase, also here we could be nice and make the argument optional with a @trigger_error(), see my recent entity_manager issues for examples. See below for a bunch of these. It's a bit annoying, but you just need to add a = NULL and a copy & paste a bunch of if () statements in all these constructors.
Found two subclasses for this and the only reason they didn't have a constructor is because the maintainer didn't bother to do dependency injection:
http://grep.xnddx.ru/node/17593559
http://grep.xnddx.ru/node/24785995
same.
same here, one of the examples I mentioned is actually right below this :)
another.
one more.
another.
Comment #168
kim.pepperComment #169
Berdir1. I'm not quite sure myself, but yes, I think that is fine.
I don't really have anything to complain anymore at this point. Did multiple extensive reviews and I think it looks good now. It's definitely not perfect, not everything is injected (but there are some nasty recursions in some cases), we don't replace all usages of the deprecated constants and more, but I think it is a large step in the right direction (both conceptually and in patch size) and we have tons of follow-ups about all these issues.
One thing I'm not 100% sure about is FileSystemTest, it is nice test coverage of lots of special cases, but it likely also overlaps with a lot of existing test coverage that we have. @Wim in a recent review really liked the test, and reviewing it to see which parts of it are really new test coverage vs. duplications would be very time sensitive.
Getting this in asap would be extremely useful for tons of related and follow-up issues.
Comment #170
larowlanThis looks great and is a big step towards modernising this code
There are a few places we're now missing try/catch in my book.
inheritdoc?
no need for the else here because of the throw? would make it a bit cleaner
we need a try/catch here
same
same
same
same
same
same?
could we do them both in one try/catch?
should we return false here too?
need try catch
can't we just inject this and remove the wrapper now?
do these need try/catch?
Comment #171
kim.pepperThanks for the review!
Comment #173
kim.pepperOops. Accidentally changed prepareDestination() to protected.
Comment #174
BerdirRe #170.2, I'm also not sure what it refers to, the only else I found after validScheme() is the one in doPrepareDestination() but that makes sense, we are basically trying to show the passed in path and the resolved realname if there is one.
One thing I just noticed is that these exception message use sprintf(), I think that's a practice that we used for a while but then stopped again. Most of the strings that use it already use double quotes, so just change
sprintf("Failed to unlink file '%s'.", $path)
to"Failed to unlink file '$path'."
.I'm not sure how the doX() makes the make-internal easier, keeeping it in 9.x with that name would be a bit strange and it now means that we are calling a method on the file_system service that is not on the interface, which seems problematic.
What if we simply make it protected *now* and keep file_unmanaged_prepare() unchanged except adding a @trigger_error() that says that it is deprecated and should *not* be used. Because the deprecation message right now says you should use "\Drupal\Core\File\UnmanagedFileHandler::prepareDestination()", which is also the old class name (You should search for UnmanagedFileHandler, there are about 7 references to that left). And since getDestinationFilename() seems to be a possible replacement, maybe mention that in the deprecation message.
Comment #175
kim.pepperThanks @Berdir.
Comment #176
kim.pepperOops. Missed a line when reverting file_unmanaged_prepare().
Comment #178
BerdirThanks, missed that file_unmanaged_prepare() stuff in earlier reviews, I think all feedback from @larowlan (except maybe that one else thing where we weren't 100% sure what it is about) and me got addressed, so back to RTBC.
Comment #179
alexpottWe still need to do the if null dance here. I.e make $file_system optional and use \Drupal::service
Maybe we should consider making FileException abstract because I think the mix of generic FileExceptions and specific exception classes is confusing. Not sure.
The comment and the code do not match.
Afaics there's no need to make $destination and $replace optional.
I think we should remove comments like this - they add no value.
Let's add a follow-up to check all the comments in this file because I these are copy pastes.
This method has zero dependencies on anything and good be static. I wonder if we should consider that.
Comment #180
Berdir2. I pointed this out in #167 too, but since this is a new constructor, the NULL-dance is a bit more complicated, as written there, we would need to add a getFileSystem() method that does the fallback.
7. Not sure, I always think that could be an issue if we ever do need to change it. And if static, on Core or Component FileSystem class?
Comment #181
kim.pepperI re-rolled after #3023802: Show media add form directly on media type tab in media library went in.
Re: #179:
3. If we make it abstract, we need a separate sub-class exception for every situation. I think a generic file exception is warranted given we can't predict all possible use cases.
4. I re-copied over the original comment which makes sense.
5. Fixed
6. Created a follow up #3033942: Clean up comments in FileSystem
@Berdir covered 2. and 7. above.
I also re-added some comments on file_unmanaged_prepare() that were accidentally removed.
Comment #183
BerdirOk, back to RTBC to get feedback from @alexpott on the remaining points.
Comment #184
alexpottre: #179.2 given that the only contrib extension doesn't have a constructor doing the normal NULL dance would fix that we should at least do that. We could also add a getFileSystem() method if we want to be 100% BC - which I think we do. So let's do both.
re: #179.7 Sometimes I feel we should be trying to get all of this into component so we can ensure that other dependencies don't creep into this. We need to move StreamWrapper there and resolve the lovely circular dependency between \Drupal\Core\StreamWrapper\StreamWrapperManager::getViaUri() and \Drupal\Core\File\FileSystem::uriScheme() but as a long term goal I think we should pursue it. For now leaving createFilename as a public method feels okayish. The other thing that bothers me is that I'm not sure that using create filename directly is correct. I think we should consider protecting the method and telling people to call
\Drupal\Core\File\FileSystem::getDestinationFilename($destination, FileSystemInterface::EXISTS_RENAME)
instead. We then would have less surface area. And there'd be less chance of people calling php's builtinbasename()
which has a problematic bug we work around.Comment #185
BerdirWe discussed the constructor. There is no scenario where making the constructor argument optional would help, there is nothing that could have called it before and would need to be updated now. But we agreed on adding the getFileSystem() method, probably best as a private method that we can remove again later whenever we feel like it.
About StreamWrapperManager vs FileSystem, I just created #3034072: Move file uri/scheme functions from file.inc and FileSystem to StreamWrapperManager, I thought we had an issue for that already but apparently not. And there's also still the meta issue #2229865: [meta] Modernize File/StreamWrapper API.
Comment #186
kim.pepperFix for #179.2
I agree about trying to make it a component. As well as stream wrapper, we'd also need to remove logger and settings too.
Logging was discussed in #82 and @larowlan suggested although it should be moved to the calling code, we keep it like-for-like.
Settings is being used to get chmod modes. If this were a component, again we'd move this to the calling code.
Comment #187
Berdirthis is not necessary now, that's what I was trying to say above. It's impossible that someone was calling it without that argument, because the method did not exist.
Comment #188
alexpott@Berdir actually is that strictly true? http://grep.xnddx.ru/search?text=AssetDumper&filename= reveals at least one call to
new AssetDumper()
. With the patch in #186 they'll get a deprecation notice from the constructor. If we do it as you suggest they'll get a hard break.Comment #189
BerdirYou're right, I only thought about subclasses. Then back to RTBC. The deprecation message will not be thrown by the constructor but only from the getFileSystem() method, but I think that's enough?
Comment #190
amateescu CreditAttribution: amateescu for Pfizer, Inc. commentedLooking at the patch, it seems that we're not changing anything for managed file APIs, so we need a more accurate issue title :)
Comment #192
kim.pepperRe-roll of #186
Comment #193
kim.pepperJust a re-roll so back to RTBC
Comment #194
alexpottAdding issue credit to @andypost, myself, @Wim Leers, @acbramley for review comments that affected the patch.
Comment #195
alexpottCommitted b2baebe and pushed to 8.7.x. Thanks!
Comment #196
alexpottAdded release notes snippet.
Comment #199
xjmUnfortunately this issue is causing test failures on PHP 5.5:
https://www.drupal.org/pift-ci-job/1209388
Reverted so that the issue can be tracked down and resolved. Even though PHP 5.5 and 5.6 support will be officially EOL as of 8.7.0, we still are avoiding unnecessary PHP 5 breaks on the branch.
Thanks!
Comment #200
alexpottJust not using our PHPUnit compatibility bridge.
Comment #201
alexpottCommitted 76bf269 and pushed to 8.7.x. Thanks!
Comment #203
Mile23Current change breaks
simpletest_script_open_browser()
, which apparently does not have any test coverage.Running run-tests.sh with
--browser
gives you this:Needs a
use Drupal\Core\File\FileSystemInterface;
at the top.Comment #204
kim.pepperI created #3037218: run-tests.sh with --browser throws error Class 'FileSystemInterface' not found
Comment #205
dww@Mile23: Thanks for the bug report.
@kim.pepper: Thanks for moving it to a follow-up.
Back to fixed.
Cheers,
-Derek