Search code examples
phpswiftmacosprocessfoundation

Running PHP script with Foundation Process - Mac OS Monterey - Swift


Everybody knows some computer that was once setup and is not touchable by the end of its life. I had this kind of device. It was a simple macOS app that fetch data from MySQL and send it to the external server. From some reason, it couldn't be done otherwise.

The goal is to run a PHP Script in the Process task on macOS Monterey. To achieve it I made few steps on clear instance:

  1. Brew installation - Brew
  2. PHP installation - brew install php
  3. Preparing PHP Script:
<?php
    $servername = "www.example.com";
    $username = "login";
    $password = "pass";
    $dbname = "database";
    $port = 12345;

    $db = new mysqli($servername, $username, $password, $dbname, $port);
    
    if ($db->connect_error) {
        die('Connect Error (' . $db->connect_errno . ') ' . $db->connect_error);
    }
    
    $query = $argv[1];
    $result = mysqli_query($db, $query);
    $db->next_result();
    
    $rows = [];
    while($row = $result->fetch_array(MYSQLI_ASSOC)) {
        $object = new stdClass();
        foreach ($row as $key => $value) {
            $object->$key = $value;
        }
        
        $rows[] = $object;
    }
    
    echo json_encode($rows);
    exit;
?>
  1. Prepare Swift code to run PHP Script:
     let outputPipe = Pipe()
     let errorPipe = Pipe()

     let phpPath = Bundle.main.path(forResource: "Query", ofType: "php")!
     
     let process = Process()
     process.launchPath = "/opt/homebrew/Cellar/php/8.1.1/bin/php" //Could be found by using `brew info PHP` in terminal
     process.arguments = [phpPath, "call prepared_database_function(null)"]

     process.standardOutput = outputPipe
     process.standardError = errorPipe

     process.terminationHandler = { _ in
         let data = outputPipe.fileHandleForReading.readDataToEndOfFile()
         print(String(data: data, encoding: .utf8))

         let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
         print(String(data: errorData, encoding: .utf8))
     }

     do {
         try process.run()
     } catch {
         print(error)
     }
  1. I am running the program and...

And nothing happens... The problem is that Activity Monitor presents PHP task and I can Force Quit this process. When I will do it terminationHandler is called and then the output is printed.

How to force a process to end itself, as it is done in a terminal?


Solution

  • I found the solution which is an order of function calls. There is a proper Swift code to run PHP script inside macOS app:

    let errorPipe = Pipe()
    let outputPipe = Pipe()
    let phpPath = Bundle.main.path(forResource: "Query", ofType: "php")!
    
    let process = Foundation.Process()
    process.executableURL = URL(fileURLWithPath: "/opt/homebrew/Cellar/php/8.1.1/bin/php") //Could be found by using `brew info php` in terminal
    process.arguments = [phpPath, "call prepared_database_function(null)"]
    
    process.standardOutput = outputPipe
    process.standardError = errorPipe
            
    do {
         try process.run()
    } catch {
         print(error)
    }
            
    let data = outputPipe.fileHandleForReading.readDataToEndOfFile()
    let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
            
    process.terminationHandler = { process in
        print(String(data: data, encoding: .utf8))
        print(String(data: errorData, encoding: .utf8))
    }