Skip to content
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

Closed

Conversation

nicolasbrossard
Copy link
Contributor

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.

Copy link
Contributor

@PapillonMcGill PapillonMcGill left a 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

Copy link
Contributor

@xlecours xlecours left a 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.
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Collaborator

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";
Copy link
Contributor

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?

Copy link
Contributor Author

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;
Copy link
Contributor

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

Copy link
Contributor Author

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.
Copy link
Contributor

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

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or void

Copy link
Contributor Author

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.

Copy link
Collaborator

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change int to string

Copy link
Contributor Author

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) {
Copy link
Contributor

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

Copy link
Contributor Author

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?

Copy link
Contributor

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)) {
Copy link
Contributor

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])) {
Copy link
Contributor

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
Copy link
Contributor

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)) {
Copy link
Contributor

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.

Copy link
Contributor Author

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NULL

Copy link
Contributor Author

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NULL

Copy link
Collaborator

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."

Copy link
Contributor Author

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.

Copy link
Contributor

@PapillonMcGill PapillonMcGill Feb 12, 2018

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.

Copy link
Contributor

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.

http://php.net/manual/en/functions.returning-values.php

Copy link
Contributor

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)

Copy link
Contributor Author

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"...

Copy link
Contributor

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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protected instead of var

Copy link
Contributor Author

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;
Copy link
Contributor

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.

Copy link
Collaborator

@driusan driusan left a 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;
Copy link
Collaborator

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.

Copy link
Contributor Author

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).
*/
Copy link
Collaborator

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.

Copy link
Contributor Author

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.
Copy link
Collaborator

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"?

Copy link
Contributor Author

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;
Copy link
Collaborator

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;
Copy link
Collaborator

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.
Copy link
Collaborator

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()
Copy link
Collaborator

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?

Copy link
Contributor Author

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 {
Copy link
Collaborator

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.

Copy link
Contributor Author

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(
Copy link
Collaborator

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.

Copy link
Contributor Author

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.
Copy link
Collaborator

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok

@driusan
Copy link
Collaborator

driusan commented Feb 12, 2018

@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..)

@nicolasbrossard
Copy link
Contributor Author

Break this into 2PRs: OK

@ridz1208
Copy link
Collaborator

@nicolasbrossard what is the status of this ?
can this one be closed ?

@nicolasbrossard
Copy link
Contributor Author

@ridz yes, this PR can be closed. #3514 replaces it.

@ridz1208
Copy link
Collaborator

Closig this since it's being broken down into multiple smaller PRs

@ridz1208 ridz1208 closed this Mar 21, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants