flysystem: Introduce a common exception for failed commands
This is somewhat related to #611.
If I try to write a file on S3 using Flysystem, and a connection problem occurs, I get an S3 exception:
Aws\S3\Exception\S3Exception: Error executing “PutObject” on …
For Dropbox, I get a (subclass of) Dropbox\Exception.
First of all, these adapters do not respect the AdapterInterface contract by allowing an exception to be thrown, instead of returning false on failure.
Secondly, I would suggest throwing an exception in all cases when something goes wrong on the underlying storage. And to keep code portable (i.e. allowing to change the storage adapter at the flip of a switch), this exception should belong to the Flysystem package.
At the moment, I have to do:
try {
$filesystem->write($path, $contents);
} catch (S3Exception $e) {
// ...
}
If I decide to switch to Dropbox, I have to search through my entire codebase for Flysystem calls, and update them to something like:
try {
$filesystem->write($path, $contents);
} catch (Dropbox\Exception $e) {
// ...
}
And if I’m writing an app that can accept any adapter, using information from a config file, I have to take all possible errors into account:
try {
$filesystem->write($path, $contents);
} catch (S3Exception $e) {
// ...
} catch (Dropbox\Exception $e) {
// ...
}
And so on, without forgetting any adapter. As you can see, this is not a very good practice.
Introducing a common interface for these errors would allow us to call:
try {
$filesystem->write($path, $contents);
} catch (AdapterException $e) {
// ...
}
This way, there is no need to rewrite any code when switching to another adapter.
As I understand the project in its current status, you decided to return false in some methods, when something goes wrong. The fact is, the few adapters I had a chance to look at do not respect this contract properly (S3 and Dropbox thrown their own exceptions, Ftp and Local raise PHP errors).
There are two solutions to this problem:
- Fix all the adapters to catch any exception or error (including PHP warnings for adapters relying on native functions) and return
falsewhere appropriate - Change the contract to never return
falseon methods such aswrite(), and throw anAdapterExceptionon failure instead
I would strongly suggest the second option, because it breaks the application flow when something goes wrong; otherwise an error could go unnoticed if the developer forgets to check the return value using something like:
if (! $fs->write($path, $contents)) {
...
}
Plus, although returning false gives an opportunity to catch failures on operations such as write(), there is no such opportunity on methods that return boolean values, such as has().
Having all of them throw an exception in case of a failure harmonizes the whole thing.
Happily waiting for your feedback on this one. I could help rewrite the code if you agree with the idea!
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 13
- Comments: 19 (6 by maintainers)
With all due respect @frankdejonge, especially for the otherwise quality work you’ve done on this library, you don’t seem to get the point of specific, nested exceptions (no offense, I’ve been there too):
You actually closed #570 for the same reason:
If that helps, here is an excerpt from the Exception handling article on Wikipedia, which is worth reading:
You do not want to catch any exception when calling a flysystem method. You want to catch a specific exception, that every adapter will throw by wrapping their own, lower-level exception inside.
To better picture how that would translate into code, your
AdapterExceptioncould be very simple:The
FileSystemInterfacewould look like:And for example, the S3 adapter implementation would be something like:
This way, all adapters would respect a homogeneous API, and one could switch to another adapter without having to change the exception handling code, and without having to blindly catch
\Exception, which might incidentally catch some unrelated exception: for example, if the S3 adapter’supload()method suddenly throws something else than anS3Exception, it should not throw anAdapterException, and I do not want to catch this exception!Also, may I insist that several adapters don’t respect the original contract, which would be to return
falseinstead of throwing an exception or a PHP error in case of failure. Flysystem’s error handling seems to need some tidy up, and this would be a very nice opportunity.I was about to send an issue about that but this post is just perfect. I don’t even understand why we have to talk about it and why after all this time nothing change, it should be obvious.
Adapters wich doesn’t respect the interface are knowing the interface is bad about error handling, we are talking about API here, we have to try catch every API call to handle remote exceptions. An AdapterException is the good way, and the only way. You know it. Trust the force.