-
Notifications
You must be signed in to change notification settings - Fork 175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Refactoring] Proposed refactoring of FileUpload.class.inc #3476
[Refactoring] Proposed refactoring of FileUpload.class.inc #3476
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
well structured!
I haven't tested the code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review for DefaultFilesUploadHandler.class.inc
Comments, suggestions and change requests
* @param int $dirGroup if directory $moveDirPath has to be created | ||
* it will have this access group. | ||
* | ||
* @return nothing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This do net need to be specified. And it implicitly return the new instance of that class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Funny it passes phpcs...Anyway, I'll remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised it passed Travis. I thought phpcs complains if a constructor declares a return?
* Default directory to move the uploaded files to if none is explicitely | ||
* specified. | ||
*/ | ||
public static $DEFAULT_MOVE_DIR = __DIR__ . "../user_uploads"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this directory created in the install script?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No.
) { | ||
$this->_lorisForm = $lorisForm; | ||
$this->_moveDirPath = is_null($moveDirPath) | ||
? self::$DEFAULT_MOVE_DIR : $moveDirPath; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Null coalesce operator would make it fit in one line.
http://php.net/manual/fr/migration70.new-features.php#migration70.new-features.null-coalesce-op
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Will change.
* uploaded file. | ||
* @param string $errorMsg error message text. | ||
* | ||
* @return nothing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NULL
would be more accurate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or void
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no idea why phpcs forces me to have a return statement for a "void" method...Will change to @return NULL as requested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think @return NULL
is correct unless it's explicitly returning a value of null. Check what phan does, if PHPCS is allowing anything.. I'm fairly certain @return void
is the most robust thing for static analysis tools.
/** | ||
* Accessor for field $_dirGroup. | ||
* | ||
* @return int value for field $_dirGroup. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change int
to string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
try { | ||
if ($this->isValid()) { | ||
foreach ($this->getFiles() as $field => $file) { | ||
if ($file !== false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isValid
would be false in that case. This if statement can be removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually this was put there for a reason: to allow handling of optional files (i.e. those that might be uploaded but do not necessarily have to). The isValid() method would return true in case one of the optional files is missing and the if(...) code would still be executed. If it was not implemented that way, derived classes would have to override the process method to handle optional files. Makes sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then isValid
should handle optional files. And you could consider using the final
keyword if the behavior is critical.
} | ||
} | ||
// File name exists. Make sure it refers to a directory. | ||
} else if (!is_dir($newDir)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could have been checked in the constructor and throw an exception if !(new SPLFileInfo("$moveDir"))->isWritable()
} | ||
|
||
// If everything went OK so far | ||
if (!isset($this->_errors[$field])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if statement could be skipped if a continue
was added after the addError
call
* | ||
* @return SplInfo the path information for the new file location. | ||
*/ | ||
public function getNewLocation(string $tmpFilePath) : SplFileInfo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By passing the LorisFormFileElement instead of its tmp_name attribute, the foreach loop could be avoided. And this could instanciate the SPLFileInfo and check if it is writable before returning it. throw an exception maybe.
$newDir = $newFile->getPath(); | ||
|
||
// Check to see if directory exists | ||
if (!file_exists($newDir)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole block that check if the directory exists and create it if not should be encapsulated in a function so this processAll()
would look less convoluted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. I'll refactor.
* (i.e. at the very end of processAll()). Cleanup code typically is put into | ||
* this function. | ||
* | ||
* @return nothing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NULL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
* the file was successfully moved to its new location, the value | ||
* for this parameter will be null. | ||
* | ||
* @return nothing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NULL
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does PHPCS say it should be? I'm surprised it passed travis with "nothing."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it passes Travis and phpcs does not complain. I'll replace with @return NULL if that is the convention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@return void should be used to indicate the function doesn't return a value. @return Null
indicate that the function return the null value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docs says:
If the return is omitted the value NULL will be returned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer void since it reminds me of C++ void main (void)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer void too but since the doc says that a void method returns NULL, I think that I have to use "@return NULL"...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok for null, unlike C, php function always return something.
@@ -2224,6 +2257,19 @@ class LorisForm | |||
*/ | |||
class LorisFormElement | |||
{ | |||
var $el; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
protected
instead of var
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
* | ||
* @return SplFileInfo all the information regarding the new file location. | ||
*/ | ||
public function getNewLocation(string $fileName) : SplFileInfo; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it need to be public? I don't think it needs to be in the interface even.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
feedback on DefaultFilesUploadHandler
/** | ||
* The LorisForm used to upload the file(s). | ||
*/ | ||
private $_lorisForm; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This adds unnecessary coupling to LorisForm.
The places where it is used, it would make more sense to use a PSR7 ServerRequestInterface to get the files out of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The coupling is intentional. The purpose of the entire class is to provide default behaviour for entities that handle files uploaded via a LorisForm (which is what will be the case in LORIS). I think the coupling had to be avoided in the interface, so that you could have a concrete class that implements it but still uploads files via a mechanism other than LorisForm.
/** | ||
* When creating $_moveDirPath, these are the access rights to give it | ||
* (should be an octal number). | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it a string representing an octal number or is it an int? If the latter, the parenthesis is meaningless. Numbers don't have number bases as they're passed around in memory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment was more an indication of the literal representation that should be used for this number so that the calling code is easy to read and very UNIX-like. 0755 and 493 (0755 in decimal form) are valid arguments for this method and produce the same result, but 0755 is easier to read. I can remove the comment if you think it is misleading though.
*/ | ||
private $_dirPermissions; | ||
/** | ||
* When creating $_moveDirPath, give it this access group. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's an "access group"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be file group instead.
* All the properties of the uploaded files. Equivalent to the $_FILES | ||
* superglobal. | ||
*/ | ||
private $_files = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would make more sense to me to base this on \Psr\Http\Message\UploadedFileInterface than on the _FILES superglobal.
* in this array is a SplFileInfo. Each key is the name of the LorisForm | ||
* field used to upload the file. | ||
*/ | ||
private $_movedFiles; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This (and most of the things that have higher than private getter/setters) should be protected.
* in the LorisForm, then the upload is considered invalid and the error for that | ||
* field will be set to false. This method assumes that all the fields used for | ||
* file uploads have a value (i.e. no "optional" upload is allowed). This | ||
* behavior can be overridden bu subclasses should they need to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bu->by
* | ||
* @return nothing. | ||
*/ | ||
public function processAll() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be a lot more robust if it took a parameter of what needs to be processed (maybe defaulting to 'all' if null), rather than always needing to process all files. What if there's multiple handlers on a page that process different things?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add another method, say shouldProcess($file)
(or something along those lines, with a default implementation that always returns true
) and have process()
call shouldProcess()
to determine if a file should be processed or not?
*/ | ||
public function processAll() | ||
{ | ||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will partially process the array and leave things in an inconsistent state if something in the middle throws an exception.
It's debatable whether the try should be moved inside the loop or not, but regardless the content of the loop should be moved to a protected processFile($file) : bool
(with some appropriate type for $file
, or maybe throwing an exception instead of returning a bool) which is called for each file, so that a mechanism can be put in place for the programmer to determine how to deal with error cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You do have access to the _errors and _movedFiles array so you could perform any "cleanup" in processingDone if anything goes seriously wrong (provided the exception is passed to processingDone, which is not the case right now). This would provide similar behaviour IMHO. I do see your point though. I am kind of on the fence regarding this...
* | ||
* @return nothing. | ||
*/ | ||
public function onFileMoveEvent( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be a good idea to declare this as abstract rather than having a default which does nothing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think providing a default no-op method makes sense (e.g for handlers that only store files in specific locations and do not do anything else)
* nothing by default. Subclasses should override this method with any cleanup | ||
* code they require. | ||
* | ||
* @return nothing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs to specify that it's called regardless of whether or not the processing was successful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
@nicolasbrossard Can you break this into 2 PRs: 1 that just has the interface so that the appropriate design can be discussed, and then once that one goes in a second one to provide the default implementation? I think the reviews on this are mixing design and implementation concerns which muddies the waters a bit (partially because github is showing the implementation before the interface in the diff..) |
Break this into 2PRs: OK |
@nicolasbrossard what is the status of this ? |
Closig this since it's being broken down into multiple smaller PRs |
This PR is the proposed interface for classes that handle file uploads. It also contains a default implementation that could be used as the base class for all handlers in LORIS that will process one or more uploaded files. I have coded an implementation for the imaging uploader as a proof of concept and things seem to work fine (will be the object of another PR). After review, I can start refactoring all modules in LORIS so that this new class is used every time uploaded files are processed.