介紹一個船新的 PHP SDK + Runtime: PeachPie

前言

這幾天想基於 .NET Core 搞一個自己的部落格網站,於是在網上搜刮各種部落格引擎,找到了這些候選:Blogifier、Miniblog 以及 edi 寫的 Moonglate。

Blogifier:這是前端是個 Angular SPA 應用,不利於 SEO,同時首屏載入速度慢,因此排除。

Miniblog:顧名思義 Mini,可以完美承載內容但是主題實在是過於簡單,沒有可自定義性,因此排除。

Moonglate:總體感覺不錯,介面設計得也很好,功能全面,然而需要 SQL Server 作為資料庫,然而 SQL Server 雖然有 Linux 版本,但受限於主機配置和預算因此也被排除。

難道就沒有適合我需求的部落格引擎了嗎?答案當然是:有。

眾所周知 PHP 是世界上最好的語言(滑稽),還是眾所周知有一個叫做 WordPress 的部落格引擎生態非常龐大,而且是使用 PHP 構建的。

可是 PHP 和 .NET 又有什麼關係呢?

PeachPie

PeachPie 是一個完全構建於 .NET Standard 之上的一套完整的 PHP SDK + Runtime,包含編譯器和運行時等等,兼容 PHP 5.4-7.4(當然部分功能仍在開發中)。

官網://www.peachpie.io

那麼 PeachPie 有什麼優點呢:

  • 開源://github.com/peachpiecompiler/peachpie
  • 跨平台:因為 PeachPie 完全構建於 .NET 之上,因此也就跟著跨平台了,Windows、MacOS、Linux 等等,從架構上跨 x86、x86_64、ARM、ARM64,未來甚至還會有 MIPS、MIPS64、Risc-V 等等……
  • 純託管程式碼:藉助 VS 強大的調試器和 IDE 體驗,從開發、調試到測試、Profile 一條龍非常爽
  • 編譯:PHP 是沒有編譯之說的,這門動態類型語言和 Python 面臨一樣的問題,幾乎無法在編譯時發現程式碼中的錯誤,即便藉助 linter 診斷出了語法錯誤也很難診斷出類型的錯誤。而 PeachPie 則有完善的編譯器套件將 PHP 程式碼完整的編譯為 .NET Standard 程式集,意味著在編譯期就做好了語法和類型檢查,保證了運行時不會因為程式碼問題導致程式崩潰,同時應用分發的時候也不需要源程式碼,確保了源碼安全
  • 與 .NET 互操作:PeachPie 在保留了 PHP 原本的生態基礎上做到了 PHP 和 .NET 的互操作,一個 PeachPie 項目不但可以使用 PHP 原有生態中的包和插件,還能享受 .NET 的生態,快樂超級加倍
  • 運行在 .NET 上:CLR/CoreCLR 自帶久經考驗的 JIT 和 GC,因此通過 PeachPie 編譯的程式集運行在 CLR/CoreCLR 之上則無需做任何的程式碼改動即可享受到這些東西,在 php-bench 中,藉助 CoreCLR 平台的 JIT,函數調用性能拉開了原來 PHP 幾個數量級
  • .NET Foundation 項目:背後有 .NET Foundation 支援,瓦利亞高品質,有保證

可是有人就要問了,為什麼我不直接用 PHP 而是選用 PeachPie 曲線救國呢?

因為我樂意,雨女無瓜(逃

開始使用

本文開發環境採用 Visual Studio Code(需要安裝 PeachPie 插件),當然你也可以用 Visual Studio 等其他開發工具。

安裝 PeachPie 最新的項目模板:

dotnet new -i Peachpie.Templates::*

然後就會出現三個新的項目模板:Console Application、Class library 和 ASP.NET Core Empty。

我們這次整個 Console Application 看看。

dotnet new console -lang PHP

然後隨便寫點程式碼:

<?php

function main()
{
    $students = 
        array(
            array("first_name" => "Joe", "score" => 83, "last_name" => "Smith"),
            array("first_name" => "Frank", "score" => 92, "last_name" => "Barbson"),
            array("first_name" => "Benji", "score" => 90, "last_name" => "Warner")   
        );
    
    foreach ($students as $value) {
        echo $value["first_name"], " ", $value["last_name"], "'s score is ", $value["score"], "\n";
    }
}

main();

用配置 .NET Core 項目的方式寫好 Visual Studio Code 需要的 tasks.json 和 launch.json,隨便下點斷點然後編譯 + F5 運行!

編譯輸出(請無視掉我的霓虹語電腦環境):

.NET Core 向け Microsoft (R) Build Engine バージョン 16.7.0-preview-20220-01+80e487bff
Copyright (C) Microsoft Corporation.All rights reserved.

  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  プレビュー版の .NET Core を使用しています。//aka.ms/dotnet-core-preview をご覧ください
  PeachPie PHP Compiler version 0.9.981+565af85b9aafc42fe1af2f30ccd73ff093a2fad7
  PeachPieConsole -> C:\Users\hez20\source\repos\PeachPieConsole\bin\Debug\netcoreapp3.1\PeachPieConsole.dll

ビルドに成功しました。
    0 個の警告
    0 エラー

経過時間 00:00:12.98

Voila!

Debug

輸出:

Joe Smith's score is 83
Frank Barbson's score is 92
Benji Warner's score is 90

如果去掉打錯一個變數 $value 變成 $vuale 會怎麼樣呢?

<?php

function main()
{
    $students = 
        array(
            array("first_name" => "Joe", "score" => 83, "last_name" => "Smith"),
            array("first_name" => "Frank", "score" => 92, "last_name" => "Barbson"),
            array("first_name" => "Benji", "score" => 90, "last_name" => "Warner")   
        );
    
    foreach ($students as $value) {
        echo $vuale["first_name"], " ", $value["last_name"], "'s score is ", $value["score"], "\n";
    }
}

main();

編譯輸出:

.NET Core 向け Microsoft (R) Build Engine バージョン 16.7.0-preview-20220-01+80e487bff
Copyright (C) Microsoft Corporation.All rights reserved.

  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  プレビュー版の .NET Core を使用しています。//aka.ms/dotnet-core-preview をご覧ください
  PeachPie PHP Compiler version 0.9.981+565af85b9aafc42fe1af2f30ccd73ff093a2fad7
program.php(13,14): warning PHP5007: Undefined variable: $vuale [C:\Users\hez20\source\repos\PeachPieConsole\PeachPieConsole.msbuildproj] 
  PeachPieConsole -> C:\Users\hez20\source\repos\PeachPieConsole\bin\Debug\netcoreapp3.1\PeachPieConsole.dll

ビルドに成功しました。

program.php(13,14): warning PHP5007: Undefined variable: $vuale [C:\Users\hez20\source\repos\PeachPieConsole\PeachPieConsole.msbuildproj] 
    1 個の警告
    0 エラー

経過時間 00:00:09.51

由於上述程式碼在 PHP 中是合法程式碼,因此為了保持兼容性,PeachPie 不會報錯而是給了警告。

但如果我們少一個分號呢:

<?php

function main()
{
    $students = 
        array(
            array("first_name" => "Joe", "score" => 83, "last_name" => "Smith"),
            array("first_name" => "Frank", "score" => 92, "last_name" => "Barbson"),
            array("first_name" => "Benji", "score" => 90, "last_name" => "Warner")   
        )
    
    foreach ($students as $value) {
        echo $value["first_name"], " ", $value["last_name"], "'s score is ", $value["score"], "\n";
    }
}

main();

編譯輸出:

.NET Core 向け Microsoft (R) Build Engine バージョン 16.7.0-preview-20220-01+80e487bff
Copyright (C) Microsoft Corporation.All rights reserved.

  復元対象のプロジェクトを決定しています...
  復元対象のすべてのプロジェクトは最新です。
  プレビュー版の .NET Core を使用しています。//aka.ms/dotnet-core-preview をご覧ください
  PeachPie PHP Compiler version 0.9.981+565af85b9aafc42fe1af2f30ccd73ff093a2fad7
program.php(12,5): error PHP2014: Syntax error: unexpected token 'foreach' [C:\Users\hez20\source\repos\PeachPieConsole\PeachPieConsole.msbuildproj]

ビルドに失敗しました。

program.php(12,5): error PHP2014: Syntax error: unexpected token 'foreach' [C:\Users\hez20\source\repos\PeachPieConsole\PeachPieConsole.msbuildproj]
    0 個の警告
    1 エラー

経過時間 00:00:01.77

這次就會直接報錯了。

由此可見,使用 PeachPie 能夠無需第三方工具輔助,直接在編譯時就驗證程式碼正確性,對項目的健壯性有很大幫助。

PHP 與 .NET 互操作

我們試試互操作,在 PHP 裡面創建一個 .NET 中的 HashSet<TValue>

<?php

function main()
{
    $list = new System\Collections\Generic\HashSet<string>;
    $list->Add("test");
    $list->Add("hello");
    $list->Add("hello");
    $list->Add("lol");
    foreach ($list as $key => $value) {
        echo $key, ": ", $value, "\n";
    }
}

main();

輸出:

0: test
1: hello
2: lol

完美,另外,鑒於 PHP 程式碼最後都會被編譯成 .NET Standard 程式集,因此反過來當然也沒問題,就不做介紹了。

一些坑

當然,PeachPie 現在還處於比較早期階段,儘管大多數 PHP 程式碼都能正常運行,但是標準庫仍存在一些兼容性問題,具體可以去這裡跟蹤://docs.peachpie.io/compatibility-status

也正是因為還是處於早期狀態,所以很多優化工作(比如數組的優化)都沒有做,性能方面還有很大的提升空間。

不過官方目前開發進度十分快,因此短時間內就能看到大量的新庫函數被實現,到目前已經是 0.9.800,1.0 正式版也快要發布了,很快就能正式投入生產使用啦。

Blog 搭建

回到前面的主題,有了 PeachPie,我就能把 WordPress 放到 .NET Core 上面跑啦。

當然,直接下載下來 WordPress 的源程式碼編譯跑到 ASP.NET Core 上面時會出現一些問題,比如資源載入全部 404,這是因為 PeachPie 在編譯 PHP 程式碼時默認不會將非 .php 的文件包含到編譯過程中,我們需要修改 .msbuildproj 調整項目屬性將資源文件包含在編譯過程中,並作為 Content 引入。

另外由於 WordPress 首次配置會現場生成一個 config.php 文件,但是由於該文件是編譯後的程式集在運行時生成的,未參與編譯過程,因此運行時是找不到這個類的,除非重新編譯一遍。因此我們想採用更清真的方式,直接在 appsettings.json 裡面寫入配置然後運行時讀入代替原來的 config.php。

總之需要經過一系列操作,並且編寫少量程式碼。不過,PeachPie 已經幫我們做好了這一切:iolevel 提供了一個即插即用的 WordPress 包 PeachPied.WordPress.AspNetCore//github.com/iolevel/wpdotnet-sdk ),可直接作為 ASP.NET Core 中間件使用,非常方便。

那麼事情就簡單了:

dotnet new web
dotnet add package PeachPied.WordPress.AspNetCore --version 1.0.0-*

然後編寫少量服務端程式碼,配置一下 https 跳轉、響應壓縮和靜態文件什麼的,再加入 WordPress 中間件:

Startup.cs

using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace KeBlogs
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit //go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddResponseCompression(options =>
            {
                options.Providers.Add<BrotliCompressionProvider>();
                options.Providers.Add<GzipCompressionProvider>();
                options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] {
                    "image/svg+xml",
                    "image/png",
                    "font/woff",
                    "font/woff2",
                    "font/ttf",
                    "font/eof",
                    "image/x-icon",
                    "application/json",
                    "application/octet-stream" });
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseHttpsRedirection();
            app.UseResponseCompression();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseDefaultFiles();
            app.UseStaticFiles();

            app.UseWordPress();
        }
    }
}

程式碼部分搞定,當然上述程式碼你也可以用 PHP 來寫。

然後在 appsettings.json 寫入自己的配置,比如(SALT 部分可以沒有):

{
  "WordPress": {
    "dbhost":        "localhost",
    "dbpassword":    "password",
    "dbuser":        "root",
    "dbname":        "wordpress",
    "dbTablePrefix": "wp_",
    "SALT": {
      "AUTH_KEY":         "r(EoMbKEvlg){+!T42fh-e+~IGj-4q}g8HHB9hjbiC0J*ySU1Y*3z[3c}F;6=TA5",
      "AUTH_SALT":        "q0#AzvJ*[4~Bexa9*M(sC_#pDuGQBdjL1}j*RilSe0ku]P~KuTir[7PxjE:4)_zR",
      "LOGGED_IN_KEY":    "!AAienFSridCUzF(v}m#}_;+t%Rclg;mOPKwe;w7dN0M{d,]?8V+TRW_UG)tSswa",
      "LOGGED_IN_SALT":   "C=(4(8WPMeRu_h?g7!ddI*P:+SYU=3C%g)92oV}-y5tE0r?DHWl!fjPOp=bjx2YJ",
      "NONCE_KEY":        "Z[e37@=y)m.CHa:OSldh#RT@nIZxKYGwu!/hd:vK#^{_Ec7e{KNb(G.8ch/MkH(d",
      "NONCE_SALT":       ";v7Wv/BV)Pz{W,FaAKC0buH*5U4:g]qn~;b94x]f8=lm6!yyYSbW5*2y*kRXXEF5",
      "SECURE_AUTH_KEY":  "pc}_Pv52,m=j9l#llSkLVQib.Zm!;9FRzg:{(G]tM8}[}]pPDwB4k{xV+!e)9lmR",
      "SECURE_AUTH_SALT": "#n]+o^w/%-~MVzf{AUuxUAwF[n03r{kr^r1V?wqQ?Vjt}!0HSkCB-):u-ra1%tB="
    },
    "constants": {
    }
  }
}

然後發布我們的 WordPress!

dotnet publish -c Release

最後打包 bin/Release/netcoreapp3.1/publish 上傳到伺服器上面,搭建好資料庫然後運行即可。

完結撒花

進入管理面板,大多數主題、插件都能正常工作,安裝點主題,配置配置插件和 SMTP,就全部搞定啦。

Admin

記憶體佔用 195 MB,運行在 .NET Core 3.1.3 上,非常清真!

至此我的 Blog 搭建完成,歡迎大家訪問://hez2010.com

評論和註冊什麼的也開放了,歡迎大家常光臨~

後續我也會不斷在上面更新文章,當然,這個 Blog 上面的內容也就不僅限於編程啦,敬請期待~

Blog

完結撒花~