How to handle PHP errors in Swift
I am using PHP and JSON to fetch some data from a database.
This is my PHP file
<?php
error_reporting(0);
ini_set('error_reporting', E_ALL);
ini_set('display_errors','Off');
$mysqli = new mysqli("localhost", "root", $_REQUEST['password'], "");
if ($mysqli->connect_errno) {
echo "Failed to connect to DB.";
die();
} else {
$dbs = array();
$res = $mysqli->query("SHOW DATABASES");
$res->data_seek(0);
if ($res->num_rows > 0) {
while($row = $res->fetch_assoc()) {
$dbs[] = $row;
}
echo json_encode($dbs);
} else {
echo "Failed to get list of databases from server.";
die();
}} ?>
If the password is incorrect, the system displays the message "Failed to connect to the database"
In my program, I have things for error handling, but I'm stuck on one side.
let urlString = "http://\(hostTextField.text):\(portTextField.text)/dblist.php? &password=\(passTextField.text)"
let url: NSURL = NSURL(string: urlString)!
let urlSession = NSURLSession.sharedSession()
println(url)
println(urlSession)
//2
let jsonQuery = urlSession.dataTaskWithURL(url, completionHandler: { data, response, error -> Void in
println(response)
println(data)
if (error != nil) {
println("Can't connect using credentials")
dispatch_async(dispatch_get_main_queue(), {
HUDController.sharedController.hide(afterDelay: 0.1)
})
sleep(1)
var refreshAlert = UIAlertController(title: "Camaleon Reports", message: "Can't connect to the database", preferredStyle: UIAlertControllerStyle.Alert)
refreshAlert.addAction(UIAlertAction(title: "Retry", style: .Default, handler: { (action: UIAlertAction!) in
println("Yes Logic")
}))
self.presentViewController(refreshAlert, animated: true, completion: nil)
return }
var err: NSError?
var jsonResult: [Dictionary<String, String>] = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as [Dictionary<String, String>]
// 3
if (err != nil) {
println("Still cant connect....")
println("JSON Error \(err!.localizedDescription)")
}
var jsonDB : [Dictionary<String, String>] = jsonResult
for currentDictionary in jsonDB{
var currentEntry = currentDictionary["Database"] as String!
My program crashes if I don't have the correct password, but you have the correct IP and port / user for the MYSQL database.
Failure:
fatal error: unexpectedly found nil while unwrapping an Optional value
and points to jsonResult . This makes sense because I am not fetching two lines.
My problem is if my password is off then my PHP file is repeating the line. How can I find this line so that I can use the if statement and stop the application from crashing?
source to share
Your problem is probably on this line (wrapped for clarity):
var jsonResult: [Dictionary<String, String>] =
NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.MutableContainers,
error: &err) as [Dictionary<String, String>]
When your PHP script reports an error by simply returning a string, it returned invalid JSON. When you use NSJSONSerialization.JSONObjectWithData
to parse it, this method will return nil
if JSON is invalid like yours.
Then you take that value and assign it to the Swift variable you declared is not optional. Attempting to assign to a nil
variable not declared with ?
or !
is a runtime error in Swift. (You don't get a compile-time error because you are using as
to return a value.)
One way to fix this is to change your PHP so that the error is correct JSON:
echo "{ \"error\": \"Failed to connect to DB.\" }"; # or something, my PHP is rusty
But that still leaves your Swift program in a fragile state; getting anything other than proper JSON from the server will crash.
Better to declare the variable jsonResult
as optional:
var jsonResult: [Dictionary<String, String>]? =
NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions.MutableContainers,
error: &err) as [Dictionary<String, String>]?
Then in your code, you can explicitly check for jsonResult
nil, and if so, you know that an error has occurred and you can go back and look at the object data
to see what it is.
Even that, however, can leave you in trouble. The root of the JSON document does not have to be a dictionary; it could be an array. And even if it is a dictionary, the values may not be strings; they can be numbers, booleans or nested arrays or dictionaries!
Objective-C weak type checking makes this easy to deal with, but Swift is more strict. Your best bet would be to use one of the JSON libraries . This will make your code much more robust.
Good luck!
source to share
There are two questions. One is PHP and the other is Swift.
-
Your PHP should never really just report an error. I would assume it always returns JSON. This will make it easier for your client code to detect and handle errors appropriately.
<?php header("Content-Type: application/json"); $response = array(); error_reporting(0); ini_set('error_reporting', E_ALL); ini_set('display_errors','Off'); if (!isset($_REQUEST['password'])) { $response["success"] = false; $response["error_code"] = 1; $response["error_message"] = "No password provided"; echo json_encode($response); exit(); } $mysqli = new mysqli("localhost", "root", $_REQUEST['password'], ""); if ($mysqli->connect_errno) { $response["success"] = false; $response["error_code"] = 2; $response["mysql_error_code"] = $mysqli->connect_errno; $response["error_message"] = $mysqli->connect_error; echo json_encode($response); exit(); } if ($res = $mysqli->query("SHOW DATABASES")) { $dbs = array(); $res->data_seek(0); if ($res->num_rows > 0) { while($row = $res->fetch_assoc()) { $dbs[] = $row; } $response["success"] = true; $response["results"] = $dbs; } else { $response["success"] = false; $response["error_code"] = 3; $response["error_message"] = "Failed to get list of databases from server."; } $res->close(); } else { $response["success"] = false; $response["error_code"] = 4; $response["mysql_error_code"] = $mysqli->errno; $response["error_message"] = $mysqli->error; } $mysqli->close(); echo json_encode($response); ?>
Note:
-
Sets the title
application/json
forContent-Type
; -
Always returns a dictionary containing
-
the key "success", which is either true or false;
-
if error, a
error_code
indicating the type of error (1 = no password specified; 2 = connection failed, 3 = no databases found, 4 = some SQL error); -
if error, a string
error_msg
indicating the error message string; and -
if success, an array
results
(just like you used to return at the root level).
-
-
-
On the Swift side, you need:
-
Modify it to look for these various server-level errors (note that I am making the top-level structure a dictionary, and your original array of dictionaries a specific value;
-
You might want to proactively check the
statusCode
objectresponse
to make sure the server provided a return code200
(for example,404
means the page was not found, etc.) ..); -
You can also check for JSON errors for parsing (in case some error on the server did not allow to return correct JSON); and
-
You should really avoid the password (because if it includes
+
or characters&
, it won't pass successfully otherwise).
Thus, you might have something like:
let encodedPassword = password.stringByAddingPercentEncodingForURLQueryValue()! let body = "password=\(encodedPassword)" let request = NSMutableURLRequest(URL: URL!) request.HTTPBody = body.dataUsingEncoding(NSUTF8StringEncoding)! request.HTTPMethod = "POST" let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in // detect fundamental network error guard error == nil && data != nil else { print("network error: \(error)") return } // detect fundamental server errors if let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode != 200 { // some server error print("status code was \(httpResponse.statusCode); not 200") return } // detect parsing errors guard let responseObject = try? NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String : AnyObject] else { // some problem parsing the JSON response print(String(data: data!, encoding: NSUTF8StringEncoding)) return } // now parse app-level response to make sure `status` was `true` guard let success = responseObject!["success"] as? NSNumber else { print("Problem extracting the `success` value") // we should never get here return } if !success.boolValue { print("server reported error") if let errorCode = responseObject!["error_code"] as? NSNumber { switch (errorCode.integerValue) { case 1: print("No password provided") case 2: print("Connection failed; probably bad password") case 3: print("No databases found") case 4: print("Some SQL error") default: print("Unknown error code: \(errorCode)") // should never get here } } if let errorMessage = responseObject!["error_message"] as? String { print(" message=\(errorMessage)") } return } if let databases = responseObject!["results"] as? [[String : AnyObject]] { print("databases = \(databases)") } } task.resume()
Percentage skipping code is in the category
String
:extension String { // see RFC 3986 func stringByAddingPercentEncodingForURLQueryValue() -> String? { let characterSet = NSCharacterSet(charactersInString:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~") return self.stringByAddingPercentEncodingWithAllowedCharacters(characterSet) } }
-
Several other auxiliary observations:
-
Never send passwords to the box. Put them in the request body
POST
(not the URL) and then use the URLhttps
. -
I personally would not use the MySQL password part for application level authentication. I would keep the MySQL authentication logic server side coded and then use your own user authentication table.
source to share