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是于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包括多个部分独立的组件,可以用来开发运行在.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在很大程度上与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中让你感兴趣的内容。
Phalanger之所以能够高效地运行PHP应用程序,是因为以下两个原因。 首先,它会编译PHP源代码,而不是解释它;其次,它会把应用作为ASP.NET应用程序运行,那会在Windows下提供额外的性能优势。
编译过程如图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进行了进一步优化。
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类似的功能,但是不会有跨进程通信造成的负载。
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如何做到这一点。
如果你对代码不感兴趣,那么就可以略过这个部分,直接查看第三种情况。 但是,我们不会查看技术细节,只是对让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。
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#编写的,如代码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 streamusing(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开发者可以在表现层中直接使用它。