Debugging PHP applications in terminal

Posted by Dan Sosedoff on July 03, 2010

Most of Ruby web frameworks have terminal logging in development environments. It makes application debugging process much easier than using file logging. Especially for AJAX requests. Of course there is simple solution – use Firebug and javascript console. Unfortunately it is not that convenient.

So, one day i came up with idea to make the same ruby-like style of application logging, even with colorized output.
I decided to use EventMachine library as a server platform. And API is based on JSON packets, via json/pure. Simple, isnt it?

And i called it DebugServer. Pretty understandable :)

Installation

To install just type

$ sudo gem install debugserver

And for information type:

$ debugserver -i
Usage: debugserver [options]
    -h, --host HOSTNAME              Server hostname
    -p, --port PORT                  Server port
    -i, --info                       Get usage information

By default it will start on localhost:9000.

Usage in PHP

First, download library from GitHub repository: http://github.com/sosedoff/debugclient-php

And then:

// place it into app initialization file
$debug = DebugClient::instance();
$debug->connect();
 
// .... and write it to terminal output
$obj = array(
  'id' => rand(0, 0xFFFF),
  'name' => 'Sample name',
  'time' => strftime('%m-%d-%Y', time())
);
 
// these functions are globally defined
debug_clear(); // clear terminal
debug('This is a plain text message.');
debug_info('This is an informational message.');
debug_warning('This is a warning message.');
debug_error('This is an error message.');
debug_dump($obj);
 
// optional
$debug->close();

Results:
Output

Sources

DebugServer: http://github.com/sosedoff/debugserver
PHP Client: http://github.com/sosedoff/debugclient-php

Generate sitemaps with Ruby and XmlSitemap gem

Posted by Dan Sosedoff on June 18, 2010

Made a simple gem for website sitemap generation. Could be used in any Ruby/Rails/Merb/Sinatra application. It does not have any caching in that case if you want to use framework built-in cache methods.

Installation:

$ sudo gem install xml-sitemap

Example

pages = Page.all(:order => [:updated_at.desc] # DM model
map = XmlSitemap::Map.new('somedomain.com') do |m|
  m.add(:url => '/', :period => :daily, :priority => 1.0)
  m.add(:url => '/contractors', :period => :daily, :priority => 1.0)
  pages.each do |p|
    m.add(
      :url => url_for_page(p),
      :updated => p.updated_at,
      :priority => 0.5,
      :period => :never
    )
  end
end
# render the sitemap
puts map.render

Sinatra Example

# ... your code
 
get '/sitemap.xml' do
  map = XmlSitemap::Map.new('domain.com') do |m|
    m.add(:url => '/')
    m.add(:url => '/posts', :period => :weekly)
  end
 
  headers['Content-Type'] = 'text/xml'
  map.render
end
 
# ... more code

Options

:url – page path, relative to domain (ex.: /test), String.
:period – freqchange attribute, Symbol, :none, :never, :always, :hourly, :daily, :weekly, :monthly, :yearly
:priority – priority attribute, Float class,(0.0..1.0)
:updated – (optional) last_update attribute, Time class

Source Code

http://github.com/sosedoff/xml-sitemap

Making HTTP requests from different network interfaces with Ruby and Curb

Posted by Dan Sosedoff on June 09, 2010

At some point you will find that you have reached requests per IP limit while using some API or crawling resources. And if you`re doing it via standard Net::HTTP you`ll face the problem that you cannot assign request class to specified network interface (or IP). Bummer? No. Even if you cant do it with core class you might take a look on Curb – libcurl ruby binding. It has everything that you need to make regular get/post/etc requests. And of course – easy.

A simple example (real ip`s are changed):

require 'rubygems'
require 'curb'
 
ip_addresses = [
  '1.1.1.1',
  '2.2.2.2',
  '3.3.3.3',
  '4.4.4.4',
  '5.5.5.5'
]
 
ip_addresses.each do |ip|
  req = Curl::Easy.new('http://www.ip-adress.com/')
  req.interface = ip
  req.perform
  result_ip = req.body_str.scan(/<h2>My IP address is: ([\d\.]{1,})<\/h2>/).first
  puts("for #{ip} got response: #{result_ip}")
end

Output (ip`s are changed):

for 1.1.1.1 got response: 1.1.1.1
for 2.2.2.2 got response: 2.2.2.2
for 3.3.3.3 got response: 3.3.3.3
for 4.4.4.4 got response: 4.4.4.4
for 5.5.5.5 got response: 5.5.5.5

At least its working. Havent done any performance tests.
Sample on pastie: http://pastie.org/private/afxlcuk1npwjov3wer5hw

How to identify your clients` browser type

Posted by Dan Sosedoff on October 02, 2009

Some time ago i wrote a simple regex patterns to determine whether my client crawler bot, mobile client or just regular one. Easy to expand and to use.

function is_mobile($agent) {
    $pattern = '/(blackberry|motorokr|motorola|sony|windows ce|240x320|176x220|palm|mobile|iphone|ipod|symbian|nokia|samsung|midp)/i';
    return (bool)preg_match($pattern, $agent);
}
 
function is_crawler($agent) {
    pattern = '/(google|yahoo|baidu|bot|webalta|ia_archiver)/';
    return (bool)preg_match($pattern, $agent);
}

Rails-like PHP url router

Posted by Dan Sosedoff on September 20, 2009

My previous version of php url router was not good enough, so i`ve done some core modification to the class. Its more flexible now. The whole idea is very similar to Rails` and Merb`s url router (merb is a rails-fork). In fact, previous version doesn`t even support GET variables in the request uri. New version just merges variables from request string into the same parameters array where other variables are stored. Also there is a command “default_routes” that maps default routes like this “/:controller/:action/:id”. So, basically you have two options – use defaults & custom routes or use only custom, which means that all other requests will be ignored. Function default_routes must be declared last.

Here is the sources:

define('ROUTER_DEFAULT_CONTROLLER', 'home');
define('ROUTER_DEFAULT_ACTION', 'index');
 
class Router {
  public $request_uri;
  public $routes;
  public $controller, $controller_name;
  public $action, $id;
  public $params;
  public $route_found = false;
 
  public function __construct() {
    $request = $_SERVER['REQUEST_URI'];
    $pos = strpos($request, '?');
    if ($pos) $request = substr($request, 0, $pos);
 
    $this->request_uri = $request;
    $this->routes = array();
  }
 
  public function map($rule, $target=array(), $conditions=array()) {
    $this->routes[$rule] = new Route($rule, $this->request_uri, $target, $conditions);
  }
 
  public function default_routes() {
    $this->map('/:controller');
    $this->map('/:controller/:action');
    $this->map('/:controller/:action/:id');
  }
 
  private function set_route($route) {
    $this->route_found = true;
    $params = $route->params;
    $this->controller = $params['controller']; unset($params['controller']);
    $this->action = $params['action']; unset($params['action']);
    $this->id = $params['id']; 
    $this->params = array_merge($params, $_GET);
 
    if (empty($this->controller)) $this->controller = ROUTER_DEFAULT_CONTROLLER;
    if (empty($this->action)) $this->action = ROUTER_DEFAULT_ACTION;
    if (empty($this->id)) $this->id = null;
 
    $w = explode('_', $this->controller);
    foreach($w as $k => $v) $w[$k] = ucfirst($v);
    $this->controller_name = implode('', $w);
  }
 
  public function execute() {
    foreach($this->routes as $route) {
      if ($route->is_matched) {
        $this->set_route($route);
        break;
      }
    }
  }
}
 
class Route {
  public $is_matched = false;
  public $params;
  public $url;
  private $conditions;
 
  function __construct($url, $request_uri, $target, $conditions) {
    $this->url = $url;
    $this->params = array();
    $this->conditions = $conditions;
    $p_names = array(); $p_values = array();
 
    preg_match_all('@:([\w]+)@', $url, $p_names, PREG_PATTERN_ORDER);
    $p_names = $p_names[0];
 
    $url_regex = preg_replace_callback('@:[\w]+@', array($this, 'regex_url'), $url);
    $url_regex .= '/?';
 
    if (preg_match('@^' . $url_regex . '$@', $request_uri, $p_values)) {
      array_shift($p_values);
      foreach($p_names as $index => $value) $this->params[substr($value,1)] = urldecode($p_values[$index]);
      foreach($target as $key => $value) $this->params[$key] = $value;
      $this->is_matched = true;
    }
 
    unset($p_names); unset($p_values);
  }
 
  function regex_url($matches) {
    $key = str_replace(':', '', $matches[0]);
    if (array_key_exists($key, $this->conditions)) {
      return '('.$this->conditions[$key].')';
    } 
    else {
      return '([a-zA-Z0-9_\+\-%]+)';
    }
  }
}

Setup example:

$r = new Router(); // create router instance 
 
$r->map('/', array('controller' => 'home')); // main page will call controller "Home" with method "index()"
$r->map('/login', array('controller' => 'auth', 'action' => 'login'));
$r->map('/logout', array('controller' => 'auth', 'action' => 'logout'));
$r->map('/signup', array('controller' => 'auth', 'action' => 'signup'));
$r->map('/profile/:action', array('controller' => 'profile')); // will call controller "Profile" with dynamic method ":action()"
$r->map('/users/:id', array('controller' => 'users'), array('id' => '[\d]{1,8}')); // define filters for the url parameters
 
$r->default_routes();
$r->execute();

Usage example:

$router = new Router();
// ... some configs ...
$controller = $router->controller; // will return name as it appears in url, ex: 'user_images'
$controller = $router->controller_name; // will return processed name of controller
// for example, if class name in url is 'user_images', then 'controller_name' var will be UserImages
$router->action;
$router->id; // if parameter :id presents
$router->params; // array(...)
$router->route_matched; // true - if route found, false - if not

Small and useful class.

Identify www bots and mobile devices

Posted by Dan Sosedoff on March 12, 2009

Need to identify web crawlers and mobile devices with your web-app ? Here is the list of couple regular expressions you can use (most common values):

Mobile Devices:

/(blackberry|motorokr|motorola|sony|windows ce|240x320|176x220|palm|mobile|iphone|ipod|symbian|nokia|samsung|midp)/i

Web Spyders:

/(google|yahoo|baidu|bot|webalta|archiver|crawler|spyder)/i