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?

+3


source to share


2 answers


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!

+2


source


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

      for Content-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

      object response

      to make sure the server provided a return code 200

      (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 URL https

    .

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

+2


source







All Articles