Running PHP applications in Apache Tomcat 7

Apache Tomcat 7 is a popular server for running Java applications – but say you have a unique instance where you want to run both PHP and Java applications on Tomcat but don’t want to run two separate servers (i.e. Apache HTTPD + Tomcat) to achieve it?

PLEASE NOTE THAT THIS METHOD DOES NOT WORK ON TOMCAT 8 AS OF THE DATE OF WRITING.

For this we’re going to use the PHP – Java Bridge for Tomcat, which is a handy little package that will sort out PHP execution on top of Tomcat.

The first step is to download the Javabridge template from this link at SourceForge. This will give you a WAR file that you can directly drop in to your Tomcat server’s webapps folder.

For Windows users, the next step is to download PHP and extract it somewhere:

php

 

For Linux users, just install php5 using apt-get, yum, or your preferred package management tool.

Next step is to add PHP to the PATH variable. Append the path to your PHP folder to the end of your respective PATH variable.

Next, drop the JavaBridgeTemplate61.war file in to your Tomcat server’s webapps folder and start Tomcat.

When you navigate to your server’s Java Bridge folder (this is http://localhost:8080/JavaBridgeTemplate61/ by default), you’ll be able to see the Java Bridge’s index page:

Javabridge welcome page

 

If you go to javabridge/test.php, you’ll be able to see the output from the phpinfo() function:

phpinfo

 

If you open up test.php in the JavaBridgeTemplate folder in your Tomcat’s webapp folder, you can edit some of the code:

test

hello

 

 

Now you can deploy any PHP application within the JavaBridgeTemplate folder and rename the folder to whatever you want. You can add many folders like this for different PHP applications.

Note that the JavaBridge runs PHP as CGI. If you want to enable/disable specific PHP extensions you can simply enable them as you would do normally on your php.ini file.

GSoC 2015 – Moorsp Plugin for Moodle – Update 7

During the past week, I’ve been busy working on testing the Moorsp plugin – through Unit Tests and Behat tests. I set up the PHPUnit environment for my Moodle instance and got it up and running. I also wrote most of the unit tests needed to test the class functions in the plugin, and had an interesting discussion on the Moodle forum whether form building functions should be tested on PHPUnit or Behat.

I also ran in to a slight problem running Behat tests after running PHPUnit, I posted a question on the forum and hope someone will be able to clarify that for me.

The current tests can be found here, and I will be adding to them continuously.

That’s it for this week!

GSoC 2015 – Moorsp Plagiarism Plugin for Moodle

For the 3rd time in my life, I have been selected as a Google Summer of Code student (after mentoring for the past 2 years, I might add) to work on a project for Moodle.

Moodle is one of the most prominent and widely-used Free and Open Source Learning Management Systems in the world today, and has a widespread developer community. I had particular interest in Moodle because it is the LMS used by most Sri Lankan universities to manage their degree programmes, which speaks volumes for the robustness and feature-rich nature of Moodle. Moodle development is done through PHP and MySQL.

My project involves the development of Moorsp; a skeleton plagiarism plugin for Moodle which integrates with the plagiarism framework within the system. The purpose of this plugin is to provide an effective testing mechanism for the plagiarism framework, as all current plagiarism plugins connect to commercial 3rd party APIs and testing them is not possible without a paid account. Moorsp will integrate with the plagiarism framework, implementing all its hooks and events, thereby providing a platform for acceptance tests (Behat) to be written for the plagiarism framework itself. This would provide a free plugin that could be utilized for regression runs during continuous integration on Moodle.

GSoC is in the Community Bonding phase at the moment. I have been engaging with the community on their forums and have also provided some Behat feature files for QA tests that were not yet automated.

I’m pretty excited, and looking forward to a great summer with this awesome organization!

Moodle Logo

 

Behat testing PHP applications with Moodle as an example

The Behat testing framework  advocates the relatively-new concept of Behavior-Driven Development (BDD)/Acceptance-Test Driven Development (ATDD), where human-readable tests are written for highly user-oriented tasks by the developers themselves. This automates the User Acceptance Testing (UAT) process to a certain degree as the tests themselves are not written in highly-technical terminology and follow front-end testing paradigms. Essentially, the idea is that you define a test on how your application should work from the front-end, and then develop that feature from there. Behat works on PHP 5.3 and upwards.

Moodle is a Free and Open Source Learning Management Tool with an active community from all around the world. Moodle is also a good example for extensive use of Behat to test its features. (I should note that Sahana makes use of the Robot framework for ATDD as well)

I thought of covering some of the basics in the Behat framework using examples from how Moodle does it. While Moodle’s implementation might be somewhat different from other projects that use Behat, the basics of each implementation should be reasonably similar.

The following is an example for a typical Behat test, which Behat calls a feature file.

@auth
 Feature: Login
   In order to login
   As a moodle user
   I need to be able to validate the username and password against moodle

   Scenario: Login as an existing user
     Given I am on "login/index.php"
     When I fill in "username" with "admin"
     And I fill in "password" with "admin"
     And I press "loginbtn"
     Then I should see "Moodle 101: Course Name"

   Scenario: Login as a non-existent user
     Given I am on "login/index.php"
     When I fill in "username" with "admin"
     And I fill in "password" with "admin"
     And I press "loginbtn"
     Then I should see "Moodle 101: Course Name"

Notice that the feature is written in Gherkin,  which is a feature language taken from Cucumber, the ATDD framework for Ruby on Rails. These Gherkin commands are tied in to a PHP function on the Behat framework, for example,

And I press "loginbtn"

ties in to;

/**
 * Presses button with specified id|name|title|alt|value.
 *
 * @When /^I press "(?P<button_string>(?:[^"]|\\")*)"$/
 * @throws ElementNotFoundException Thrown by behat_base::find
 * @param string $button
 */
public function press_button($button) {

    // Ensures the button is present.
    $buttonnode = $this->find_button($button);
    $buttonnode->press();
}

in behat_forms.php in the Behat framework. It is also possible to re-use these functions to write very specific test functions for your application. For example, Moodle contains an ‘Editing mode’ on the Courses module which allows course administrators and teachers to see edit links on various nodes within a course.  Moodle’s implementation of Behat provides

And I turn editing mode on

which translates to

/**
 * Turns editing mode on.
 * @Given /^I turn editing mode on$/
 */
 public function i_turn_editing_mode_on() {
   return new Given('I press "' . get_string('turneditingon') . '"');
 }

which is implemented in the behat_course.php custom class within Moodle’s course core module. Note that get_string($string) is a function specific to Moodle’s framework.

An interesting feature of Behat is its integration with Mink and Selenium Webdriver to execute the tests on UI itself. Once you run the tests, Selenium will open up a browser window and execute the steps described in each feature file sequentially, while you can watch it being executed.

The Moodle community provides a very comprehensive guide on how to get started with Behat on Moodle, so I will not reproduce that here. I will get on to describing more of the finer points of Behat testing later on.

Connecting SAP and PHP

SAP is said to be the most comprehensive and widely-used Enterprise Resource Planning system ever. Due to its widespread use, SAP users and developers come across a variety of situations where they need to connect an installation of SAP to a different platform/programming language so that the data and data interpretations contained in relation to that installation can be accessed by that platform or programming language. Hence a myriad of SAP connectors for different programming languages have been born.

PHP, being the most widely used server-side programming language on the web, is one of the most probable languages that would need to connect to SAP at some point or the other. For this, the extremely handy SAPRFC module for PHP has been developed. This post will discuss an end-to-end implementation of the SAPRFC PHP module and explain how to connect SAP to the module as well.

The first step is to go to the above link and download the complete saprfc package. The easiest way to interface saprfc with PHP and to get it to work is to download XAMPP 1.7.1 (NOT the latest version). This contains PHP 5.28 which seems to be the only PHP version which works correctly with the SAPRFC module.

Unzip XAMPP and copy php_saprfc_528.dll from your saprfc/ext folder over to the /php/ext/ folder in your XAMPP directory. Locate the php.ini file in XAMPP and open it for editing. Here you will have to find the extensions section and add the line

extension=php_saprfc_528.dll

to this section. Now open the XAMPP Control Panel and start Apache. If you followed the above steps correctly, Apache should start now without any problems. IF it gives an error, go over the above steps again.

First of all, we’ll see what we need to do on the SAP side to expose a Remote Function Module which can be accessed via RFC. All you need to do is to create a ‘remote-enabled’ Function Module in SAP. This can be done by ticking ‘Remote-enabled module’ under the function module’s attributes, as shown below:

SAP RFC

You can write whatever program logic in ABAP in the Function Module. Pay special attention to the Importing and Exporting parameters of the Function Module as these will be what are passed to and from your PHP program.

Next, you need to copy over the saprfc.php library file over from the saprfc module folder to your PHP project directory. You can use the functions contained in this file to create a saprfc object which can login to SAP and access your remote-enabled Function Module.

The following code is essential for your PHP program to access SAP:

require_once('saprfc.php');
$submitted = $_GET['submitted'];
/**
 * Login to SAP system
 * @param String $user
 * @param String $pwd
 */
function login($user, $pwd) {
	//Create SAPRFC instance
	$sap = new saprfc(array(
			"logindata" =>; array(
			      "ASHOST"	=>; "HOSTNAME",
			      "SYSNR"	=>; "SYSTEM NUMBER",
			      "CLIENT"	=>; "CLIENT NUMBEr",
		              "USER"	=>; "USERNAME",
			      "PASSWD"	=>; "PASSWORD"
					),
			"show_errors"=>;true,
			"debug"	     =>;false));

	return $sap;
}
function logoff($sap) {
	$sap->;logoff();
}
/**
 * Function to call SAP RFC
 * @param saprfc $sap
 */
function callRFC($sap, $params) {
	$cust_params = $params['cust_params'];
	$task_params = $params['task_params'];
	$proj_params = $params['proj_params'];
	$result = $sap->;callFunction("ZGRAPH_TOTALDAYS_RFC",
             array(
                   array("IMPORT","CUST_PARAMS",$cust_params),
                   array("IMPORT","TASK_PARAMS",$task_params),
                   array("IMPORT","PROJ_PARAMS",$proj_params),
                   array("EXPORT","CATEGORIES",array()),
                   array("EXPORT","DATA_ACTUAL",array()),
                   array("EXPORT","DATA_ESTIMATE",array())
				       )
					);
	return $result;
}

Note the callRFC function: Importing parameters on the SAP RFC are passed to the $sap->callFunction function as “IMPORT” and Exporting parameters are defined as “EXPORT”. By leaving the “debug” value as ‘true’ on the login function, it is possible to get debug output for each SAP RFC call, as seen here:

SAP PHP Debug Output

Now you’re ready to go out in to the world with your own PHP program that connects to SAP! Good Luck!

Here’s a final look at what my program looks like. It connects to SAP to pull out project data for reporting using the JavaScript HighCharts library:

Charts from SAP Data in PHP!

A PHP Singleton Database Access Class for MySQL based on Joomla’s DB Class

I’ve always been enamored with Joomla’s Database Access functions. They removed all the annoyance from accessing MySQL databases and made it an easy-to-do operation. The Joomla database access methodology follows a simple step-by-step sequence:

  1. Create an instance of the db access class.
  2. Assign your query to the newly created object.
  3. Execute the query and load the result, as you would prefer, in to an associative array, a list of arrays, a list of associative lists, an object, a list of objects and so many more.

Couple this with the flexibility of PHP and you have an almost endless list of options that you could turn to when loading results from your MySQL database. Having used this functionality when programming for Joomla, I really wanted to re-use it in my own PHP applications. So I developed the following class based on Joomla’s db access class:

/**
 * @name dbAccess
 *
 * @author Ramindu Deshapriya, derived from Joomla
 *
 * @desc Class to handle database connections
 */
class dbAccess {
	protected static $dParams = array(
						'host' 		=> 'HOST_NAME',
						'user' 		=> 'USERNAME',
						'password' 	=> 'PASSWORD',
						'database'	=> 'DB_NAME'
					);

	protected $_connection = null;

	/**
	 * The database driver name
	 *
	 * @var string
	 */
	protected $_sql = null;

	/**
	 *  The null/zero date string
	 *
	 * @var string
	 */
	protected $_nullDate = '0000-00-00 00:00:00';

	/**
	 * Quote for named objects
	 *
	 * @var string
	 */
	protected $_nameQuote = '`';
	protected static $dbInstance;
	final private function __construct($host,$user,$password,$db) {
		if ( !($this->_connection = @mysql_connect($host,$user,$password,true)) ) {
			die('Unable to connect to database, recheck your DB server');

		}
		mysql_select_db($db, $this->_connection);
		return $this;

	}
	final public static function getInstance() {
		if ( is_null(dbAccess::$dbInstance) ) {
			dbAccess::$dbInstance = new dbAccess(	dbAccess::$dParams['host'],
											dbAccess::$dParams['user'],
											dbAccess::$dParams['password'],
											dbAccess::$dParams['database']
										);
		}
		return dbAccess::$dbInstance;

	}
	public function setQuery($query, $offset = 0, $limit = 0)
	{
		$this->_sql		= $query;
		$this->_limit	= (int) $limit;
		$this->_offset	= (int) $offset;

		return $this;
	}
	public function query()
	{
		if (!is_resource($this->_connection)) {
			echo 'DB Connection not available';
			return false;
		}

		// Take a local copy so that we don't modify the original 
  <span></span> //query and cause issues later
	$sql = (string) $this->_sql;
		if ($this->_limit > 0 || $this->_offset > 0) {
			$sql .= ' LIMIT '.$this->_offset.', '.$this->_limit;
		}
		$this->_errorNum = 0;
		$this->_errorMsg = '';
		$this->_cursor = mysql_query($sql, $this->_connection);

		if (!$this->_cursor) {
			$this->_errorNum = mysql_errno($this->_connection);
			$this->_errorMsg = mysql_error($this->_connection)." SQL=$sql";

			return false;
		}
		return $this->_cursor;
	}
	/**
	 * Description
	 *
	 * @access public
	 */
	function insertid()
	{
		return mysql_insert_id( $this->_connection);
	}
	/**
	 * Get a database escaped string
	 *
	 * @param	string	The string to be escaped
	 * @param	boolean Optional parameter to provide extra escaping
	 * @return	string
	 * @access	public
	 * @abstract
	 */
	function getEscaped( $text, $extra = false )
	{
		$result = mysql_real_escape_string( $text, $this->_connection );
		if ($extra) {
			$result = addcslashes( $result, '%_' );
		}
		return $result;
	}
	/**
	* Get a quoted database escaped string
	*
	* @param	string	A string
	* @param	boolean Default true to escape string, false to leave the string unchanged
	* @return	string
	* @access public
	*/
	function Quote( $text, $escaped = true )
	{
		return '\''.($escaped ? $this->getEscaped( $text ) : $text).'\'';
	}
	public function nameQuote($s)
	{
		$q = $this->_nameQuote;

		if (strlen($q) == 1) {
			return $q.$s.$q;
		} else {
			return $q{0}.$s.$q{1};
		}
	}
	public function isQuoted($fieldName)
	{
		if ($this->_hasQuoted) {
			return in_array($fieldName, $this->_quoted);
		} else {
			return true;
		}
	}

/**
	 * This method loads the first field of the first row returned by the query.
	 *
	 * @return	mixed The value returned in the query or null if the query failed.
	 */
	public function loadResult()
	{
		if (!($cur = $this->query())) {
			return null;
		}
		$ret = null;
		if ($row = mysql_fetch_row($cur)) {
			$ret = $row[0];
		}
		mysql_free_result($cur);
		return $ret;
	}

	/**
	 * Load an array of single field results into an array
	 */
	public function loadResultArray($numinarray = 0)
	{
		if (!($cur = $this->query())) {
			return null;
		}
		$array = array();
		while ($row = mysql_fetch_row($cur)) {
			$array[] = $row[$numinarray];
		}
		mysql_free_result($cur);
		return $array;
	}

	/**
	 * Fetch a result row as an associative array
	 *
	 * @return	array
	 */
	public function loadAssoc()
	{
		if (!($cur = $this->query())) {
			return null;
		}
		$ret = null;
		if ($array = mysql_fetch_assoc($cur)) {
			$ret = $array;
		}
		mysql_free_result($cur);
		return $ret;
	}

	/**
	 * Load a assoc list of database rows.
	 *
	 * @param	string	The field name of a primary key.
	 * @param	string	An optional column name.&nbsp;
<span> </span>* Instead of the whole row, only this column value will be in the return array.
	 * @return	array	If key is empty as sequential list of returned records.
	 */
	public function loadAssocList($key = null, $column = null)
	{
		if (!($cur = $this->query())) {
			return null;
		}
		$array = array();
		while ($row = mysql_fetch_assoc($cur)) {
			$value = ($column) ? (isset($row[$column]) ? $row[$column] : $row) : $row;
			if ($key) {
				$array[$row[$key]] = $value;
			} else {
				$array[] = $value;
			}
		}
		mysql_free_result($cur);
		return $array;
	}

	/**
	 * This global function loads the first row of a query into an object.
	 *
	 * @param	string	The name of the class to return (stdClass by default).
	 *
	 * @return	object
	 */
	public function loadObject($className = 'stdClass')
	{
		if (!($cur = $this->query())) {
			return null;
		}
		$ret = null;
		if ($object = mysql_fetch_object($cur, $className)) {
			$ret = $object;
		}
		mysql_free_result($cur);
		return $ret;
	}

	/**
	 * Load a list of database objects
	 *
	 * If key is not empty then the returned array is indexed by the value
	 * the database key.  Returns null if the query fails.
	 *
	 * @param	string	The field name of a primary key
	 * @param	string	The name of the class to return (stdClass by default).
	 *
	 * @return	array	If key is empty as sequential list of returned records.
	 */
	public function loadObjectList($key='', $className = 'stdClass')
	{
		if (!($cur = $this->query())) {
			return null;
		}
		$array = array();
		while ($row = mysql_fetch_object($cur, $className)) {
			if ($key) {
				$array[$row->$key] = $row;
			} else {
				$array[] = $row;
			}
		}
		mysql_free_result($cur);
		return $array;
	}

	/**
	 * Description
	 *
	 * @return The first row of the query.
	 */
	public function loadRow()
	{
		if (!($cur = $this->query())) {
			return null;
		}
		$ret = null;
		if ($row = mysql_fetch_row($cur)) {
			$ret = $row;
		}
		mysql_free_result($cur);
		return $ret;
	}

	/**
	 * Load a list of database rows (numeric column indexing)
	 *
	 * @param	string	The field name of a primary key
	 * @return	array	If key is empty as sequential list of returned records.
	 * If key is not empty then the returned array is indexed by the value
	 * the database key.  Returns null if the query fails.
	 */
	public function loadRowList($key=null)
	{
		if (!($cur = $this->query())) {
			return null;
		}
		$array = array();
		while ($row = mysql_fetch_row($cur)) {
			if ($key !== null) {
				$array[$row[$key]] = $row;
			} else {
				$array[] = $row;
			}
		}
		mysql_free_result($cur);
		return $array;
	}

	/**
	 * Load the next row returned by the query.
	 *
	 * @return	mixed The result of the query as an array, false if there are no more rows, or null on an error.
	 *
	 * @since	1.6.0
	 */
	public function loadNextRow()
	{
		static $cur;

		if (!($cur = $this->query())) {
			return $this->_errorNum ? null : false;
		}

		if ($row = mysql_fetch_row($cur)) {
			return $row;
		}

		mysql_free_result($cur);
		$cur = null;

		return false;
	}

	/**
	 * Load the next row returned by the query.
	 *
	 * @param	string	The name of the class to return (stdClass by default).
	 *
	 * @return	mixed The result of the query as an object, false if there are no more rows, or null on an error.
	 *
	 * @since	1.6.0
	 */
	public function loadNextObject($className = 'stdClass')
	{
		static $cur;

		if (!($cur = $this->query())) {
			return $this->_errorNum ? null : false;
		}

		if ($row = mysql_fetch_object($cur, $className)) {
			return $row;
		}

		mysql_free_result($cur);
		$cur = null;

		return false;
	}

	/**
	 * Inserts a row into a table based on an objects properties
	 *
	 * @param	string	The name of the table
	 * @param	object An object whose properties match table fields
	 * @param	string	The name of the primary key. If provided the object property is updated.
	 */
	public function insertObject($table, &amp;$object, $keyName = NULL)
	{
		$fmtsql = 'INSERT INTO '.$this->nameQuote($table).' (%s) VALUES (%s) ';
		$fields = array();

		foreach (get_object_vars($object) as $k => $v) {
			if (is_array($v) or is_object($v) or $v === NULL) {
				continue;
			}
			if ($k[0] == '_') { // internal field
				continue;
			}
			$fields[] = $this->nameQuote($k);
			$values[] = $this->isQuoted($k) ? $this->Quote($v) : (int) $v;
		}
		$this->setQuery(sprintf($fmtsql, implode(",", $fields) ,  implode(",", $values)));
		if (!$this->query()) {
			return false;
		}
		$id = $this->insertid();
		if ($keyName &amp;&amp; $id) {
			$object->$keyName = $id;
		}
		return true;
	}

	/**
	 * Description
	 *
	 * @param [type] $updateNulls
	 */
	public function updateObject($table, &amp;$object, $keyName, $updateNulls=false)
	{
		$fmtsql = 'UPDATE '.$this->nameQuote($table).' SET %s WHERE %s';
		$tmp = array();

		foreach (get_object_vars($object) as $k => $v) {
			if (is_array($v) or is_object($v) or $k[0] == '_') { // internal or NA field
				continue;
			}

			if ($k == $keyName) {
				// PK not to be updated
				$where = $keyName . '=' . $this->Quote($v);
				continue;
			}

			if ($v === null) {
				if ($updateNulls) {
					$val = 'NULL';
				} else {
					continue;
				}
			} else {
				$val = $this->isQuoted($k) ? $this->Quote($v) : (int) $v;
			}
			$tmp[] = $this->nameQuote($k) . '=' . $val;
		}

		// Nothing to update.
		if (empty($tmp)) {
			return true;
		}

		$this->setQuery(sprintf($fmtsql, implode(",", $tmp) , $where));
		return $this->query();
	}
}