Tuesday, October 8, 2013

PHP-In-Java : Execute PHP code from within Java


Introduction :
Years ago an enterprising company named Caucho decided to write the "most reliable application server in the Open Source market." Their core product is a Java EE compliant Web server they call Resin, and at its heart is Quercus: a fast PHP interpreter written in cross-platform Java.

What is Quercus :
Quercus is pioneering a new mixed Java/PHP approach to web applications and services. On Quercus, Java and PHP is tightly integrated with each other - PHP applications can choose to use Java libraries and technologies like JMS, EJB, SOA frameworks, Hibernate, and Spring. This revolutionary capability is made possible because 1) PHP code is interpreted/compiled into Java and 2) Quercus and its libraries are written entirely in Java. This lets PHP applications and Java libraries to talk directly with one another at the program level. To facilitate this new Java/PHP architecture, Quercus provides an API and interface to expose Java libraries to PHP.

Benefits of Quercus :
Quercus and Quercus' PHP libraries are written entirely in Java, thereby taking the advantages of Java applications and infusing them into PHP. PHP applications running on Quercus are simply faster, easier to develop, more capable, more secure, and more scalable than any other PHP solution.
Quercus gives both Java and PHP developers a fast, safe, and powerful alternative to the standard PHP intepreter. Developers ambitious enough to use PHP in combination with Java will benefit the most from what Quercus has to offer.


So if your goal is to run PHP in a Java EE Web container, then Resin is your tool.

But github contributor collegeman have used Quercus from within any old Java application, and he also access PHP libraries from within Java.

Using the PHP Wrapper API :
You can use each script individually or in combination to create PHP wrappers.

Execute PHP Script :
You can use load PHP script from local path, via Remove HTTP or within classpath (because of java code in jar file).
PHP php = new PHP("classpath:/com/demo/requireExample.php");

Once it is loaded into the memory, you can easily call it’s functions.
php.fx("func1", "Hello World").toString()

In return type Quercus sends a special kind of object called Value, you can access that value by PHPObject Api
  • A String type value by using toString()
  • Set or Get properties of object by using getProperty(String property), setProperty(String property, Object newProperty)
  • fetch direct access to Wrapper value through getWrapperedValue() method.

Lets move to the simple example :
In this example we will go through with simple inline php code, execute PHP file and create a simple db connection with table creation.
We have used Eclipse IDE for all the below examples, if you have it than you are good to go or else download it from here

Below is the project structure :


Create two main java class which check your php code with the help of quercus and resin libs.
You can download resin libraries from here

Create project web.xml

  TestPHPInterpreter
  
    index.html
  


Create java class PHP.java

package com.test.phpinjava;

/**
 * PHP-in-Java PHP wrapper for Java and Groovy.
 * Copyright (C) 2009-2010 Collegeman.net, LLC.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.util.logging.Logger;

import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

import com.caucho.quercus.Quercus;
import com.caucho.quercus.env.Env;
import com.caucho.quercus.env.QuercusClass;
import com.caucho.quercus.env.Value;
import com.caucho.quercus.page.QuercusPage;
import com.caucho.vfs.FilePath;
import com.caucho.vfs.StreamImpl;
import com.caucho.vfs.StringStream;
import com.caucho.vfs.WriteStream;
import com.caucho.vfs.WriterStreamImpl;

/**
 * Instances of this class wrap one or more PHP scripts, making it possible for
 * those scripts to be compiled into memory and


 * 
    *
  • for functions defined therein to be called from within Java code
  • *
  • for classes defined therein to be instantiated and used from within Java code
  • *
  • for the entire script to be executed, the output buffered for use within Java code
  • *
* Also, once instantiated, the PHP object can be used to compile and cache * arbitrary snippets of PHP into a single PHP script. *

Quercus and Resin versus PHP-in-Java

* Our PHP-in-Java library is built on top of Quercus. * Quercus is a PHP interpreter written in Java, distributed by Caucho. * Quercus is embedded inside a Java EE compliant application server called Resin. Resin gives * you the flexibility of writing PHP applications that have access to the Java ecosystem, and is * many times more efficient than running PHP in Apache. * If you're wanting to run large-scale PHP applications on Java, you need to take a look at Resin. * But if you're just looking to make PHP libraries like GeSHi * available to your Java and Groovy code, our PHP-in-Java library is for you. *

Using PHP-in-Java

* For usage instructions, please refer to README on our Github project. * @author Aaron Collegeman aaron@collegeman.net */ public class PHP { private static final Logger log = Logger.getLogger(PHP.class.getName()); private static Quercus quercus; private synchronized Quercus getQuercus() { if (quercus == null) { quercus = new Quercus(); } return quercus; } /** * Initialize a PHP wrapper with either an intial PHP script or a local directory. * url can take one of several forms: *
    *
  • A classpath reference, taking the form classpath:/path/to/file/or/directory
  • *
  • A remote script reference, taking the form http://path/to/script
  • *
  • All other forms are assumed to be file references, referring to files available locally
  • *
* @param url An initial PHP script to load or a local directory */ public PHP(String url) { this(url, PHP.class.getClassLoader()); System.out.println(url); } private QuercusPage main; /** * Create an empty instance of PHP. This instance can be used to execute arbitrary * snippets of PHP. But if you're looking to load a PHP library and execute snippets against that, * best to use one of the other constructors, {@link #PHP(String)} or {@link #PHP(String, ClassLoader)} */ public PHP() {} /** * Initialize a PHP wrapper with a specific ClassLoader instance. Refer * to the doc for {@link #PHP(String)} for a description of the url parameter. */ public PHP(String url, ClassLoader classLoader) { if (url == null || url.length() < 1) throw new IllegalArgumentException("[url] parameter must be defined"); if (classLoader == null) throw new IllegalArgumentException("[classLoader] parameter must be defined"); // classpath reference if (url.indexOf("classpath:/") == 0) { // System.out.println(url); URL resource = classLoader.getResource(url.substring(11)); File ref = new File(resource.getPath()); initByFile(ref); } // remote script else if (url.indexOf("http://") == 0 || url.indexOf("https://") == 0) { try { URLConnection conn = new URL(url).openConnection(); initByInputStream(conn.getInputStream()); } catch (Exception e) { throw new RuntimeException(e); } } // file reference else { initByFile(new File(url)); } } /** * Initialize a PHP wrapper with a specific File loaded by the host. * @param file A file full of PHP script */ public PHP(File file) { initByFile(file); } private void initByInputStream(InputStream stream) { try { StringBuilder sourceCode = new StringBuilder(1024); BufferedReader in = new BufferedReader(new InputStreamReader(stream)); char[] buffer = new char[1024]; int read = 0; while ((read = in.read(buffer)) != -1) sourceCode.append(buffer, 0, read); in.close(); snippet(sourceCode.toString()); } catch (Exception e) { throw new RuntimeException(e); } } private void initByFile(File ref) { if (!ref.exists()) { throw new RuntimeException(new FileNotFoundException("No PHP file or directory at ["+ref.getAbsolutePath()+"]")); } if (ref.isDirectory()) { snippet(""); getEnv().setPwd(new FilePath(ref.getAbsolutePath())); } else { try { main = getQuercus().parse(new FilePath(ref.getAbsolutePath())); } catch (IOException e) { throw new RuntimeException(e); } initEnv(main); File dir = ref.getParentFile(); if (dir != null) getEnv().setPwd(new FilePath(ref.getParentFile().getAbsolutePath())); main.executeTop(getEnv()); } } private Env env; private MockHttpServletRequest request; private MockHttpServletResponse response; private StreamImpl out; private WriteStream ws; private void initEnv(QuercusPage page) { if (env == null) { request = new MockHttpServletRequest(); response = new MockHttpServletResponse(); WriterStreamImpl writer = new WriterStreamImpl(); try { writer.setWriter(response.getWriter()); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } out = writer; ws = new WriteStream(out); ws.setNewlineString("\n"); env = getQuercus().createEnv(page, ws, request, response); env.setPwd(new FilePath(System.getProperty("user.dir"))); env.start(); } } /** * Retrieves the Quercus execution environment, which your Java code * can use to interact directly with the Quercus parsing engine. * @link http://www.caucho.com/resin-javadoc/com/caucho/quercus/env/Env.html * @throws IllegalStateException When environment has not yet been initialized */ public final Env getEnv() { if (env == null) throw new IllegalStateException("Environment not yet initialized"); return env; } /** * Set the value of a global variable in the PHP execution environment. * @param name The name of the global parameter to create/update * @param obj The value to store there */ public PHP set(String name, Object obj) { snippet(""); getEnv().setGlobalValue(name, toValue(getEnv(), obj)); return this; } /** * Retrieve the value of a global varialbe in the PHP execution environment. * @param name The name of the global parameter tto read */ public PHPObject get(String name) { snippet(""); return new PHPObject(getEnv(), getEnv().getGlobalValue(name)); } /** * Ensures that obj is of type or wrapped in an instance * of Quercus' Value, with respect to the given execution * environment env. * @return obj or obj wrapped in a Value instance. */ public static Value toValue(Env env, Object obj) { if (obj == null) return env.wrapJava(obj); else if (obj instanceof PHPObject) return ((PHPObject) obj).getWrappedValue(); else if (obj instanceof Value) return (Value) obj; else return env.wrapJava(obj); } /** * Parse and execute a snippet of PHP script, adding to the * execution context any artifacts and/or output generated by the code. */ public PHP snippet(String snippet) { try { QuercusPage page = getQuercus().parse(StringStream.open(snippet)); initEnv(page); page.executeTop(getEnv()); } catch (IOException e) { throw new RuntimeException(e); } return this; } /** * Retrieve the output generated by all PHP scripts executed in this context. */ public String toString() { if (env != null) { try { ws.flush(); return response.getContentAsString(); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } else { return null; } } /** * Clear any text buffered by script execution, presumably in preparation for * executing another PHP snippet. * @return This instance of PHP, to support method chaining. */ public PHP clear() { response.setCommitted(false); response.reset(); return this; } /** * Call the PHP function named fxName with arguments arguments * @return An instance of PHPObject, wrapped around the return value of the function. */ public PHPObject fx(String fxName, Object ... arguments) { if (arguments != null && arguments.length > 0) { Value[] values = new Value[arguments.length]; for (int i=0; i < arguments.length; i++) values[i] = toValue(getEnv(), arguments[i]); return new PHPObject(getEnv(), getEnv().call(fxName, values)); } else { return new PHPObject(getEnv(), getEnv().call(fxName)); } } /** * Create a new instance of the PHP class className, initialized with * arguments arguments. * @return An instance of PHPObject, wrapping the new instance of className. */ public PHPObject newInstance(String className, Object ... arguments) { QuercusClass clazz = getEnv().findClass(className); if (clazz == null) throw new RuntimeException(new ClassNotFoundException("PHP:"+className)); if (arguments != null && arguments.length > 0) { Value[] values = new Value[arguments.length]; for (int i=0; i < arguments.length; i++) values[i] = toValue(getEnv(), arguments[i]); return new PHPObject(getEnv(), clazz.callNew(getEnv(), values)); } else { return new PHPObject(getEnv(), clazz.callNew(getEnv(), new Value[]{})); } } }

Create java class PHPObject.java

package com.test.phpinjava;

/**
 * PHP-in-Java PHP wrapper for Java and Groovy.
 * Copyright (C) 2009-2010 Collegeman.net, LLC.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

import com.caucho.quercus.env.*;

/**
 * A thin wrapper around instances of com.caucho.quercus.env.Value, themselves representing
 * instances of PHP objects. This wrapper makes it easier to invoke methods and set and get properties.
 * @author Aaron Collegeman aaron@collegeman.net
 */
public class PHPObject {
 
 private Value wrapped;
 
 private Env env;
 
 public PHPObject(Env env, Value value) {
  this.env = env;
  this.wrapped = value;
 }
 
 /**
  * Invoke the method name on wrapped Value object, passing
  * into the invocation parameters args.
  * @return An instance of PHPObject, wrapping any return value of nameed method.
  */
 public final PHPObject invokeMethod(String name, Object ... args) {
  if (args != null && args.length > 0) {
   Value values[] = new Value[args.length];
   for(int i=0; i< args.length; i++)
    values[i] = PHP.toValue(env, args[i]);
    
   return new PHPObject(env, wrapped.callMethod(env, new StringBuilderValue(name), values));
  }
  else {
   return new PHPObject(env, wrapped.callMethod(env, new StringBuilderValue(name), new Value[]{}));
  } 
 }
 
 /**
  * Set a public property of the wrapped Value to value.
  * @return This instance of PHPObject, to support method chaining.
  */
 public final PHPObject setProperty(String name, Object value) {
  wrapped.putField(env, new StringBuilderValue(name), PHP.toValue(env, value));
  return this;
 }
 
 /**
  * Retrieve a public property name of the wrapped Value.
  * @return A new instance of PHPObject, wrapping the retrieved property.
  */
 public final PHPObject getProperty(String name) {
  return new PHPObject(env, wrapped.getField(env, new StringBuilderValue(name)));
 }
 
 public Value getWrappedValue() {
  return wrapped;
 }
 
 public String toString() {
  return wrapped.toJavaString();
 }
 
}

Create java class PHPInterpeterDemo.java

package com.test.phpinjava;

import com.test.phpinjava.PHP;

public class PHPInterpreterDemo {
 public static void main(String[] args) {
  System.out.println("*********** START ************");
  PHPInterpreterDemo demo = new PHPInterpreterDemo();
  demo.inlineCode();
  demo.fileCode();
  demo.fileFunctionCode();
  demo.remoteURL();
  demo.dbConnection();
  System.out.println("*********** END **************");
 }
 
 public void inlineCode(){
  PHP php = new PHP().snippet("");
  System.out.println("Inline Code: " + php.toString());
 }
 
 public void fileCode(){
  System.out.println("From File: " + new PHP("classpath:/com/test/phpinjava/phpfile/helloWorld.php").toString());
 }
 
 public void fileFunctionCode(){
  PHP php = new PHP().snippet("");  
  System.out.println("Function Code 1: " + php.fx("func1", "Hello, world!").toString());
  System.out.println("Function Code 2: " + php.fx("func2", "").toString());
 }

 public void remoteURL(){
  System.out.println("Remote URL: " + new PHP("http://php.net/manual/en/tutorial.php").toString());
 }
 
 private void dbConnection(){
  PHP php = new PHP("classpath:/com/test/phpinjava/phpfile/dbConnection.php");
  System.out.println("DB Connection: " + php.toString());
 }
 
}

Create Php file helloWorld.php



Create Php file dbConnection.php



All done. You can get the project zip from here. Extract and import it to eclipse and you are good to go.. :)

References :
https://github.com/collegeman/php-in-java
http://www.caucho.com/resin-3.1/doc/quercus.xtp
www.eclipse.org/downloads/
www.sunilgulabani.com

Share:

3 comments:

  1. Hi i download the zip file. and import in eclipse then run it. its not run. display 404 error.

    ReplyDelete
    Replies
    1. Hello Chinnadurai,

      Kidnly check the lib folder for all the jar files and if it is not there than download all the needed jar files from given references links.

      Delete
  2. Hi Haresh, We need Java developer, I think your profile is matching with our requirement, we are working on an exciting project on Cloud , Mobile & BigData domain. I would like to discuss our Project & requirement with you. Please share your contact details with me.

    Thx!
    dp.dev3@gmail.com

    ReplyDelete