PHP 怎么Electron PHP

wen PHP项目 2

本文目录导读:

PHP 怎么Electron PHP

  1. 方案一:使用 PHP 内置服务器(最简单)
  2. 方案二:使用 PHP Web 服务器库(更专业)
  3. 方案三:使用 PHP 编译为原生模块
  4. 生产环境最佳实践
  5. 使用建议
  6. 常见问题解决

我来详细解释如何在 Electron 中使用 PHP,有几种不同的方案:

使用 PHP 内置服务器(最简单)

项目结构

my-electron-app/
├── main.js
├── package.json
├── php/
│   └── server.php
└── public/
    ├── index.html
    └── api/
        └── test.php

主进程配置 (main.js)

const { app, BrowserWindow, shell } = require('electron');
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
let phpServer = null;
let mainWindow = null;
// 查找 PHP 可执行文件
function findPHP() {
  const candidates = process.platform === 'win32' 
    ? ['C:/php/php.exe', 'C:/wamp64/bin/php/php7.4.9/php.exe']
    : ['/usr/bin/php', '/usr/local/bin/php', 'php'];
  for (const candidate of candidates) {
    if (fs.existsSync(candidate)) {
      return candidate;
    }
  }
  return 'php'; // 最后尝试用系统 PATH 里的
}
function startPHPServer() {
  const phpPath = findPHP();
  const phpScriptPath = path.join(__dirname, 'php', 'server.php');
  phpServer = spawn(phpPath, ['-S', '127.0.0.1:8000'], {
    cwd: path.join(__dirname, 'public'),
    stdio: ['ignore', 'pipe', 'pipe']
  });
  phpServer.stdout.on('data', (data) => {
    console.log(`PHP Server: ${data}`);
  });
  phpServer.stderr.on('data', (data) => {
    console.error(`PHP Server Error: ${data}`);
  });
}
function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js')
    }
  });
  // 启动 PHP 服务器
  startPHPServer();
  // 加载 PHP 服务器上的页面
  mainWindow.loadURL('http://127.0.0.1:8000');
  // 处理外部链接
  mainWindow.webContents.setWindowOpenHandler(({ url }) => {
    shell.openExternal(url);
    return { action: 'deny' };
  });
}
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
  // 关闭 PHP 服务器
  if (phpServer) {
    phpServer.kill();
  }
});
app.on('activate', () => {
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow();
  }
});

PHP 启动脚本 (php/server.php)

<?php
// 简单的路由
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
switch ($uri) {
  case '/':
    echo file_get_contents(__DIR__ . '/../public/index.html');
    break;
  case '/api/data':
    header('Content-Type: application/json');
    echo json_encode(['message' => 'Hello from PHP!']);
    break;
  default:
    http_response_code(404);
    echo json_encode(['error' => 'Not found']);
}
?>

前端页面 (public/index.html)

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">Electron + PHP</title>
</head>
<body>
  <h1>Electron + PHP</h1>
  <button id="fetchData">获取数据</button>
  <div id="result"></div>
  <script>
    document.getElementById('fetchData').addEventListener('click', async () => {
      try {
        const response = await fetch('/api/data');
        const data = await response.json();
        document.getElementById('result').textContent = JSON.stringify(data);
      } catch (error) {
        console.error('Error:', error);
      }
    });
  </script>
</body>
</html>

使用 PHP Web 服务器库(更专业)

使用 ReactPHP 或 Workerman

// main.js - 使用子进程
const { spawn } = require('child_process');
function startPHPBackend() {
  const phpScript = `
    <?php
    require __DIR__ . '/vendor/autoload.php';
    use Workerman\\Worker;
    $worker = new Worker('http://127.0.0.1:8000');
    $worker->count = 4;
    $worker->onMessage = function($connection, $request) {
        $connection->send('Hello from PHP with Workerman!');
    };
    Worker::runAll();
  `;
  // 写入临时文件
  fs.writeFileSync('php_backend.php', phpScript);
  const backend = spawn('php', ['php_backend.php']);
  return backend;
}

使用 PHP 编译为原生模块

使用 phpmain 或 php-wasm

// 使用 PHP WebAssembly (php-wasm)
const { loadPHPRuntime } = require('php-wasm');
async function measurePerformance() {
  const php = await loadPHPRuntime();
  const output = php.run(`<?php echo "Hello from WASM PHP!";`);
  console.log(output);
}

生产环境最佳实践

打包时内置 PHP(使用 electron-builder)

package.json 中:

{
  "build": {
    "extraResources": [
      {
        "from": "php/",
        "to": "php/",
        "filter": ["**/*"]
      }
    ]
  }
}

完整的主进程代码

const isDev = !app.isPackaged;
function getPHPPath() {
  if (isDev) {
    return 'php'; // 开发环境
  } else {
    // 生产环境路径
    return path.join(process.resourcesPath, 'php', 'php.exe');
  }
}
function initPHP() {
  const phpPath = getPHPPath();
  // 每次启动都使用新的端口,避免冲突
  const port = 8000 + Math.floor(Math.random() * 1000);
  phpServer = spawn(phpPath, ['-S', `127.0.0.1:${port}`], {
    cwd: isDev ? path.join(__dirname, 'public') : path.join(process.resourcesPath, 'public'),
    env: {
      ...process.env,
      PORT: port
    }
  });
  return `http://127.0.0.1:${port}`;
}

安全考虑

// preload.js
const { contextBridge } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
  getPlatform: () => process.platform,
  // 只暴露必要的 API
  phpEndpoint: 'http://127.0.0.1:8000'
});

使用建议

  1. 开发时:直接使用本地 PHP 服务器
  2. 生产时:将 PHP 编译为二进制文件,或使用 Docker 容器
  3. 性能要求高:考虑使用 PHP 编译为本地二进制(如 RoadRunner)
  4. 简单需求:如果只是字符串处理,可以用 php-wasm

常见问题解决

PHP 不在 PATH 中

// 检测并添加 PHP 路径
function addPHPPath() {
  const phpPaths = {
    win32: 'C:\\xampp\\php',
    darwin: '/Applications/MAMP/bin/php/php8.0.0/bin',
    linux: '/usr/bin/php'
  };
  process.env.PATH += path.delimiter + phpPaths[process.platform] || '';
}

端口冲突

const net = require('net');
function getAvailablePort(start = 8000) {
  return new Promise((resolve, reject) => {
    const server = net.createServer();
    server.listen(start, () => {
      server.close(() => resolve(start));
    });
    server.on('error', () => resolve(getAvailablePort(start + 1)));
  });
}

这种方案完全可行,特别适合需要 PHP 后端的 Electron 应用,生产环境建议将 PHP 打包进应用,减少外部依赖。

抱歉,评论功能暂时关闭!