Custom PHP Binaries with sqlite-vec SQLite Extension
I recently needed to add vector similarity search capabilities to a PHP application using SQLite. The sqlite-vec extension provides exactly this functionality, allowing you to store and query vector embeddings directly in SQLite. However, getting it to work with PHP turned out to be more complex than expected.
#The Challenge with SQLite Extensions in PHP
When you use SQLite from PHP, you may not be using a system-installed SQLite binary. Instead, often the SQLite library is compiled directly into the PHP binary itself. This means that if you want to use a SQLite extension like sqlite-vec, you can't simply install it system-wide and expect it to work.
Instead you have two options:
- Load the extension dynamically using PDO's
loadExtension()
method - Compile the extension directly into your PHP binary
Unfortunately, many PHP distributions have loadExtension()
disabled for security reasons. You can check if your PHP binary supports loading extensions by running the following command:
1php -d detect_unicode=0 -r '2 $pdo = new PDO("sqlite::memory:");3 var_dump(method_exists($pdo, "loadExtension"));4'
If this returns true
, you're in luck! You can simply grab a precompiled sqlite-vec build from the releases page and load it like this:
1$pdo = new PDO("sqlite::memory:");2$pdo->loadExtension('/path/to/vec0.dylib');
But if you don't have loadExtension enabled (as is the case in many production PHP builds), you'll need to compile your own custom php binary.
#What is sqlite-vec?
Before diving into the compilation process, let's understand why you might want to add this extension in the first place. Sqlite-vec is a SQLite extension that brings vector similarity search capabilities to SQLite databases. It allows you to:
- Store vector embeddings as a native SQLite data type
- Perform fast similarity searches using various distance metrics
- Build applications with semantic search, recommendation systems, and RAG (Retrieval-Augmented Generation) capabilities
This is particularly useful if you're working with AI models, embeddings from OpenAI, or building search features that go beyond simple keyword matching.
#Compiling sqlite-vec into PHP
Since we can't load the extension dynamically, we'll compile it directly into the PHP binary. For this task, I'm using static-php-cli, a powerful tool for building custom PHP binaries with specific extensions and configurations.
The process involves:
- Setting up the build environment
- Downloading the sqlite-vec source code
- Creating a patch script to integrate sqlite-vec into PHP's SQLite3 extension
- Building the custom PHP binary
Here's the complete build process for macOS on Apple Silicon. The general approach works on Linux and Windows too, though some specific commands will differ.
#1. Set Up Your Environment
First, install the necessary build tools:
1xcode-select --install2brew install automake autoconf libtool cmake pkg-config gzip wget coreutils
Create a workspace and download static-php-cli:
1mkdir -p ~/sqlite-vec-php/{php-bin,static-php-cli}2cd ~/sqlite-vec-php/static-php-cli3curl -fsSL -o spc.tgz \4 https://github.com/crazywhalecc/static-php-cli/releases/download/2.6.0/spc-macos-aarch64.tar.gz5tar -xzf spc.tgz && rm spc.tgz6chmod +x spc
#2. Create the Patch Script
The key to this process is a patch script that tells static-php-cli how to integrate sqlite-vec into the PHP build. Create a file called patch_sqlitevec.php
within the static-php-cli directory:
1<?php 2// patch_sqlitevec.php – works with static-php-cli ≥ 2.0 3use SPC\store\FileSystem as FS; 4 5$vecDir = __DIR__ . '/sqlite-vec'; // where sqlite-vec.c/h live 6$phpDir = SOURCE_PATH . '/php-src'; // root of extracted php-src 7$sqliteExtDir = $phpDir . '/ext/sqlite3'; 8$sqliteDir = $phpDir . '/sqlite3'; 9 10// 1 Create core_init.c manually11if (patch_point() === 'after-php-extract') {12 $coreInit = <<<C13#define SQLITE_CORE 114#include "sqlite3.h"15#include "sqlite-vec.h"16#include <stdio.h>17 18int core_init(const char *dummy) {19 return sqlite3_auto_extension((void *)sqlite3_vec_init);20}21C;22 23 file_put_contents($sqliteExtDir . '/core_init.c', $coreInit);24 file_put_contents($sqliteExtDir . '/sqlite-vec.c', file_get_contents($vecDir . '/sqlite-vec.c'));25 file_put_contents($sqliteExtDir . '/sqlite-vec.h', file_get_contents($vecDir . '/sqlite-vec.h'));26}27 28// 2 Overwrite config0.m4 with working content29if (patch_point() === 'before-php-configure') {30 $cfg = $sqliteExtDir . '/config0.m4';31 $workingM4 = <<<M432PHP_ARG_WITH([sqlite3],33 [whether to enable the SQLite3 extension],34 [AS_HELP_STRING([--without-sqlite3],35 [Do not include SQLite3 support.])],36 [yes])37 38if test \$PHP_SQLITE3 != "no"; then39 PHP_SETUP_SQLITE([SQLITE3_SHARED_LIBADD])40 AC_DEFINE([HAVE_SQLITE3], [1],41 [Define to 1 if the PHP extension 'sqlite3' is available.])42 43 PHP_CHECK_LIBRARY([sqlite3], [sqlite3_errstr],44 [AC_DEFINE([HAVE_SQLITE3_ERRSTR], [1],45 [Define to 1 if SQLite library has the 'sqlite3_errstr' function.])],46 [],47 [\$SQLITE3_SHARED_LIBADD])48 49 PHP_CHECK_LIBRARY([sqlite3], [sqlite3_expanded_sql],50 [AC_DEFINE([HAVE_SQLITE3_EXPANDED_SQL], [1],51 [Define to 1 if SQLite library has the 'sqlite3_expanded_sql' function.])],52 [],53 [\$SQLITE3_SHARED_LIBADD])54 55 PHP_CHECK_LIBRARY([sqlite3], [sqlite3_load_extension],56 [],57 [AC_DEFINE([SQLITE_OMIT_LOAD_EXTENSION], [1],58 [Define to 1 if SQLite library was compiled with the59 SQLITE_OMIT_LOAD_EXTENSION and does not have the extension support with60 the 'sqlite3_load_extension' function. For usage in the sqlite3 PHP61 extension. See https://www.sqlite.org/compile.html.])],62 [\$SQLITE3_SHARED_LIBADD])63 64 PHP_NEW_EXTENSION([sqlite3],65 [sqlite3.c],66 [\$ext_shared],,67 [-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1])68 PHP_SUBST([SQLITE3_SHARED_LIBADD])69fi70 71dnl ---- sqlite-vec static compile ----72AC_MSG_NOTICE([Adding sqlite-vec.c + core_init.c to SQLite3 static library])73AC_DEFINE([SQLITE_ENABLE_VEC0], [1], [Enable sqlite-vec virtual table])74AC_DEFINE([SQLITE_CORE], [1], [Build sqlite-vec as builtin])75PHP_ADD_SOURCES(ext/sqlite3, [sqlite-vec.c core_init.c])76 77PHP_SQLITE3_PRIVATE_SRCS="\$PHP_SQLITE3_PRIVATE_SRCS sqlite-vec.c"78AC_DEFINE([SQLITE_VEC_ENABLE_NEON],1,[Enable NEON])79AC_DEFINE([SQLITE_VEC_STATIC],1,[Static sqlite-vec])80M4;81 82 file_put_contents($cfg, $workingM4);83}84 85// 3 hook the extension in MINIT just before make86if (patch_point() === 'before-php-make') {87 $cfile = $phpDir . '/ext/sqlite3/sqlite3.c';88 FS::replaceFileUser($cfile, function ($code) {89 if (str_contains($code, 'sqlite3_vec_init')) return $code; // already patched90 $inject = "\n extern int sqlite3_vec_init(sqlite3*,char**,const sqlite3_api_routines*);\n"91 . " sqlite3_auto_extension((void(*)(void))sqlite3_vec_init);\n";92 return preg_replace('/PHP_MINIT_FUNCTION\\(sqlite3\\)\\s*\\{/', '$0' . $inject, $code, 1);93 });94}
#3. Configure Extensions and Libraries
Create configuration files for the PHP extensions and libraries you want to include:
1cd ~/sqlite-vec-php2cat > php-extensions.txt <<'TXT'3bcmath,bz2,ctype,curl,dom,fileinfo,filter,gd,iconv,mbstring,opcache,openssl,pdo,pdo_sqlite,phar,session,simplexml,sockets,sqlite3,tokenizer,xml,zip,zlib4TXT5 6cat > php-libraries.txt <<'TXT'7libjpeg,freetype,libwebp8TXT
#4. Download Dependencies
Download PHP 8.4 and all required dependencies:
1cd ~/sqlite-vec-php/static-php-cli2EXT=$(grep -v '^\s*#' ../php-extensions.txt | tr -d '\n ')3LIB=$(grep -v '^\s*#' ../php-libraries.txt | tr -d '\n ')4./spc download --with-php=8.4 --for-extensions "$EXT" --prefer-pre-built
#5. Get sqlite-vec Source
Download the sqlite-vec amalgamation build:
1curl -L -o sqlite-vec.zip \2 https://github.com/asg017/sqlite-vec/releases/download/v0.1.7-alpha.2/sqlite-vec-0.1.7-alpha.2-amalgamation.zip3unzip sqlite-vec.zip -d sqlite-vec
#6. Build Your Custom PHP Binary
Now for the main event - building PHP with sqlite-vec included:
1EXT=$(tr -d '\n ' < ../php-extensions.txt)2LIB=$(tr -d '\n ' < ../php-libraries.txt)3 4./spc build --build-cli --build-fpm "$EXT" --with-libs "$LIB" \5 -P patch_sqlitevec.php --debug
#7. Test Your New Binary
Once the build completes, test that sqlite-vec is working:
1buildroot/bin/php -r 'echo (new SQLite3(":memory:"))->querySingle("SELECT vec_version()"), PHP_EOL;'
If everything worked correctly, you should see the sqlite-vec version number printed to the console.
#Using sqlite-vec in Your PHP Application
With your custom PHP binary, you can now use sqlite-vec directly without any extension loading:
1$vectorDb = new PDO('sqlite::memory:'); 2$vectorDb->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 3 4// Create a table with vector column 5$vectorDb->exec('CREATE TABLE embeddings (id INTEGER PRIMARY KEY, vec F32_BLOB(384))'); 6 7// Insert a vector 8$vector = array_fill(0, 384, 0.1); 9$blob = pack('f*', ...$vector);10$stmt = $vectorDb->prepare('INSERT INTO embeddings (vec) VALUES (?)');11$stmt->bindValue(1, $blob, PDO::PARAM_LOB);12$stmt->execute();13 14// Query similar vectors15$queryVector = array_fill(0, 384, 0.15);16$queryBlob = pack('f*', ...$queryVector);17$stmt = $vectorDb->prepare('SELECT id, vec_distance_l2(vec, ?) as distance FROM embeddings ORDER BY distance LIMIT 5');18$stmt->bindValue(1, $queryBlob, PDO::PARAM_LOB);19$stmt->execute();20dd($stmt->fetchAll(PDO::FETCH_ASSOC));
#Packaging for NativePHP
If you're building this for use with NativePHP, you can package it like this:
1ROOT=~/sqlite-vec-php2SPC=$ROOT/static-php-cli3PKG=$ROOT/php-bin/bin/mac/arm644mkdir -p "$PKG"5cp $SPC/buildroot/bin/php "$PKG/php"6(cd "$PKG" && zip -q php-8.4.zip php && rm php)
#Conclusion
The sqlite-vec extension opens up exciting possibilities for PHP applications, from semantic search to recommendation systems. By following this guide, you can bring these vector search capabilities to your PHP projects without relying on external vector databases.
For more information about sqlite-vec capabilities and usage, check out the official documentation. And if you're interested in building custom PHP binaries for other purposes, the static-php-cli documentation is an excellent resource.