We may replace a PHP app with a Java EE app, but the problem is we wanna replace the modules one by one, which means two apps would co-exist and communicate with each other.
So is it possible to share the user session between the 2 apps? Or use a cookie to solve the problem?
Sharing a regular Java EE session with PHP can be done very efficient and fast with PHP-java-bridge.
This solution offer superior performance over using a database as it does not generate any disk IO. It also does not need any changes on the PHP webserver or Java EE server. Just add some code, configure and you're done.
Setting up the php-java-bridge can be confusing, but if you know how to do it, it's only a 10-minute job. As I just did a proof of concept my self I can hand you the cookbook solutions:
Download PHP-java-bridge files. I downloaded JavaBridgeTemplate610.zip (for the needed jar files) and php-java-bridge_6.1.0_documentation.zip for the needed examples, php include file and sample code (session sharing!).
Add the "JavaBridge.jar", "php-script.jar" and "php-servlet.jar" to you're webapp by putting it in the "/WEB-INF/lib/" of you're Java EE server.
Add a "test.jsp" to you're Java EE servers "/web" directory:
<HTML>
<TITLE>PHP and JSP session sharing</title>
<BODY>
<%
javax.servlet.http.HttpSession $session = request.getSession();
if($session.getAttribute("counter")==null) {
$session.setAttribute("counter", new java.lang.Integer(1));
}
int $counter = ((java.lang.Integer)$session.getAttribute("counter")).intValue();
out.println ("HttpSession variable \"counter\": " + $counter + "<br>");
java.lang.Integer $next = new java.lang.Integer($counter+1);
session.setAttribute("counter", $next);
%>
PHP page
</BODY>
</HTML>
Configure the JavaBridge servlet so it can be used by PHP to communicate to the Java EE server. Just add the following lines to you're web.xml:
<servlet>
<servlet-name>PhpJavaServlet</servlet-name>
<servlet-class>php.java.servlet.PhpJavaServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PhpJavaServlet</servlet-name>
<url-pattern>*.phpjavabridge</url-pattern>
</servlet-mapping>
Add a file named "test.php" to you're webserver root and make sure you edit the JAVA_HOSTS and JAVA_SERVLET to correctly point to the Javabridgeservlet as configured in the web.xml.
<?php
define ("JAVA_HOSTS", "127.0.0.1:8084");
define ("JAVA_SERVLET", "/JavaBridge.phpjavabridge");
require_once("java/Java.inc");
$session = java_session();
?>
<HTML>
<TITLE>PHP and JSP session sharing</title>
<BODY>
<?php
if(is_null(java_values($session->get("counter")))) {
$session->put("counter", 1);
}
$counter = java_values($session->get("counter"));
print "HttpSession variable \"counter\": $counter<br>\n";
$session->put("counter", $counter+1);
?>
JSP page
</BODY>
</HTML>
Install the needed "java.inc" php include file. You will find the file in the downloaded "php-java-bridge_6.1.0_documentation.zip" in the src.zip. Copy the "java.inc" file in the "/java" directory (just this one php file!).
Restart Application server
Start the test.php script (for example goto: http://127.0.0.1/test.php)
If you click on the links to the jsp and php file back, you will notice that the counter shares the Java session between both JSP and PHP scripts!
In order to share the same JSession cookie in a JSP/Servlet and PHP they both need to run on the same domain name (also make sure that JAVA_HOSTS is the PHP file uses the same domain name!).
One option you might want to look at is Quercus which is tied into Resin, and allows you to run PHP code on a Java EE app server, and enables some communication between the 2 platforms.
Save your session data to a database with session_set_save_handler().
UPDATE
Efficiency wise it would be very mininal, the difference from reading a text file to querying a database (presumably using an existing connection).
Some example code (simplified from what I use)
class Session {
public function __construct() {
session_start();
$this->clean();
}
public function __destruct() {
session_write_close();
}
public function open() {
return TRUE;
}
public function close() {
return TRUE;
}
public function read($id) {
global $Database;
if ($Database->select('session_data FROM sessions WHERE session_id="%s"', $id)) {
list($data) = $Database->fetch(MYSQL_NUM);
return $data;
} else {
return '';
}
}
public function write($id, $data) {
global $Database;
return $Database->replace('sessions SET session_id="%s", session_data="%s", session_updated=%d', array($id, $data, time()));
}
public function destroy($id) {
global $Database;
$_SESSION = array();
return $db->delete('sessions WHERE session_id="%s"', $id);
}
public function clean($expire = 600) {
global $Database;
$Database->delete('FROM sessions WHERE session_updated<%d', $time - $expire);
return TRUE;
}
}
// Declare the functions to use:
session_set_save_handler(array('Session', 'open'), array('Session', 'close'), array('Session', 'read'), array('Session', 'write'), array('Session', 'destroy'), array('Session', 'clean'));
$Session = new Session;
The you can read/write to the session data using $_SESSION['name'] = data; in the usual technique.
Related
I'm a newbie with XCache and I'm trying to use this feature for have an editable configuration over the air in my application.
So I need to store some data, for doing this I did:
class Settings
{
private $_config = array();
function __construct()
{
$file = 'config.php'; //return $config content
require_once $file;
$this->_config = $config;
foreach($config as $item => $value)
{
if(!xcache_isset($item))
{
xcache_set($item, $value);
}
}
}
}
Unfortunately today the official site seems down, so I can't follow the documentation to check if I did something wrong.
I've created also two method:
public static function setItem($name, $value)
{
xcache_set($name, $value);
}
public static function getItem($name)
{
return xcache_get($name);
}
now getItem after 15/20 minute can't get the key value. Why?
UPDATE
Okay, the problem it's when an header('Location..) is called. Infact if I do a redirection I lost the value stored in cache, anyone know why?
As the name implies, XCache is a cache, not a database. Values that you store in the cache may be purged without warning if space is needed for other data, and will be lost entirely when the web server is restarted. It's not an appropriate place to store configuration information.
I can't say for certain why you're seeing values become unavailable after a redirect, though. That shouldn't happen.
I am creating a cron job that will run every few minutes and it will read from database data to see which function needs to be called and act accordingly to the data but half the crons are written in codeigniter and the other half in native php.
Is there any way to do this? I have been googling but the anwser. I came up with is that it is impossible. I tried changing directory and than including or requiring index.php from the codeigniter, in which the function is that i need to call.
while doing this if my class is written in native php, It returns some errors that don't make sense and if I correct those errors, I would say that half the system function from codeigniter would be gone. It would still be a question if it will work even then.
If my class is written in codeigniter, when I include index.php, it just breaks. No errors no response or it says that the "ENVIRONMENT" variables are already defined. Thus I have been looking for a way to undefine those variables from config file or overwrite them to null or empty string but nothing works.
If you have any ideas it would be much appreciated.
I saw a question question link about some cron jobs in php where the user #michal kralik gave an answer about what i am doing in general with database and one cron class that will call other crons (coould use help for that too).
btw forgot to mention that using curl and exec will not work, because on our servers they sometimes just stop working for no reason.
UPDATE 1:
this is my class currently after so many tries:
class Unicron extends MY_Controller {
public $config;
function __construct() {
parent::__construct();
}
public function init(){
$config['base_url'] = "http://localhost/test";
define('EXT_CALL', true); // Added EXT_CALL constant to mark external calls
$_GET['controller/method'] = ''; // add pair to $_GET with call route as key
$current = getcwd(); // Save current directory path
chdir('C:/inetpub/wwwroot/test/'); // change directory to CI_INSTALLATION
include_once 'index.php'; // Add index.php (CI) to script
define('BASEPATH', 'C:/inetpub/wwwroot/test/system/');
$this->load->library("../../application/controllers/controller.php");
$job = new $this->controller->Class();
$isDone = $job->exportExcel(somekey);
echo $isDone;
$CI =& get_instance(); // Get instance of CI
$CI->exportExcel('baseparts', 'exportExcel');
// FOR STATIC CALLING!!
//$CI->method('controller','method');
//replace controller and method with call route
// eg: $CI->list('welcome','list'); If calling welcome/list route.
//$OUT->_display(); // to display output. (quick one)
// Or if you need output in variable,
//$output = $CI->load->view('VIEW_NAME',array(),TRUE);
//To call any specific view file (bit slow)
// You can pass variables in array. View file will pick those as it works in CI
chdir($current); // Change back to current directory
echo $current;
}
where i try to define BASEPATH it does not define it nor override the previous value.
In the index.php of other codeigniter i put:
if(!defined('ENVIRONMENT'))
define('ENVIRONMENT', 'development');
this way i resolved my issue with ENVIRONMENT already being set error.
this is a few things i found and combined together, hoping it could work but still when i call it via command line it shows nothing (even tried echo anything everywhere and nothing).
This may be a long comment rather than a answer as the code supplied requires a lot of work to make it useful.
Running multiple instances of 'codeigniter' - executed from codeigniter.
Using the 'execute programs via the shell' from PHP. Each instance runs in its own environment.
There are some excellent answers already available:
By default the PHP commands 'shell' wait for the command to complete...
732832/php-exec-vs-system-vs-passthru.
However, we want to 'fire and forget' quite often so this answer is quite useful...
1019867/is-there-a-way-to-use-shell-exec-without-waiting-for-the-command-to-complete
All i did was use this show an example of how to use 'codeigniter' to do this. The example was the 'Hello World' cli example from user manual. The version of ci is 2.1.14. I haven't used 'ci' before.
It is tested and works on 'PHP 5.3.18' on windows xp.
As well as the usual 'Hello World' example, i used an example of a a command that uses 'sleep' for a total of 20 seconds so that we can easily see that the 'ci' instances are separate from each other while executing.
Examples:
<?php
class Tools extends CI_Controller {
// the usual 'hello world' program
public function message($to = 'World')
{
echo "Hello {$to}!".PHP_EOL;
}
// so you can see that the processes are independant and 'standalone'
// run for 20 seconds and show progress every second.
public function waitMessage($to = 'World')
{
$countDown = 20;
while ($countDown >= 0) {
echo "Hello {$to}! - ending in {$countDown} seconds".PHP_EOL;
sleep(1);
$countDown--;
}
}
}
'ci' code to run 'ci' code...
<?php
class Runtools extends CI_Controller {
/*
* Executing background processes from PHP on Windows
* http://www.somacon.com/p395.php
*/
// spawn a process and do not wait for it to complete
public function runci_nowait($controller, $method, $param)
{
$runit = "php index.php {$controller} {$method} {$param}" ;
pclose(popen("start \"{$controller} {$method}\" {$runit}", "r"));
return;
}
// spawn a process and wait for the output.
public function runci_wait($controller, $method, $param)
{
$runit = "php index.php {$controller} {$method} {$param}";
$output = exec("{$runit}");
echo $output;
}
}
How to run them from the cli...
To run the 'ci' 'nowait' routine then do:
php index.php runtools runci_nowait <controller> <method> <param>
where the parameters are the ci controller you want to run. Chnge to 'runci_wait' for the other one.
'Hello World: 'wait for output' - (ci: tools message )
codeigniter>php index.php runtools runci_wait tools message ryan3
Hello ryan3!
The waitMessage - 'do not wait for output' - (ci : tools waitMessage )
codeigniter>php index.php runtools runci_nowait tools waitMessage ryan1
codeigniter>php index.php runtools runci_nowait tools waitMessage ryan2
These will start and run two separate 'ci' processes.
I am trying to run a selenium test case using PHPUnit. And the first thing I do is trying the login function, this works perfect but then I want to run a function to check information on the page following the login but it opens a new browser instead of continuing in the current browser window. The reason this is a problem is because the page is setup to remove login authentication when the window is closed so if you use $this->url() to go to the page it gives the error that I need to login. This is my code right now, It starts the browser and runs the function to test the login form, then it closes the browser, open a new one and run the link check. This of course results in an error due to the authentication error because the window was closed. I could run all the tests in one function but that is really sloppy coding and I want to avoid this. Anyone know how to solve this?
<?php
class TestMyTest extends PHPUnit_Extensions_Selenium2TestCase {
public function setUp()
{
$this->setBrowser("firefox");
$this->setBrowserUrl("https://**************************");
}
public function testLoginForm()
{
$this->url("login.php");
$this->byLinkText('Forgot your password?');
$form = $this->byCssSelector('form');
$this->byName('username')->value('test');
$this->byName('password')->value('1234');
$form->submit();
}
public function testCheckForMainMenueLinks ()
{
$this->url("index.php");
$this->byLinkText('Home');
$this->byLinkText('Products');
$this->byLinkText('About us');
$this->byLinkText('Contact');
}
}
?>
To share browser sessions in Selenium2TestCase, you must set sessionStrategy => 'shared' in your initial browser setup:
public static $browsers = array(
array(
'...
'browserName' => 'iexplorer',
'sessionStrategy' => 'shared',
...
)
);
The alternative (default) is 'isolated'.
Okej so I guess you can just call the function directly from another function like so:
public function testOne
{
#code
$this->Two();
}
public function Two()
{
#code
$this->Three();
}
public function Three()
{
#code
}
and so on, this will just run the next function without a new browser, however, if it fails anywhere in any test the whole test is stoped so the feedback wont bee as good as individual tests.
make assetrions in one function because this is functional test.
i am new to phpunit and selenium too, but I successfully test all in one like this:
public function testAuth(){
$this->open('register.php&XDEBUG_SESSION_START=PHPSTORM');
$this->assertTextPresent('Register');
$this->type('name=email', "...");
$this->type('name=firstname', "...");
$this->type('name=lastname', "...");
$this->type('name=password', "...");
$this->type('name=verifyPassword', "...");
$this->click("reg-butt");
$this->waitForPageToLoad("5000");
$this->assertTextPresent('Profile');
$this->open('logout.php');
$this->assertTextPresent('text from redirect page');
$this->open('login.php');
.....
}
An elegant way to set the session shared is to use PHPUnit's setUpBeforeClass() method:
public static function setUpBeforeClass()
{
self::shareSession(true);
}
You can call PHPUnit_Extensions_SeleniumTestCase::shareSession(true) to enable browser window reuse.
In the manual it says:
From Selenium 1.1.1, an experimental feature is included allowing the user to share the session between tests. The only supported case is to share the session between all tests when a single browser is used. Call PHPUnit_Extensions_SeleniumTestCase::shareSession(true) in your bootstrap file to enable session sharing. The session will be reset in the case of not successul tests (failed or incomplete); it is up to the user to avoid interactions between tests by resetting cookies or logging out from the application under test (with a tearDown() method).
I've one page where i do a long polling i've to use at the begin of this page this
session_start();
session_write_close();
Because :
to prevent concurrent writes only one script may operate on a session at any time
So if i do not and the long polling is running the user will not be able to load another page.
So accessing to my data in session from this polling page is possible but at some point in my script i've to save my session back to the server because i made some change in it.
What's the way to do it?
That will be very nice it'll be a way to do something like
session_write_open();
//do stuff
session_write_close();
But the session_write_open() doesn't exist!
Thanks
Before you make some change to the session, call session_start again. Make the changes, and if you still do not want to exit call session_write_close once more. You can do this as many times as you like.
The previous solution will create a session ids and cookies... I wouldn't use it as is:
Session is created every time you call session_start(). If you want
to avoid multiple cookie, write better code. Multiple session_start()
especially for the same names in the same script seems like a really
bad idea.
see here : https://bugs.php.net/bug.php?id=38104
I am looking for a solution right now too and I can't find one. I agree with those who say this is a "bug".
You should be able to reopen a php session, but as you said session_write_open() does not exist...
I found a workaround in the above thread. It involves sending a header specifying manually the session id's cookie after processing the request. Luckily enough I am working with a home-brewed Front Controller that works so that no sub-controller will ever send data on its own.
In a nutshell, it works perfectly in my case. To use this you might just have to use ob_start() and ob_get_clean(). Here's the magic line:
if (SID) header('Set-Cookie: '.SID.'; path=/', true);
EDIT : see CMCDragonkai's answer below, seems good!?
All of the answers here seem to be saying to use the session methods in ways that they were clearly not intended to be used...namely calling session_start() more than once.
The PHP website offers an example SessionHandlerInterface implementation that will work just like existing sessions but without locking the file. Just implementing their example interface fixed my locking issue to allow for concurrent connections on the same session without limiting my ability to add vars to the session. To prevent some race conditions, since the app's session isn't fully stateless, I did have to make a way to save the session mid-request without closing it so that important changes could save immediately after change and less important session vars could just save at the end of the request. See the below example for usage:
Session::start();
echo("<pre>Vars Stored in Session Were:\n");print_r($_SESSION);echo("</pre>");
$_SESSION['one'] = 'one';
$_SESSION['two'] = 'two';
//save won't close session and subsequent request will show 'three'
Session::save();
$_SESSION['three'] = 'three';
If you replace that Session::start() with session_start() and Session::save() with session_write_close(), you'll notice that subsequent requests will never print out the third variable...it will be lost. However, using the SessionHandler (below), no data is lost.
The OOP implementation requires PHP 5.4+. However, you can provide individual callback methods in older versions of PHP. See docs.
namespace {
class Session implements SessionHandlerInterface {
/** #var Session */
private static $_instance;
private $savePath;
public static function start() {
if( empty(self::$_instance) ) {
self::$_instance = new self();
session_set_save_handler(self::$_instance,true);
session_start();
}
}
public static function save() {
if( empty(self::$_instance) ) {
throw new \Exception("You cannot save a session before starting the session");
}
self::$_instance->write(session_id(),session_encode());
}
public function open($savePath, $sessionName) {
$this->savePath = $savePath;
if (!is_dir($this->savePath)) {
mkdir($this->savePath, 0777);
}
return true;
}
public function close() {
return true;
}
public function read($id) {
return (string)#file_get_contents("$this->savePath/sess_$id");
}
public function write($id, $data) {
return file_put_contents("$this->savePath/sess_$id", $data) === false ? false : true;
}
public function destroy($id) {
$file = "$this->savePath/sess_$id";
if (file_exists($file)) {
unlink($file);
}
return true;
}
public function gc($maxlifetime) {
foreach (glob("$this->savePath/sess_*") as $file) {
if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
unlink($file);
}
}
return true;
}
}
The other answers here present pretty good solutions. As mentioned by #Jon, the trick is to call session_start() again before you want to make changes. Then, when you are done making changes, call session_write_close() again.
As mentioned by #Armel Larcier, the problem with this is that PHP attempts to generate new headers and will likely generate warnings (e.g. if you've already written non-header data to the client). Of course, you can simply prefix the session_start() with "#" (#session_start()), but there's a better approach.
Another Stack Overflow question, provided by #VolkerK reveals the best answer:
session_start(); // first session_start
...
session_write_close();
...
ini_set('session.use_only_cookies', false);
ini_set('session.use_cookies', false);
//ini_set('session.use_trans_sid', false); //May be necessary in some situations
ini_set('session.cache_limiter', null);
session_start(); // second session_start
This prevents PHP from attempting to send the headers again. You could even write a helper function to wrap the ini_set() functions to make this a bit more convenient:
function session_reopen() {
ini_set('session.use_only_cookies', false);
ini_set('session.use_cookies', false);
//ini_set('session.use_trans_sid', false); //May be necessary in some situations
ini_set('session.cache_limiter', null);
session_start(); //Reopen the (previously closed) session for writing.
}
Original related SO question/answer: https://stackoverflow.com/a/12315542/114558
After testing out Armel Larcier's work around. Here's my proposed solution to this problem:
ob_start();
session_start();
session_write_close();
session_start();
session_write_close();
session_start();
session_write_close();
session_start();
session_write_close();
if(SID){
$headers = array_unique(headers_list());
$cookie_strings = array();
foreach($headers as $header){
if(preg_match('/^Set-Cookie: (.+)/', $header, $matches)){
$cookie_strings[] = $matches[1];
}
}
header_remove('Set-Cookie');
foreach($cookie_strings as $cookie){
header('Set-Cookie: ' . $cookie, false);
}
}
ob_flush();
This will preserve any cookies that were created prior to working with sessions.
BTW, you may wish to register the above code as function for register_shutdown_function. Make sure to run ob_start() before the function, and ob_flush() inside the function.
In ASP.NET if I declare a variable (or object) static (or if I make a singleton) I can have it persist across multiple sessions of multiple users (it it registered in a server scope) so that I don't have to initialize it at every request.
Is there such a feature in PHP? Thanks
You can set up APC and use the apc_store and apc_fetch functions.
http://us.php.net/manual/en/book.apc.php
You can do that with a PHP extension (written in C).
But if you want to write it in PHP, no. The best alternative is to write the variable to a file (file_put_contents()) at the end of each request, and open it at the start of each request (file_get_contents()).
That alternative isn't going to work for high volume sites because the processes will be doing read/write at the same time and the world will go all BLAAA-WOOO-EEE-WOHHH-BOOOM.
That doesn´t exist in PHP, however, you can serialize the data and put it either in a file on your hard drive or in /dev/shm/. You can also use memcache.
If you put your data in /dev/shm/ or use memcache the data will disappear on reboot.
Sadly, no. PHP's static keyword is limited to the current script instance only.
To persist data across script instances for the same session, you would use the session handling features.
To persist data across sessions, you would have to use something like memcache, however that requires additional set-up work on server side.
Symfony and other frameworks uses "PHPFastCache" who supports a wide range of drivers for caching data including APC, SQLite, MongoDB or simply your file system.
You can donwnload it at https://github.com/PHPSocialNetwork/phpfastcache
Here is an example with file caching :
use Phpfastcache\Helper\Psr16Adapter;
$defaultDriver = 'Files';
$Psr16Adapter = new Psr16Adapter($defaultDriver);
// Setter action
if(!$Psr16Adapter->has('test-key')) {
$data = 'lorem ipsum';
$Psr16Adapter->set('test-key', 'lorem ipsum', 300); // kept in cache for 300 seconds (5 minutes)
}
// Getter action
else {
$data = $Psr16Adapter->get('test-key');
}
You can use the Session Storage for this purpose, if you use the same sessionId for all sessions.
session_id('xyz');
session_start();
for ($i=0; $i < 100000; $i++) {
$_SESSION['counter'] = isset($_SESSION['counter']) ? $_SESSION['counter'] + 1 : 0;
}
echo "<br>session_id(): ".session_id() . "<br>counter: ".$_SESSION["counter"];
Try this script with 2 browsers and you will see that this method shares the data across both browsers - and is very, very fast.
you could store serialized copies of an object inside session
class test{
private static $instance;
public property;
private __construct(){}
public getInstace(){
if(!self::$instance){
self::$instance = new test;
}
return self::$instance;
}
}
$p = test->getInstance();
$p->property = "Howdy";
$_SESSION["p"] = $p;
next page
$p = $_SESSION["p"];
echo $p->property; // "Howdy"