Packing PHP Libraries into NuGets

Packaging code libraries is a common way of providing and deploying a maintainable piece of code with an individual functionality. Packages are self-contained and their consumers usually don’t have to deal with their dependencies and requirements. In this article, we bridge two seemingly distant technologies and demonstrate how to pass a PHP library as a NuGet package to a generic C# project.

Peachpie Recap

In order to be able to provide PHP code as a native .NET component, it has to be presented as a .NET assembly. To achieve this with the maximum performance, without any communication overhead and fully portably, we took advantage of Peachpie Compiler. It generates true managed code from PHP sources, which can be packaged and fully integrated into other .NET projects written, for example, in C#. Peachpie allows for a compilation for the .NET Framework 4.6, .NET Core 1.0, Mono, respecting .NET Standard 1.5 and newer.

NuGet

Every platform has its own package format. In .NET, there is the standard NuGet mechanism with a free public repository on www.nuget.org, to which every developer can contribute their own packages. Each package is self-describing and contains the compiled library as a .dll file, specifying its dependencies to other NuGet packages, and optionally the license, tags, description and more. Nowadays, this is the common industry-standard way of providing a component.

Companies and organizations usually maintain their own private repositories in order to consume packages across a company privately.

PHP Project for .NET

The first step is to make use of the MSBuild compilation chain by creating an `msbuild` project file. The following process makes use of MSBuild version 15.0, which is included in .NET Core and Visual Studio 2017 or newer. In any case, make sure you installed the .NET Core SDK from https://www.microsoft.com/net/download/core.

The project is an XML file describing the project source files, content, as well as various tasks and properties. See https://github.com/peachpiecompiler/peachpie/wiki/msbuild for a reference project file for PHP. We have created templates with pre-prepared projects. Install the templates first:
[code]dotnet new -i Peachpie.Templates::*[/code]

Then, simply create the project on your favorite shell:
[code]dotnet new peachpie-classlibrary[/code]

Note: on Linux (where the full .NET Framework is not available), restoring the project for `net46` would fail. Change the TargetFrameworks from `net46;netstandard1.6` to just `netstandard1.6`, as such:

[xml]<TargetFrameworks>netstandard1.6</TargetFrameworks>[/xml]

Notice that the command created an .msbuildproj file, inside which we have already specified that all the files with the .php extension will be compiled. Moreover, the build process will take advantage of Peachpie.NET.Sdk, which defines the compilation and other dependencies. Another important part is:
[xml]<OutputType>Library</OutputType>[/xml]
This specifies that we are going to create a library, not an executable. The following commands compile the contained .php files into a single .dll:
[code]
dotnet restore
dotnet build
[/code]

Packing a Project into NuGet

Producing the actual package itself is in fact the easiest part of the whole process. If you create the project following the steps above, it already contains the most important property:
[xml]<GeneratePackageOnBuild>True</GeneratePackageOnBuild>[/xml] This tells MSBuild to pack the result of the compilation into a NuGet package file on every `build`. With this property set, you can already find the package with the extension .nupkg in the bin/Debug/ sub-folder.

In case it is not desirable to create packages on every build, for performance reasons for example, set the property to `false` or remove it completely and use the following command to build the package:
[code]dotnet pack[/code]

Original Source Files

In the world of PHP, source files and various resource files may be necessary to be contained in the package as well. Such files are called “content” and will be extracted in the root of the consuming project if not specified otherwise. In case of PHP, it is important to be aware of content files, especially if the PHP project uses some file system functions or requires the original PHP files to be present for any other reason (e.g. WordPress touching physical files in order to get a list of themes and plugins).

To contain all the jpg, js and css content files, add the following to the project file:
[xml]<Content Include="**/*.jpg;**/*.js;**/*.css" />[/xml]

To include source files as well, add the following:
[xml]<Content Include="**/*.php" />[/xml]

For additional packing options, see https://docs.microsoft.com/en-us/dotnet/core/tools/csproj.

Sample 1 – PHP Class Library for .NET

PhpLib1.msbuildproj
[xml]
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworks>netstandard1.6;net46</TargetFrameworks>
<Description>.NET class library in PHP</Description>
<AssemblyName>PhpLib1</AssemblyName>
<PackageId>PhpLib1</PackageId>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<!– we specify version suffix to make this package a pre-release version – this is mandatory since we still have a pre-release versions of dependencies –>
<VersionSuffix>prerelease</VersionSuffix>
</PropertyGroup>
<ItemGroup>
<Compile Include="**/*.php" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Peachpie.Compiler.Tools" Version="0.7.0-*" />
<PackageReference Include="Peachpie.NET.Sdk" Version="0.7.0-*" PrivateAssets="Build" />
</ItemGroup>
</Project>
[/xml]
lib.php
[php]
<?php
namespace Library {
/** A greeter class. */
class Greeter {
/** A method that returns hello. */
public function Greet() : string {
return "Hello world!";
}
}
}
[/php]

Build the package with the following commands:
[code]
dotnet restore
dotnet build
[/code]

Distributing the NuGet Package

The next step has nothing to do with PHP or Peachpie itself. In order for your package to be available, you have to publish it to a channel. The popular choices are continuous integration platforms, such as myget.org or Visual Studio Team Services, which automatically builds the project from a source repository, conserves the resulting .nupkg and provides it through their own channel.

For demonstration purposes, we won’t publish the package on the Internet. Instead, we will consume it directly from the directory.

Consuming the NuGet package

The final step is to use the package in a C# project. The beauty of the solution lies in the fact that the consumer doesn’t have to deal with PHP and only makes use of it. Even a die hard C# developer will surely recognize that the vast ecosystem of the PHP language offers a plethora of useful libraries. Thanks to this solution, these all become available neatly packaged in a NuGet, without having to deal with them on a code-level.

Adding Package Reference

After creating a C# project, you can use Visual Studio or the command line to add a package reference. Assuming our PHP library was named `PhpLib1`, the following line will be added to the C# project `.csproj` file:
[xml]<PackageReference Include="PhpLib1" Version="1.0.0-*" />[/xml]

For our demonstration purposes, the package is only available in a local directory, e.g. .../bin/Debug/PhpLib1.nupkg. We have to add this source to the NuGet configuration and we have to delete it from the cache every time we rebuild the PHP project.
1. Inside the C# project directory, create the `NuGet.Config`:
[xml]<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="local-packages" value="../PhpLib1/bin/Debug" />
</packageSources>
</configuration>[/xml]
2. After building the PHP project, delete the old package from the local NuGet cache, so that any consumer is forced to re-acquire it and update the cache. The old package will be in folder %userprofile%\.nuget\packages\PhpLib1.

Restore the C# project package references in order to download (copy) the NuGet package to the cache and bind the reference to the contained PhpLib1.dll file:
[code]dotnet restore[/code]

Using the PHP class

Once the C# project has a PHP NuGet package reference, you are free to use its content. This topic cannot be covered in its entirety within the scope of this article, since you can take a number of different advantages of the Peachpie API. The most common uses are:
– extending a PHP class or interface with a C# implementation
– instantiating PHP classes in C# code and calling their methods
– invoking PHP functions and passing C# objects inside

PHP objects have one requirement – an instance of the `Context` class, which simulates the PHP runtime in a single web request and provides an additional API. Create one with `Context.CreateEmpty()` and pass the resulting instance where needed. Note that the object is not thread safe by design.

Sample 2 – C# Program

program.csproj
[xml]
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PhpLib1" Version="1.0.0-*" />
</ItemGroup>
</Project>
[/xml]
program.cs
[csharp]
using System;
using Pchp.Core;

class Program {
static void Main(string[] args) {
using (var ctx = Context.CreateEmpty()) {
var phpgreeter = new Library.Greeter(ctx);
Console.WriteLine(phpgreeter.Greet());
}
}
}
[/csharp]

Run the following commands to restore the project package references and run the C# console application:
[code]
dotnet restore
dotnet run
[/code]

Conclusion

We have prepared a complete solution of the aforementioned steps:

[Download complete solution here]

This article describes how to create a NuGet package from PHP source code and demonstrates its use on a sample C# project. Ignoring the complexity of handling the NuGet packages locally, the solution is straightforward and simple on both sides – packaging the PHP code itself and consuming it in C# projects.

By removing the necessity to deal with PHP libraries on a code-level and allowing them to be consumed as NuGet packages, .NET projects can seamlessly access the vast array of existing PHP libraries that would otherwise be complex or often non-existent in .NET. In this way, Peachpie can bridge the gap between the .NET and PHP worlds by providing PHP packages in the form of NuGets.

Posted on August 2, 2017, in category Tutorial, tags: , , , , ,