phpswiftpostmultipartform-dataoptional-chaining

$_POST variable from request contains optional instead of string in PHP


The use case is a user/client sends a request to a server running PHP. I am trying to write the PHP code on the server e.g. an API endpoint to process the request.

I have existing production PHP code that extracts a JSON web token (in the form of a string) from a JSON request where the content-type specified in the request from the client is application/json. The following code extracts the string using php://input which works as long as the content-type of the request is application/json.

$jsonString = file_get_contents('php://input');
$jsonArray = json_decode($jsonString, true);
$jwt =$jsonArray['jwt'];
var_dump($jwt); //THIS IS FOR DEBUGGING. COMMENTED OUT IN PRODUCTION

It returns the following string:

string(414) "hlMox0-LOTS OF CHARACTERS-za2b"

Please note the prefix string(414) is added by var_dump. If I merely echo the variable $jwt, it displays the string by itself without that prefix.

This JSON web token string can be successfully decoded to authenticate user.

I now need to write the code for another endpoint for client requests that enable the client to send an image file and metadata. In this case, to support file transfer, the user/client must specify content-type multipart/form-data in the request. However, php/input does not work with multipart data and the only way to retrieve the meta data is from the $_POST variable. In this case the resulting string when extracted and displayed with var_dump() is designated by var_dump as optional.

$jwt = $_POST['jwt'];
var_dump($jwt);

This yields:

"Optional(414)("hlMox0-LOTS OF CHARACTERS-za2b")";

Once again, var_dump adds the prefix Optional(414). If I just echo the value, then it is identical to the string in the first case.

Although this string when echoed appears identical to the one above, I cannot decode this variable. Var_dump suggests that although the strings echo the same they are different. The second version is an "optional." For those not familiar with the term optional, it is used in many programming languages to designate a variable that explicitly can either have a nil value or not. Evidently, PHP recognizes this idea with the var_dump function. However, I cannot find anything that expands on this in the documentation.

Why would a string accessed through the $_POST variable be different than one accessed through php://input?

Insofar as var-dump is identifying a difference between optional and string, why would accessing the data through $_POST yield an optional instead of a string?

Ultimately, how can I convert the $_POST['jwt'] obtained from $_POST to an ordinary string. What could be wrong with it?


Solution

  • Swift optionals are used where a value may be absent.

    An optional can be unwrapped to produce the wrapped value, if there is one.

    In your Swift code you are using string(forKey:) to obtain the JWT from User Defaults (As an aside, UserDefaults isn't a very secure place to store a token; The Keychain is a better choice).

    string(forKey:) returns an optional String because there may not be a value in UserDefaults for the specified key.

    When you access an optional in a String context, it returns "Optional value"`.

    You are accessing your values in a String context because you are using string interpolation ("\(value)"). This is because your dictionary elements are of type Any.

    Swift is a strongly typed language and wherever possible you should use specific types, rather than Any. If your dictionary was [String:String], for example, you would have received a compiler warning when you tried to store ajwt in the dictionary, because a String? (Optional String) is not a String - Strong typing catches errors at compile time.

    If your values aren't all strings you need to keep the dictionary as [String:Any].

    Either way, you need to unwrap the optional. The best way to do this is with a conditional let in a guard statement:

    if let ajwt = UserDefaults.standard.string(forKey: "ajwt") else {
        // Report some error because there is no JWT available
        print("No JWT")
        return
    }
    let parameters: [String:Any] = ["query":query, "jwt":ajwt,"id":id]
    ...
    

    Now, ajwt will be a String because you have conditionally unwrapped, printing an error and returning early if the jwt is not available. Your function could also throw an error in this case.