使用 Phalanger 整合 PHP 和 .Net

Phalanger 是一种 PHP 语言编译器,也是针对 .NET 的 PHP 运行时。 它可以用于把 PHP web 项目编译成 .NET 字节码,并在 Windows 中使用 IIS 或者在 Linux 上使用 Mono 和 Apache 作为 ASP.NET 应用程序来执行。 然而,Phalanger 不仅仅是把已经存在的 PHP 应用编译到 .NET 中。

我们可以使用 Phalanger 创建组合 .NET 和 PHP 的解决方案,所采用的方式用标准的 PHP 解释器是不可能做到的。 有了 Phalanger 扩展,PHP 程序可以直接使用 .NET 类,而 .NET 程序(比方说用 C# 编写的)也可以动态地调用 PHP 脚本,或者使用在 PHP 中实现的函数和类【6】。

本文中,我么会简要地介绍 Phalanger,然后查看三种使用方案。 我们会讨论如何整合 PHP 应用程序和 .NET;如何高效地在 Windows 上运行 PHP 应用程序,以及如何使用 PHP 作为 ASP.NET 的视图引擎(view engine)。

Phalanger 简介

Phalanger 已经存在一段时间了。 第一版 Phalanger 是于 2003 年在布拉格的查尔斯大学作为软件项目创建的。稍后就开始了2.0版本的开发,并且于 2006 年在 CodePlex 作为开源项目发布。 微软支持了这个项目一段时间,后来一位 Phalanger 开发者加入微软,并从事动态语言运行时方面的工作。

Phalanger 相关活动在 2008 年恢复,这多亏有了与 Jadu 的合作,它使用 Phalanger 为在 PHP 中开发的 CMS 构建了 .NET 版本。 从 2010 年开始,Phalanger 的开发主要由 DEVSENSE 提供资金支持,它也为 Phalanger 提供了商业支持。 最近发布的版本 Phalanger 2.1【7】,其中提升了与标准 PHP 实现的兼容性,在动态操作的实现过程中利用了 DLR,并提供了 PHP 和其他 .NET 语言(像C#、F#和 Visual Basic)之间的互操作性。

Phalanger 的组件

Phalanger 包括多个部分独立的组件,可以用来开发运行在 .NET 上的 PHP 应用程序,并使用 .NET 或 Mono 来运行它们:

  • Phalanger 编译器Phalanger 会把 PHP 源代码编译成 .NET 程序集,它可以使用 .NET JIT(Just-in-time 编译器,它会为当前平台生成本地代码)执行。 编译后的 PHP 代码会使用 Phalanger 运行时和动态语言运行时,从而提供了 PHP 语言动态特性的高效率实现。
  • Phalanger 运行时和类库Phalanger 运行时提供了对数组之类 PHP 特性的实现。 Phalanger 还包含了针对I/O、正则表达式以及其他标准 PHP 类库的 .NET 实现。
  • 本地扩展在 32 位 Windows 平台上,Phalanger 可以通过本地的桥接程序使用所有现存的 PHP 4 扩展。 尽管这会带来一些运行时负载,但这让我们不需要额外工作就可以运行某些 PHP 应用程序。
  • 托管的扩展 通过包装 .NET 中提供的类似功能,PHP 扩展也可以重新实现。 这些扩展可以是由任何 .NET 语言编写,并提供很好的性能。 Phalanger 中包含多个扩展,包括 SPL、JSON、SimpleXML、MySQL 和 MS SQL 的提供程序。 DEVSENSE【9】还提供了附加的扩展,像 Memcached、图像和 cURL 等。
  • 与 Visual Studio 的集成Phalanger 还与 Visual Studio 集成(最近的更新支持 Visual Studio 2010)。 集成功能添加了针对 PHP 文件的颜色突出显示和智能提示功能,让我们可以调试使用 Phalanger 运行的 PHP 应用程序。

Phalanger 使用案例

Phalanger 在很大程度上与 PHP 5 兼容,可以运行大量开源的 PHP 项目,包括 WordPress 和 MediaWiki。 我们可以使用它把这些项目集成到 .NET 生态系统中,也可以开发新的项目,它会兼有 PHP 和 .NET 的优势。 在本文剩下的内容中,我们会讨论以下三种使用案例:

  • 方案1: 高效运行 PHP 应用程序。 使用 PHalanger 在 Windows 上编译的 PHP 应用程序的性能,要比通过 FastCGI 使用标准 PHP 解释器运行的高。 这使得选择 Phalanger 在 Windows 环境中部署 PHP 很具有吸引力。
  • 方案2: 把 WordPress 与 ASP.NET 整合。 使用 Phalanger 编译的 PHP 代码能够调用所有 .NET 程序库。 这可以用于在 PHP 和 ASP.NET 应用程序之间共享用户数据库或者其他数据。
  • 方案3: 从 ASP.NET 应用程序中调用 PHP。 PHP 的灵活性对于编写脚本或者编写 web 应用程序的表现层非常有用。 有了 Phalanger,我们就可以在 .NET 中开发应用程序,并使用 PHP 作为脚本语言或者视图引擎。

以下三个部分会详细讨论各种方案。 我们首先会给出概览,然后查看一些技术细节,它会说明 Phalanger 中让你感兴趣的内容。

方案1: 高效运行 PHP 应用程序

Phalanger 之所以能够高效地运行 PHP 应用程序,是因为以下两个原因。 首先,它会编译 PHP 源代码,而不是解释它;其次,它会把应用作为 ASP.NET 应用程序运行,那会在 Windows 下提供额外的性能优势。

使用 Phalanger 和 .NET 编译 PHP

编译过程如图 1 所示。正如图上所显示的,Phalanger 会把 PHP 源代码编译成 .NET IL(中间语言),那是与架构独立的低级字节码。 编译后的代码会使用 PHP 核心库(Phalanger 的一部分)和动态语言运行时(DLR)来执行标准的 PHP 操作。 当应用程序启动时,.NET JIT(just-in-time)编译器会把这些组件转换为针对当前处理器架构优化过的本地代码。

图1. 使用 Phalanger 把 PHP 源代码编译成本地代码的过程

正如 Phalanger 评测显示【10】,使用 Phalanger 编译的 WordPress 在 Windows 下的性能比通过 FashCGI 使用标准 PHP 解释器的好,也比通过 WinCache 使用 PHP 的稍好一些。 然而,评测没有测试 Phalanger 最新的版本,它使用 DLR 进行了进一步优化。

使用 ASP.NET 部署 PHP 应用程序

Phalanger 应用程序的运行方式和 ASP.NET 应用程序完全相同。 这让它具有了重要的性能优势,特别是在 Windows 系统下,进程要比线程耗费更多资源。

图 2 显示了运行 PHP 应用程序的不同可选方案。

当使用标准 CGI 模式时,web 服务器会为每个进入的请求启动新的进程。 在 Windows 下,这样做的效率不高,它还阻止了共享位于共享内存中的状态,也很难进行进程中缓存(in-process caching)。 当使用 FashCGI 模式时,web 服务器会重用进程,这样它不需要为每个请求启动新的进程。 然而,这还是无法共享内存中的状态,因为不同的进程拥有不同的状态。

图2. 使用 CGI、FashCGI 和 Phalanger 运行 PHP

Phalanger 的行为方式和所有 ASP.NET 应用一样。 单独的叫做应用池(Application Pool)的 ASP.NET 进程会处理所有进入的请求。 我们甚至可以在单一进程(应用池)中配置多个 PHP 应用程序(像多个 WordPress 的独立实例)。 在进程中,会有多个线程,这些线程会被重用以处理单独的请求。 在 Windows 下,线程要比过程更轻量级,所以这种解决方案更有效率,并且会消耗更少的内存。

对于运行在单一进程中的应用程序,我们可以进行进一步的优化,并采用其它有趣的方案。 例如,Phalanger 会使用动态语言运行时(DLR)来做动态方法调用。 DLR 会使用与时间相适应的缓存机制,因此在几次请求之后,DLR 就会“知道”应用程序使用的是哪个方法,并变得更快一些。 这只有在单一进程中处理请求的情况下才可能做到。

在单一进程中运行所有代码也意味着应用程序可以在内存中存储全局状态。 这可以用于实现与 WinCache 提供的 User Cache 类似的功能,但是不会有跨进程通信造成的负载。

方案2: 把 WordPress 与 ASP.NET 整合

PHP 的一点优势就在于拥有大量优秀的开源 CMS 系统(WordPress、Joomla 等等)、表单应用程序(phpBB 及其他)和 wiki(Mediawiki 及其他),其中很多都通过了 Phalanger 的测试。

这些应用通常会比 .NET 平台下类似的程序包提供更多特性。 开发基于 ASP.NET 网页的公司可能会面临以下情况:

  • 它需要向现存的 ASP.NET 解决方案中添加 wiki、论坛或者博客,但是只有在 PHP 中存在合适的应用程序(例如,免费并且带有所有必要特性)。
  • 应用程序可能会在子域下运行,但是它应该共享用户数据库。 此外,一旦用户登录到主页,那么他就应该同时登录到 wiki、论坛和博客上。

ASP.NET 应用程序可以使用 ASP.NET 的成员管理(ASP.NET Membership),它还用来管理用户、角色和功能的标准机制。 有了 Phalanger,我们就可以修改开源的 PHP 项目,从而使用同样的机制。 下一部分会演示使用 WordPress 如何做到这一点。

为 WordPress 实现 ASP.NET 成员管理插件

如果你对代码不感兴趣,那么就可以略过这个部分,直接查看第三种情况。 但是,我们不会查看技术细节,只是对让 PHP 调用 .NET 程序库的 PHP 扩展做简要的概述。

我们可以使用插件轻松地自定义在 WordPress 管理用户的方式。 管理用户的插件需要实现一个 PHP 类,其中有各种成员函数。 其中最值得期待的功能就是身份验证,它会获得用户名和密码。 它应该填充当前用户的信息,或者,当用户不存在的时候,就会把名称设置为 NULL。

为了使用 .NET 中的 ASP.NET 成员管理来实现身份验证功能,我们可以使用 System.Web.Security 命名空间中的功能。 静态方法 Membership.ValidateUser 会检查密码是否正确,而 Membership.GetUser 会返回用户的基本信息。 使用 Phalanger,我们可以访问 .NET 对象,就像它是标准的 PHP 对象一样,这样实现验证机制就很简单了。 代码 1 展示了简化后的代码。

代码 1 在 WordPress 插件中实现身份验证功能的函数

import namespace System:::Web:::Security;

function authenticate (&$username,$password) {

global $errors;

// Test whether the password is correct

if (Membership::ValidateUser ($username,$password)) {

// Get information about the user and fill $userarray

$user = Membership::GetUser ($username);

$userarray[‘user_login’] = $user->UserName;

$userarray[‘user_email’] = $user->Email;

$userarray[‘display_name’] = $username;

$userarray[‘user_pass’] = $password;

// Loading of roles & profiles omitted for simplicity

// Update or create the user information in WordPress

if ($id = username_exists ($username)) {

$userarray[‘ID’] = $id;

wp_update_user ($userarray);

}

else

wp_insert_user ($userarray);

} else {

// Report error if the login failed

$errors->add (‘user-rejected’, ‘Log-in failed!’);

$username = NULL;

}

}

代码首先声明了重要的命名空间。 这是一个非标准的 Phalanger 扩展,它从引用的程序库的 .NET 命名空间中导入了功能(我们可以使用 web.config 文件来引用程序库)。 在将来的版本中,Phalanger 会使用 PHP 5.3 支持的标准命名空间,但是这项改变还没有完全实现。

剩余部分的代码看起来和标准的 PHP 代码一样。 然而,Membership 类实际上是标准的 .NET 类。 Phalanger 会把 PHP 类和 .NET 类同等对待,所以我们可以使用标准的语法来调用 .NET 方法。 函数 ValidateUser 和 GetUser 都是静态函数,所以使用::语法来调用。 GetUser 的结果是一个 .NET 的 MembershipUser 对象, 其中带有各种属性,包括关于用户的基本信息。 我们仍然可以使用标准的标记法来访问对象的字段(它们被实现为 .NET 的属性)。

正如你所看到的,我们可以很自然地在 PHP 中使用 .NET 功能。 由于代码会被编译成 .NET 程序,所以在调用 .NET 库时不会有任何负载。 下一部分展示的是反方向的整合——从 .NET 应用程序中调用 PHP。

方案3: 从 ASP.NET 应用程序中调用 PHP

PHP 的主要优势就在于灵活性和简单性,这使得它成为编写脚本和实现渲染 HTML 很棒的语言。 然而,有些人发现,想要实现大型应用程序,那么在静态类型语言——像 Java 或C#——会更容易一些。 使用 Phalanger,我们可以同时获得两方面的优势。

这个部分所讨论的方案演示了一种组合 ASP.NET 和 PHP 的方式。 它基于先进的 ASP.NET MVC(模型、视图、控制器)框架,将表现层、负责交互的层和应用程序的业务逻辑分离开来。 我们可以使用不同的语言来开发单独的组件:

  • C#模型和控制器 模型和控制器会在 C# 中编写。 应用程序的这个部分会实现业务逻辑,通常这在静态类型语言中编写更容易一些,特别是在业务逻辑非常复杂的情况下。 此外,我们还可以使用像 LINQ 之类的技术来存储数据,使用任务并行库(Task Parallel Library)使用多线程来实现高性能计算。
  • PHP 视图 应用程序的表现层会用 PHP 编写。 在这里,PHP 的简单性和灵活性会提供最大的好处。 此外,这意味着应用程序的这个部分可以由开发经验比较少的开发者来编写,因为大多数 web 开发者的 web 设计师都对 PHP 有些了解。

还有一些情况,从 C# 中调用 PHP 会很有用。 例如,你可以在大型的 C# 项目中使用 PHP 作为脚本语言。 这也非常有用,因为 PHP 是一种广为所知的语言。 另一种情况是,当在 C# 中使用 PHP 程序库的时候——正因为有了 Phalanger 的 duck typing 机制,这才得到了很大程度的简化,该机制甚至可以为调用文档齐备的 PHP 代码生成静态类型的 C# 接口。

在本文剩余的内容中,我们会着重讨论使用 PHP 实现 ASP.NET 应用程序表现层的方案。 你可以在文章末尾找到其他方案(像编写脚本)的参考信息。

在 C# 和 PHP 中创建模型-视图-控制器应用程序

首先让我们看下使用 C# 和 PHP 组合创建出来的简单应用程序。 应用程序的模型和控制器都是使用 C# 编写的,如代码 2 所示。在这个例子中,模型只是一个简单的 C# 类,它表示的是产品信息。 在现实情况下,这个类可能会负责从数据库载入数据,并且可能使用 LINQ 来实现。

代码2: 示例 web 应用程序(C#)的模型和控制器

public class Product {

public string ProductName { get; set }

public double Price { get; set }

}

public class HomeController : Controller {

public ActionResult Index () {

ViewData.Model = new Product { ProductName = "John Doe", Price = 99.9 };

return View ();

}

}

控制器组件是通过 HomeController 类实现的,它会继承 ASP.NET MVC 控制器。 类中只包含一个动作,展现应用程序的索引页面。 当用户访问/Home/Index(或者根 URL)的时候就会触发这个动作。 它会创建模型(Product 类的实例)并把它传递给视图组件。

在标准的 ASP.NET MVC 应用程序中,视图组件通常会使用 ASPX 页面或者使用带有使用 C# 或 Visual Basic 编写的代码的 Razor 视图来实现。 Phalanger 让我们可以使用 PHP 来实现视图。 代码 3 展示了这个例子。

代码 3 示例 Web 应用程序(PHP)的视图

<html><head>

<title>Sample view written in PHPtitle>

head>

<body>

<h1>Product Listing using Phalangerh1>

Product: $MODEL->ProductName; ?><br />

Price: $MODEL->Price; ?>

body>html>

视图会使用下面描述的 ASP.NET MVC 扩展来渲染。 扩展会执行代码 3 中所示的 PHP 脚本,并定义名为$MODEL 的全局变量,其中会包含控制器返回的数据。 在上述示例中,$MODEL 是对标准 .NET 类的引用。 Phalanger 会对 .NET 类和 PHP 对象同等对待,所以使用 echo 结构,我们很容易就可以显示产品的属性。

示例显示了应用程序的基本结构,但是它极为简单,所以不会真正显示出在表现层使用 PHP 所能给我们带来的好处:

  • PHP 与生俱来的动态特性使得渲染任何结构的数据都很简单。 视图并不仅限与简单脚本,并且可以使用任何现存的 PHP 库,包括流行的模板引擎(templating engines)。
  • 视图可以使用 PHP 的 include 功能实现多文件的结构,这样你可以完全控制页面如何生成。
  • 创建视图的开发者不需要知道任何关于 .NET 的知识。 这意味着从 PHP 转型为 C# 的公司,仍然支持现存的开发者技能。

为了让你更好地了解这个方案的工作方式,以下部分会说明关于 PHP 和 C# 整合的技术细节。 如果你对细节不感兴趣,那么就可以直接跳到总结部分。

透过现象看本质

这个部分所描述的方案基于 PicoMVC 项目【4】,它让我们可以组合 PHP 和F#。 为了让示例更简单,我把代码从F#转换为C#。 在 PicoMVC 中 PHP 整合的核心是一个简单的函数,它会取得 PHP 脚本的文件名,并使用 Phalanger 运行时来运行。 函数如代码 4 所示。

代码 4 从 ASP.NET web 应用程序调用 PHP 脚本

void PhalanagerView (string fileName, object model, HttpContext current) {

// Initialize PHP request context and output stream

using (var rc = RequestContext.Initialize (ApplicationContext.Default, current))

using (var byteOut = HttpContext.Current.Response.OutputStream)

using (var uftOut = new StreamWriter (byteOut)) {

// Current context for evaluating PHP scripts

var phpContext = ScriptContext.CurrentContext;

// Redirect PHP output to the HTTP output stream

phpContext.Output = uftOut;

phpContext.OutputStream = byteOut;

// Declare global $MODEL variable (if model is set)

if (model != null)

Operators.SetVariable (phpContext, null, "MODEL",

ClrObject.WrapDynamic (model));

phpContext.Include (fileName, false);

}

}

PhalangeriView 方法会获得文件名(指向 PHP 脚本)、代表作为模型返回的数据的 .NET 对象以及当前的 HTTP 上下文。 它首先会初始化 RequestContext,从而 Phalanger 知道它是在处理作为 HTTP 请求一部分的脚本。 然后,它会确保所有 PHP 脚本生成的输出都会直接作为 HTTP 响应发送。 当作为脚本运行 PHP 的时候,输出可以重定向到内存流,从而以不同的方式处理。 最后,方法会声明全局变量 MODEL,并使用 Phalanger 所提供的 Include 方法来执行 PHP 脚本。

这个例子并不完全是从 C# 调用 PHP 的指引,你可以在 Phalanger 博客的文章中找到更详细的信息。 然而,它应该可以说明,使用 Phalanger 从 C# 调用 PHP 脚本相当容易。 这在本节讨论的 web 编程情况下会很有用,但是它给了我们更多选择。

总结

本文简要地介绍了 Phalanger——针对 .NET 的 PHP 编译器——以及几种方案,我们可以在实践中使用它来解决重要问题。 最近 Phalanger 项目非常活跃,2.1版本中包含了很多兼容性方面的改善、使用动态语言运行时(DLR)以获得更好的性能,以及与 Visual Studio 2010 的集成。

我们看了三种可以在 web 开发中使用 Phalanger 的方案。 第一种方案是使用 Phalanger 在 Windows 环境下运行未经修改的开源 PHP 项目(像 WordPress)。 使用 Phalanger 编译的应用程序可以运行在 ASP.NET 下,这种主机会更轻量级,运行效率也更高。

在第二种方案中,我们查看了集成在 .NET 生态系统中的 PHP 应用程序。 有了 Phalanger 扩展,我们就可以在 PHP 代码中直接调用 .NET 程序库。 例如,这可以用来整合 ASP.NET 应用程序和 WordPress 之间的用户数据库。

最后一种方案演示了一种 web 框架,它使用 PHP 作为在 ASP.NET MVC 中编写视图的语言。 通过这种方式,.NET 开发者可以很容易地提供应用程序的业务功能,而 PHP 开发者可以在表现层中直接使用它。

关于作者


Tomas Petricek是微软的C# MVP 和F#布道师。 他和 Jon Skeet 一起编写了《Real-World Functional Programming》一书,说明了函数式的概念,并向有 C# 背景的开发者说明如何使用F#。 Tomas 还是 DEVSENSE 的共同创始人,他对很多开源项目都做出了贡献,包括 Phalanger 和F#语言针对 MonoDevelop 的集成。

参考信息

[1] The Phalanger 网站包含了关于项目的最新消息。

[2] 文档和介绍(Phalanger Wiki)

[3] Phalanger 2.1下载包括安装程序和源代码(CodePlex)

[4] PicoMVC 项目使用 Phalanger 作为轻量级的视图引擎(Robert Pkckering 的 Strange 博客)

[5] PHP 作为针对 C# 的脚本语言 讨论了另一种对 Phalanger 的用法(Phalanger 博客)

[6]以类型安全的方式在 C# 中使用 PHP 对象(Using PHP objects from C# in a type-safe way) 说明了 Phalanger 提供的,用于从 C# 访问 PHP 对象的安全机制。

[7]Phalanger 利用 DLR 的优势宣布了 Phalanger 2.1 的发布(Phalanger 博客)

[8] Jadu CMS 和 Microsoft .NET ——使用 Phalanger 把 PHP 应用程序编译到 .NET 的案例学习 [9]Phalanger 支持包含了额外托管的 PHP5 扩展(DEVSENSE)

[10] Phalanger 评测 包含额外的性能信息(Phalanger 博客)