When the client is started, it waits for a PHP script to start, and then begins to take input
from the user.
The source formatting doesn't look too great, but it's readable, I guess...
We start off with the actual server that listens to port 9000:
#!/usr/bin/php
/*
* Xdebug settings in php.ini
*
* xdebug.remote_autostart=on
* xdebug.remote_enable=on
* xdebug.remote_handler=dbgp
* xdebug.remote_mode=req
* xdebug.remote_host=localhost
* xdebug.remote_port=9000
*/
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
// PEAR
require_once 'Net/Server.php';
require_once 'lib/CLIDebug.php';
$server = &Net_Server::create('sequential', 'localhost', 9000);
$server->setCallbackObject(new CLIDebug());
$server->setEndCharacter("\0");
$server->setDebugMode(false);
$server->start();
?>
Our XDebug client is added via the "setCallbackObject" method, and
looks like this:
/**
* Description of CLIDebug
*
* @author Morten Amundsen
*/
class CLIDebug extends Net_Server_Handler
{
private $_clientid = 0;
private $_appid = 0;
private $_app = '';
private $_status = '';
private $_currline = 0;
/**
*
* @param int $client
* @param string $data
* @return boolean
*/
public function onReceiveData($client = 0, $data = '')
{
$this->_client = $client;
if (is_numeric(trim($data)) or !trim($data)) return true;
$xml = simplexml_load_string($data);
if ($xml) {
if (!empty($xml['appid']) and !empty($xml['fileuri'])) {
$this->_appid = $xml['appid'];
$this->_app = $xml['fileuri'];
$this->_printHelp();
}
$this->_status = (string) $xml['status'];
if ($this->_status == 'stopping') {
$this->_server->closeConnection();
die("Done.\n");
}
$command = (string) $xml['command'];
switch ($command) {
case 'source':
$code = (string) $xml;
$code = base64_decode($code);
echo $code."\n";
break;
case 'step_over':
case 'step_into':
$xdebug = $xml->children('http://xdebug.org/dbgp/xdebug');
$this->_printFileAndLine($xdebug);
if ($this->_cmdSourceLineXdebug($xdebug)) {
return true;
}
default:
}
flush();
} else {
return true;
}
do {
echo ">";
flush();
while (($cmd = $this->waitForCommand()) === false);
} while (!$this->_executeCommand($cmd));
}
/**
*
* @return mixed
*/
public function waitForCommand()
{
if ($fh = fopen("php://STDIN", 'r')) {
$cmd = fgets($fh, 100);
fclose($fh);
return trim($cmd);
} else {
echo "Unable to connect to php://STDIN\n";
return false;
}
}
/**
*
* @return string Command
*/
private function _executeCommand($cmd)
{
$cmd = trim(strtolower($cmd));
switch ($cmd) {
case 'o':
case 'step_over':
return $this->_cmdStepOver();
break;
case 'i':
case 'step_into':
return $this->_cmdStepInto();
break;
case 'h':
case 'help':
$this->_printHelp();
return false;
break;
case 'q':
case 'quit':
$this->_server->closeConnection();
die("Quitting.\n");
default:
echo "Unknown command: ".$cmd."\n";
return false;
}
return true;
}
/**
*
* @return boolean
*/
private function _cmdStepOver()
{
$cmd = "step_over -i {$this->_appid}\0";
$this->_server->sendData($this->_clientid, $cmd);
return true;
}
/**
*
* @return boolean
*/
private function _cmdStepInto()
{
$cmd = "step_into -i {$this->_appid}\0";
$this->_server->sendData($this->_clientid, $cmd);
return true;
}
/**
*
* @param SimpleXMLElement $xdebug
* @return boolean
*/
private function _cmdSourceLineXdebug($xdebug)
{
$attr = $xdebug->attributes();
if ($attr and isset($attr['lineno'])) {
$line = (string) $attr['lineno'];
$file = (string) $attr['filename'];
$cmd = "source -i {$this->_appid} -b {$line} -e {$line} -f {$file}\0";
$this->_server->sendData($this->_clientid, $cmd);
return true;
} else {
return false;
}
}
/**
*
* @param SimpleXMLElement $xdebug
* @return boolean
*/
private function _cmdSourceLines($file, $begin, $end)
{
$cmd = "source -i {$this->_appid} -b {$begin} -e {$end} -f {$file}\0";
$this->_server->sendData($this->_clientid, $cmd);
return true;
}
/**
*
* @param SimpleXMLElement $xdebug
*/
private function _printFileAndLine($xdebug)
{
$attr = $xdebug->attributes();
$this->_app = (string) $attr['filename'];
$line = (string) $attr['lineno'];
$this->_currline = $line;
echo "File: ".(string) $this->_app . " - Line: " . (string) $attr['lineno']." (".$this->_status.")\n";
}
/**
*
*/
private function _printHelp()
{
echo "i|step_into\n\tsteps to the next statement, if there is a function call\n\tinvolved it will break on the first statement in that function\n";
echo "o|step_over\n\tsteps to the next statement, if there is a function call\n\ton the line from which the step_over is issued then the debugger engine will stop at the\n\tstatement after the function call in the same\n\tscope as from where the command was issued\n";
echo "h|help\n\tThis help text\n";
echo "q|quit\n\tQuit\n";
flush();
}
}
?>
Now it's only a matter of filling in the blanks with the rest of the DBGP protocol.

0 comments:
Post a Comment