Skip to content

Targeting a library for both PHP and .NET

There is a massive amount of libraries and frameworks written in PHP, providing useful functionalities, some even commercially. You may have come across libraries, such as PDF generators, the Twig templating engine, random data generators, etc. Let’s take a look at how to develop the library for both PHP and .NET, so that C# developers will be able to seamlessly consume the functionality, as well.

Intro

The following guide explains how to target both PHP and .NET at the same time. The same PHP code that already works will become available as a .NET library, as well. Most importantly, however, a C# developer won’t even notice the library had ever been written in PHP, nor will it matter.

dotnet add package mjaschen.phpgeo --version 3.2.0-*

// "Coordinate" and "Vincenity" classes are a part of PHPGeo library which is written in PHP
// It's used in the following sample C# code:

var coordinate1 = new Coordinate(19.820664, -155.468066); // Mauna Kea Summit
var coordinate2 = new Coordinate(20.709722, -156.253333); // Haleakala Summit

Console.WriteLine(coordinate1.getDistance(coordinate2, new Vincenty());
As an example, we can take a look at PHPGeo, which provides bstractions to geographical coordinates (including support for different ellipsoids) and allows you to calculate geographical distances between coordinates with high precision.

There are a few recommended things to keep in mind when developing for both PHP and .NET. Most of the existing packages are actually following those recommendations already.

Sample library

Let’s create a sample library – Dogchi – that provides an instance of a dog with some actions, like the Tamagotchi. The project structure will be as follows:

- src
  - dog.php
- composer.json
- dogtchi.msbuildproj

Our sample dog.php code will look like this:

<?php

namespace Dogtchi;
use DateTime;

/** Our dog-tamagotchi */
class Dog {
    private $name;
    private $birth;

    function __construct(string $name) {
        $this->name = $name;
        $this->birth = new DateTime();
    }

    function feedMeat(int $amount) : void {
        // decrease hunger
    }

    function pet() : void {
        // increase happiness
    }

    /** Returns the age of dog in dog years. */
    function getAge() : int {
        return 7 * $this->birth->diff(new DateTime())->format('%y');
    }

    /**
     * Combine two dogs and get a new one!
     * @param Dog $another second dog.
     * @return Dog Their child.
     */
    function breed(Dog $another) : Dog {
        $child = new Dog($this->name . "-" . $another->name);
        //$child->color = ...
        return $child;
    }
}

Strong typing

The most important part of the project is strong typing. In order to provide a great experience for a C# developer, type hint all the parameters and return types. This has several benefits:

  • The code is cleaner; obviously, your code will be self-descriptive and forces you to provide a unified and safe API. Also, the runtime will take care of the wrong parameters and NULL checks automatically.
  • The code editor will become smarter; your development environment can understand your code better and provide better code completion and error checking.
  • Finally, the provided library will have the type-information. This is important in strongly typed languages, such as C#, where every variable has to be typed. Additionally, the C# editor will understand your code and the user will benefit from the guidance IntelliSense provides – without having to read any of your documentation.

composer.json:

{
    "name": "jakubmisek/dogtchi",
    "description": "Sample Dog Tamagotchi",
    "type": "project",
    "require": {
        "php": ">=5.5.0"
    },
    "license": "MIT",
    "authors": [ { "name": "Jakub Misek" } ],
    "autoload": {
        "psr-4": {
            "Dogtchi": "src/"
        }
    }
}
A standard project descriptor – composer.json – provides various metadata. The most important part is the "autoload" section. In .NET, this will be used to take care of the autoloading by itself, fully handled by the compiler and runtime. As a result, the C# developer who is not used to explicitly include the magic “autoload.php” script, won’t have to worry about this specific PHP behavior at all.

Getters and setters

Properties in PHP are somewhat magical; they can be references, or not. They can be typed, or not. For convenience reasons, it is better to provide the get/set function, like function getAge(): int.

Doc comments

PHPDoc comments are well understood by everyone. When building a .NET library, all of the source code is removed, but PHPDoc comments become a part of the library and will be provided in the editor’s IntelliSense.

Sample use of the dog class in C# editor

Write the doc comments on all the public functions, so that the users have their documentation where it’s most useful.

Project file

After you have written the PHP library, you are ready to target .NET with it. All of the original source code remains the same, you only have to add a project file – dogtchi.msbuildproj. Its features are described in our docs.

<Project Sdk="Peachpie.NET.Sdk/1.0.0-preview3">
  <PropertyGroup>
    <OutputType>library</OutputType>
    <TargetFramework>netstandard2.1</TargetFramework>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="**/*.php" />
  </ItemGroup>
</Project>
This file is somewhat similar to the composer.json file, but it’s supposed to be used by the dotnet build tool.The important part is the <Compile> element, where you specify what is included in the resulting library.

Having installed your .NET Core SDK, run the following on the command line, or “build” the project from a development environment, such as Microsoft Visual Studio. The IDE will verify whether everything is set up correctly and compiles your project.

dotnet build

In order to create a NuGet package that can be consumed by C# developers, run the following command. It builds the release version of the library and packs it into the package:

dotnet pack -c Release -p:Version=0.1.2

Microsoft (R) Build Engine version 16.8.0-preview-20451-02+51a1071f8 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  Restored dogtchi.msbuildproj (in 445 ms).
  dogtchi -> bin\Release\netstandard2.1\dogtchi.dll
  Successfully created package 'bin\Release\user.dogtchi.0.1.2.nupkg'.

user.dogtchi.0.1.2.nupkg is the newly created NuGet package containing the library without the original source code. Basically, it is a ZIP file containing a descriptor and the library compiled into a dogtchi.dll file.

Consuming the libary in C#

Now, to demonstrate how it looks from the C# developer’s perspective, let’s use the library in a C# project. There are two ways of consuming the package – from the sources and from the package. During the development, we choose the first option. Consuming the library from the package is for our users and customers.

Create a sample C# console app

Let’s create a sample .NET console application. In an empty folder, write the following. It creates a new C# project and adds a dependency to the PHP library project.

dotnet new console
dotnet add reference ..\dogtchi\dogtchi.msbuildproj
In the newly created code, you are already ready to use the Dog class:

var dog = new Dog("Smurf");
dog.pet();
Sample integrating the PHP library in your C# program

Using the library from the package

Once you deploy the package dogtchi.nupkg to a public NuGet feed, e.g. nuget.org or myget.org, or your private NuGet feed, like the one you can have on Azure DevOps or GitHub, you can consume the library as a package. The following command adds <PackageReference> to the C# project:

dotnet add package dogtchi

Conclusion

Thanks to PeachPie and the .NET development toolchain, it is possible to provide virtually any PHP library or app, regardless of its size, as a single NuGet package, which is perfectly understood by the entire .NET ecosystem. Not only does this mean you can take whatever PHP application you have and distribute it for .NET developers to use, it also means you can provide your app without ever giving away its source code.

That means it’s now possible to sell your PHP application to the .NET world without actually providing your sources.

But what about web applications? For those, the process is quite different from regular console libraries – we’ll cover that in another blog post in the future.