PDA

View Full Version : Alternative Ext Direct PHP Implementation



TommyMaintz
11 May 2009, 2:40 PM
Updated 08/04/2009: Fixed len property issue and class level path's

Hi guys,

I thought it would be nice to show another way of implementing Direct on a PHP server stack. It is using PHP comments and reflection to create the API for you automagically. I haven't documented it properly yet, and I dont have a client-side example to show it of, but I thought I would put it out here already anyway for you to try. I think the result is quite flexible and the router part should be easily extensible to use this with any server-side MVC framework out there . You can download the source code here (http://extjs.com/playpen/ext-direct-php-1.01.zip).

It consists of 3 classes called:

ExtDirect_API
ExtDirect_Router
ExtDirect_CacheProvider


An example api.php that you would include in a <script> tag:


<?php
session_start();

// Include ExtDirect PHP Helpers
require_once('ExtDirect/API.php');
require_once('ExtDirect/CacheProvider.php');

// I created a Cache to save the parsed API so it doesnt have to
// do all the reflecting and parsing over and over again
$cache = new ExtDirect_CacheProvider('cache/api_cache.txt');

// Create an ExtDirect_API instance
$api = new ExtDirect_API();

// Set up some special config options
$api->setRouterUrl('router.php'); // default
$api->setCacheProvider($cache);
$api->setNamespace('Ext.ss');
$api->setDescriptor('Ext.ss.APIDesc'); // defaults to Ext.app.REMOTING_API

// These defaults will be applied to each class when using the add method later on
$api->setDefaults(array(
'autoInclude' => true, // this will automatically figure out file paths and include them
'basePath' => 'classes' // the base folder in which all your classes exist
));
// if you want to use autoInclude you have to make sure that your classes (without the prefix)
// are named the exact same as the name of the file it exists in and that only one
// class exists in each file.

// Now we have to add all the classes we want to scan for remotable methods
$api->add(
array(
'Echo' => array('prefix' => 'Class_'), // Echo is a reserved keyword so i used a prefix
'Exception' => array('prefix' => 'Class_'), // Same reason as Echo
'Time',
'File'
)
);
// The config options you can specify for each individual class are:
// - autoInclude
// - subPath (to specify classes inside a nested folder structure, starts from the basePath)
// - prefix

// this will generate the API and output it properly for you
$api->output();

// by saving the state the router doesnt have to do all this parsing and
// reflecting again. here i chose to save the state in a session but you
// can save it wherever you want
$_SESSION['ext-direct-state'] = $api->getState();
?>


Obviously another important part of the implementation is the router. Here is an example of a router.php:


<?php
session_start();

require_once('ExtDirect/API.php');
require_once('ExtDirect/Router.php');

// this should always be set but if its not, then execute api.php without outputting it
if(!isset($_SESSION['ext-direct-state'])) {
ob_start();
include('api.php');
ob_end_clean();
}

// A router needs an API instance aswell so thats why we create one and set its
// state to what we saved in the session in api.php
$api = new ExtDirect_API();
$api->setState($_SESSION['ext-direct-state']);

// Create the router, dispatch the call and output the response
$router = new ExtDirect_Router($api);
$router->dispatch();
$router->getResponse(true); // true to print the response instantly
?>


I also included some sample classes inside the /classes/ folder inside the rar file, but basically its a matter of putting comments about your function that include tags like @remotable, @formHandler and @remoteName (to specify a different name for a method on the client side). Here is an example of a simple Echo class that returns whatever you pass its send function:


<?php
class Class_Echo {
/**
* @remotable
*/
public function send($string){
return $string;
}
}
?>



Here is a simple File class that shows of some of the other options like @formHandler (see how it handles file uploads nicely) and @remoteName (list is a reserved keyword in PHP so thats why we have to use the @remoteName option)


<?php
class File {
/**
* @remotable
* @remoteName list
*/
public function listFiles($folder){
if(substr($folder, 0, 3) === '../') {
return 'Nice try buddy';
}
return array_slice(scandir($folder), 2);
}

/**
* @remotable
* @formHandler
*/
public function add($post, $files){
if($files && !empty($files[$post['formField']])) {
$file = $files[$post['formField']];
move_uploaded_file($file['tmp_name'], 'data/' . $file['name']);
return pathinfo('data/' . $file['name']);
}
}
}
?>

You can find all the source code and more examples inside the rar file.

fabrizim
11 May 2009, 5:17 PM
Hi Tommy-

That is great. I wrote something similar for database field instantiantion / altering by parsing the files for doc comments in PHP4 - I didn't even realize the PHP5 reflection API lets you grab the doc comments. I plan on continuing development with my stuff and if I come up with any additional / generic parsing stuff, I'll post it back.

Best Regards-
Mark

TommyMaintz
12 May 2009, 8:28 AM
Hi Fabrizim,

I had never looked at the Reflection capabilities in PHP5 before, but when I started doing this PHP router I thought it would be very useful. And it turned out it was. Im interested in what you come up with in regards to additional / generic parsing of the classes and looking forward to your post.

mrsunshine
14 May 2009, 8:24 AM
Ported your nice classes to a TYPO3 Extension will it release it soon

steffenk
18 May 2009, 5:08 AM
yeah, this can be used in TYPO3 very well, great! (increase the TYPO3 lobby :D)

meza
29 May 2009, 3:22 AM
Hi!

There are some huge problems with the ExtJs's ExtDirectPack.
The first enormous design error is, that we definitely don't want a third-party tool to know the way our classes should be constructed. First I tried the reverse-engineers way to implement the Sphicy style automatic dependency injections. But still, that's not what we want from a third-party. The other trouble was the untestability. Anyone interested in php unit-testing can find a great slide by Sebastian Bergman (http://sebastian-bergmann.de/) at this (http://www.slideshare.net/sebastian_bergmann/quality-assurance-in-php-projects-1163460) link.

So I've started to solve these problems, and below is the first peak of it.
The key concept is to add already constructed instances to the api instead of classnames.

First of all, How a simple class looks like:
TestClass.php

<?php
class TestClass
{
/**
* @remotable
*/
public function shout($what)
{
return 'SHOUT: '.$what;
}

/**
* @remotable
*/
public function postHandler($name,$files=array()){
return array(
'gotName'=>$name,
'gotFiles'=>$files
);
}
}
?>

Now for the Api. It works the same way, you include it on client-side in a <script>

api.php

<?php

require_once('TestClass.php');
require_once('ExtDirect/Api.php');

$api=new ExtDirect_Api();
$api->add(new TestClass());

//$api->setRouterUrl('router.php');

echo $api->output();

?>

Because the router.php is still the default, it is commented out.

router.php

<?php

require_once('TestClass.php');
require_once('ExtDirect/Api.php');
require_once('ExtDirect/Router.php');

$api=new ExtDirect_Api();
$api->add(new TestClass());

//$api->setRouterUrl('router.php');

$router = new ExtDirect_Router($api);

echo $router->getResponse();

?>

And that's about it.
Grab the source from here (http://www.meza.hu/ExtDirect.zip).

The development is far from finished. Caching and the yet not refactored candy-s are soon to come. This release is just the absolute core :)

Due to the refactoring, the code is all covered with tests which ensure the stability and functionality. A bit later the project will be public to see for anyone.

mark_l_lewis
4 Jun 2009, 2:44 AM
L]hi,

I'm having a little problem with the Ext.Direct PHP implementation. I have copied the entire example locally.

I then created a basic html file, included the api.php as script source. added a button and then implemented a handler.
The Handler below is called successfully, but the return value is undefined.


var rv = Ext.ss.Echo.send('hello, world');

alert (rv);

Using firebug I examined the router.php response as follows:


{"type":"rpc","tid":2,"action":"Echo","method":"send","result":"hello, world"}

I'm not sure why the
rv is coming up as undefined?

I have downloaded the latest version of Ext RC2

Any help or direction would be appreciated.

Thanks,
Mark

mrsunshine
4 Jun 2009, 3:00 AM
i think you have to handle the response like that,



Ext.ss.Echo.send(text.getValue(), function(result, e){
var t = e.getTransaction();
out.append(String.format('<p><b>Successful call to {0}.{1} with response:</b><xmp>{2}</xmp></p>',
t.action, t.method, Ext.encode(result)));
out.el.scrollTo('t', 100000, true);
});

mark_l_lewis
4 Jun 2009, 3:11 AM
and you truly are, mr sunshine :-)

that works a treat. although, is there another implementation which doesn't require a function handler for the return value?

the docs are a little thin on direct, although i'm piecing the bits together.

JeffHowden
10 Jun 2009, 11:16 PM
var rv = Ext.ss.Echo.send('hello, world');

alert (rv);

Keep in mind that Ext.Direct is asynchronous. So, that means that in order to act upon the value being returned by the server, you'll have to specify a callback in your method call to the server. Mr. Sunshine illustrates that, but didn't specifically indicate the issue was with not staying within the asynch paradigm.

Dumbledore
22 Jun 2009, 9:15 AM
is there a reason for following code?

ExtDirect/API.php Line 95


$cSettings['fullPath'] = $this->getClassPath($name, $Settings);

when i change this line to


$cSettings['fullPath'] = $this->getClassPath($name, $cSettings);

i was able to set individiual paths for each Class via basePath...


Bye, Dumbledore

thesilentman
16 Jul 2009, 6:50 AM
Tommy,
did you have something special in mind in using

getNumberOfRequiredParameters()

instead of

getNumberOfParameters()

to get the functions parameters in order to construct the length?

I stumbled upon this on a function where I was initializing some parameters in the function declaration, and they were not counting in the parameter len config for the remote procedure.

Greets,
Frank

thesilentman
18 Jul 2009, 8:17 AM
Hello community,
just wanted to share this experience I had, in case somebody else stumbles upon this behavior. Hopefully it will save you some time ;)

I don't know how many of you have tested this solution with the reflection capabilities of php, but I found something strange happening.

I was playing around with the example classes, and changed the file class a bit.
After that when I called the api.php, I was only getting back the actions of the file class and not any of the other files.

I debugged the php side and found out that once a class has been analyzed with the reflection tools, the next time it get's analyzed, the


if( $rMethod->isPublic() &&
strlen($rMethod->getDocComment()) > 0
)

code fails. This meant that for some reason the 'getDocComment()' command returned real data only the first time it parses a class method.

Since I am new to reflections and just starting to experiment I was not sure if this is a bug in PHP or some config issue with Tommy's implementation.

I did some goggleing and found this bug which kind of reminded me of this behaviour:
http://eaccelerator.net/ticket/229

and indeed it was an eaccelerator (that I am using) bug which was fixed and the solution is also explained in the link I provided. You should also install the latest version of course....

Greets,
Frank

TommyMaintz
4 Aug 2009, 1:53 PM
You can find an updated version in the original post.

mor
9 Sep 2009, 8:06 AM
Hi :) Could anyone explain how to catch up the exception result formed in Router.php:


}
catch(Exception $e)
{
$response = array(
'type' => 'exception',
'tid' => $cdata->tid,
'message' => $e->getMessage(),
'where' => $e->getTraceAsString()
);
}

In Ext script I'm on success and failure events and just can't catch this response


FormPanel constructor
...
buttons:[{
text: 'Enter',
id: 'submit',
handler: doSubmit
}],
// configs for BasicForm
api: {
// The server-side must mark the submit handler as a 'formHandler'
submit: Ext.ss.Auth_Demo.login
},
// specify the order for the passed params
paramsAsHash: true
});

function doSubmit()
{
basicInfo.getForm().submit(
{
waitMsg: 'Ожидайте',
waitTitle: 'Идет проверка..',
params:
{
username : 'username',
password : 'password'
},
success: function(form, action)
{
Ext.Msg.alert(':)');

basicInfo.ownerCt.close();
},
failure:function(form, action)
{
if(action.failureType == 'server')
{
Ext.Msg.alert(':(');
}
}
});
}

Thanx :)

mrsunshine
8 Oct 2009, 4:59 AM
I did some goggleing and found this bug which kind of reminded me of this behaviour:
http://eaccelerator.net/ticket/229

and indeed it was an eaccelerator (that I am using) bug which was fixed and the solution is also explained in the link I provided. You should also install the latest version of course....

Greets,
Frank


thx for the hint, had the same problem!

thesilentman
8 Oct 2009, 5:16 AM
thx for the hint, had the same problem!

glad it helped someone :)

Greets,
Frank

willf1976
20 Oct 2009, 8:54 PM
Hi

Thanks for posting this updated version.

I found a bug in API.php --

Line 54 reads:



if(isset($state['nameAttribute'])) {
$this->setFormAttribute($state['nameAttribute']);
}


It should read:




if(isset($state['nameAttribute'])) {
$this->setNameAttribute($state['nameAttribute']);
}


This was causing errors when working with forms and direct.

Best Regards

Will Ferrer

Freman
29 Oct 2009, 4:36 AM
Hi :) Could anyone explain how to catch up the exception result formed in Router.php:


I too would like to know how to catch an exception from this, firebug says "result is undefined" when the result is



{"type":"exception","tid":"2","message":"New and repeat passwords do not match","where":"#0 [internal function]: ExtLib_Password->passwd(Array, Array)\n#1 \/var\/www\/localhost\/htdocs\/lib\/ExtDirect\/Router.php(176): call_user_func_array(Array, Array)\n#2 \/var\/www\/localhost\/htdocs\/lib\/ExtDirect\/Router.php(62): ExtDirect_Router->rpc(Object(BogusAction))\n#3 \/var\/www\/localhost\/htdocs\/lib\/router.php(17): ExtDirect_Router->dispatch()\n#4 {main}"}

mrsunshine
30 Oct 2009, 3:01 AM
you can for example catch the ext direct exception event


Ext.Direct.on(
'exception'
function(e){
// do something with the exception information
console.log(e);
}
);

steffenk
15 Mar 2010, 10:07 AM
Hi Tommy,

something is missing in your implementation, and it belongs to file upload.
Please have a look to our TYPO3 implementation i posted some minutes ago (when it is reviewed - why are new posts here are reviewed?)

Just in short:

in Router:
$data->data = array($_POST, $_FILES);
should read
$data->data = array($_POST + $_FILES);

and for the response of file uploads you will need something like


$response = json_encode($response);
$response = preg_replace('/&quot;/', '\\&quot;', $response);

$response = array(
'<html><body><textarea>' .
$response .
'</textarea></body></html>'
);

j.bruni
16 Mar 2010, 11:20 AM
Hi, guys,

Would you mind taking a look at my own approach to the Ext.Direct / PHP integration?

http://www.extjs.com/forum/showthread.php?t=95022

Andrew Peacock
28 May 2010, 2:33 AM
Hi,
I'm relatively new to Extjs, but am getting on OK, until I hit Direct. I'm writting an app with a lot of server-side code, so I thought I'd do it right from the beginning with this interface.

Here's the code I've got in a basic test page, stripped to the minimum. I've used the download from this thread with no other changes except to move the sessions_start to my parent page, rather than within the api.php page, as I'll need it in the parent for my real application:


<?
session_start();
?>
<html>
<head>
<title>Ext.Direct Test</title>
<link rel="stylesheet" type="text/css" href="ext/resources/css/ext-all.css"/>
<script type="text/javascript" src="ext/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="ext/ext-all-debug.js"></script>
<script type="text/javascript" src="api.php"></script>

<script type="text/javascript">
Ext.onReady(function() {
Ext.BLANK_IMAGE_URL = 'images/s.gif';

// Test the direct setup
var rv = Ext.ss.Echo.send('hello, world');
alert ("Direct response: " + rv);

});
</script>

</head>
<body>
Done
</body>
</html>


I'm getting the error:"Ext.ss.echo is null or not an object" on line 14

Can anyone point me in the right direction please?

Regards,
Andy

jai1485
23 Jul 2010, 5:24 AM
My question may be silly.
Please help me out of this issue.
I am using a form with direct api and trying to insert records in to the db. But form values are not submitting to the server side.
My Code:
formadd= new Ext.form.FormPanel({
// configs for FormPanel
title: ‘Add record’,
border: false,
padding: 10,
buttons:[{
text: 'Submit',
handler: function(){
formadd.getForm().submit({
params: {
foo: 'bar',
uid: 34
}
});
}
}],
defaultType: ‘textfield’,
items: [{
fieldLabel: 'Name',
name: 'name'
},{
fieldLabel: 'Email',
msgTarget: 'side',
name: 'logo_url'
}],
api: {
// The server-side method to call for load() requests
load: Vendor.read,
// The server-side must mark the submit handler as a ‘formHandler’
submit: Vendor.save
},
// specify the order for the passed params
paramOrder: ['uid', 'foo']
});
Please help me soonnnnnnnnnnnn!!!!!!!

jai1485
26 Jul 2010, 1:39 AM
Please help me from this issue ANYONE. SOON

Andrew Peacock
26 Jul 2010, 3:46 AM
Try the following amendment:


api: {
// The server-side method to call for load() requests
load: Ext.php.Vendor.read,
// The server-side must mark the submit handler as a ‘formHandler’
submit: Ext.php.Vendor.save
},

This assumes you've included the ExtDirect API with a line something like the following:


<script type="text/javascript" src="classes/Vendor.php?javascript"></script>

Hope that helps,
Andy

Andrew Peacock
26 Jul 2010, 3:49 AM
Oops. Just realised that I thought this was the thread at
http://www.sencha.com/forum/showthread.php?102357-Extremely-Easy-Ext.Direct-integration-with-PHP

The answer I've given only applies (I'm sure) if you're using the PHP classes mentioned in that thread (which I recommend, as they are re-vamped version of the classes in this thread).

Regards,
Andy

jai1485
26 Jul 2010, 4:36 AM
Its shows the following error Error: Ext.php is undefined

jai1485
26 Jul 2010, 4:37 AM
---------------------------------

I am using codeigniter for server side and implementing Direct on a PHP server stack.

Listed all the db table records in a grid using endpoint Vendor. Using like this


stud.data.vendors = new Clutch.data.DirectStore({

endpoint: Vendor,
// autoLoad: False,
fields: [
{ name: 'id' },
{ name: 'name', type: 'string' }
]
})

jai1485
26 Jul 2010, 4:39 AM
Clutch.data.DirectStore = Ext.extend(Ext.data.DirectStore, {
constructor: function(config) {
config = Ext.apply({
root: 'records',
autoLoad: true,
autoSave: true,
remoteSort: true,
batch: false,
writer: new Ext.data.JsonWriter({
encode: false,
writeAllFields: true
})
}, config);

if (config.endpoint && !config.directFn) {
config.api = {
read : config.endpoint.read,
create : config.endpoint.save,
update : config.endpoint.save,
destroy : config.endpoint.destroy
}
}
Clutch.data.DirectStore.superclass.constructor.call(this, config);
}
});

jai1485
26 Jul 2010, 4:39 AM
Then i created a add form and trying to insert record. Add form posted above
Please help me soon...........
Please reply me soon.. Please.........................
Please...................

jai1485
26 Jul 2010, 9:26 PM
Hi, I am waiting for your reply. please help me...

Yasheena
2 Nov 2010, 7:14 AM
Hello Thommy,
I'm using your direct php package in version 1.01 and I'm very enthusiastic.

I found an improvement: to use server functions with optional parameters make the following changes:

File 'API.php' about line 161:


$method = array(
'name' => $rMethod->getName(),
'len' => $rMethod->getNumberOfParameters(),
'req' => $rMethod->getNumberOfRequiredParameters()
);


File 'Router.php' about line 162:


if(count($params) < $mconf['req']) {
throw new Exception("Not enough required params specified for method: $method on class $class");
}


And I have a question: In Router.php starting with line 109 there is an else block: For what purposes is this block? It seems it does nearly the same as the corresponding code in API.php.

Greetings,
Wolfgang Mattis

milanz
19 Mar 2011, 10:14 PM
Sorry I don't have much time to investigate whether this is my problem (since I have modified your implementation over time) or if this is a problem in the original code base; however, I just wanted to make this known to anyone (even using any other framework) who might be having problems with file uploads using Direct.

My problem was that for some reason I was able to post a form including files to the formHandler method; however, for some reason my success and failure callbacks would never get called even though the response was definitely correct. It turns out that the response wasn't correct though, the problem being that a response Content-Type header of text/javascript was being returned for file-uploads. The BasicForm handler doesn't like it when the wrong content type is returned to the iframe and the success, failure, waitMsg, etc. callbacks are never called. The fix I used was modifying your _print method to force the Content-Type to be text/html when it's a file-upload:



private function _print($response) {
if(!$this->isForm) {
header('Content-Type: text/javascript');
}
else if($this->isUpload)
{
header('Content-Type: text/html');
}

echo $response;
}


Hope this helps someone!

Cheers,
Milan

avishnev
29 Jul 2011, 2:15 PM
I downloaded the file and unpacked it in my html directory. When I run a sample html page with included api.php it, the developer web inspector window is telling me that Ext.ss.Echo.send is undefined. What am i doing wrong?

extjs@kingsquare.nl
29 Aug 2011, 7:19 AM
Hi there,

I noticed a bug where the autoInclude option was ignored by the Router.php class.

This patch fixes this!27738

Regards,

Tim

Francol
26 Sep 2011, 12:08 AM
thank you, extjs@kingsquare.nl! it was very much of help!