如何在Swift中处理PHP错误

时间:2014-12-08 19:22:48

标签: php mysql swift error-handling nsjsonserialization

我正在使用PHP& JSON从数据库中提取一些数据。

这是我的PHP文件

<?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();
}} ?>

如果密码错误,则系统输出&#34;无法连接到DB&#34;

在我的程序中,我有处理错误的事情,但我被困在一个部分。

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!

如果我没有正确的密码,但是我的程序崩溃,但是拥有正确的IP地址和MYSQL数据库的端口/用户。

它崩溃了:

fatal error: unexpectedly found nil while unwrapping an Optional value

并指向 jsonResult 。这是有道理的,因为我没有找回两个字符串。

我的问题是,如果我的密码关闭,那么我的PHP文件会回显一个字符串。如何搜索该字符串以便我可以使用if语句并阻止我的应用程序崩溃?

2 个答案:

答案 0 :(得分:2)

你的问题可能在这一行(为清晰起见而包裹):

var jsonResult: [Dictionary<String, String>] = 
    NSJSONSerialization.JSONObjectWithData(data, 
        options: NSJSONReadingOptions.MutableContainers, 
        error: &err) as [Dictionary<String, String>]

当您的PHP脚本通过返回字符串报告错误时,它返回了无效的JSON。当您使用NSJSONSerialization.JSONObjectWithData对其进行解析时,如果JSON无效,则该方法将返回nil,就像您的那样。

然后取出该值并将其分配给您声明不是可选的Swift变量。尝试将nil分配给未使用?!声明的变量是Swift中的运行时错误。 (您在编译时没有收到错误,因为您使用as来转换值。)

解决此问题的一种方法是更改​​PHP,以便错误是正确的JSON:

echo "{ \"error\": \"Failed to connect to DB.\" }"; # or something, my PHP is rusty

但是这仍然使你的Swift程序处于脆弱状态;从服务器获取正确的JSON以使其崩溃。

最好将jsonResult变量声明为可选:

var jsonResult: [Dictionary<String, String>]? = 
    NSJSONSerialization.JSONObjectWithData(data, 
        options: NSJSONReadingOptions.MutableContainers, 
        error: &err) as [Dictionary<String, String>]?

然后在您的代码中,您可以显式检查jsonResult是否为nil,如果是,您知道发生了错误,并且可以返回查看data对象以查看它是什么是

即便如此,也会让你陷入困境。 JSON文档的根目录不必是字典;它可能是一个数组。即使它是字典,值也可能不是全部字符串;它们可以是数字,布尔值,嵌套数组或字典!

Objective-C相对宽松的类型检查使这很容易处理,但Swift更严格。最好的方法是使用特定于Swift的JSON libraries之一。这将使您的代码更加健壮。

祝你好运!

答案 1 :(得分:2)

有两个问题。一个是PHP,一个是Swift。

  1. 您的PHP真的不应该只报告错误消息。我建议它总是返回JSON。这将使您的客户端代码更容易适当地检测和处理错误。

    <?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);
    
    ?>
    

    请注意:

    • application/json;

    • 指定Content-Type标头
    • 始终返回包含

      的字典
      • a&#34;成功&#34;密钥,无论是真还是假;

      • 如果出现错误,error_code表示错误类型(1 =未提供密码; 2 =连接失败; 3 =未找到数据库; 4 =某些SQL错误);

      • 如果是错误,则表示错误消息字符串的error_msg字符串;以及

      • 如果成功,则为results数组(非常类似于您以前在根级别返回的数据)。

  2. 在Swift方面,你需要:

    • 更改它以查找这些各种服务器应用程序级错误(请注意,我将顶级结构设为字典,并将原始字典数组设为特定值;

    • 您可能需要主动检查statusCode对象的response,以确保服务器为您提供200返回码(例如404表示找不到页面等等;)

    • 您可能还想检查JSON解析错误(如果服务器中的某些错误导致无法返回格式正确的JSON);以及

    • 你真的应该百分之百地逃避密码(因为如果它包含+&个字符,否则它将无法成功传输。

      < / LI>

    因此,你可能有类似的东西:

    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()
    

    转义百分比的代码位于String类别中:

    extension String {
    
        // see RFC 3986
    
        func stringByAddingPercentEncodingForURLQueryValue() -> String? {
            let characterSet = NSCharacterSet(charactersInString:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")
            return self.stringByAddingPercentEncodingWithAllowedCharacters(characterSet)
        }
    }
    

  3. 其他一些辅助观察:

    1. 切勿以明文形式发送密码。将它们放在POST请求(不是网址)的正文中,然后使用https网址。

    2. 我个人不会使用应用级身份验证的MySQL密码部分。我将MySQL身份验证逻辑编码在服务器端,然后使用您自己的用户身份验证表。