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 ]
April 8, 2026 | 12:00 pm (Virtual) Panel
EFS Seminar
Grants & Funding
Pre/Post Doc
April 9, 2026 | 5:00 pm Must have submitted survey of intent by March 19
Edge Review
Pre/Post Doc
Early career faculty
Mid career faculty
Senior faculty
April 10, 2026 | 2:00 pm (In-Person) Dr. Julie Bastarache | 2525 West End, VICTR 6th Floor Lounge
Productivity
Pre/Post Doc
Early career faculty
Mid career faculty
Senior faculty
April 29, 2026 | 12:00 pm (Virtual) TBA
EFS Seminar
Faculty Life
Writing & Publishing
Doing Research
Networking & Collaboration
Pre/Post Doc
Early career faculty
July 23, 2026 | 5:00 pm
Edge Review
Pre/Post Doc
Early career faculty
Mid career faculty
Senior faculty
August 13, 2026 | 5:00 pm Must have submitted survey of intent by July 23
Edge Review
Pre/Post Doc
Early career faculty
Mid career faculty
Senior faculty
Testimonials
Success

Assistant Professor

Department
Medicine (Cardiovascular Medicine)
Award Type
  • ASCI Physician Scientist Award

Assistant Professor

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

Assistant Professor

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

Assistant Professor

Department
Medicine, Division of Rheumatology and Immunology
Award Type
  • K23



find out first