The ultimate guide to migrating a PHP project to .NET with PeachPie

PHP is an incredibly rich ecosystem, but with the growing popularity of .NET Core, you may want to combine these two worlds. Let’s take a look at how we migrate a PHP project to .NET with the help of VS Code and PeachPie.


The following process does not require any PHP to be installed anywhere. We just make .NET “think” that your PHP code is just another C# / F# / VB.NET project in your solution, and compile it to a managed DLL. So what are the actual requirements:

  • .NET Core 3.0 SDK (installed standalone or you might
    already have it as a part of Visual Studio 2019)
  • Visual Studio Code (runs on Linux, macOS or Windows)

In summary, the steps will be roughly the following:

  1. Create a project file describing your project; how it should be compiled, which files represent actual code and which ones are only content.
  2. Run the analysis; reveal errors in the code or possible issues. Be prepared for a common PHP project to have many.
  3. Run the code on .NET! See what’s not working, watch the debug output window for runtime issues and warnings.
  4. Build a sort of .NET app around it; utilize the compiled PHP library.
  5. Additionally, take a look at the performance diagnostics.

Important myths debunked and facts stated

Before moving on, let’s quickly address a few common questions and misunderstandings:

  • The PHP code is not translated to C#.
  • Only the .NET Runtime is needed. PHP or PHP’s extensions are not.
  • The following process changes the way you approach your PHP code; it becomes a .NET project with all its pros and cons.
  • The project must be re-compiled every time you change the source code.
  • You can keep working on the code in the PHP language, or extend it seamlessly in C#.
  • The result of the compilation is a DLL file (optionally packed into a NuGet package), which can be used as a natural dependency of your C# project.
  • PHP classes become available as .NET classes.
  • You can seamlessly use .NET references in your PHP code as well; naturally use anything from .NET in your PHP code and vice versa – that is the actual benefit of the migration.

Grab the extension for VS Code

Let’s start by opening Visual Studio Code and installing the extension for development with PeachPie; it provides features that understand .NET and PHP together, while running the compiler itself on the background.

The extension is continuously being improved to provide the tools needed to analyze and migrate code from the PHP runtime to the .NET runtime. For more information, see the extension’s readme page at


Let’s start with a single folder containing your PHP website root. A similar approach applies for console applications. Open the folder in VSCode:

Open the root of your website.

Now we create a project file – a project file is an XML that describes the project- what language it is, what files are compiled, what are the dependencies. See for more information. Bring up VS Code’s Command Palette (F1) and execute the Create a class library command:

Create a default project file for PHP code.

Notice: there is no straightforward “website” option (yet). The reason is that the website is actually a “library” of compiled PHP scripts, functions and classes. This library is then utilized by the actual web server (to follow later in this article).

You’ll see that you need the C# Extension as well now. The command above activates it for you and also downloads all the necessary development dependencies. What does the project file look like? Go on and open the newly created library.msbuildproj file.

Default project file for compiling PHP files.

As soon as the file is created and the dependencies are downloaded, the extension fires up the actual compiler on the background. Keep in mind, the compiler is an analysis engine in the first place – it runs an analysis of your code. As a result you’ll see a bunch of diagnostics in a few seconds:

Sample PHP code diagnostics.

Fix errors

Most problems are warnings or informational in nature and severity. But at this point, you may already see error diagnostics (), which prevent you from progressing and must be resolved. See the full reference of error diagnostics and hints on how to resolve them at For the most part, issues are real bugs in the code, but sometimes it is simply something you don’t need. You can either:

  • Resolve the error appropriately. You’ll need to edit the source code for this.
  • Exclude the script file from your project (usually you exclude test files and all the packages you don’t actually need).

Excluding may break the project even more – you should be quite certain of what you’re doing. If the excluded script contains a class that is a dependency to another class, you’ll get another error of an undefined base class (PHP4044). The pattern for excluding a set of files form the project may look as follows. Alter your library.msbuildproj and save.


For more information see How to: Exclude files from the build.

dotnet build

You’ll get the same diagnostics by running the full compilation. Just to make sure we bring up the terminal and try to actually compile the library to get a .dll file.

> dotnet build

You should see the resulting library.dll file in the ./bin/debug/netstandard2.0/ directory. Together with .pdb and .xml which will be useful later.

The debug build for the website.msbuildproj project; if there are no warnings and errors.

// Sometimes the compiler crashes (it happens…). The exception usually contains the details and we have to deal with it somehow.

Create a bootstrapper

Good; at this point the PHP code compiles. Every time the code changes, run dotnet build to make sure the project is rebuilt. You can also set up the build on your favorite continuous integration platform to see diagnostics. The library.dll, however, does nothing by itself. We have to utilize it.

We’ll create a regular ASP.NET Core program (acting as the bootstrapper) that will handle web requests. The requests corresponding to a PHP script will be handled by the compiled library.dll. Anything else (e.g. requests to any type of content – images, .js, .css, etc.) will be handled by the usual ASP.NET Core request middlewares as nicely documented on Microsoft docs.

Normally, next to your directory with the PHP project, we will create another folder named app/, for instance, with the following C# code:

<Project Sdk="Microsoft.NET.Sdk.Web">
    <PackageReference Include="Peachpie.AspNetCore.Web" Version="0.9.600" />
    <ProjectReference Include="..\library\library.msbuildproj" />

app/app.csproj: C# project file for the ASP.NET Core program

using System.IO;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace app
    class Program
        static void Main(string[] args)

    class Startup
        public void ConfigureServices(IServiceCollection services)
            // Adds a default in-memory implementation of IDistributedCache.

            services.AddSession(options =>
                options.IdleTimeout = TimeSpan.FromMinutes(30);
                options.Cookie.HttpOnly = true;

        public void Configure(IApplicationBuilder app)
            // enable session:

            // enable .php files from compiled assembly:
            app.UsePhp( new PhpRequestOptions() { ScriptAssembliesName = new[] { "library" } } );


app/program.cs: Default implementation of ASP.NET Core program.

The above is a minimalist implementation of an ASP.NET Core website only handling requests to *.php files and content files. You can actually use your own existing ASP.NET Core application; just extend it with the call to app.UsePhp();. This app implements the entry point of the application.

Now we can build on top of it. The following are some features we can add to the PHP program by implementing it in C#:

Run it

Now we’re ready to start the whole thing for the first time. We’ll be running the app project as it’s usually done in the dotnet world.

Important! we want to debug it as well. It is unrealistic to run an app for the first time expecting not to run into any issues. You might have the .vscode/launch.json and
.vscode/tasks.json files created already. You can delete them and create from scratch:

Create tasks.json and launch.json for .NET Core.

Open those two files and ensure they point to the app project as roughly depicted below:

“args”: [“build”, “${workspaceFolder}/app/app.csproj”],

“program”: “${workspaceFolder}/app/bin/Debug/netcoreapp3.0/app.dll”,

Starting the project (F5) should compile your project if anything changed and start it within VSCode’s Debug Console:

Debugging app in VS Code.

Watch this window carefully;

  • Content root path: might need to change; by default, it’s a place where your content files are looked for. Usually you want this to be the root of your original PHP project. You might need to change it.
  • Now listening on: is where your development app server listens for a connection. Enter this URL into your browser and you should hopefully see some results.

Any runtime warning or error will be logged here!

The debugger is attached to the application already and all the debug messages are shown in the DEBUG CONSOLE. You don’t have to inspect log files etc. Also, the debugger breaks our code when an exception occurs. All the other trace messages are logged there as well. Any PHP warning in this window deserves to be considered, since it might mean “something”.

An example of a debug log – either the constant is invalid or PeachPie does not implement it!

Inspecting your code

Now for the fun part. You have all the tools to compile and debug the program. Something might not work well – you can work around it or report an issue on But what about improving the code – finding incompatibilities with .NET or navigating through the project?

Expected output of the “Language Server” within VSCode.

First, ensure that the compiler managed to load the project – in the Output panel, switch to PeachPie Language Server and make sure it loaded your project. If you don’t see it there, you may want to add a Visual Studio solution file (.sln) into the root of your workspace.

Watching .NET types and methods

One of the most interesting features of the compilation to .NET is the seamless interoperability with the rest of the .NET ecosystem. All the classes and methods can be used as they are.

Check whether the method got resolved correctly by watching the tooltips. A correctly resolved method will give you a tooltip with the method’s summary:

Seamlessly interacting with native .NET types and methods

This cross-language tooltip information is based on XMLDoc. Since both PHP’s PHPDoc and C# XML comments are compiled to the same XMLDoc file, the IDE provides the same experience for PHP functions and .NET methods. It doesn’t matter that whether the .NET method comes from a C# project, a NuGet package or a referenced .dll assembly as long as it has its .xml XMLDoc next to it.

Inspecting type-less types

The compiler runs on the background all the time even though the project might not be running. VS Code allows you to see what compiler sees in the tooltips:

Variable tooltips reveals how the compiler sees your local variables.

This can be very useful because of PHP’s ambiguous type system. In PHP, it might not matter, but on PeachPie, you can boost the performance of your app significantly if the compiler will resolve types correctly.

The same applies to functions. The tooltip over a function reveals what the compiler knows about it. You either see a tooltip with the complete signature, its resolved (.NET) return type and possibly the PHPDoc documentation, or you don’t see anything, which means the function could not be resolved.

A function, whose return type was not resolved! You can improve the program by specifying the return type hint or ensure that the code inside returns strongly typed values.
The iteration variable can overflow to double in PHP! This may confuse the compiler and worsen the app’s performance.
Call to an unknown function. Maybe a typo, a file missing from the project or the function is not implemented in PeachPie.

Checking BCL remarks

Some standard functions have remarks and specifications of the value being returned. In the C# world, we are used to XML Doc comments and we specify such behavior in there. Since the compiler and all the standard library is written in C# and all the code is annotated with XML Doc, we can present the information right in the IDE as well:

BCL function remarks.
Checking more detailed information about the function.
Checking what the function actually returns, right in the code.

Seeing diagnostics

You may find it useful to see the compilation diagnostics right in the code. This is actually quite helpful, because the editor shows you exactly what the compiler sees. In this way, you can fix most of the issues without even running the project.

Diagnostics are underlined right in the code as you are used to.


There it is, hopefully an exhaustive, all-inclusive guide on how to migrate your PHP app to .NET using PeachPie. In this tutorial, we hopefully explained how to get started with the basics and how to work your way through some of the common problems. You’ll also have learned what our VS Code extension’s tooltips reveal about your code and how you can instantly improve your app’s performance with just a few minor tweaks. If anything major is missing in this article, please don’t hesitate to get in touch!


Posted on December 1, 2019, in category Information, Tutorial, tags: , , , , , , , ,