PHP-Node란 무엇인가?
@platformatic/php-node는 Node.js 애플리케이션 내에서 PHP 코드를 직접 실행할 수 있게 해주는 혁신적인 모듈입니다. 이 모듈의 가장 큰 특징은 별도의 프로세스나 네트워크 연결 없이 동일한 프로세스 내에서 Node.js와 PHP가 직접 통신한다는 점입니다.
왜 PHP-Node가 필요한가?
많은 기업과 개발자들이 레거시 PHP 애플리케이션을 보유하고 있으면서도, 동시에 Node.js의 현대적인 생태계를 활용하고 싶어합니다. PHP-Node는 이러한 요구사항에 대한 완벽한 솔루션을 제공합니다.
- 레거시 보존: 기존 PHP 코드베이스를 그대로 유지하면서 점진적 마이그레이션 가능
- 성능 향상: 네트워크 오버헤드 없이 메모리 내 직접 통신으로 빠른 처리
- 유연한 아키텍처: WordPress 백엔드와 Next.js 프론트엔드를 자연스럽게 통합
핵심 특징 및 장점
1. 동일 프로세스 내 실행
PHP-Node의 가장 혁신적인 특징은 Node.js와 PHP가 같은 프로세스에서 실행된다는 점입니다. 이는 다음과 같은 이점을 제공합니다.
- 제로 네트워크 레이턴시: HTTP 요청/응답이 메모리 내에서 직접 처리
- 리소스 효율성: 별도의 PHP-FPM이나 Apache 프로세스 불필요
- 간편한 배포: 단일 프로세스로 전체 애플리케이션 관리
2. 양방향 통신 지원
Node.js에서 PHP로, PHP에서 Node.js로의 원활한 데이터 전달이 가능합니다.
// Node.js에서 PHP로 데이터 전송
const request = new Request({
method: 'POST',
url: 'http://example.com/api/user',
headers: { 'Content-Type': ['application/json'] },
body: Buffer.from(JSON.stringify({ name: '홍길동', age: 30 }))
});
// PHP에서 처리된 결과를 Node.js에서 받기
const response = await php.handleRequest(request);
const result = JSON.parse(response.body.toString());
3. 비동기/동기 방식 모두 지원
개발자의 요구사항에 따라 두 가지 처리 방식을 선택할 수 있습니다.
- 비동기 방식 (권장):
handleRequest()
- Node.js의 이벤트 루프를 블로킹하지 않음 - 동기 방식:
handleRequestSync()
- 특별한 경우에만 사용 권장
4. 완벽한 HTTP 인터페이스
Node.js의 표준 HTTP 객체와 유사한 인터페이스를 제공하여 학습 곡선을 최소화합니다.
Request
: HTTP 요청 생성 및 조작Response
: HTTP 응답 처리Headers
: 헤더 관리를 위한 풍부한 API
설치 및 환경 구성
1. 시스템 요구사항
PHP-Node는 현재 다음 플랫폼을 지원합니다.
- Linux: x64 아키텍처
- macOS: x64 및 arm64 (Apple Silicon) 아키텍처
- Windows: 향후 지원 예정 (이슈를 통해 요청 가능)
2. 의존성 라이브러리 설치
Linux (Ubuntu/Debian 계열)
# 시스템 패키지 업데이트
sudo apt-get update
# 필수 라이브러리 설치
sudo apt-get install -y \
libssl-dev \ # SSL/TLS 지원
libcurl4-openssl-dev \ # cURL 기능
libxml2-dev \ # XML 파싱
libsqlite3-dev \ # SQLite 데이터베이스
libonig-dev \ # 정규표현식 엔진
re2c # 렉서 생성기
macOS (Homebrew 사용)
# Homebrew가 설치되어 있지 않다면 먼저 설치
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# 필수 라이브러리 설치
brew install \
openssl@3 \ # 최신 OpenSSL
curl \ # cURL
sqlite \ # SQLite
libxml2 \ # XML 파서
oniguruma # 정규표현식
3. NPM 패키지 설치
시스템 라이브러리 설치가 완료되면 npm을 통해 PHP-Node를 설치합니다.
# npm 사용
npm install @platformatic/php-node
# yarn 사용
yarn add @platformatic/php-node
# pnpm 사용
pnpm add @platformatic/php-node
기본 사용법
1. Hello World 예제
가장 간단한 PHP-Node 사용 예제입니다.
// index.js
import { Php, Request } from '@platformatic/php-node';
// PHP 인스턴스 생성
const php = new Php({
docroot: './public' // PHP 문서 루트 디렉토리
});
// HTTP 요청 생성
const request = new Request({
url: 'http://localhost/index.php'
});
// 요청 처리 및 응답 받기
const response = await php.handleRequest(request);
console.log('응답 상태:', response.status);
console.log('응답 내용:', response.body.toString());
// public/index.php
<?php
echo "Hello from PHP running inside Node.js!";
?>
2. GET 파라미터 전달
URL 쿼리 스트링을 통한 데이터 전달
const request = new Request({
url: 'http://localhost/search.php?keyword=node.js&limit=10'
});
const response = await php.handleRequest(request);
// public/search.php
<?php
$keyword = $_GET['keyword'] ?? '';
$limit = intval($_GET['limit'] ?? 10);
echo json_encode([
'keyword' => $keyword,
'limit' => $limit,
'results' => ['결과1', '결과2', '결과3']
]);
?>
3. POST 데이터 전송
JSON 또는 폼 데이터를 POST로 전송
// JSON 데이터 전송
const jsonRequest = new Request({
method: 'POST',
url: 'http://localhost/api/user.php',
headers: {
'Content-Type': ['application/json']
},
body: Buffer.from(JSON.stringify({
name: '김철수',
email: 'chulsoo@example.com',
age: 25
}))
});
// 폼 데이터 전송
const formData = new URLSearchParams();
formData.append('username', 'user123');
formData.append('password', 'securepass');
const formRequest = new Request({
method: 'POST',
url: 'http://localhost/login.php',
headers: {
'Content-Type': ['application/x-www-form-urlencoded']
},
body: Buffer.from(formData.toString())
});
API 레퍼런스
1. Php 클래스
생성자
new Php(config?: PhpConfig)
PhpConfig 옵션
docroot
(string): PHP 문서 루트 디렉토리 (기본값:process.cwd()
)
메서드
handleRequest(request: Request): Promise
비동기적으로 PHP 요청을 처리합니다. Node.js의 이벤트 루프를 블로킹하지 않습니다.
const response = await php.handleRequest(request);
handleRequestSync(request: Request): Response
동기적으로 PHP 요청을 처리합니다. 스레드를 블로킹하므로 특별한 경우에만 사용하세요.
const response = php.handleRequestSync(request);
2. Request 클래스
생성자
new Request(options?: RequestOptions)
RequestOptions
method
(string): HTTP 메서드 (기본값: 'GET')url
(string): 요청 URLheaders
(object): HTTP 헤더 객체 { [name]: [values] }body
(Buffer | Uint8Array): 요청 본문
속성
request.method // HTTP 메서드 읽기/설정
request.url // URL 읽기/설정
request.headers // Headers 객체
request.body // 요청 본문
3. Response 클래스
생성자
new Response(options?: ResponseOptions)
ResponseOptions
status
(number): HTTP 상태 코드headers
(object): 응답 헤더body
(Buffer): 응답 본문log
(Buffer): PHP 실행 로그
속성
response.status // HTTP 상태 코드
response.headers // Headers 객체
response.body // 응답 본문 (Buffer)
response.log // PHP 로그 (Buffer)
4. Headers 클래스
완벽한 HTTP 헤더 관리를 위한 API
// 헤더 설정 (기존 값 덮어쓰기)
headers.set('Content-Type', 'application/json');
// 헤더 추가 (여러 값 지원)
headers.add('Set-Cookie', 'session=abc123');
headers.add('Set-Cookie', 'user=john');
// 헤더 값 가져오기
const contentType = headers.get('Content-Type'); // 마지막 값
const allCookies = headers.getAll('Set-Cookie'); // 모든 값 배열
const cookieLine = headers.getLine('Set-Cookie'); // 쉼표로 연결된 문자열
// 헤더 존재 확인
if (headers.has('Authorization')) {
// 인증 헤더 처리
}
// 헤더 삭제
headers.delete('X-Temp-Header');
// 모든 헤더 삭제
headers.clear();
// 헤더 순회
for (const [name, values] of headers.entries()) {
console.log(`${name}: ${values.join(', ')}`);
}
// 헤더 개수
console.log(`총 ${headers.size}개의 헤더`);
실전 활용 예제
1. WordPress와 Next.js 통합
WordPress를 백엔드로, Next.js를 프론트엔드로 사용하는 현대적인 웹 애플리케이션
// pages/api/wordpress/[...path].js
import { Php, Request } from '@platformatic/php-node';
const php = new Php({
docroot: './wordpress' // WordPress 설치 디렉토리
});
export default async function handler(req, res) {
// Next.js 요청을 PHP 요청으로 변환
const phpRequest = new Request({
method: req.method,
url: `http://localhost${req.url.replace('/api/wordpress', '')}`,
headers: req.headers,
body: req.method === 'POST' ? Buffer.from(await getRawBody(req)) : undefined
});
// WordPress 처리
const phpResponse = await php.handleRequest(phpRequest);
// PHP 응답을 Next.js 응답으로 변환
res.status(phpResponse.status);
// 헤더 복사
for (const [name, values] of phpResponse.headers.entries()) {
res.setHeader(name, values);
}
res.end(phpResponse.body);
}
2. 레거시 PHP API 마이그레이션
기존 PHP API를 Node.js로 점진적으로 마이그레이션
// api-gateway.js
import express from 'express';
import { Php, Request } from '@platformatic/php-node';
const app = express();
const php = new Php({ docroot: './legacy-api' });
// 새로운 Node.js 엔드포인트
app.get('/api/v2/users', async (req, res) => {
// Node.js로 구현된 새 API
res.json({ users: await getUsersFromMongoDB() });
});
// 레거시 PHP 엔드포인트 프록시
app.all('/api/v1/*', async (req, res) => {
const phpRequest = new Request({
method: req.method,
url: `http://localhost${req.url}`,
headers: req.headers,
body: req.body ? Buffer.from(JSON.stringify(req.body)) : undefined
});
const phpResponse = await php.handleRequest(phpRequest);
res.status(phpResponse.status);
res.set(Object.fromEntries(phpResponse.headers.entries()));
res.send(phpResponse.body);
});
app.listen(3000);
3. PHP 템플릿 엔진과 Node.js SSR
PHP 템플릿 엔진을 활용한 서버 사이드 렌더링
// ssr-engine.js
import { Php, Request } from '@platformatic/php-node';
class PHPTemplateEngine {
constructor() {
this.php = new Php({ docroot: './templates' });
}
async render(template, data) {
// 데이터를 PHP로 전달
const request = new Request({
method: 'POST',
url: `http://localhost/render.php?template=${template}`,
headers: { 'Content-Type': ['application/json'] },
body: Buffer.from(JSON.stringify(data))
});
const response = await this.php.handleRequest(request);
if (response.status !== 200) {
throw new Error(`템플릿 렌더링 실패: ${response.status}`);
}
return response.body.toString();
}
}
// 사용 예시
const engine = new PHPTemplateEngine();
const html = await engine.render('product-detail', {
product: {
name: '노트북',
price: 1500000,
description: '고성능 게이밍 노트북'
}
});
// templates/render.php
<?php
$template = $_GET['template'] ?? '';
$data = json_decode(file_get_contents('php://input'), true);
// 템플릿 파일 확인
$templateFile = __DIR__ . "/views/{$template}.php";
if (!file_exists($templateFile)) {
http_response_code(404);
exit('Template not found');
}
// 데이터를 변수로 추출
extract($data);
// 템플릿 렌더링
ob_start();
include $templateFile;
$output = ob_get_clean();
echo $output;
?>
4. 파일 업로드 처리
Node.js에서 받은 파일을 PHP로 처리
// file-upload.js
import multer from 'multer';
import { Php, Request } from '@platformatic/php-node';
const upload = multer({ dest: 'uploads/' });
const php = new Php();
app.post('/upload', upload.single('file'), async (req, res) => {
// 파일 정보를 PHP로 전달
const phpRequest = new Request({
method: 'POST',
url: 'http://localhost/process-upload.php',
headers: { 'Content-Type': ['application/json'] },
body: Buffer.from(JSON.stringify({
filename: req.file.filename,
originalName: req.file.originalname,
mimetype: req.file.mimetype,
size: req.file.size,
path: req.file.path
}))
});
const phpResponse = await php.handleRequest(phpRequest);
res.json(JSON.parse(phpResponse.body.toString()));
});
활용 시나리오
1. 엔터프라이즈 애플리케이션 현대화
많은 기업들이 레거시 PHP 시스템을 보유하고 있습니다. 아래와 같이 PHP-Node를 활용할 수 있습니다.
- 리스크 최소화: 검증된 PHP 코드를 그대로 유지
- 점진적 전환: 새로운 기능만 Node.js로 개발
- 팀 역량 활용: PHP와 Node.js 개발자가 협업
2. 마이크로서비스 아키텍처
PHP 모놀리스를 마이크로서비스로 분해
// gateway-service.js
const services = {
auth: new Php({ docroot: './services/auth' }),
catalog: new Php({ docroot: './services/catalog' }),
order: new Php({ docroot: './services/order' })
};
app.use('/api/auth/*', createProxy(services.auth));
app.use('/api/catalog/*', createProxy(services.catalog));
app.use('/api/order/*', createProxy(services.order));
3. 개발 환경 통합
프론트엔드와 백엔드 개발 환경을 하나로
// dev-server.js
import { createServer } from 'vite';
import { Php, Request } from '@platformatic/php-node';
const vite = await createServer();
const php = new Php({ docroot: './backend' });
// Vite 개발 서버와 PHP 백엔드 통합
app.use(vite.middlewares);
app.use('/api/*', async (req, res) => {
// PHP API 프록시
const phpResponse = await php.handleRequest(/* ... */);
// ...
});
성능 및 최적화
1. 성능 벤치마크
PHP-Node는 전통적인 PHP 실행 방식 대비 다음과 같은 성능 이점을 제공합니다.
- 레이턴시 감소: 네트워크 오버헤드 제거로 50-70% 응답 시간 단축
- 처리량 증가: 동일 하드웨어에서 2-3배 높은 RPS (Requests Per Second)
- 메모리 효율: 프로세스 간 통신 오버헤드 제거
2. 최적화 팁
PHP 인스턴스 재사용
// ❌ 잘못된 예: 매 요청마다 새 인스턴스 생성
app.get('/api/*', async (req, res) => {
const php = new Php(); // 매번 생성
// ...
});
// ✅ 올바른 예: 인스턴스 재사용
const php = new Php({ docroot: './public' });
app.get('/api/*', async (req, res) => {
// 기존 인스턴스 사용
const response = await php.handleRequest(/* ... */);
// ...
});
병렬 처리 활용
// 여러 PHP 요청을 병렬로 처리
async function fetchDashboardData(userId) {
const [profile, posts, stats] = await Promise.all([
php.handleRequest(new Request({ url: `/api/user/${userId}` })),
php.handleRequest(new Request({ url: `/api/posts?user=${userId}` })),
php.handleRequest(new Request({ url: `/api/stats/${userId}` }))
]);
return {
profile: JSON.parse(profile.body.toString()),
posts: JSON.parse(posts.body.toString()),
stats: JSON.parse(stats.body.toString())
};
}
캐싱 전략
import NodeCache from 'node-cache';
const cache = new NodeCache({ stdTTL: 600 }); // 10분 캐시
async function getCachedPHPResponse(request) {
const cacheKey = `${request.method}:${request.url}`;
// 캐시 확인
const cached = cache.get(cacheKey);
if (cached) return cached;
// PHP 실행
const response = await php.handleRequest(request);
// 성공 응답만 캐싱
if (response.status === 200) {
cache.set(cacheKey, response);
}
return response;
}
일반적인 오류와 해결 방법
"Cannot find module '@platformatic/php-node'"
원인: 시스템 라이브러리가 누락되어 네이티브 모듈 빌드 실패
해결:
# 시스템 라이브러리 재설치
sudo apt-get install -y libssl-dev libcurl4-openssl-dev libxml2-dev
# node_modules 삭제 후 재설치
rm -rf node_modules
npm install
"PHP execution failed with status 500"
원인: PHP 에러 발생
해결:
// 에러 로그 확인
const response = await php.handleRequest(request);
if (response.status === 500) {
console.error('PHP Error:', response.log.toString());
}
메모리 누수 문제
원인: 대용량 파일 처리 시 메모리 해제 지연
해결:
// 스트리밍 처리로 메모리 사용량 최소화
import { pipeline } from 'stream/promises';
app.get('/download/:file', async (req, res) => {
const request = new Request({
url: `/download.php?file=${req.params.file}`
});
const response = await php.handleRequest(request);
// 스트리밍으로 응답 전송
res.status(response.status);
res.set(Object.fromEntries(response.headers.entries()));
await pipeline(
Readable.from(response.body),
res
);
});
PHP 코드 디버깅 팁
// debug.php
<?php
// 디버그 정보 출력
error_log("Debug: " . json_encode($_REQUEST));
error_log("Debug: " . print_r($_SERVER, true));
// Xdebug 사용 가능
if (extension_loaded('xdebug')) {
xdebug_break();
}
?>
Node.js 측 디버깅 팁
// 요청/응답 로깅 미들웨어
function logPHPCommunication(request, response) {
console.log('PHP Request:', {
method: request.method,
url: request.url,
headers: Object.fromEntries(request.headers.entries()),
body: request.body?.toString()
});
console.log('PHP Response:', {
status: response.status,
headers: Object.fromEntries(response.headers.entries()),
body: response.body.toString().substring(0, 200) + '...'
});
if (response.log.length > 0) {
console.log('PHP Logs:', response.log.toString());
}
}
PHP-Node는 레거시 PHP 애플리케이션과 현대적인 Node.js 생태계를 연결하는 강력한 도구입니다.
- 제로 네트워크 오버헤드: 동일 프로세스 내 실행으로 최고의 성능
- 점진적 마이그레이션: 리스크 없이 레거시 시스템 현대화
- 유연한 아키텍처: 다양한 하이브리드 구성 가능
- 개발자 친화적: 익숙한 HTTP 인터페이스
추천 사용 사례
- WordPress + Next.js 같은 하이브리드 웹 애플리케이션
- 레거시 PHP 시스템의 단계적 Node.js 전환
- 마이크로서비스 아키텍처에서 PHP 서비스 통합
- 개발 환경에서 프론트엔드/백엔드 통합
향후 전망
PHP-Node는 계속 발전하고 있으며, 커뮤니티의 피드백을 통해 더 많은 기능이 추가될 예정입니다.
- Windows 플랫폼 지원
- 더 많은 PHP 확장 모듈 호환성
- 성능 최적화 및 메모리 관리 개선
댓글