OpenAI in PHP

OpenAI in PHP

How AI works in theory& how to use it in PHP

Week 1 – Introduction & How LLMs Work: Slides and video

Week 2 – Prompt Engineering: Slides and video

Week 3 – Retrieval-Augmented Generation: Slides and video

Week 4 – Fine Tuning & Agents: Slides and video

Week 5 – VUMC’s OpenAI in PHP: Slides and video

Generic OpenAI Class

namespace OpenAIApplication;
# Basic implementation; need to make more sophisticated for each use case
class OpenAI {
    const ENDPOINT = "https://vumc-openai-24.openai.azure.com/openai/deployments/";
    const URL_TAIL = "/chat/completions?api-version=2024-12-01-preview";     // might need to update with a version
    const NO_DATA = "No Data";

    public function __construct() {
        $this->apiKey = self::getAPIKey();
        $this->setDeployment("gpt-4.1");  // coordinated with OpenAI Studio
        $this->temperature = 0.7;   // normally creative, between 0.0 (deterministic) - 2.0 (crazy)
    }

    # TODO Edit with location of credentials directory
    public static function getAPIKey(): string {
        $apiKey = "";
        $credentialsDir = "/credentials";     // TODO
        if (!$credentialsDir) {
            die("Invalid credentials");
        }
        include($credentialsDir."/APP_NAME_HERE/openai.php");    // TODO
        if (!$apiKey) {
            throw new \Exception("No API Key!");
        }
        return $apiKey;
    }

    # TODO Example prompt; need to change
    public function getPubs(): string {
        $prompt = "Without any commentary, provide the plain text for all publications, one per line, under the publications header. Do not summarize or reformat the Markdown in any way and include it verbatim. If no publications are provided, provide blank text. Check your work step by step.\n\n".$this->markdown;
        $response = $this->submitPrompt($prompt);
        return self::getMessage($response);
    }

    public function setDeployment(string $deploymentId): void {
        $this->deploymentId = $deploymentId;
    }

    private static function getMessage(array $response, int $index = 0): string {
        return $response["choices"][$index]["message"]["content"] ?? $response["error"]["message"] ?? self::NO_DATA;
    }

    private function getURL(): string {
        return self::ENDPOINT.$this->deploymentId.self::URL_TAIL;
    }

    private function submitPrompt(string $message, bool $retryOnFailure = TRUE): array {
        $postdata = [
            "messages" => [
                [
                    "role" => "system",     // or "user"
                    "content" => $message
                ]
            ],    // can have multiple messages, especially one from "system" (context) and the other from "user"
            "temperature" => $this->temperature,
            "seed" => self::getSeed(),
        ];
        $opts = [
            CURLOPT_HTTPHEADER => [
                "api-key: ".$this->apiKey,
            ],
        ];

        list($resp, $json) = self::downloadURLWithPOST($this->getURL(), $postdata, $opts);
        $response = json_decode($json, TRUE);
        if ($resp == 200) {
            return $response;
        } else if (isset($response['error']['message'])) {
            if (preg_match("/Please retry after (\d+) seconds/i", $response['error']['message'], $matches)) {
                $sleepSecs = ((int) $matches[1]) + 1;
            } else {
                $sleepSecs = 30;
            }
            if ($retryOnFailure) {
                sleep($sleepSecs);
                return $this->submitPrompt($message, FALSE);
            } else {
                throw new \Exception("ERROR: ".$response['error']['message']);
            }
        } else {
            throw new \Exception("ERROR: $json");
        }
    }

    # Can set to fixed integer if desiring more determinism
    private static function getSeed(): int {
        return rand(0, 1000000);
    }

    private static function getDefaultCURLOpts(): array {
        return [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_VERBOSE => 0,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_AUTOREFERER => true,
            CURLOPT_MAXREDIRS => 10,
            CURLOPT_FRESH_CONNECT => 1,
            CURLOPT_TIMEOUT => 300,     // some OpenAI parsing can take a long time
            CURLOPT_SSL_VERIFYHOST => 2,
            CURLOPT_SSL_VERIFYPEER => TRUE,
        ];
    }

    public static function downloadURLWithPOST(string $url, array $postdata = [], array $addlOpts = []): array {
        if (!$url) {
            throw new \Exception("Invalid URL!");
        }
        $defaultOpts = self::getDefaultCURLOpts();
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        foreach ($defaultOpts as $opt => $value) {
            if (!isset($addlOpts[$opt])) {
                curl_setopt($ch, $opt, $value);
            }
        }
        foreach ($addlOpts as $opt => $value) {
            if ($opt != CURLOPT_HTTPHEADER) {
                curl_setopt($ch, $opt, $value);
            }
        }
        if (!empty($postdata)) {
            if (is_string($postdata)) {
                $json = $postdata;
            } else {
                $json = json_encode($postdata);
            }
            $json = Sanitizer::sanitizeJSON($json);
            if (!$json) {
                throw new \Exception("Invalid POST parameters!");
            }
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
            curl_setopt($ch, CURLOPT_POSTFIELDS, $json);
            curl_setopt($ch, CURLOPT_HTTPHEADER, array_merge([
                'Content-Type: application/json',
            ], $addlOpts[CURLOPT_HTTPHEADER] ?? []));
        }
        $data = (string) curl_exec($ch);
        $resp = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        if(curl_errno($ch)){
            throw new \Exception("Error number ".curl_errno($ch)." cURL Error: ".curl_error($ch));
        }
        curl_close($ch);
        return [$resp, $data];
    }

    protected $apiKey;
    protected $deploymentId;
    protected $temperature;
}
Questions
Email the Edge team at info@edgeforscholars.org
upcoming OpenAI in PHP events [ view all events ]
September 9, 2026 | 8:30 am (In-Person) Camp Widjiwagan, Percy Priest Lake
Faculty Life
Mentoring
Pre/Post Doc
Early career faculty
Mid career faculty
Senior faculty
September 15, 2026 | 11:00 am (In-Person) Dr. Eric Austin | 2525 West End, VICTR Suite 609 Boardroom
Grant Pacing Workshop
Grants & Funding
Early career faculty
September 17, 2026 | 12:00 pm (In-Person) Panel Discussion | MCN C-2209
EFS Seminar
Grants & Funding
Early career faculty
September 24, 2026 | 12:00 pm (Virtual) Panel Discussion
EFS Seminar
Grants & Funding
Pre/Post Doc
Early career faculty
September 28, 2026 | 9:00 am (In-Person) Dr. Eric Austin | 2525 West End, VICTR Suite 609 Boardroom
Grant Pacing Workshop
Grants & Funding
Early career faculty
October 7, 2026 | 11:00 am (In-Person) Dr. Julie Bastarache | 2525 West End, VICTR Suite 609 Boardroom
Grant Pacing Workshop
Grants & Funding
Early career faculty
October 8, 2026 | 12:00 pm (Virtual) Dr. Todd Rice
EFS Seminar
Doing Research
Management & Leadership
Early career faculty
October 14, 2026 | 1:00 pm (In-Person) Dr. Julie Bastarache | 2525 West End, VICTR Suite 609 Boardroom
Grant Pacing Workshop
Grants & Funding
Early career faculty
November 5, 2026 | 12:00 pm (Virtual) Dr. Mark de Caestecker
EFS Seminar
Grants & Funding
Faculty Life
Writing & Publishing
Pre/Post Doc
November 12, 2026 | 12:00 pm (Virtual) Tara Helmer & Kayce Gill
EFS Seminar
Writing & Publishing
Pre/Post Doc
Early career faculty
January 14, 2027 | 12:00 pm (In-Person) Panel Discussion | Location TBA
EFS Seminar
Mentoring
Pre/Post Doc
Early career faculty
January 28, 2027 | 12:00 pm (Virtual) Dr. Stephany Duda and Dr. Alvin Jeffery
EFS Seminar
Productivity
Life Hacks
Pre/Post Doc
Early career faculty
February 4, 2027 | 12:00 pm (In-Person) Panel Discussion | Location TBA
EFS Seminar
Grants & Funding
Writing & Publishing
February 18, 2027 | 12:00 pm (Virtual) Dr. Alvin Jeffery
EFS Seminar
Faculty Life
Pre/Post Doc
Early career faculty
February 25, 2027 | 12:00 pm (Virtual) Dr. Evan Brittain
EFS Seminar
Faculty Life
Early career faculty
Testimonials
Success

Assistant Professor

Department
Obstetrics & Gynecology, Division of Quantitative & Division of General Obstetrics
Award Type
  • K01 Recipient

Assistant Professor

Department
Medicine, Division of Allergy, Pulmonary and Critical Care Medicine
Award Type
  • K08 Recipient

Assistant Professor

Department
Pediatrics, General Pediatrics
Award Type
  • Academic Pediatric Association Research Award - Early Career



find out first