EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / Web / Build APM บน Windows ตอนที่ 6: build PHP 8.4 พร้อม redis imagick
Web

Build APM บน Windows ตอนที่ 6: build PHP 8.4 พร้อม redis imagick

โดย Ton-Or ·22 ต.ค. 2026·อ่าน 14 นาที
Build APM บน Windows ตอนที่ 6: build PHP 8.4 พร้อม redis imagick

PHP คือชิ้นที่ต้องอัปเดตเร็วที่สุดในสแตกนี้ และเป็นชิ้นที่ build ยากที่สุดบน Windows เพราะ flag ไม่เหมือน Linux ตอนนี้เลยเป็นตอนที่ละเอียดที่สุด ทุกบรรทัดของสคริปต์ถูกอธิบายพร้อมผลลัพธ์จริงที่ควรเห็น

พอดีถึงตอนที่ผมรอมาตลอดทั้งซีรีส์ คือ build PHP เอง ที่ทำซีรีส์นี้ขึ้นมาก็เพราะ PHP ปล่อย 8.4.27 แก้ช่องโหว่ RCE สองตัวเมื่อวันที่ 21 ตุลาคม 2026 (เรื่องเต็มอยู่ในข่าว php2shell) ขณะที่ XAMPP ในเครื่องผมยังเป็น PHP 8.2.12 จากปี 2023 ถ้ารอ bundle ออกรุ่นใหม่ก็ต้องรอไปอีกนาน ผมเลยทำให้ PHP เป็นไฟล์สคริปต์ตัวเดียวคือ scripts\30-php.ps1 ที่แก้เลขเวอร์ชันในไฟล์เดียวแล้วสั่ง build ใหม่ได้ภายในวันเดียวกับที่ upstream ปล่อย patch

ตอนนี้ผมจะพาไล่สคริปต์นั้นทีละบรรทัด ผมแยกเป็นขั้นย่อยเรียงเลข ทุกขั้นบอกว่ารันที่ terminal ไหน อยู่โฟลเดอร์ไหน ควรเห็นอะไร และตรวจอย่างไรว่าผ่าน คนที่ไม่เคย build โปรเจกต์ C/C++ มาก่อนทำตามได้โดยไม่ต้องเดา

กติกาของทั้งตอนนี้

ทุกคำสั่งรันใน PowerShell 7 (pwsh) แบบ user ปกติ ไม่ใช่ admin โดยโฟลเดอร์ปัจจุบันคือ C:\apm-build เสมอ ไม่ต้องเปิด Developer Prompt เพราะสคริปต์โหลด environment ของ MSVC ให้เอง

ก่อนเริ่ม ต้องมีของสี่อย่างรออยู่แล้ว

PHP ใช้ของที่ตอนก่อนหน้าเตรียมไว้ ถ้าขาดอย่างใดอย่างหนึ่งสคริปต์จะล้ม หรือที่แย่กว่าคือ build ผ่านแต่ขาด imagick โดยไม่มีข้อความเตือน ผมเลยให้ตรวจก่อนทุกครั้ง

ขั้นที่ 1: เปิด terminal ที่ถูกต้อง

กดปุ่ม Windows พิมพ์ pwsh แล้วเปิด PowerShell 7 (ไอคอนสีดำ ไม่ใช่สีน้ำเงินของ Windows PowerShell เวอร์ชันเก่า) จากนั้นรัน

cd C:\apm-build

ตรวจว่ามาถูกที่ด้วยคำสั่งนี้ ต้องเห็น path ตามนี้

Get-Location
Path
----
C:\apm-build

ขั้นที่ 2: ตรวจว่าของที่ต้องใช้ครบ

รันสี่บรรทัดนี้ (แต่ละบรรทัดมีคอมเมนต์บอกว่ากำลังตรวจอะไรและมาจากตอนไหน)

Test-Path C:\apm-build\toolchain\php-sdk\phpsdk-starter.bat   # php-sdk จากตอนที่ 2 (00-fetch)
Test-Path C:\apm-build\src\php-8.4.27                         # source ของ PHP จากตอนที่ 2
Test-Path C:\apm-build\build\im-dev                           # header + lib ของ ImageMagick จากตอนที่ 5
Get-ChildItem C:\apm-build\src -Directory -Filter 'pecl-*' | Select-Object -ExpandProperty Name

ผลที่ควรเห็นคือ True สามบรรทัด ตามด้วยชื่อโฟลเดอร์ PECL สามตัว

True
True
True
pecl-igbinary-3.2.16
pecl-imagick-3.8.1
pecl-redis-6.3.0

ถ้าบรรทัดไหนเป็น False ให้กลับไปทำตอนที่ระบุในคอมเมนต์ก่อน อย่าเพิ่งรัน build

กับดัก build\im-dev

สคริปต์ใส่ --with-imagick เฉพาะเมื่อโฟลเดอร์ build\im-dev มีอยู่ ถ้าไม่มี มันข้ามไปเงียบๆ แล้ว build PHP ที่ไม่มี imagick ออกมาเฉยๆ ไม่มี error ผมรู้เพราะอ่านสคริปต์ ไม่ได้ลองทำให้พังจริง วิธีป้องกันคือตรวจบรรทัดที่สามด้านบนให้เป็น True ทุกครั้ง

ขั้นที่ 3: รู้ก่อนว่าจะใช้เวลาเท่าไร

รอบแรกใช้เวลาประมาณ 12 นาทีบนเครื่องผม (i5-12500H, RAM 32 GB) รอบที่สองที่ cache ยังอยู่ใช้ 5.0 นาที ตลอดการ build ตัวสคริปต์ ไม่พิมพ์อะไรบนหน้าจอเลย เพราะส่งทุกอย่างเข้า log ไฟล์ ผมจะบอกวิธีเปิดดู log สดๆ ในหัวข้อที่รัน build

12 นาทีbuild รอบแรก
5.0 นาทีbuild ซ้ำ (cache อยู่)
127 MBstage\php ที่ได้
51module ใน php -m

ภาพรวมของ 30-php.ps1 และโหมด -Step

สคริปต์มีพารามิเตอร์ตัวเดียวคือ -Step มีสี่ค่า ใช้แยกงานได้เพื่อไม่ต้องรอทั้งก้อนเวลาตรวจชื่อ flag หรืออัปเดตแค่ DLL ของ deps

-Step ทำอะไร ไฟล์ log ใช้เมื่อ
deps phpsdk_deps -u -b 8.4 ดึงชุด DLL ของ PHP team ลง build\php-deps php-deps.log อยากอัปเดต lib อย่างเดียว
help รัน buildconf แล้วพิมพ์ configure --help php-help.log ตรวจชื่อ flag ก่อนแก้ configure
build ลบ obj เดิม, buildconf, configure, nmake, nmake install, คัดลอก DLL php-build.log deps ยังอยู่ แก้แค่ option หรือ source
all deps ต่อด้วย build php-all.log build ครั้งแรกและทุกครั้งที่อัปเดตเวอร์ชัน

ทุกขั้นรันใน environment ของ php-sdk ผ่านตัวเปิดชื่อ phpsdk-starter.bat ซึ่งโหลด MSVC ชุด vs17 แล้วเพิ่ม bison กับ re2c ของ SDK เข้า PATH ให้ ผมแสดงลำดับของ -Step all ในผังนี้

-Step all คือแปดขั้นต่อกัน (รอบแรก 12 นาที)1 depsphpsdk_deps -uโหลด DLL ชุด 8.42 buildconf.\buildconf.batสร้าง configure.js3 configure.\configure.batเลือก ext และ lib4 nmakenmakeคอมไพล์ทั้งหมด5 installnmake installลง stage\php6 copy DLLdumpbinไล่หา DLL ที่ใช้จริง7 ImageMagickCORE_RL_*.dll+ IM_MOD_RL_*, *.xml8 php -vphp.exe -vยืนยันเวอร์ชันก่อนขั้น 2 สคริปต์ลบ build\php-obj กับ stage\php ทิ้งทุกครั้ง-Step deps = ขั้น 1 · help = ขั้น 2 + configure --help · build = ขั้น 2 ถึง 8 · all = ขั้น 1 ถึง 8
ลำดับของ -Step all กล่องสีแดงคือจุดที่ผมเสียเวลามากที่สุด เพราะชื่อ flag บน Windows ไม่เหมือน Linux

นี่คือสคริปต์ทั้งไฟล์จาก kit ที่ดาวน์โหลด ผมไม่ได้ตัดบรรทัดไหนออก ลองไล่อ่านคร่าวๆ ก่อน แล้วค่อยดูคำอธิบายทีละช่วงในหัวข้อถัดไป

# 30-php.ps1 - build PHP 8.4 NTS x64 (vs17) with php-sdk + PHP-team deps series
# Usage: pwsh scripts\30-php.ps1 [-Step deps|help|build|all]
param([ValidateSet('deps','help','build','all')][string]$Step = 'all')
$ErrorActionPreference = 'Stop'
$Root = Split-Path $PSScriptRoot -Parent
$V    = Get-Content "$Root\versions.json" -Raw | ConvertFrom-Json
$PhpV = $V.sources.php.version

$Sdk   = "$Root\toolchain\php-sdk"
$Src   = "$Root\src\php-$PhpV"
$Deps  = "$Root\build\php-deps"
$Pecl  = "$Root\build\php-pecl"
$Obj   = "$Root\build\php-obj"
$Stage = "$Root\stage\php"
New-Item -ItemType Directory -Force $Deps, $Pecl, $Obj, "$Root\build\logs" | Out-Null

# PECL sources -> build\php-pecl\<extname> (buildconf --add-modules-dir wants ext-named folders).
# Refresh on every run: the folder name carries no version, so a stale copy would be reused silently.
foreach ($e in 'redis','igbinary','imagick') {
    $from = "$Root\src\pecl-$e-$($V.sources."pecl-$e".version)"
    if (-not (Test-Path $from)) { throw "[ERR] $from missing - run 00-fetch.ps1" }
    if (Test-Path "$Pecl\$e") { Remove-Item "$Pecl\$e" -Recurse -Force }
    Copy-Item $from "$Pecl\$e" -Recurse
}

$Configure = @(
  '--disable-all','--disable-zts','--enable-cli','--enable-cgi',
  '--enable-opcache','--enable-opcache-jit',
  '--with-openssl','--with-curl','--enable-zlib','--with-libxml','--with-dom','--with-xml',
  '--enable-xmlreader','--enable-xmlwriter','--with-simplexml','--enable-tokenizer',
  '--enable-session','--enable-filter','--enable-ctype','--enable-phar','--with-iconv',
  '--enable-mbstring=shared','--enable-mbregex','--enable-intl=shared','--with-gd=shared',
  '--with-libwebp','--with-libavif','--with-pcre-jit','--enable-redis-session',  # default-on, but --disable-all turns them off
  '--with-gettext=shared','--with-ffi=shared','--enable-fileinfo=shared','--enable-exif=shared',
  '--enable-bcmath','--enable-calendar','--enable-ftp=shared','--enable-shmop=shared',
  '--enable-sockets=shared','--enable-soap=shared','--with-sodium=shared','--with-xsl=shared',
  '--enable-zip=shared','--with-mysqlnd','--with-mysqli=shared','--enable-pdo',
  '--with-pdo-mysql=shared','--with-pdo-sqlite=shared','--with-sqlite3=shared',
  '--enable-igbinary=shared','--enable-redis=shared','--enable-redis-igbinary',
  $(if (Test-Path "$Root\build\im-dev") { "--with-imagick=shared,$Root\build\im-dev" }),  # from 25-imagemagick.ps1
  "--with-extra-includes=$Pecl",  # redis includes "igbinary/igbinary.h"
  "--with-php-build=$Deps","--enable-object-out-dir=$Obj","--with-prefix=$Stage"
) -join ' '

$task = switch ($Step) {
  'deps'  { "call phpsdk_deps -u -b $($V.php_deps_series) -d `"$Deps`" || exit /b 1" }
  'help'  { "call .\buildconf.bat --force --add-modules-dir=`"$Pecl`" || exit /b 1`r`ncall .\configure.bat --help" }
  'build' { @"
call .\buildconf.bat --force --add-modules-dir="$Pecl" || exit /b 1
call .\configure.bat $Configure || exit /b 1
nmake || exit /b 1
nmake install || exit /b 1
"@ }
  'all'   { @"
call phpsdk_deps -u -b $($V.php_deps_series) -d "$Deps" || exit /b 1
call .\buildconf.bat --force --add-modules-dir="$Pecl" || exit /b 1
call .\configure.bat $Configure || exit /b 1
nmake || exit /b 1
nmake install || exit /b 1
"@ }
}

# configure option changes do not trigger recompiles of existing objects -> always build clean
if ($Step -in 'build','all') {
    foreach ($d in $Obj, $Stage) { if (Test-Path $d) { Remove-Item $d -Recurse -Force } }
    New-Item -ItemType Directory -Force $Obj | Out-Null
}

# Keep XAMPP's php off PATH so configure/tools never pick it up
$env:Path = ($env:Path -split ';' | Where-Object { $_ -notmatch '(?i)xampp' }) -join ';'
$bat = "$Root\build\php-task-$Step.bat"
"@echo off`r`ncd /d `"$Src`"`r`n$task`r`nexit /b 0" | Set-Content $bat -Encoding ascii
$log = "$Root\build\logs\php-$Step.log"
& cmd /c "`"$Sdk\phpsdk-starter.bat`" -c vs17 -a x64 -t `"$bat`" > `"$log`" 2>&1"
$rc = $LASTEXITCODE
Get-Content $log -Tail 40
if ($rc) { throw "[ERR] php step '$Step' failed rc=$rc (log: $log)" }
Write-Host "[OK] php step '$Step' (log: $log)"
if ($Step -notin 'build','all') { return }

# Copy only the dependency DLLs that php/ext actually import (transitively), from deps\bin
. "$PSScriptRoot\lib.ps1"; Enter-VsEnv
$avail = @{}; Get-ChildItem "$Deps\bin\*.dll" | ForEach-Object { $avail[$_.Name.ToLower()] = $_.FullName }
$queue = [System.Collections.Generic.Queue[string]]::new()
Get-ChildItem "$Stage\*.exe", "$Stage\php8.dll", "$Stage\ext\*.dll" | ForEach-Object { $queue.Enqueue($_.FullName) }
$need = @{}
while ($queue.Count) {
    dumpbin /nologo /dependents $queue.Dequeue() | ForEach-Object {
        $n = $_.Trim().ToLower()
        if ($avail.ContainsKey($n) -and -not $need.ContainsKey($n)) { $need[$n] = $avail[$n]; $queue.Enqueue($avail[$n]) }
    }
}
$need.Values | Copy-Item -Destination $Stage -Force
Write-Host "[OK] runtime DLLs: $(($need.Keys | Sort-Object) -join ' ')"

# ImageMagick: CORE_RL_* plus the coder modules (IM_MOD_*) and delegates it loads at runtime, plus its
# config xml files - all next to php.exe/php-cgi.exe, same as the official imagick-on-Windows layout.
$ImBin = "$Root\src\imagemagick-windows\Artifacts\bin"
if (Test-Path "$Stage\ext\php_imagick.dll") {
    Copy-Item "$ImBin\*.dll", "$ImBin\*.xml" $Stage -Force
    Write-Host "[OK] ImageMagick runtime -> $Stage"
}
& "$Stage\php.exe" -v

อ่านสคริปต์ตามลำดับที่มันรัน

ผมแบ่งสคริปต์เป็นเก้าช่วง แต่ละช่วงบอกว่าทำไมต้องมี และมันป้องกันปัญหาอะไรที่ผมเจอมาเอง

ช่วงที่ 1: ตัวแปรพาธ

บรรทัด $Root = Split-Path $PSScriptRoot -Parent ทำให้สคริปต์รู้ตัวเองว่าอยู่ที่ C:\apm-build เลยรันจากโฟลเดอร์ไหนก็ได้ เลข 8.4.27 ไม่ได้ฝังในสคริปต์ มันอ่านจาก versions.json ($V.sources.php.version) ตรงนี้คือหัวใจของการอัปเดต security patch ที่ผมเล่าในตอนที่ 7 เปลี่ยนเลขในไฟล์เดียว สคริปต์ก็ชี้ไป src\php-<เวอร์ชันใหม่> เอง

ช่วงที่ 2: คัดลอก PECL ไป build\php-pecl

buildconf --add-modules-dir ต้องการโฟลเดอร์ที่ ชื่อตรงกับชื่อ extension แต่ source ที่ดาวน์โหลดมาชื่อ pecl-redis-6.3.0 สคริปต์เลยคัดลอกเป็น build\php-pecl edis, igbinary, imagick โดยลบของเดิมทิ้งแล้วคัดลอกใหม่ ทุกรอบที่รัน และถ้าหาโฟลเดอร์ต้นทางไม่เจอจะหยุดด้วย [ERR] ... missing - run 00-fetch.ps1 ไม่เดินต่อเงียบๆ

เคยเป็นบั๊ก: แก้เลข PECL แล้วได้ของเก่า

อาการ คุณแก้เลขเวอร์ชัน redis, igbinary หรือ imagick ใน versions.json แล้วโหลด source ใหม่ แต่ build ออกมายังเป็น extension รุ่นเก่า และไม่มี error สักบรรทัด สาเหตุ สคริปต์รุ่นแรกคัดลอกเมื่อโฟลเดอร์ build\php-pecl\<ext> ยังไม่มีเท่านั้น และชื่อโฟลเดอร์ไม่มีเลขเวอร์ชัน มันเลยใช้สำเนาเก่าต่อไปเรื่อยๆ ทางแก้ ตอนนี้สคริปต์ลบโฟลเดอร์ทิ้งแล้วคัดลอกใหม่ทุกรอบ เลยไม่ต้องไป Remove-Item build\php-pecl\... เองอีก ใครโหลด kit ก่อนบ่ายวันที่ 22 ต.ค. ให้โหลดใหม่ ผมอ่านเจอจากสคริปต์เท่านั้น ยังไม่ได้ลองเปลี่ยนเลข PECL จริงเพื่อดูผลทั้งสองแบบ

ช่วงที่ 3: อาร์เรย์ $Configure

รายการ flag ทั้งหมดถูกรวมเป็นสตริงเดียวด้วย -join ' ' ผมแยกอธิบายเป็นกลุ่มในหัวข้อถัดไป จุดเล็กๆ ที่ควรเห็นตอนนี้คือบรรทัด $(if (Test-Path ...im-dev)...) ที่ผมพูดถึงในขั้นที่ 2 และบรรทัด --with-extra-includes ที่ชี้ไปโฟลเดอร์ PECL เพราะ redis เขียน #include "igbinary/igbinary.h" ตรงๆ

ช่วงที่ 4: สร้างคำสั่งของแต่ละ -Step

ตัวแปร $task คือข้อความของไฟล์ .bat ที่จะรันในสภาพแวดล้อม php-sdk มีรายละเอียดสองจุดที่เล็กแต่สำคัญ

  • call ใช้เรียก .bat ตัวอื่นแล้วกลับมารันบรรทัดถัดไป ถ้าเขียนโดยไม่มี call บรรทัดหลังจะไม่ถูกรัน
  • || exit /b 1 หยุดทันทีเมื่อคำสั่งก่อนหน้าล้ม ไม่งั้น batch จะเดินต่อไป nmake ทั้งที่ configure พัง แล้วคุณจะได้ error ที่ไม่เกี่ยวกับสาเหตุจริง

ทำไมต้องเขียน .\buildconf.bat มี .\ นำหน้า เพราะเครื่องผมตั้ง environment NoDefaultCurrentDirectoryInExePath=1 ไว้ cmd เลยไม่ค้นหา exe หรือ bat ในโฟลเดอร์ปัจจุบัน ครั้งแรกที่รันผมเจอ error ว่า 'buildconf.bat' is not recognized ทั้งที่ไฟล์อยู่ตรงหน้า วิธีแก้ที่ปลอดภัยทุกเครื่องคือระบุ .\ ให้ชัด

ช่วงที่ 5: ลบ obj และ stage ทิ้งทุกครั้ง

if ($Step -in 'build','all') {
    foreach ($d in $Obj, $Stage) { if (Test-Path $d) { Remove-Item $d -Recurse -Force } }
    New-Item -ItemType Directory -Force $Obj | Out-Null
}

บล็อกนี้มาจากความผิดพลาดของผมโดยตรง รอบแรก GD ไม่มี WebP ผมเลยเติม --with-libwebp แล้ว build ใหม่ แต่ผลคือ WebP ยัง ปิดอยู่เหมือนเดิม เพราะ nmake ใช้ไฟล์ .obj เก่าต่อ การเปลี่ยน option ของ configure ไม่ทำให้ nmake รู้ว่าต้องคอมไพล์ใหม่ สคริปต์เลยล้างสะอาดทุกครั้ง แลกกับการที่ build ซ้ำไม่ได้เป็น incremental แต่ 5 นาทีรอบซ้ำที่วัดได้ผมยอมรับได้

ช่วงที่ 6: เอา XAMPP ออกจาก PATH

บรรทัด $env:Path = ... -notmatch '(?i)xampp' ตัด path ที่มีคำว่า xampp ออกเฉพาะใน process นี้ ไม่แตะ PATH ของระบบ เหตุผลคือ configure ของ PHP จะเรียกเครื่องมือหลายตัวตาม PATH ถ้ามี php.exe หรือ perl ของ XAMPP ติดมาก่อน มันอาจหยิบผิดตัว

ช่วงที่ 7: เขียน .bat แล้วรันผ่าน phpsdk-starter

สคริปต์เขียน build\php-task-all.bat (ชื่อตาม -Step) ขึ้นต้นด้วย cd /d เข้า src\php-8.4.27 แล้วเรียก

phpsdk-starter.bat -c vs17 -a x64 -t build\php-task-all.bat

พารามิเตอร์: -c vs17 เลือกคอมไพเลอร์ชุด Visual Studio 2022, -a x64 สถาปัตยกรรม, -t ไฟล์ task ที่จะรัน เอาต์พุตทั้งหมด (stdout และ stderr) ถูกเขียนลง build\logs\php-all.log สุดท้ายสคริปต์พิมพ์ 40 บรรทัดท้ายของ log ออกจอ และถ้า rc ไม่ใช่ศูนย์จะ throw พร้อมบอกที่อยู่ log

บรรทัดแรกของ log จะมีข้อความนี้ทุกครั้ง ไม่ต้องตกใจ มันไม่ทำให้ build ล้ม

'vswhere.exe' is not recognized as an internal or external command,
operable program or batch file.
[vcvarsall.bat] Environment initialized for: 'x64'

ช่วงที่ 8 และ 9: คัดลอก DLL แล้วพิมพ์ php -v

สองช่วงสุดท้ายคือไล่คัดลอก DLL ของ deps และ runtime ของ ImageMagick ไปวางข้าง php.exe แล้วพิมพ์ php -v ผมแยกไปอธิบายพร้อมผังในหัวข้อ "DLL ที่ต้องวางข้าง php.exe"

คำสั่ง configure ทีละกลุ่ม

บรรทัด configure ยาว 60 กว่า flag ผมจัดเป็นกลุ่มตามหน้าที่ รายการนี้ล้อตาม extension ของเครื่อง production ที่ผมต้องการให้ตรงกัน ไม่ใช่รายการที่ผมคิดเองว่าอยากได้อะไร

ลำดับคิดปิดทุกอย่าง--disable-all->เปิดทีละกลุ่มตามเครื่อง production->shared หรือ staticext\*.dll หรือ php8.dll
กลุ่ม flag หน้าที่ / เหตุผล
ตัวพื้นฐาน --disable-all --disable-zts ปิดทุก extension ก่อนแล้วเปิดเอง; --disable-zts คือ NTS เพราะ PHP รันผ่าน FastCGI แยก process (mod_fcgid) ไม่ได้เป็น module ในตัว Apache
SAPI --enable-cli --enable-cgi php.exe ใช้ทดสอบและรันสคริปต์ cron; php-cgi.exe คือตัวที่ mod_fcgid เรียก
ความเร็ว --enable-opcache --enable-opcache-jit --with-pcre-jit OPcache เก็บ bytecode, JIT คอมไพล์ส่วนที่ร้อน, PCRE JIT เร่ง regex
เครือข่ายและเข้ารหัส --with-openssl --with-curl --enable-zlib --with-iconv HTTPS ขาออก, cURL, บีบอัด, แปลง charset
ตระกูล XML --with-libxml --with-dom --with-xml --enable-xmlreader --enable-xmlwriter --with-simplexml --with-xsl=shared ตัวอ่าน/เขียน XML ที่โปรเจกต์ PHP ทั่วไปเรียกใช้
ภาษาและ session --enable-tokenizer --enable-session --enable-filter --enable-ctype --enable-phar ของพื้นฐานที่ framework ใช้
ข้อความหลายภาษา --enable-mbstring=shared --enable-mbregex --enable-intl=shared --with-gettext=shared เว็บภาษาไทยหนีไม่พ้น mbstring และ intl
รูปภาพ --with-gd=shared --with-libwebp --with-libavif --enable-exif=shared --enable-fileinfo=shared --with-imagick=shared,<path> GD อ่าน/เขียน WebP และ AVIF, exif อ่านข้อมูลรูป, imagick ใช้ ImageMagick จากตอนที่ 5
เบ็ดเตล็ด --enable-bcmath --enable-calendar --enable-ftp=shared --enable-shmop=shared --enable-sockets=shared --enable-soap=shared --with-sodium=shared --enable-zip=shared --with-ffi=shared ตรงกับ extension ที่เครื่อง production เปิดไว้
ฐานข้อมูล --with-mysqlnd --with-mysqli=shared --enable-pdo --with-pdo-mysql=shared --with-pdo-sqlite=shared --with-sqlite3=shared ต่อ MariaDB ผ่าน mysqlnd (ไม่ต้องมี libmysqlclient)
PECL --enable-igbinary=shared --enable-redis=shared --enable-redis-igbinary --enable-redis-session redis client พร้อม serializer igbinary และ session handler
พาธ --with-extra-includes --with-php-build --enable-object-out-dir --with-prefix บอกว่า header ของ PECL, DLL ของ deps, ที่เก็บ obj และที่ติดตั้งอยู่ตรงไหน

คำว่า =shared หมายถึง extension นั้นถูก build เป็นไฟล์ ext\php_xxx.dll แยก ต้องมีบรรทัด extension=xxx ใน php.ini ถึงจะโหลด ส่วนที่ไม่มี =shared ถูกฝังใน php8.dll ตลอด log ของ configure สรุปเป็นตารางให้ ผมย่อมาให้ดู (รายการจริงมี static 26 ตัว shared 22 ตัว และ opcache เป็น Zend extension แยกอีกตัว)

โหมด extension
shared (22) exif ffi fileinfo ftp gd gettext igbinary imagick intl mbstring mysqli openssl pdo_mysql pdo_sqlite redis shmop soap sockets sodium sqlite3 xsl zip
static (26) bcmath calendar ctype curl date dom filter hash iconv json libxml mysqlnd pcre pdo phar random reflection session simplexml spl standard tokenizer xml xmlreader xmlwriter zlib

ข้อสังเกต: openssl ก็เป็น shared ด้วย ทั้งที่ผมไม่ได้ใส่ =shared ตัว configure ของ PHP 8.4 เลือกให้เอง ดังนั้น php.ini ต้องมี extension=openssl ไม่งั้น https:// จากฝั่ง PHP จะพัง ตอนที่ 7 ผมใส่ไว้ใน template แล้ว

ใครเขียนลงโฟลเดอร์ไหนตอน build PHPsrc\php-8.4.27source ของ PHPsrc\pecl-*copy ใหม่ทุกรอบ -> build\php-peclbuild\php-depsDLL + header ของ libbuild\im-devheader + lib ของ ImageMagickbuild\php-objไฟล์ระหว่างคอมไพล์ลบทิ้งทุกรอบstage\phpphp.exephp-cgi.exeext\*.dllลูกศรซ้าย = nmake อ่านเข้ามาคอมไพล์ · ลูกศรขวา = nmake installbuild\php-obj กับ stage\php ถูกลบทุกครั้งก่อน -Step build และ all · php-pecl ลบแล้ว copy ใหม่ทุกรอบ
ของที่อ่านอย่างเดียว (ซ้าย) กับของที่สคริปต์ลบแล้วสร้างใหม่ทุกรอบ (กลางและขวา) การแยกแบบนี้ทำให้ build ซ้ำได้โดยไม่มีของเก่าค้าง

ชื่อ flag บน Windows ไม่เหมือน Linux

ถ้าคุณเคย build PHP บน Linux มาก่อน อย่าลอก flag มาตรงๆ ผมลอกแล้วเจอ error ทันที --enable-gd ใช้ไม่ได้ บน Windows ต้องเป็น --with-gd และ --with-dom (ไม่ใช่ --enable-* แบบ Linux) ตัวช่วยตรวจชื่อ flag คือ -Step help

ขั้นที่ 4: ตรวจชื่อ flag ด้วย -Step help (ไม่บังคับ แต่ช่วยได้มาก)

รันที่ C:\apm-build

pwsh -NoProfile -File scripts\30-php.ps1 -Step help

คำสั่งนี้ใช้เวลาไม่กี่สิบวินาที ผลคือ build\logs\php-help.log ที่มี configure --help ทั้งหมด ลองค้นเฉพาะ flag ที่เราสนใจ

Get-Content build\logs\php-help.log | Select-String -Pattern '--(with|without)-(gd|dom|libwebp|libavif|pcre-jit) '

ผลที่ควรเห็น (บรรทัดจริงจาก log ของผม)

  --without-gd                      Bundled GD support
  --without-libwebp                 webp support
  --without-libavif                 avif support
  --without-pcre-jit                Enable PCRE JIT support
  --without-dom                     DOM support

วิธีอ่าน: ชื่อที่ขึ้นต้นด้วย --without- แปลว่า option นั้น เปิดอยู่เป็นค่าเริ่มต้น ส่วน --with- แปลว่าปิดอยู่เป็นค่าเริ่มต้น และตัวที่ขึ้นต้น --enable- หรือ --disable- ก็ตามหลักเดียวกัน นี่คือเหตุผลที่ต้องเข้าใจกับดักถัดไป

กับดัก --disable-all

--disable-all ปิด ทุก option รวมถึงอันที่ปกติเปิดเอง configure รอบแรกของผมไม่ได้ใส่ flag พวกนี้ เพราะเห็นจาก -Step help ว่ามันเปิดเป็นค่าเริ่มต้นอยู่แล้ว แต่พอ build เสร็จ GD ไม่มี WebP กับ AVIF และ PCRE JIT ไม่ทำงาน เพราะ --disable-all ปิดมันไปด้วย ทางแก้คือสั่งเปิดเองทีละตัว: --with-libwebp --with-libavif --with-pcre-jit --enable-redis-session ซึ่งอยู่ในสคริปต์พร้อมคอมเมนต์ default-on, but --disable-all turns them off

เปลี่ยน flag แล้วผลไม่เปลี่ยน

ถ้าคุณแก้ $Configure แล้ว build ซ้ำด้วยตัวเอง (เช่น รัน nmake ในโฟลเดอร์ source) ผลจะไม่เปลี่ยนเพราะ .obj เก่าถูกใช้ต่อ ให้รันผ่านสคริปต์ด้วย -Step build เสมอ มันลบ obj ให้ก่อนทุกครั้ง

หลัง build ผ่าน คุณดูบรรทัด configure ที่ใช้จริงได้จากไฟล์ config.nice.bat ที่ configure บันทึกไว้ใน C:\apm-build\src\php-8.4.27 (log บอกว่า Saving configure options to config.nice.bat)

รัน build จริง

ขั้นที่ 5: เปิด terminal ตัวที่สองไว้ดู log

เพราะสคริปต์เงียบตลอดการ build ให้เปิดหน้าต่าง pwsh ตัวที่สอง (กดปุ่ม Windows พิมพ์ pwsh เปิดอีกอัน) ยังไม่ต้องรันอะไรตอนนี้ ผมจะกลับมาใช้ในขั้นที่ 7

ขั้นที่ 6: สั่ง build ที่ terminal ตัวแรก

cd C:\apm-build
pwsh -NoProfile -File scripts\30-php.ps1 -Step all

ทางเลือกอีกทางที่ได้ผลเหมือนกันคือ build.cmd -From php ซึ่งเรียกสคริปต์นี้ด้วย -Step all ให้ (PHP เป็นขั้นสุดท้ายของ build.cmd)

เคอร์เซอร์จะค้างโดยไม่มีข้อความ นั่นคือปกติ ห้ามกด Ctrl+C ระหว่างรอ ยกเว้นกรณีเดียวที่ผมบอกไว้ในขั้นที่ 7 ว่า imagick หาย

ขั้นที่ 7: ดู log สดที่ terminal ตัวที่สอง

Get-Content C:\apm-build\build\logs\php-all.log -Wait -Tail 5

คุณจะเห็นช่วงต่างๆ ผ่านไปตามลำดับ ตัวอย่างบรรทัดจริงจาก log ของผม (ผมแทนพาธเครื่องผมด้วย C:\apm-build)

ช่วง phpsdk_deps ดึง DLL ชุด 8.4 (ไม่กี่นาทีแรก ขึ้นกับเน็ต)

Configuration: 8.4-vs17-x64-stable

Processing package ICU-77.1-1-vs17-x64.zip
Processing package apache-2.4.69-vs17-x64.zip
Processing package brotli-1.2.0-vs17-x64.zip
...
Processing package zlib-1.3.2-vs17-x64.zip
Updates performed successfully.

ช่วง buildconf และ configure (ตรวจเครื่องมือ แล้วเลือก extension)

Rebuilding configure.js
Adding C:\apm-build\build\php-pecl to the module search path
Now run 'configure --help'
PHP Version: 8.4.27

Saving configure options to config.nice.bat
Checking for cl.exe ...  <in default path>
  Detected compiler Visual C++ 2022
  Detected x64 compiler
Checking for link.exe ...  <in default path>
Checking for nmake.exe ...  <in default path>
Checking for bison.exe ...  <in default path>
  Detected bison version 3.3.2
Checking for re2c.exe ...  <in default path>
  Detected re2c version 1.1.1
Enabling multi process build
Enabling SAPI sapi\cgi
Enabling SAPI sapi\cli
...
Enabling extension ext\gd [shared]
...
Enabling extension ..\..\build\php-pecl\imagick [shared]
...
Enabling extension ..\..\build\php-pecl\igbinary\src\php7 [shared]
Enabling extension ..\..\build\php-pecl\redis [shared]

สังเกตสามบรรทัดสุดท้าย: ถ้าไม่เห็นบรรทัด imagick [shared] แปลว่า build\im-dev หายตามที่เตือนไว้ในขั้นที่ 2 ให้กด Ctrl+C หยุดแล้วแก้ก่อน

ปลายช่วง configure จะพิมพ์ตารางสรุป ซึ่งใช้ตรวจว่าตั้งค่าตรงที่ตั้งใจ

Enabled Zend extensions:
----------------------
| Extension | Mode   |
----------------------
| opcache   | shared |
----------------------

Enabled SAPI:
-------------
| Sapi Name |
-------------
| cgi       |
| cli       |
-------------

| Build type          | Release         |
| Thread Safety       | No              |
| Compiler            | Visual C++ 2022 |
| Target Architecture | x64             |
| Optimization        | PGO disabled    |
| Native intrinsics   | SSE2            |

Thread Safety No คือ NTS ตามที่ต้องการ ส่วน PGO disabled แปลว่าไม่ได้ build แบบ profile-guided optimization ซึ่ง PHP ทางการใช้ ดังนั้นความเร็วอาจต่างจากไบนารีทางการเล็กน้อย ผมไม่ได้วัดส่วนต่างนี้

จากนั้นถึงช่วง nmake (นานที่สุด) บรรทัดไล่ชื่อไฟล์ .c ไปเรื่อยๆ และมีคำเตือน LINK : warning LNK4286 กับ LNK4217 โผล่มา ทั้งสองอย่างเป็นแค่ warning ของตัวลิงก์ใน build ของผม ไม่ได้ทำให้ล้ม ช่วงท้ายสุดคือ nmake install

        1 file(s) copied.
C:\apm-build\build\php-obj\Release\php_redis.lib
        1 file(s) copied.
401 File(s) copied

ขั้นที่ 8: รอจนสคริปต์จบแล้วดูข้อความสุดท้าย

เมื่อ terminal ตัวแรกกลับมา prompt ได้ ให้เลื่อนดูท้ายสุด ต้องมีบรรทัดแบบนี้ (ตัวอักษรหลัง runtime DLLs: คือรายชื่อ DLL ที่คัดลอก ผมอธิบายในหัวข้อถัดไป)

[OK] php step 'all' (log: C:\apm-build\build\logs\php-all.log)
[OK] runtime DLLs: brotlicommon.dll brotlidec.dll icudt77.dll ...
[OK] ImageMagick runtime -> C:\apm-build\stage\php
PHP 8.4.27 (cli) (built: Oct 22 2026 12:11:45) (NTS Visual C++ 2022 x64)

ถ้าเห็นบรรทัดสุดท้ายเป็น PHP 8.4.27 (cli) ... (NTS Visual C++ 2022 x64) แปลว่า build จบและ php.exe ที่เพิ่ง build รันได้ คำสั่งนี้รันโดยไม่มี php.ini เลยยังไม่เห็น extension อื่น

DLL ที่ต้องวางข้าง php.exe

ช่วงสุดท้ายของสคริปต์แก้ปัญหาที่ผมเจอตอนรัน php.exe ครั้งแรกหลัง install: โปรแกรมไม่ทำงานเลยและออกด้วยรหัส 53 ไม่มีหน้าต่างบอกสาเหตุ เหตุผลคือ php.exe กับ extension ต้องใช้ DLL ของ lib ภายนอกอย่าง ICU, OpenSSL, libsodium ซึ่งอยู่ใน build\php-deps\bin ไม่ได้อยู่ข้างไฟล์ที่เพิ่งติดตั้ง Windows ค้นหา DLL จากโฟลเดอร์เดียวกับ exe ก่อน เมื่อไม่เจอก็ล้ม

ทางแก้แบบง่ายคือคัดลอก DLL ทั้งโฟลเดอร์ แต่ build\php-deps\bin มี DLL ของ lib ที่ PHP ไม่ได้ใช้ในชุดนี้ปนอยู่ด้วย (เช่น lib ของ PostgreSQL, SNMP, LDAP) ผมเลยให้สคริปต์ไล่เฉพาะที่ใช้จริง

ไล่หา DLL ที่ php ต้องใช้จริง ทีละชั้นจนไม่มีตัวใหม่คิวเริ่มต้นphp.exe php-cgi.exephp8.dll + ext\*.dlldumpbin/nologo /dependentsดึงรายชื่อ DLL ที่ importอยู่ใน build\php-deps\bin ?เทียบชื่อแบบตัวพิมพ์เล็กคัดลอกเข้า stage\phpแล้วเอา DLL ตัวนั้นเข้าคิวต่อข้ามDLL ของ Windowsใช่ไม่ใช่วนซ้ำจนไม่มี DLL ตัวใหม่ตัวอย่างผลจริงจากบันทึกการ build (12 ตัว)brotlicommon brotlidec icudt77 icuin77 icuio77 icuuc77libcrypto-3-x64 libssl-3-x64 libsodium libsqlite3 libssh2 nghttp2
ลูปที่สคริปต์ใช้คัดลอก DLL ต้องไล่เป็นชั้น เพราะ DLL ของ lib เองก็ import DLL ตัวอื่นต่อได้

กลไกในสคริปต์ มีสามส่วน

  1. สร้างตารางตัวอักษรเล็กของ DLL ทั้งหมดที่มีใน build\php-deps\bin ($avail)
  2. ใส่ *.exe, php8.dll และ ext\*.dll ทุกไฟล์ใน stage เข้าคิว
  3. วนเอาไฟล์ออกจากคิว รัน dumpbin /nologo /dependents อ่านรายชื่อ DLL ที่ไฟล์นั้น import ถ้าชื่อไหนอยู่ในตาราง $avail และยังไม่เคยเลือก ก็จดไว้ใน $need แล้วเอาตัวนั้นเข้าคิวด้วย (เพราะมันอาจ import ตัวอื่นต่อ)

พอคิวว่าง $need.Values | Copy-Item -Destination $Stage คัดลอกทุกตัวที่จดไว้ข้างไฟล์ php.exe ชื่อ DLL ที่เป็นของ Windows เอง เช่น KERNEL32.dll ไม่อยู่ในตาราง $avail เลยถูกข้ามอัตโนมัติ

dumpbin มาจาก MSVC ที่สคริปต์โหลดผ่าน Enter-VsEnv ใน lib.ps1 ตอนที่ 1 อธิบายไว้แล้ว

รายชื่อ 12 ตัวในผังมาจากบันทึกการ build รอบแรกของผม (ก่อนเพิ่ม imagick) รอบของคุณอาจต่างได้เล็กน้อย ถ้า PHP team เปลี่ยนชุด deps ใช้บรรทัด [OK] runtime DLLs: ในผลของสคริปต์เป็นตัวอ้างอิงของจริง

ส่วน ImageMagick

imagick ต้องใช้ DLL ของ ImageMagick เองด้วย สคริปต์คัดลอก *.dll และ *.xml ทั้งหมดจาก src\imagemagick-windows\Artifacts\bin มาวางข้าง php.exe ได้แก่ CORE_RL_* (แกนหลักและ delegate อย่าง webp, png, tiff), IM_MOD_RL_* (โมดูลอ่าน/เขียนแต่ละฟอร์แมต) และไฟล์ xml ตั้งค่า เหตุผลที่ต้องอยู่ข้าง php.exe คือ layout ที่ imagick บน Windows ใช้กัน ผลคือ stage\php หนักประมาณ 127 MB

ทดสอบผล ว่าได้ PHP ที่ใช้งานได้จริง

stage\php ยังไม่มี php.ini (ตอนที่ 7 ค่อยสร้างจาก template) การทดสอบเลยต้องสร้าง ini ชั่วคราวที่โหลด extension ทุกตัว

ลำดับการโหลดสำคัญ

ใน ini ทดสอบต้องโหลด igbinary ก่อน extension อื่น เพราะ redis ลิงก์กับ serializer ของ igbinary ถ้าลำดับกลับกัน redis จะโหลดไม่ขึ้น และ opcache ต้องโหลดด้วย zend_extension ไม่ใช่ extension

ขั้นที่ 9: สร้าง ini ทดสอบ

รันที่ C:\apm-build บล็อกนี้สร้างไฟล์ apm-test-php.ini ในโฟลเดอร์ TEMP ของคุณ

$P = "stage\php"; $ini = "$env:TEMP\apm-test-php.ini"
$exts = Get-ChildItem "$P\ext\php_*.dll" | ForEach-Object { $_.BaseName -replace '^php_','' } | Where-Object { $_ -ne 'opcache' }
@("extension_dir=`"$((Resolve-Path $P).Path)\ext`"","zend_extension=opcache","opcache.enable_cli=1","opcache.jit=tracing","opcache.jit_buffer_size=64M","extension=igbinary") +
  ($exts | Where-Object { $_ -ne 'igbinary' } | ForEach-Object { "extension=$_" }) | Set-Content $ini

ตัวแปร $P กับ $ini ต้องอยู่ใน session เดียวกับขั้นถัดไป ห้ามปิด terminal ระหว่างทาง คำสั่งนี้ไม่พิมพ์อะไรเมื่อสำเร็จ ตรวจว่าไฟล์เกิดขึ้นด้วย

Get-Content $ini | Select-Object -First 6

ผลที่ควรเห็น หกบรรทัดแรกของไฟล์

extension_dir="C:\apm-build\stage\php\ext"
zend_extension=opcache
opcache.enable_cli=1
opcache.jit=tracing
opcache.jit_buffer_size=64M
extension=igbinary

ขั้นที่ 10: ตรวจเวอร์ชัน

& "$P\php.exe" -c $ini -v

ผลที่ควรเห็นบรรทัดแรก ตัวเลขต้องเป็นเวอร์ชันที่ตั้งใน versions.json

PHP 8.4.27 (cli) (built: Oct 22 2026 12:11:45) (NTS Visual C++ 2022 x64)

หลังบรรทัดนี้จะมีข้อความลิขสิทธิ์และบรรทัดของ Zend Engine กับ OPcache ตามมา ซึ่งผมย่อไว้

ขั้นที่ 11: ตรวจว่า extension สำคัญโหลดครบ

& "$P\php.exe" -c $ini -m | Select-String -Pattern '^(gd|igbinary|imagick|intl|mbstring|redis|sodium)$'

ผลที่ควรเห็นคือชื่อทั้งเจ็ดตัวนี้ ตัวละบรรทัด เรียงตามตัวอักษร ถ้าขาดตัวไหน แปลว่าไฟล์ php_<ชื่อ>.dll โหลดไม่ขึ้น ให้รัน & "$P\php.exe" -c $ini -r "echo 1;" ดูข้อความ error ตอนโหลด ทั้งชุด php -m ของผมได้ 51 module ตรงกับเครื่อง production ทุกตัว ยกเว้น posix, sysvmsg, sysvsem, sysvshm ซึ่ง Windows ไม่มี

ขั้นที่ 12: ตรวจ JIT, WebP และ imagick ในคำสั่งเดียว

& "$P\php.exe" -c $ini -r 'echo json_encode(["jit"=>opcache_get_status(false)["jit"]["on"], "webp"=>gd_info()["WebP Support"], "imagick"=>Imagick::getVersion()["versionString"]]);'

ผลที่ควรเห็น บรรทัดเดียว (เลข commit ของ ImageMagick อาจต่างตามรอบ)

{"jit":true,"webp":true,"imagick":"ImageMagick 7.1.2-34 Q16-HDRI x64 a0c4bf729:20261014 https:\/\/imagemagick.org"}

สามค่านี้ตอบปัญหาที่ผมเจอมาหมด jit:true ยืนยันว่า OPcache และ JIT ทำงาน, webp:true ยืนยันว่ากับดัก --disable-all หลุดไปแล้ว, และ imagick ได้ชื่อเวอร์ชัน ImageMagick 7.1.2-34 ที่ build ในตอนที่ 5

ขั้นที่ 13: ให้ imagick อ่านและเขียนไฟล์จริง

ไม่พอแค่โหลดขึ้น ผมสั่งให้สร้างภาพสีล้วนแล้วแปลงเป็นสี่ฟอร์แมต

& "$P\php.exe" -c $ini -r '$i=new Imagick(); $i->newImage(320,200,new ImagickPixel("#3366cc")); foreach(["webp","png","jpg","avif"] as $f){$c=clone $i; $c->setImageFormat($f); echo $f,"=",strlen($c->getImageBlob())," ";}'

ผลที่ควรเห็น ตัวเลขคือจำนวนไบต์ของแต่ละไฟล์ที่ได้ ซึ่งต้องมากกว่าศูนย์ทุกตัว

webp=1696 png=2266 jpg=5862 avif=1456

ini ทดสอบนี้มีแค่ opcache.enable_cli กับ JIT เพราะรันด้วย php.exe ครั้งเดียวแล้วจบ ส่วน php.ini ที่ตอนที่ 7 deploy ให้ตั้ง opcache.cache_id แยกตามโฟลเดอร์ติดตั้ง กับ file cache ใน tmp\opcache ไว้ด้วย เพราะถ้ารัน APM สองโฟลเดอร์พร้อมกันจะชนกันเรื่อง ASLR (อาการและวิธีแก้อยู่ในตอนที่ 7)

หมายเหตุ: ตัวเลขไบต์ของคุณอาจต่างเล็กน้อยได้ ที่ต้องดูคือสี่ฟอร์แมตเขียนออกมาได้ ส่วน HEIC เขียนไม่ได้ในชุดนี้ ตามที่ผมบอกไว้ตอนที่ 5

ถ้าพังตรงนี้

ทุกแถวเป็นอาการที่ผมเจอจริงตอน build PHP พร้อมสาเหตุและทางแก้ ที่ใส่ในสคริปต์แล้วผมระบุไว้ในคอลัมน์สุดท้าย

อาการ สาเหตุ ทางแก้
'buildconf.bat' is not recognized environment ตั้ง NoDefaultCurrentDirectoryInExePath=1 cmd เลยไม่ค้นในโฟลเดอร์ปัจจุบัน เรียกเป็น .\buildconf.bat / .\configure.bat (อยู่ในสคริปต์แล้ว)
configure ไม่รู้จัก --enable-gd ชื่อ flag บน Windows ต่างจาก Linux ใช้ --with-gd, --with-dom และตรวจด้วย -Step help
GD ไม่มี WebP/AVIF, PCRE JIT ไม่ทำงาน --disable-all ปิด option ที่ปกติเปิดอยู่ ใส่ --with-libwebp --with-libavif --with-pcre-jit --enable-redis-session (อยู่ในสคริปต์แล้ว)
แก้ configure แล้วผลไม่เปลี่ยน nmake ใช้ .obj เดิมต่อ ลบ build\php-obj และ stage\php ก่อน build (สคริปต์ทำให้แล้ว)
igbinary/igbinary.h: No such file redis include header แบบมี path ของ igbinary นำหน้า --with-extra-includes=build\php-pecl (อยู่ในสคริปต์แล้ว)
php.exe ไม่ทำงาน (exit 53) DLL ของ deps ไม่อยู่ข้าง exe ไล่หาด้วย dumpbin /dependents แล้วคัดลอก (ช่วงท้ายของสคริปต์)
ImageMagick was build with a 64 channel bit mask and that requires a C++ compiler ตอน build imagick ตอนที่ build ImageMagick ใช้ channel mask 64 bit แต่ imagick เขียนด้วย C กลับไปตอนที่ 5 แก้ Options.cpp เป็น 32 bit แล้ว build ImageMagick ใหม่
build ผ่านแต่ไม่มี imagick ไม่มี build\im-dev สคริปต์เลยข้าม --with-imagick ทำตอนที่ 5 ให้จบ แล้วรัน -Step all อีกครั้ง
build PHP หลังเปลี่ยนเลข redis/igbinary/imagick ใน versions.json แล้วยังได้ extension รุ่นเก่า kit เก่าคัดลอก PECL ลง build\php-pecl แค่ครั้งเดียว แก้แล้ว: 30-php.ps1 รุ่นใหม่ลบแล้วคัดลอกใหม่ทุกรอบ โหลด kit ใหม่ได้เลย ไม่ต้องลบโฟลเดอร์เอง
[ERR] ...\src\pecl-redis-... missing - run 00-fetch.ps1 ยังไม่ได้โหลด source ของ PECL รุ่นที่ระบุใน versions.json รัน pwsh -File scripts\00-fetch.ps1 -Only pecl-redis,pecl-igbinary,pecl-imagick แล้วรัน build ใหม่
Class "Imagick" not found ตอนรันจริงบน Apache php.ini ไม่มี extension = imagick template ของตอนที่ 7 ใส่ไว้แล้ว ถ้า deploy ไปก่อนหน้านี้ให้รัน deploy.cmd -ResetConfig

ถ้าเจออาการที่ไม่อยู่ในตาราง ให้เปิด build\logs\php-all.log ค้นคำว่า error (ตัวเล็กตัวใหญ่ก็ได้) ผมเจอว่าคำที่โผล่เยอะแต่ไม่ใช่ปัญหาคือชื่อไฟล์ที่มีคำว่า error อยู่ เช่น pcre2_error.c, intl_error.c ซึ่งเป็นแค่ชื่อไฟล์ที่กำลังคอมไพล์

สิ่งที่ต้องมีก่อนไปตอนที่ 7

  1. บรรทัดสุดท้ายของสคริปต์เป็น PHP 8.4.27 (cli) ... (NTS Visual C++ 2022 x64)
  2. โฟลเดอร์ C:\apm-build\stage\php มี php.exe, php-cgi.exe, php8.dll และโฟลเดอร์ ext ที่มี php_imagick.dll, php_redis.dll, php_igbinary.dll
  3. ผลของขั้นที่ 11 เห็นครบเจ็ดชื่อ (gd igbinary imagick intl mbstring redis sodium)
  4. ผลของขั้นที่ 12 ได้ "jit":true และ "webp":true
  5. ผลของขั้นที่ 13 เขียนได้ครบสี่ฟอร์แมต (webp png jpg avif)
  6. ตอนที่ 3 (stage\apache) และตอนที่ 4 (stage\mariadb) ผ่านแล้วเช่นกัน เพราะตอนที่ 7 จะนำทั้งสามอย่างไปติดตั้งพร้อมกัน
ไม่อยาก build เอง

ใครอยากได้ APM ไปใช้เลยโดยไม่ต้องคอมไพล์ ผมปล่อยตัวที่ build เสร็จแล้วไว้ที่ github.com/ChonLaWan/APM-Server โหลด APM-1.0.0-win64.zip (ราว 140 MB) จากหน้า Releases เทียบ sha256 กับไฟล์ .sha256 ที่แนบไว้ แตกไว้ที่ไหนก็ได้ แล้วเปิด manager\APMManager.exe ตัว Manager จะเลือกพอร์ต เขียน config ออก cert และสร้างฐานข้อมูลให้เอง มีปุ่ม Check for updates ที่โหลดรุ่นใหม่จาก Releases มาตรวจ sha256 แล้วแทน binary ให้ โดยไม่แตะ config ฐานข้อมูล และเว็บของเรา

ซีรีส์ Build APM บน Windows

สารบัญ · ตอนก่อนหน้า: ตอนที่ 5 ImageMagick · ตอนถัดไป: ตอนที่ 7 Deploy รัน และอัปเดต

ตอนหน้าเป็นตอนสุดท้ายของซีรีส์ ติดตั้งทั้งสามส่วนลง C:\APM สตาร์ตครั้งแรก และขั้นตอนอัปเดตเมื่อมี security release รอบหน้า ส่วนตัวผมคิดว่าถ้าอ่านตอนนี้จบแล้วทำตามได้ครบ คุณมี PHP ที่ build เองในมือแล้วหนึ่งตัว เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ

อยากให้เราทำให้ไหม?

ให้เราช่วยติดตั้ง ตั้งค่า ย้ายระบบ หรือแก้ปัญหาเซิร์ฟเวอร์ เพื่อให้คุณมีเวลาไปทำงานที่สำคัญกว่า

คุยเรื่องงานกับเรา

เคยเจองานแบบนี้ไหม? ติดตรงไหน?

มาแลกวิธีกันต่อในบอร์ด LinuxThai

→ คุยต่อในบอร์ด

ความคิดเห็น 0

ยังไม่มีความคิดเห็น — เป็นคนแรกเลย