Using PHP objects from C# in a type-safe way
When you want to call PHP scripts from mainstream .NET languages, like C# you can follow two different ways. First you can use the pure mode as I demonstrated in one of the earlier articles on PHP application called Texy! [1]. This approach can be used only for some specific applications, because pure mode has several restrictions - the two most important restrictions are that no global code or inclusions are allowed (you have to specify all source files during the compilation), but thanks to this restrictions Phalanger is able to produce classes that are compatible with .NET and can be called from C#. Second option is to create object dynamically by its name and perform all method invocations by name too. This approach can be used with any PHP scripts, but it isn't very convenient. In this article we present new features in the Phalanger beta 4 which extend the second approach and make it possible to use objects from any PHP script in C# using type-safe way.
Duck typing introduction
Let's start with a simple PHP object:
class SampleObj {
public $Message = "Hello world!";
function Add($a, $b) {
return $a + $b;
}
}
To compile this fairly simple PHP code using Phalanger, use either Visual Studio or the following simple command:
phpc /target:dll /out:PhpClassLib.dll main.php
Now, let's get to the interesting part - how to create instance of SampleObj
from C# and how to
read it's member field Message
or call the Add
function. The new technique
in Phalanger is based on principle called duck typing (kind of structural typing system),
which says that object is compatible with an interface if it has all methods and properties required by
the interface, regardless whether the object actually implements the interface or not. This is very interesting
principle that contrasts with the approach in languages like C# or Java, where
you can't cast object to an interface if the object doesn't implement the interface even though the object
may have all the methods required by the interface and so accessing to the object through this
interface couldn't harm encapsulation of the objects in any way.
From the description you could have guessed that we will first declare C# interface which will
correspond to the SampleObj
and later we will create instance of this object and cast it
to the declared interface using duck typing type coercion to get an view of the object allowing us
to easily call are required PHP methods and access PHP fields:
[DuckType]
public interface ISampleObj
{
string Message { get; set; }
int Add(int i1, int i2);
}
First we need to load the PHP library in the C# application:
// Get current PHP script context, set output to the console
ScriptContext ctx = ScriptContext.CurrentContext;
ctx.Output = Console.Out;
// Load the PhpClassLib library and include the "main.php" source
Type library_representative = typeof(PhpClassLib);
ctx.IncludeScript("main.php", library_representative);
Now we need to get an instance of the SampleObj
class and coerce it to the
C# ISampleObj
interface:
// Create instance of object 'SampleObj' declared in PHP
ISampleObj so = ctx.NewObject<ISampleObj>("SampleObj");
// Set and then read the 'Message' property
so.Message = "Hello from C# (via PHP)!";
Console.WriteLine(so.Message);
// Call add method with integers as an arguments
int r1 = so.Add(24, 18);
Console.WriteLine("Math: 24 + 18 = {0}", r1);
As you can see in this example, once you defined the interface, calling PHP objects is
amazingly simple! You use the interface as a type parameter of the NewObject
method and it behind the scene creates an object that implements it, so you can use it
for calling PHP member functions of the object. You have to be careful when declaring the
interface, because if you declared a method that doesn't exist in the PHP object you will
get and runtime error (following the same pattern as if you called member function that doesn't exist
from PHP), but once the interface is declared you get all the comfort including Visual Studio
IntelliSense.
More Duck Typing Samples
In the previous sample we restricted the Add
function to parameters of type int
,
but this is of course not restriction in the PHP code, because the Add
function can
be called with floating point numbers as a parameter and it will add them and return result as a
floating point number. It isn't difficult to get the same functionality in C#, because you can
include more overloaded versions of the method in the interface:
[DuckType]
public interface ISampleObj
{
string Message { get; set; }
int Add(int i1, int i2);
double Add(double d1, double d2);
}
Another interesting situation is when the PHP object returns another PHP object as a result of the
method, but as you would expect - everything you need to support this scenario is to add another
interface, annotate it with the DuckType
attribute and use it as a return type of the
method. In this situation you can see why are interfaces need to be annotated using the attribute,
because when returning object from a method, Phalanger needs to decide whether object should be
treated as an direct implementation of the interface (for example when you return some .NET type)
or as a PHP object that should be dynamically casted to the interface using duck typing.
To make the following sample even more interesting, we will use one more dynamic PHP feature -
the magic functions __call
and __get
. These two functions are invoked
when script calls a member function or a member field that isn't explicitly declared.
class MagicSample {
function __call($name, $args) {
echo "Calling unknown function '$name' with ".count($args)." arguments.\n";
}
function __get($name) {
return "Reading unknown '$name' value.";
}
}
class SampleObj {
function NewMagic() {
return new MagicSample;
}
}
Typically, you use the magic functions, because you want to make the object flexible and it is easier
to implement some functionality using these magic functions, but in the C# code you'll typically
know the name of the function you want to call (we'll show more real-world example later). If you
want to call function SomeMethod
and read the field SomeProp
from C#,
you will need to define interfaces as follows:
[DuckType]
public interface ISampleObj {
ISecondSample NewMagic();
}
[DuckType]
public interface IMagicSample {
void SomeMethod(int p1, int p2, int p3);
string SomeProp { get; }
}
Using this two objects gives the same results you would expect when using them directly from PHP:
// Create instance of object 'SampleObj' declared in PHP
ISampleObj so = ctx.NewObject<ISampleObj>("SampleObj");
// Returning PHP objects from PHP
IMagicSample mag = so.NewMagic();
// Call unknown method
mag.SomeMethod(1, 2, 3);
// Read unknown field
Console.WriteLine(mag.SomeProp);
Executing this program prints:
Calling unknown function 'SomeMethod' with 3 arguments. Reading unknown 'SomeProp' value.
Working with XML using Duck Typing
Now, let's look at one slightly more complicated sample. In this example we will use the PHP SimpleXML extension for working with RSS feeds. When opening XML document using SimpleXML the elements and attributes can be accessed directly as a member fields, so to make it possible to access these fields from C# we will need to define a few interfaces first (the following sample includes only a few of all the required interfaces, the complete sample is attached and you can download it):
[DuckType]
public interface IRssRssNode {
IRssChannelNode channel { get; }
}
[DuckType]
public interface IRssChannelNode {
string title { get; }
[DuckName("item")]
IDuckEnumerable<IRssItemNode> items { get; }
}
[DuckType]
public interface IRssItemNode {
string title { get; }
}
This sample contains two new features that were not mentioned earlier - first we used
DuckName
attribute to rename field, which was called item
in the original
PHP object, but we wanted to expose it as a items
in the C# code. Second thing
is the usage of the IDuckEnumerable
interface, which is very useful when it is possible
to iterate over the object using PHP foreach
statement. By using IDuckEnumerable
,
you'll get exactly the same functionality in C# as well! In this example we used IDuckEnumerable<IRssItemNode>
,
where IRssItemNode
is another interface marked using DuckType
, which means that
when iterating over the elements, every element will be coerced to the interface representing the item in RSS document.
The following sample uses interfaces declared above to read sample RSS feed:
IRssXml xml = ctx.Call<IRssXml>("simplexml_load_file",
"http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml");
Console.WriteLine("Title: {0}", xml.rss.channel.title);
// Iterate over items in the channel node:
foreach (IRssItemNode item in xml.rss.channel.items) {
Console.WriteLine(" - {0}", item.title);
}
Conclusion
Duck typing is a very powerful approach for the interoperability between PHP (and in general any dynamic languages running on .NET Framework) and statically typed languages that require all methods and types to be known during the compilation. The need to declare the interface that defines the structure of the PHP object is of course not as fast as using the object directly from PHP, but thanks to the Visual Studio it is very easy (you can just type the name of the method and let IDE to generate the method for you). Also thanks to this technique you can easily create .NET wrappers for any PHP library and use it from your .NET application or even distribute it with your PHP library, because using it from .NET will be very convenient and you can also use C# XML comments to generate technical documentation for the exported interfaces.
Now we only need to find a way how to generate these interfaces automatically from the PHP source code :-), hmm...
Links and downloads
- Download Duck Typing samples (6 kB)
- [1] Compiling Texy! with Phalanger [^]
- [2] Using PHP Library from C# [^]
Published: Monday, 30 April 2007, 1:26 AM
Author: Tomas Petricek
Typos: Send me a pull request!
Tags: phalanger