PHP to Java bridge
The PHP Java™ Bridge in WebSphere sMash can be used to interact with Java Objects, implement Java™ Interfaces and instantiate and extend Java™ classes.
When to use the PHP to Java bridge
The Java bridge for PHP in IBM® WebSphere® sMash can help provide access to Java classes and functionality in PHP. This feature can help an application developer, for example, who wants to use an existing Java library from a WebSphere sMash application.
Note: Using the bridge requires PHP developers to have a basic appreciation for Object Oriented Programming. Ideally they would need to read and understand the Javadoc for the Java classes that they are accessing. Another viable approach is to import the Java classes and then use PHP reflection to understand the shape of the imported classes.
If you do not want PHP developers to have to understand the Java classes at all then a better approach is to write an extension to the PHP language using the extension interfaces described in the Extending PHP article. This enables you to provide a procedural PHP view of the function provided by the Java class.
Using the Java bridge in WebSphere sMash
You can use JAR files in WebSphere sMash applications by copying them to the lib
folder. You will need to run a resolve on the application so that the JAR files are added to the application
class path.
Example
The following code snippet shows how to access Java classes from a PHP script.
<?php
$date = new Java("java.util.Date", 70, 9, 4);
var_dump($date->toString());
$map = new Java("java.util.HashMap");
$map->put("title", "Java Bridge!");
$map->put("when", $date);
echo $map->get("when")->toString()."\n";
echo $map->get("title")."\n";
$array = array(1,2,3,4,5);
$map->put("stuff", $array);
var_dump($map->get("stuff"))."\n";
$system = new JavaClass("java.lang.System");
echo "OS: ".$system->getProperty("os.name")."\n";
$math = new JavaClass("java.lang.Math");
echo "PI: ".$math->PI."\n";
?>
The new Java("java.util.Date", 70, 9, 4) call creates a new instance of the java.util.Date
class using the java.util.Date(int year, int month, int day) constructor. Methods can then be called on the
object and $date->toString() string returns the following information 'Sun Oct 04 00:00:00 GMT
1970'.
The new Java("java.util.HashMap") call creates a Java hash map. The script puts the date, a string, and an
array into this collection and then retrieves them. Although the script creates a PHP array, what gets put into the hash
map is a standard Java Map. The same applies to strings. The Java bridge converts a PHP string (essentially
a byte array) into a java.lang.String string so that the Java class receives argument types that it would
normally expect if the caller were another Java class.
The new JavaClass("java.lang.System") call provides access to the java.lang.System class. Static
methods can then be called on the class. The example invokes $system->getProperty(). The same applies to
the java.lang.Math class although in this case the example code is accessing a public static field (PI)
rather than a static method.
Accessing a member
Accessing a member of an instance will first look for bean properties then public fields. For example
print $date->time will first attempt to be resolved as $date->getTime(),
then as $date.time. Both static and instance members can be accessed on an object with this syntax.
Overload resolution is in general a hard problem given the differences in types between the two languages. The Java to PHP bridge employs a simple, but fairly effective, metric for determining which overload is the best match. It chooses the method that has the best matching number of arguments (types are ignored). If there is not an exact match, then if a method exists that has less arguments than provided by the script it will be used. The opposite does not hold true, the Java to PHP bridge will not invoke a Java method that has more arguments than the script provides because Java does not have default arguments. Note that method names in PHP are not case sensitive, potentially increasing the number of overloads from which to select.
Once a method is selected, the parameters are coerced if necessary, possibly with a loss of data (example: double precision floating point numbers could be converted to boolean if that is what the Java method is expecting).
Static class members
WebSphere® sMash v2.0 has an improved syntax for accessing static fields and methods on Java classes.
This syntax removes the need to use JavaClass as static members can be referenced directly
using the class name. The Java class must be imported first using java_import.
The following code snippet shows how to access static members from a PHP script.
<?php
java_import("java.lang.Integer", NULL, FALSE);
var_dump(Integer::$MIN_VALUE);
var_dump(Integer::$MAX_VALUE);
var_dump(Integer::toHexString("1234567890"));
var_dump(Integer::toOctalString("1234567890"));
// Signatures also work as normal!
$signature = new JavaSignature(JAVA_STRING);
var_dump(Integer::parseInt($signature, "1234567890"));
?>
This snippet produces the following output:
int(-2147483648) int(2147483647) string(8) "499602d2" string(11) "11145401322" int(1234567890)
Exception handling
The Java to PHP bridge enables a PHP script to catch exceptions thrown from the Java code.
Since Java exceptions are not the same as PHP exceptions, there is some conversion required. All Java exceptions get converted into a generic PHP exception class called JavaException. The JavaException class allows a catch handler to find out the cause (to find out the Java exception that was thrown). All PHP exceptions inherit from the base PHP Exception class a message and code; for JavaException exceptions the code will always be zero and the message is the message extracted from the native Java exception.
The following sample code passes an invalid argument to getProperty() and then catches the java.lang.IllegalArgumentException.
<?php
try {
$system = new Java("java.lang.System");
$system->getProperty(FALSE);
} catch (JavaException $exception) {
echo "Cause: ".$exception->getCause()."\n";
echo "Message: ".$exception->getMessage()."\n";
}
?>
The cause is the underlying Java exception. The message in the exception is copied directly from the
Java exception message (in this example the IllegalArgumentException contains no message
and so displays nothing).
Iterating over Java objects
The Java Bridge allows Java objects implementing Iterable to be iterated over in PHP.
The following snippet shows a Java ArrayList being iterated over using a PHP
foreach statement:
<?php
$list = new Java("java.util.ArrayList");
$list->add("Hello World!");
$list->add(FALSE);
$list->add(1234567890);
foreach ($list as $key => $value) {
echo $key." ".$value."\n";
}
?>
Selecting overloads with signatures
The Java Bridge selects a method or constructor based on the number and type of arguments
supplied. If no suitable method or constructor exists then the Java Bridge throws a
JavaException. This exception can be caught in scripts if required.
If one or methods or constructors exist that have the right number of arguments, but those
arguments are not good matches for the argument types supplied by the caller, then the Java
Bridge selects the first one and tries that. It will also output a notice signalling that a
JavaSignature should be used to help the Java Bridge resolve which overload to
call (see the following section).
This problem of selecting a suitable overload has been solved in WebSphere® sMash v1.1
with the addition of a new JavaSignature class. The JavaSignature
class allows a script to specify exactly which constructor or method is invoked by defining
the argument types to look for.
The following sample code shows the JavaSignature being used to select a
constructor for the Java String class:
<?php
$signature = new JavaSignature(JAVA_STRING);
$string = new Java("java.lang.String", $signature, "Hello World!");
?>
The arguments for JavaSignature are drawn from the following PHP constants:
-
JAVA_BOOLEAN -
JAVA_BYTE -
JAVA_CHAR -
JAVA_SHORT -
JAVA_INT -
JAVA_LONG -
JAVA_FLOAT -
JAVA_DOUBLE -
JAVA_STRING -
JAVA_OBJECT
Multiple arguments are comma separated, for example, new JavaSignature(JAVA_STRING, JAVA_INT).
You can specify arrays of Java types using the JAVA_ARRAY modifier. For example, the
following selects an array of strings: new JavaSignature(JAVA_STRING | JAVA_ARRAY).
The following snippet shows a JavaSignature selecting an overload of the valueOf
method on java.lang.String. Note how the signature is passed as the first argument to the
method call. The Java Bridge knows to check there for signatures.
<?php
$class = new JavaClass("java.lang.String");
$signature = new JavaSignature(JAVA_INT);
var_dump($class->valueOf($signature, 1234567890));
?>
Java objects can be passed using the JAVA_OBJECT argument. The class name for the object
is passed as an argument like this: new JavaSignature(JAVA_OBJECT, "java.lang.String").
Case sensitive method names
Methods in PHP are case insensitive, while Java is case sensitive. The Java bridge is case sensitive and so the PHP method name must match the Java method name exactly.
Importing Java classes
The new Java(...) syntax quickly becomes tedious to use. The Java bridge can import
Java classes in to the PHP runtime using the java_import extension function.
The following example shows the Java Integer and Date classes being imported:
<?php
java_import("java.lang.Integer", NULL, FALSE);
$value = new Integer(new JavaSignature(JAVA_STRING), "1234567890");
java_import("java.util.Date");
$date = new Date(70, 9, 4);
?>
Importing interfaces
Java interfaces can also be imported - the following sample code shows how to import the
Java Comparable interface:
<?php
java_import("java.lang.Comparable");
echo(ReflectionClass::export("Comparable"));
?>
Running this script in a Web browser produces the following output:
<?php
Interface [ <internal> interface Comparable ] {
- Constants [0] {
}
- Static properties [0] {
}
- Static methods [0] {
}
- Properties [0] {
}
- Methods [1] {
Method [ <internal> abstract public method compareTo ] {
}
}
}
?>
Declaring interfaces
When a java class is imported, the resulting PHP class can optionally be defined to implement PHP interfaces representing imported Java interfaces.
The second argument to java_import is an array of interface names. The imported class
is configured to implement only those interfaces listed in the array. This is a convenience for the
script as many Java classes implement interfaces that are not required in PHP.
Note: PHP uses an interface traversable to mark classes that can be iterated over using a PHP foreach statement.
The Java Bridge contains logic to map the traversable interface to the Java Iterable.
Thus any class which implements the Java Iterable interface can be imported specifying the traversable interface in the second
argument.
Note: If only a single argument is provided to java_import then the behavior is compatible with the basic java bridge
(new java ("classname")). Thus any Java class which implements Iterable is imported as implementing the PHP interface traversable
and can be iterated over using foreach. This default behavior can be overridden by specifying an empty array for the second argument.
The following sample code shows how to import the Java File class. The PHP class is
configured so that it implements the Comparable interface. The Comparable
interface must already be in the PHP runtime when this java_import call is made:
<?php
var_dump(java_import("java.io.File", array("Comparable"), FALSE));
$file = new File(new JavaSignature(JAVA_STRING), "/");
?>
Declaring a super class
java_import will optionally declare a super class for the class being imported. If
the third argument to java_import is TRUE then the imported class is
declared with a super class. The super class must already have been imported into the PHP runtime.
The following sample code shows the Java SecureRandom being imported so that it
declares a super class of Random (this matches the Java class hierachy):
<?php
java_import("java.security.SecureRandom", NULL, TRUE);
?>
Renaming classes
To avoid class name collisions it is sometimes useful to rename classes during the import.
An optional class name can be passed as the fourth argument to java_import as follows:
<?php
var_dump(java_import("java.lang.Integer", NULL, FALSE, "TestInteger"));
$value = new TestInteger(new JavaSignature(JAVA_STRING), "1234567890");
?>
Inner classes
PHP does not have the concept of an inner class. By default all static inner classes defined in a Java class that is imported will also be imported as top level PHP classes. Only static inner classes can be imported by the Java bridge. When a PHP class is created by importing a Java static inner class, the resulting PHP class is given the name of the inner class without using the name of the enclosing Java class.
For example: If a Java Class named Zoo which has a static inner class named
Zoo.Tiger, is imported then this will result in a PHP class named Zoo
and a PHP class named Tiger.
The fifth and final argument to java_import specifies whether to also import static inner
classes. The default is true. If this argument is set to true or omitted
then any public static inner classes of the class being imported are also imported. The snippet below shows
java.util.Map being imported without its static inner class Entry.
<?php
java_import("java.util.Map", NULL, FALSE, NULL, FALSE);
echo(ReflectionClass::export("Map"));
var_dump(class_exists("Entry", FALSE)); // Does not exist!
?>
Static inner classes can also be explicitly imported by specifying their fully qualified name.
The snippet below shows the Entry static inner class of Map being explicitly imported
<?php
java_import("java.util.Map.Entry");
echo(ReflectionClass::export("Entry"));
?>
Importing static inner classes as top level classes is convenient but has the potential to cause naming conflicts.
Such a conflict can be resolved by choosing to import the outer class without importing inner classes. Any
required static inner classes can then be explicitly imported. If required the inner classes can be renamed on the import.
The snippet below shows the Entry static inner class of Map being explicitly imported and renamed:
<?php
java_import("java.util.Map", NULL, FALSE, NULL, FALSE); // Import without inner classes
java_import("java.util.Map$Entry", NULL, FALSE, "MapEntry", FALSE); // Import the Entry inner class as MapEntry
echo(ReflectionClass::export("MapEntry"));
?>
Java Bean Field Access
The Java bridge provides field access to set/get methods. The snippet below shows the
Java File class being accessed through field gets:
<?php
java_import("java.io.File");
$file = new File("/");
var_dump($file->Parent);
?>
The Java bridge turns the call to $file->Parent into $file->getParent().
Setting a field value (Length in the example below) calls the matching set method:
<?php
java_import("java.lang.StringBuffer");
$buffer = new StringBuffer("Hello World!");
$buffer->Length = 5;
var_dump($buffer->toString()); // Hello
?>
The field names for gets and sets are case sensitive.
Extending Java classes
The following sample code shows the Java File class being extended in PHP:
<?php
java_import("java.io.File");
class SuperFile extends File {
function SuperFile($signature, $path) {
parent::__construct($signature, $path);
}
function isThisCool() {
return TRUE; // Way cool
}
}
$file = new SuperFile(new JavaSignature(JAVA_STRING), "/");
var_dump($file->isDirectory());
var_dump($file->isThisCool());
?>
Java Collections
The Java bridge provides additional support for the main Java collections List,
Set and Map. This support allows natural PHP syntax to be used on
Java collections. Examples include appending to collections using the [] syntax and
checking whether a collection contains something using isset.
Java collections can be created using the new Java(...) syntax or by importing
the collection classes into the runtime:
<?php
java_import("java.util.ArrayList");
$list = new ArrayList();
$list->add("Hello World!");
$list->add(FALSE);
$list->add(1234567890);
var_dump($list);
foreach ($list as $key => $value) {
echo $key." ".$value."\n";
}
?>
Running this code snippet produces the following output:
object(ArrayList)#8 (0) {
}
0 Hello World!
1
2 1234567890
Collection Summary
The following table shows the syntax supported for the Java List,
Set and Map collections:
| Syntax |
Set
|
Map
|
List
|
isset($collection[$key])
|
Yes | Yes | Yes |
unset($collection[$key])
|
Yes | Yes | Yes |
$collection[] = $bar
|
Yes - adds the value into the set if not already present. | No - A key is always required for maps. | Yes - the value is appended to the end of the list. |
echo $collection[$key]
|
No - sets don’t have key to value mappings. | Yes | Yes - the key must be an integer position in the list.. |
(array) cast |
Yes - the key for each entry in the array is set to an integer generated as the set is iterated through. | Yes | Yes -the key for each entry is set to the integer index in the list. |
List Example
<?php
$list = new Java("java.util.ArrayList");
// Appends string to the list
$list->add("Hello World!");
// Updates the string just added
$list[0] = "Hello Again!";
// Gets a value from the list
$value = $list[0];
// Creates an array from the list
$array = (array) $list;
// Checks if the item is present
$check = isset($list[0]);
// Removes the item from the list
unset($list[0]);
// Add some more items to list
$list[] = "Hello Again!";
$list[] = TRUE;
$list[] = "Updated!";
?>
Map Example
PHP arrays and Java Maps are very similar. To simplify script development the PHP
runtime automatically converts between PHP arrays and Java Maps when it is calling
Java methods and accessing properties on Java classes/objects.
For example, if a Java method returns a Map then it is automatically converted
to a PHP array. Likewise if a PHP script calls a Java method that expects a Java Map,
then the PHP runtime will convert a PHP array argument into a Java Map and then
invoke the method.
It is still possible to explicitly create a Java Map as follows:
<?php
$map = new Java("java.util.HashMap");
// Add this title to the map
$map["title"] = "Java Bridge!";
// Gets a value from the map
$value = $map["title"];
// Creates an array from the map
$array = (array) $map;
// Checks if the item is present
$check = isset($map["title"]);
// Removes the item from the map
unset($map["stuff"]);
?>
Set Example
<?php
$set = new Java("java.util.HashSet");
// Add some items to the set
$set[] = "Hello Again!";
$set[] = TRUE;
$set[] = "Updated!";
// Creates an array from the set
$array = (array) $set;
// Checks if the item is present
$check = isset($set["Updated!"]);
// Removes the item from the set
unset($set[TRUE]);
?>
There are a few limitations to the Java bridge:
Limitations
There are a few limitations to the Java bridge:
- Only non final Java classes can be extended in PHP.
- It is not possible to extend abstract Java classes in PHP.
- The PHP class and its hierachy is only visible within the PHP runtime.
The ability of the Java Bridge to instantiate and extend Java classes is implemented using proxies. Therefore
whilst a PHP object based on a Java class can be passed outside of the PHP runtime by calling a method on a Java object and
passing a PHP object as an argument, the object passed to the Java method will be an XAPIObject
rather than an instance of the proxied Java class.
XAPIObject is the programmatic (Java) interface to a PHP object. XAPIObject
is an interface and does not extend any Java base class (for example, File in snippet above).