bw logo

Chapter 7. PHP

As a functional example, a PHP module is provided to interface with a BigWorld server through script. PHP is a very popular open-source scripting language for web development.

This sample module is designed to be used under Linux with Apache and mod_php (the PHP module for Apache). The module also works with the PHP Linux command-line interpreter. While the BigWorld sample implementations use PHP, it is possible to implement a web integration system using any such scripting language or web server. As the TwistedWeb service services HTTP requests, any mechanism that can perform HTTP requests can integrate with the TwistedWeb sample.

7.1. Installation

7.1.1. Installing PHP

You will need to be able to run PHP. It is possible to install it using yum:

# yum install php

You will also need to download and install PHP's libcurl module, which you can also do using yum:

# yum install curl

7.1.2. Testing

The easiest way to test that PHP is supported is to create a PHP script as illustrated below and use a browser to view it:

<?php
  phpinfo();
?>

Testing PHP configuration changes

Example phpinfo output

7.2. BigWorld.php

To implement a website that can access the game server, the PHP script needs to make HTTP requests to the TwistedWeb service. To help achieve this, BigWorld.php implements a PHP class, called RemoteEntity, that represents a game entity on which you can call methods. This is analogous to a BigWorld server Entity mailbox.

A RemoteEntity is created using the start of the URL path as the argument to its constructor. The path does not include the machine name and port or the method to call and method arguments. For more information about Twisted.Web and instructions on how to request entity mailboxes using the TwistedWeb service, see The TwistedWeb Service. For example, to obtain a RemoteEntity to FantasyDemo's AuctionHouse global base entity, you would call:

$this->auctionHouse = new RemoteEntity( "global_entities/AuctionHouse" );

The variable to which the RemoteEntity was assigned can subsequently be used like an Entity mailbox for the target entity.

When calling an entity's methods through a RemoteEntity, arguments are provided in the form of an array. In PHP, an array is an associated map of key/value pairs. The keys are argument names, and the values are the corresponding argument values. For example, consider the AuctionHouse entity's webCreateBidRangeCriteria() method, which takes two arguments, representing the minimum and maximum bids for an item, and returns a criteria object. This method has the following definition in AuctionHouse.def:

<webCreateBidRangeCriteria>
   <Args>
      <minBid>   GOLDPIECES   </minBid>
      <maxBid>   GOLDPIECES   </maxBid>
   </Args>
   <ReturnValues>
      <criteria>   STRING   </criteria>  <!-- Search criteria object, pickled -->
   </ReturnValues>
</webCreateBidRangeCriteria>

To invoke this method from the RemoteEntity for the AuctionHouse entity, you will need to create a PHP array of the arguments. The following code illustrates how this would be performed:

$res = $this->auctionHouse->webCreateBidRangeCriteria( array(
    "minBid" => $searchMinBid,
    "maxBid" => $searchMaxBid ) );

where the values of $searchMinBid and $searchMaxBid are defined before the call. The return values will be stored in $res. In this example, $res[ "criteria" ] will contain the string describing the criteria.

The RemoteEntity instance converts this call into a HTTP request on an appropriate TwistedWeb service fragment. It adds the method name and any arguments. It then blocks waiting for the JSON response. On success, this response is converted into a PHP object.

In addition to calling entity methods on a RemoteEntity in order to access the entity on the game server, it is possible to access the same entities directly, by instead using a RemoteEntity to access the database, using the TwistedWeb service's db URL option, for example:

$db = new RemoteEntity( "db" );

Using this RemoteEntity, it is possible to invoke commands directly on the game server. For example, to directly log an account on to the game server, you could make the following call:

$result = $db->logOn( array(
    "username" => $username,
    "password" => $pass ) );

7.2.1. BigWorld.php Error handling

It is possible for remote method calls to fail for a number of reasons, including invalid arguments or methods, or the requested entity not existing. When a call on a RemoteEntity instance fails, the TwistedWeb service will return a specially-formatted error object instead of the expected JSON object. As described in TwistedWeb Error handling, this JSON error object will have the format:

{ "excType": "ErrorType", "args": [ "Arg1", "Arg2" ... ] }

If such an error object is encountered, BigWorld.php will raise it as a PHP exception. This exception type will use the excType field as the type name, and the first item in the args list as the exception's message:

throw new $excType( $args );

For example, a JSON object representing a BWInvalidArgsError object will be raised as a BWInvalidArgsError exception, whose message will be the first argument contained in the original JSON object.

Any error object encountered by BigWorld.php will have originated as one of two categories of exception objects:

  1. Built-in BigWorld errors - BWStandardError

    These are defined in bigworld/res/scripts/server_common/BWTwoWay.py, and are explained in the Server Programming Guide's sectionBWStandardError. They originate in the server binaries, and are propagated to the TwistedWeb service.

    In order for them to be raised as exceptions, they are declared as PHP exception objects in BWError.php, sharing the class name of their python counterparts.

  2. Custom errors - BWCustomError

    These are the error types specific to the game scripts, and are described in detail in PHP Error handling. Like the classes derived from BWStandardError, these must be declared as php exception objects in order for them to be handled by normal exception-handling procedures. This is to be done in CustomErrors.php. For example, if there is a custom error called MyCustomError declared in <res>/scripts/server_common/CustomErrors.py, there should be a matching php object declared in CustomErrors.php:

    //CustomErrors.php
    
    <?php
    require_once( "BWError.php" );
    class BWCustomError extends BWFirstArgError {}
    
    class MyCustomError extends BWCustomError {}
    ?>

    If an error object is encountered by BigWorld.php that has not been declared as its own PHP exception type, it will be thrown instead as a BWGenericError, maintaining the excType field as part of its exception message:

    throw new BWGenericError( $excType, $args );

For details on handling these errors, refer to PHP Error handling.

7.2.2. Locating the ServiceApp

The Service Singleton class in BigWorld.php contains a hard-coded list of possible ServiceApp locations. When a web client starts a new session, the RemoteEntity associated with the session is given the mailbox of a random ServiceApp that is currently online and providing a fragment of the desired service. You will need to modify this list to reflect the addresses of your ServiceApp machines:

class Service
{
    private function __construct()
    {
        // *** EDIT THIS WITH YOUR ServiceApp ADDRESSES ***
        $this->urlList = array(
            "http://someMachine:8000",
            "http://localHost:8000",
            "http://someOtherMachine:8000"
        );
    }

    ...
}

A web client will access a Service through the same ServiceApp for the duration of their session. If this ServiceApp fails during that time, the RemoteEntity will find a different ServiceApp providing the same service, and store its address for the remainder of the session.

7.3. RemoteEntity Session Storage

It is possible to store a mailbox to an entity for the duration of a HTTP session. For example, the BWAuthenticator class' authenticateUserPass() method, mentioned in the previous section, is used not only to invoke the logOn command on the game server, but also to hold onto the resulting entity for the duration of the session. By storing the URL of this entity in PHP session variables, whose values are persistent for the entirety of a HTTP session, this allows the web server to require authentication from a web client only once per session.

To store an entity reference:

$_SESSION['mailbox'] = "entities_by_id/Avatar/37"; // store details to create mailbox later.

To later retrieve it:

$mailbox = new RemoteEntity( $_SESSION['mailbox'] );

For example, to log on and store the result:

$db = new RemoteEntity( "db" );

try
{
  $result = $db->logOn( array( "username" => $username, "password" => $pass ) );
}
catch( BWAuthenticateError $e )
{
  // Handle error
  ...
  return;
}

$_SESSION[ "mailbox" ] = "entities_by_id/" . $result[ "type" ] . '/' . $result[ "id" ];