Windows PHP Development

Any developer that has programmed PHP can recognize that PHP was intended for Linux distributions and web servers. However, coming from a windows background I generally fancy windows web servers (not Linux). Don.t get me wrong, I am very familiar with Linux and at one point ran my own Linux servers and X-Windows desktops. However, I do believe that Microsoft is making good headway with web servers (II7), web services (WCF) and web applications (handy APIs such as Silverlight and WPF). Every one of these products have impressed me greatly.

Now, I am faced with a decision to make. Use PHP or use C#? Hands down I prefer C#. However, this project I am going to be working on is going to involved with a person that prefers PHP. This got me thinking. Why not use both? If I am going to be handling the back-end side of the web application and the other person is going to be handling the front-end, there doesn.t seem to me to be any reason why we shouldn.t use both! Now I need a proof of concept. I need to be able to feel confident that if I offer this as a suggestion (front-end done in PHP which calls the back-end WCF web services done in C#) then I need to be able to show that it works well (efficiently, cleanly, etc).

Setting up PHP w/ IIS

Fortunately, I recently formatted my desktop, so I pretty much have nothing installed on it. After installing IIS (which, in Vista, can only be done by the “Add/Remove Features” within the “Programs and Features” control panel) I knew I needed to install PHP. The last time I installed PHP on an IIS box it was a little more complex than I would have liked. After searching Google for the term “IIS PHP” I was presented with a #1 result of http://php.iis.net/.

Absolutely brilliant! I’m not sure when, but at some point the guys at Microsoft seemed to have decided to make a handy installer for PHP on IIS/Windows-based machines. They called it “Microsoft Web Platform”. The first download, as it turns out, is just a boot-strapper. It installs a little icon on the IIS configuration screen that looks like this:

image

When you open this new configuration section within IIS called .Web Platform Installer. it brings up a screen which loads the most recent .modules. that are available to install using the tool. These include APIs/tools/services such as PHP, ASP.NET MVC, etc.

NOTE: In order to actually install these, you need to open the .What.s New.  tab of the .Web Platform Installer. configuration tool.

After checking all the installation options I thought I might need, I pressed .Install. and off it went. Within five minutes all of the options I had requested were installed and I was able to test it by dumping a phpinfo.php script into the wwwroot folder; which worked brilliantly!

Connecting to MySql from C# (file system problems)

Again, doing a simple query for ..Net MYSQL. on Google brought me a #1 result of .Connector/Net 6.1.. You have to be careful, because I think it defaults to 1.0 from Google, but once you enter the website (http://dev.mysql.com/downloads/connector/net/6.1.html) you can select 6.1 (the newest version) on the left. Again, a delightfully surprising development from the last time I was working with PHP/MySql. Not only does this API support the ability for you to easily query a MySql database from a C#/.Net application, but it integrates with Visual Studio. What more can you ask for?

At first, however, I thought the .integration with Visual Studio. was going to be some funky third party application that pops up when you click the .MySql database. link within VS, but it.s not so funky. In fact, the MySql .Net Connector integration seems to work just like you would have expected any other (MS Sql Server) database to work within VS. It.s all handled from the Data Sources pane. Now, of course, there aren.t as many options/features as you would expect to see in a MS Sql Server 200* database, but it does have everything you would need to manage a basic database (tables, views, stored procs and functions).

Note: You might also think it.s really neat that you can even create database models in Visual Studio much like you could with an entity model in VS.

Even though all of this is really cool, I stumbled into an issue (although probably this issue is not a very common one). After developing a test web application to determine whether or not I could access my MySql database from a web service I found that I couldn.t get connect to my database. I received the following error: .MySql.Data.MySqlClient.MySqlException: Unable to connect to any of the specified MySQL hosts. —> System.Security.SecurityException: Request for the permission of type ‘System.Net.SocketPermission, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77..

As it turned out, it wasn.t because the MySql Connector/NET Tool/API wasn.t working, it was because of the structure in which I have my file system laid out. I have two machines that I work with pretty heavily (a laptop and a desktop). Because of the constant synchronization headache I ended up buying a windows home server machine that stores all my files (documents, projects, images, etc). In this test.s particular example, I was trying to host a web service stored on the WHS (Windows Home Server) machine.s file system via an IIS instance on my desktop.

I was able to get the web service to work by using impersonation and in all reality the impersonation only worked because the WHS forces any connection computers to have the same user/pass as are on the WHS (kind of like mimicking a domain controller). So, for example, I have a Sean account on my desktop with password xyz123, a Sean account on my laptop with xyz123 and a Sean account on the WHS with xyz123. This allowed me to enter in .Sean. and .xyz123. into the impersonation credentials screen in the IIS config, instead of putting .SERVER\Sean. and not have any complaints from IIS (if you use full domain style credentialing IIS complains).

Note: Impersonation only works like this when the Physical Path of the virtual drive is based on a standard UNC path, not a mapped network drive. If you use a mapped network drive, it will never work. The physical path MUST be in the form of  \\server\\some\\path

image

So, because I was using impersonation on the web service, .NET was unable to grant permission to the System.Net.Socket assembly that was needed by MySql Connector/NET, hence the error. After I realized this I instead created a virtual directory on my WHS for the web service and it was able to connect to the MySql database without problem.

It seems that any time I try something new I run into issues with having a file server and two separate machines while wanting to be able to easily develop on both of them.

Unfortunately, even after creating the virtual directory on the IIS instance of the WHS I still found problems with the fact that I was unable to easily debug the application (because the process was running on the WHS not locally). In order to debug I would have to setup remote debugging and I decided it is more trouble than the benefits are worth.

I ended up creating a directory to develop on my local machine that I would just have to synchronize using something like SVN. Once everything was running off of the local file system and local IIS processes MySql Connector/Net connected to the MySql database (on my WHS) without any problems.

PHP to C# Web Service (nuSoap)

Really, calling a C# web service from PHP isn.t a matter of crossing a C#/PHP boundary it is more a matter of .how well does PHP support SOAP.. A quick Google search reveals a set of PHP classes in the form of a package called .nuSoap.. The package is a rewrite of a previously developed package called SOAPx4 from a group called NuSphere (hence the name .nuSoap.). One particularly attractive feature of nuSoap is that the package is a set of standard php classes that don.t require any additional PHP extensions. I have worked on other PHP projects that require additional extension and that always seems to lead to problems (most commonly during deployment).

In order to feel comfortable saying that PHP can work well with a C#-based SOAP web service I will need to answer some questions in a proof of concept manor:

  1. Is it easy to call a basic method on a web service using PHP?
    When you download the nuSoap package you get some samples along with the library. I had to search separately for the documentation on the PHP classes and found it at here.

    I created a basic web service that had a .HelloWorld. method which returned only a string (starting out simple). I published this website to my local IIS instance (within inetpub\wwwroot).

    I also created a virtual directory in my local IIS instance that had a simple index.php script it. Testing the index.php script.s functionality with a simple echo phpinfo(); proved that the PHP install I did earlier with the Microsoft Web Platform worked very well.

    Following the samples in the nuSoap archive I created the following PHP:

    {codecitation class=.brush: php;.}$client = new nusoap_client(http://localhost/MyService/MyService.asmx, false);
    $err = $client->getError();

    if ($err)
        echo $err;

    $client->setUseCurl(1);

    $params = array();
    $result = $client->call(“HelloWorld”, $params);

    print_r($result);{/codecitation}

    The call returned an error:

    Array ( [faultcode] => soap:Client [faultstring] => System.Web.Services.Protocols.SoapException: Server did not recognize the value of HTTP Header SOAPAction: . at System.Web.Services.Protocols.Soap11ServerProtocolHelper.RouteRequest() at System.Web.Services.Protocols.SoapServerProtocol.RouteRequest(SoapServerMessage message) at System.Web.Services.Protocols.SoapServerProtocol.Initialize() at System.Web.Services.Protocols.ServerProtocol.SetContext(Type type, HttpContext context, HttpRequest request, HttpResponse response) at System.Web.Services.Protocols.ServerProtocolFactory.Create(Type type, HttpContext context, HttpRequest request, HttpResponse response, Boolean& abortProcessing) [detail] => )

    Even though the call didn.t work I thought this was good news as it was obvious that nuSoap/PHP did connect to my web service and was receiving a proper soap fault exception.

    Being a little familiar with SOAP I quickly identified that it couldn.t figure out what method I was trying to call just based on the name of the method. SOAP requires that an Action name be associated with all of the methods in the service. If the web service does not have the Action explicitly defined then the compiler automatically generates the Action based on the name of the method for you behind the scenes.

    One way to solve this would be to pull up the WSDL of the web service and find out what the automatically generated Action name was for the HelloWorld method. The other way to solve it would be explicitly define the Action in the web service using C# meta tags. I chose to do the latter of the two options:

    {codecitation class=.brush: csharp;.}[SoapRpcMethod(Action=”01234″)]
    [WebMethod]
    public string HelloWorld(){/codecitation}

    Now that we know what the name of the Action is on the method of the web service, we can apply that to the call in the PHP code. Looking at the nuSphere PHP docs you can see that the call() method has a couple additional optional parameters:

    mixed call (string $operation, [mixed $params = array()], [string $namespace = ‘http://tempuri.org’], [string $soapAction = ”], [mixed $headers = false], [boolean $rpcParams = null], [string $style = ‘rpc’], [string $use = ‘encoded’])

    A quick change to the call to the call() method to be $client->call(.HelloWorld., $params, .http://tempuri.org/., .01234.) and a refresh on the browser reveals a successful call to the web service. My PHP script called the HelloWorld method, my HelloWorld method returned .This is a test. to the PHP script and the PHP script printed it with print_r($result);

    One of the thing I noticed was that the $result variable was translated to a mixed (string) type. I was curious whether or not it would convert it to Array(1) or if it would figure out that it was a single value return and set it to the correct mixed type. It did.

  2. How does nuSoap represent the return value of a method returning a complex type?
    This is a fairly easy test to run. I simply created an object (called User for my test) in my C# web service project and added a few properties to the object. Then I modified the HelloWorld method so that it would return the new object type and setup the body of the HelloWorld method so that it would initialize a new instance of the object with some dummy values and return the new instance of the object.

    Now that the web service was properly setup and re-deployed I re-ran my PHP script which (as I expected) printed an array of values:

    Array ( [Id] => 10 [UserName] => SomeNewUsername [PassWord] => SomeNewPassword )

    Note: The array doesn.t include any information about the type of object that was returned. I would have expected that the name of my object be included in the array returned. However, in my case, only the name of the properties (keys of the array) and the values of the properties (values of the array) are returned. This may be an issue when I get to polymorphism.

  3. How does nuSoap handle polymorphism?
    There are two main issues I want to address in the polymorphism test. One, is whether or not I can create an inherited class and have nuSoap recognize ALL properties within the class and base class. Two, is what happens when I return a base class in a method on the web service whose method returns an instance of a derived class (following the factory pattern).

    The first step was for me to setup my web service to support all these conditions. I created two additional classes called AccountStandard and AccountAdmin that are both derived from the User base class. Each of these classes got a couple additional properties that I could use to test (one is an enum which will be interesting to see represented by nuSoap).

    After my object structure was created I modified the HelloWorld method to first return the AccountAdmin type. The results were as I expected:

    Array ( [Id] => 10 [UserName] => SomeNewUsername [PassWord] => SomeNewPassword [Permissions] => Moderator )

    The results show the additional property within the AccountAdmin type. However, the name of the type being returned is still not provided. So, the real question is what happens when you set the method.s return type to the base class? Will the Permissions property disappear from the results?

    No, instead I got an error: The type SeanMcIlvenna.XXX.Objects.AccountAdmin was not expected. Use the XmlInclude or SoapInclude  attribute to specify types that are not known statically. So after changing the HelloWorld method to have an additional meta-tag [SoapInclude(typeof(AccountAdmin))] and refreshing the browser nothing has changed. The Permissions property is still returned but there is still no way to identify the type of object being returned (I can.t distinguish the difference between the AccountAdmin and AccountStandard types without identifying them by their unique set of properties . which isn.t really a good way to identify the type of object being returned).

    So, I suppose IF it was really necessary to use polymorphism in this way, you COULD have a base class that has a property that returns the name of the derived type. For example:

    {codecitation class=.brush: csharp;.}public string ObjectName
    {
      get { return this.GetType().Name; }
    }{/codecitation}

    However, I don.t think a website using a web service like this wouldn.t have a need (theoretically speaking of course) to identify the type of object being returned. By the time the results are returned to the front-end (PHP) of the website, it shouldn.t care what the data is structured in as long as the data being requested is returned to display on the webpage. In other words, I don.t think this layer of the webpage would have any need to know the structure of the data from the backend.

  4. How are parameters handled? (what happens when a method expects an int and you pass a string?)
    To start out with I modified my web service method so that it would accept a couple standard value parameters (string and int, in that order). I setup the method so that the string passed would be used as the name in my User object being returned by the method, and the int would define whether or not to create an AccountStandard or AccountAdmin object type to return.

    As you would expect, I modified the $params array in the PHP and passed in appropriate/expected values in the form of Key/Value pairs (where the key of the pair was the name of the parameter in the method, and the value of the pair was the value for the parameter of the method)  and successfully returned an AccountStandard object when passing 1 for the int and an AccountAdmin object when passing 2 for the int. The name was appropriately filled in with the value passed for the string parameter.

    However, when passing .ten. for the int, I received the following error: System.InvalidCastException: Cannot assign object of type System.String to an object of type System.Int32. This makes it obvious that PHP is doing the proper conversion to a string that is being sent over the wire and being retrieved by the C# service as a System.String type.

    The question now is .How are complex types interpreted/handled as parameters?. I modified my HelloWorld method to accept a User object as the first (and only) parameter. The method was also changed to simply return the User object passed in. I also changed the PHP code so that the array of $params was filled with valid Key/Value pairs for the User object. After running the test, it seemed to time out. This prompted me to attach my Visual Studio instance to the IIS process and debug the web service to find out what.s happening. Doing so revealed that the User parameter was coming in as null. It wasn.t that it was timing out, it was that it was returning null and null was being interpreted by PHP as nothing (an empty string).

    After taking a look at the wsdlclient3.php example included in the nuSoap package, I realized that I was trying to represent the object parameter as a flat array just like I was doing with the parameters of the method. The method itself needs a key/value-paired array to identify what values go to each parameter, but then on top of that the value of the array needs to be the key/value-paired array of values that should be assigned to the properties of the User object.

    {codecitation class=.brush: php;.}$user = array(.Id. = >10, .UserName. => .Some User., .PassWord. => .Some Pass.);
    $params = array(.user. => $user);{/codecitation}

    After changing my PHP to the above, all worked out just as you would expect.

    Note: If you don.t pass all the properties in the $user array for the User object, the properties of the User object get defaulted. In other words, not all properties are required to build the object. However, it is also worth nothing that just like you would expect in WCF complex objects, ALL properties of these complex objects exposed in web services must contain getters AND setters.

  5. How is binary data handled? (uploading and retrieving image/avatar data?)
    The first question on my mind is how is binary data identified any different from string data by nuSoap. The best way I could imagine to test this would be to read a file from the PHP script (perhaps uploaded by the user) and try to pass that as a byte array to the web service method. Then I would have the web service method save that byte array back to disc and make sure that is valid. The easiest way to test that the byte data re-written on the hard-drive is valid is by passing an executable. Because they are so picky, if ANYTHING is different about the executable it won.t launch. I would however use an executable that is stand-alone and doesn.t require any additional DLLs to be loaded (notepad.exe, for example).

    After setting up the PHP script and the web service to support the above I received the following response from the web service: HTTP/1.1 100 Continue HTTP/1.1 400 Bad Request Cache-Control: private Server: Microsoft-IIS/7.0 X-AspNet-Version: 2.0.50727 X-Powered-By: ASP.NET Date: Mon, 28 Sep 2009 22:38:51 GMT Content-Length: 0.

    Looking around for some examples on transmitting binary data using nuSoap I found one main source that seemed to indicate that it can.t be does using the C# byte type and should instead be done by encoding the binary data to a base64 encoded string and passing the binary data as a string to the web service. The web service could then take the string, re-encode the string to a byte[] array using Convert.FromBase64String() and viola! After the test was run successfully, I checked the .exe that I re-wrote to my hard-drive and it opened up right away. Worked like a charm!

    Note: I believe converting the binary data to base64 and back is a little more process intensive, but there doesn.t seem to be much other choice.

Summary

All-in-all, I believe I can do all that I would need to, on a fundamental level at least. A project could support a MySql (free) database, a PHP (free) front-end and a SOAP-based C# web service on the back-end. I think there may be some little issues here and there but nothing that couldn.t be worked out.

One thought that crossed my mind when running this proof-of-concept was questioning whether or not I was trying to fit a square into a circle. After giving it some more thought I don.t think so. I have spent many years creating robust back-ends using C# web services (everything from ASP.NET web services to WCF services hosted in IIS and on their own) and have always been quite pleased with the end product. .Granted, the enterprise projects I have worked on have ALL had their own issues, but never-the-less I have been pleased with the structure and (continued) support that C# web services have provided. Not only does it provide a soundstructure for the web services, but Microsoft keeps coming out with cool shit for C#. The Visual Studio development environment is proof of that all on its own.

Now, I am going to spend the next day or so developing a (personal and small) project on these concepts a bit more and hope that my findings and conclusion stay true.

References

Using nuSoap: http://wiki.reminderconnection.com/index.php?title=PHP_-_using_NuSoap

 

Posted in: Uncategorized

Leave a Reply

Your email address will not be published.

Humanity Verification *Captcha loading...