Checking the performance of PHP exceptions
Sometimes we use exceptions to manage the flow of our scripts. I imagine that the use of exceptions must have a performance lack. Because of that I will perform a small benchmark to test the performance of one simple script throwing exceptions and without them. Let’s start:
First a silly script to find even numbers (please don’t use it it’s only for the benchmanrk
)
error_reporting(-1);
$time = microtime(TRUE);
$mem = memory_get_usage();
$even = $odd = array();
foreach (range(1, 100000) as $i) {
try {
if ($i % 2 == 0) {
throw new Exception("even number");
} else {
$odd[] = $i;
}
} catch (Exception $e) {
$even[] = $i;
}
}
echo "odds: " . count($odd) . ", evens " . count($even);
print_r(array('memory' => (memory_get_usage() - $mem) / (1024 * 1024), 'microtime' => microtime(TRUE) - $time));
And now the same script without exceptions.
error_reporting(-1);
$time = microtime(TRUE);
$mem = memory_get_usage();
$even = $odd = array();
foreach (range(1, 100000) as $i) {
if ($i % 2 == 0) {
$even[] = $i;
} else {
$odd[] = $i;
}
}
echo "odd: " . count($odd) . ", evens " . count($even);
print_r(array('memory' => (memory_get_usage() - $mem) / (1024 * 1024), 'microtime' => microtime(TRUE) - $time));
The outcomes:
with exceptions
memory: 10.420181274414
microtime: 1.1479668617249
without exceptions
memory: 10.418941497803
microtime: 0.14752888679505
As we can see the use of memory is almost the same and ten times faster without exceptions.
I have done this test using a VM box with 512MB of memory and PHP 5.3.
Now we are going to do the same test with a similar host. The same configuration but PHP 5.4 instead of 5.3
PHP 5.4:
with exceptions
memory: 7.367259979248
microtime: 0.1864490332
without exceptions
memory: 7.3669052124023
microtime: 0.089046955108643
I’m really impressed with the outcomes. The use of memory here with PHP 5.4 is much better now and the execution time better too (ten times faster).
According to the outcomes my conclusion is that the use of exceptions in the flow of our scripts is not as bad as I supposed. OK in this example the use of exceptions is not a good idea, but in another complex script exceptions are really useful. We also must take into account the tests are iterations over 100000 to maximize the differences. So I will keep on using exceptions. This kind of micro-optimization not seems to be really useful. What do you think?
Working with clouds. Multi-master file-system replication with CouchDB
When we want to work with a cloud/cluster one of the most common problems is the file-system. It’s mandatory to be able to scale horizontally (scale out). We need to share the same file-system between our nodes. We can do it with a file server (samba for example), but this solution inserts a huge bottleneck into our application. There’s different distributed filesystems such as Apache Hadoop (inspired by Google’s MapReduce and Google File System). In this post we’re going to build a really simple distributed storage system based on NoSql. Let’s start.
NoSql (aka one of our last hypes) databases normally allow to store large files. MongoDB for example has GridFS, But in this post we’re going to do it using CouchDB. With CouchDB we can attach documents within our database as simple attachments, just like email.
The api of CouchDB to upload an attachment is very simple. It’s a pure REST api.
In order to create the http connections directly with curl commands we can use libraries to automate this process. For example we can use a simple library shown in a previous post. If we inspect the code we will see that we’re creating a PUT request to store the file in our couchDB database.
Another cool thing we can do with PHP is to create a stream-wrapper to use standard filesystem functions for read/write/delete we can see a post about this here.
As we can see is very easy to use couchdb as filesystem. but we also can replicate our couchDB databases. Imagine that we have tho couchDB servers (host1 and host2). Each host has one database called fs. If we run the following command:
curl -X POST -H "Content-Type: application/json" http://host1:5984/_replicate -d '{"source":"cmr","target":"http://host2:5984/fs","continuous":true}'
Our database will be replicated from host1 to host2 in a continuous mode. That’s means everytime we create/delete anything in host1, couchDB will replicate it to host2. A simple master-slave replica.
Now if we execute the same command in host2:
curl -X POST -H "Content-Type: application/json" http://host2:5984/_replicate -d '{"source":"cmr","target":"http://host1:5984/fs","continuous":true}'
We have a multi-master replica system, cheap and easy to implement. As we can see we only need to install couchDB in each node, activate the replica and that’s all. Pretty straightforward, isn’t it?
Coding Katas, TDD and Katayunos
2011 is about to finish, and I want to speak about my way through the world of TDD. In the beginning of the year appeared a new cool project called 12meses12katas (12 months – 12 katas). The aim of the project was to propose one coding kata per month, and allow to the people to publish their implementation of the kata over github. In the line of this project a crew of crazy coders started another cool project called Katayunos. Katayunos is a small pun with the word Desayuno (Breakfast) and Coding Kata. It’s something similar than a code retreat. The purpose of the katayunos was to meet together in one place one saturday morning, have a breakfast and play with pair programming and TDD with one coding kata. Something too geek to explain to non-geek people, I known, but if you are reading this, It’s probable that your understand this
. Our first katayuno was in the cafeteria of one Hotel. One cold Saturday morning (a really cold one believe me). The main problem was that we didn’t have any electrical plug, so our pomodoros were marked by the laptop batteries.
After this cold first katayuno we achieve new places with wifi and those kind of luxuries. Those kind of events are really interesting because we meet together different people and different programming languages. I still remember when I played with c# one time (I still have the sulfur smell in my fingers
).
Basically the idea is:
- Choose a coding kata
- Pomodoros of 25 minutes
- Pair programming (change the couples with each iteration)
- Have fun
And now I’m going to show my 12 implementations of the 12 katas from 12meses12katas’s project:
Jaunary: String-Calculator
A good kata for beginners. That’s a good one because the testing strategy is very clear and we can focus on Testing: Red (test NOK) -> implement code -> Green (test OK).
My solution: php + phpunit
February: Roman-Numerals
I really hate roman numerals after doing this kata ![]()
My solution: python + pyunit
March: FizzBuzz
A famous kata. A Really simple one. Because of that we can focus on the TDD (baby steps, refactoring, …)
My solution: php + phpunit
I also wrote a post with a crazy dynamic classes and the implementation of FizzBuzz kata with those dinamic classes:
April: Bowling
My problem was that I didn’t know the rules of Bowling so I needed to learn how to play Bowling first.
My solution: php + phpunit
May: KataLonja
A strange kata. I like it because it’s not a theoretical one. It’s close to a real problem.
My solution: php + phpunit
June: Simple-Lists
It was my first kata with javascript and node.js.
My solution: node.js + nodeunit (with John Resig’s class implementation)
July: Prime-Factors
Another famous one: Prime factors.
My solution: node.js + nodeunit
August: Minesweeper
An implementation of the famous computer game Minesweeper.
My solution: php + phpunit
October: Karate-Chop
Dual solution. With php and JavaScript (with node.js)
My solution: php + phpunit and node.js + nodeunit
November: KataPotter
This time I will use coffeescript instead of pure js
My solution: node.js + coffeeScript + jasmine
December: PomodoroKata
CoffeeScript again. One pomodoro is one pomodoro. Let’s build a pomodoro timer in a pomodoro.
My solution: node.js + coffeeScript + jasmine
So, what are you waiting for to create a local group and clone the katayuno in your city? It’s very easy: broadcast the event over twitter, pick a place, pick a kata and enjoy.
Regards, Gonzalo
Playing with the new PHP5.4 features
PHP5.4 it’s close and it’s time to start playing with the new cool features. I’ve created a new Virtual Machine to play with the new features available within PHP5.4. I wrote a post with the most exciting features (at least for me) when I saw the feature list in the alpha version. Now the Release Candidate is with us, so it’s the time of start playing with them. I also discover really cool features that I pass over in my first review
Let’s start:
Class member access on instantiation.
Cool!
class Human
{
function __construct($name)
{
$this->name = $name;
}
public function hello()
{
return "Hi " . $this->name;
}
}
// old style
$human = new Human("Gonzalo");
echo $human->hello();
// new cool style
echo (new Human("Gonzalo"))->hello();
Short array syntax
Yeah!
$a = [1, 2, 3]; print_r($a);
Support for Class::{expr}() syntax
foreach ([new Human("Gonzalo"), new Human("Peter")] as $human) {
echo $human->{'hello'}();
}
Indirect method call through array
$f = [new Human("Gonzalo"), 'hello'];
echo $f();
Callable typehint
function hi(callable $f) {
$f();
}
hi([new Human("Gonzalo"), 'hello']);
Traits
trait FlyMutant {
public function fly() {
return 'I can fly!';
}
}
class Mutant extends Human {
use FlyMutant;
}
$mutant = new Mutant("Storm");
echo $mutant->fly();
Array dereferencing support
function data() {
return ['name' => 'Gonzalo', 'surname' => 'Ayuso'];
}
echo data()['name'];
IDEs don’t support (yet) those features. That’s means IDEs will mark those new features as syntax errors but I hope that they will support them soon.
More info here
Working with Request objects in PHP (II). Back to the past
In one of my last post “Working with request objects in PHP”, I wrote a simple library to handle request objects. According that post let’s do a bit of history of PHP. In the early years of PHP (PHP3 – PHP4) one of the cool features of PHP was the “variable injection” inside our projects with register_globals=on. If you had the following a url:
index.php?parameter1=Hi
Your script had magically a variable called $parameter1 with the value “Hi”. This feature has horrible security problems, if our user can inject variables in our scripts, especially with a loose typing program language like PHP. Because of that we all swap from those injected variables to get the value from $_POST and $_GET superglobals. In fact “injected variables” are disabled long time ago within PHP configuration.
Nowadays we don’t use $_POST $_GET superglobals directly. We need to filter the input. Because of that I wrote RequestObject library. Now we’re going to back to the past and allow the use of injected variables, but filtered.
RequestObject has now an extra public function called getFilteredParameters. This function returns an array with all already filtered input parameters. So if we use “extract” function we can create variables for each input parameters, but with the filtered values:
class Request extends RequestObject
{
/** @cast string */
public $param1;
/**
* @cast string
* @default default value
*/
public $param2;
}
$request = new Request();
extract($request->getFilteredParameters());
echo "param1: <br/>";
var_dump($param1);
echo "<br/>";
echo "param2: <br/>";
var_dump($param2);
echo "<br/>";
Full library available on github here
Talk about node.js and WebSockets
Last friday I spoke about node.js and Websockets with the people of The Mêlée. The talk was an introduction to node.js and focused in the new HTML5 feature: the WebSockets.
When I spoke about Websockets I also introduced the great library socket.io. The jQuery of WebSockets.
Working with Request objects in PHP
Normally when we work with web applications we need to handle Request objects. Requests are the input of our applications. According to the golden rule of security:
Filter Input-Escape Output
We cannot use $_GET and $_POST superglobals. OK we can use then but we shouldn’t use them. Normally web frameworks do this work for us, but not all is a framework.
Recently I have worked in a small project without any framework. In this case I also need to handle Request objects. Because of that I have built this small library. Let me show it.
Basically the idea is the following one. I want to filter my inputs, and I don’t want to remember the whole name of every input variables. I want to define the Request object once and use it everywhere. Imagine a small application with a simple input called param1. The URL will be:
test1.php?param1=11212
and we want to build this simple script:
echo "param1: " . $_GET['param1'] . '<p/>';
The problem with this script is that we aren’t filtering input. And we also need to remember the parameter name is param1. If we need to use param1 parameter in another place we need to remember its name is param1 and not Param1 or para1. It can be obvious but it’s easy to make mistakes.
My proposal is the following one. I create a simple PHP class called Request1 extending RequestObject object:
Example 1: simple example
class Request1 extends RequestObject
{
public $param1;
}
Now if we create an instance of Request1, we can use the following code:
$request = new Request1(); echo "param1: " . $request->param1 . '<p/>';
I’m not going to explain the magic now, but with this simple script we will filter the input to the default type (string) and we will get the following outcomes:
test1.php?param1=11212
param1: 11212
test1.php?param1=hi
param1: hi
Maybe is hard to explain with words but with examples it’s more easy to show you what I want:
Example 2: data types and default values
class Request2 extends RequestObject
{
/**
* @cast string
*/
public $param1;
/**
* @cast string
* @default default value
*/
public $param2;
}
$request = new Request2();
echo "param1: <br/>";
var_dump($request->param1);
echo "<br/>";
echo "param2: <br/>";
var_dump($request->param2);
echo "<br/>";
Now we are will filter param1 parameter to string and param2 to string to but we will assign a default variable to the parameter if we don’t have a user input.
test2.php?param1=hi¶m2=1
param1: string(2) "hi"
param2: string(1) "1"
test2.php?param1=1¶m2=hi
param1: string(1) "1"
param2: string(2) "hi"
test2.php?param1=1
param1: string(1) "1"
param2: string(13) "default value"
Example 3: validadors
class Request3 extends RequestObject
{
/** @cast string */
public $param1;
/** @cast integer */
public $param2;
protected function validate_param1(&$value)
{
$value = strrev($value);
}
protected function validate_param2($value)
{
if ($value == 1) {
return false;
}
}
}
try {
$request = new Request3();
echo "param1: <br/>";
var_dump($request->param1);
echo "<br/>";
echo "param2: <br/>";
var_dump($request->param2);
echo "<br/>";
} catch (RequestObjectException $e) {
echo $e->getMessage();
echo "<br/>";
var_dump($e->getValidationErrors());
}
Now a complex example. param1 is a string and param2 is an integer, but we also will validate them. We will alter the param1 value (a simple strrev) and we also will raise an exception if param2 is equal to 1
test3.php?param2=2¶m1=hi
param1: string(2) "ih"
param2: int(2)
test3.php?param1=hola¶m2=1
Validation error
array(1) { ["param2"]=> array(1) { ["value"]=> int(1) } }
Example 4: Dynamic validations
class Request4 extends RequestObject
{
/** @cast string */
public $param1;
/** @cast integer */
public $param2;
}
$request = new Request4(false); // disables perform validation on contructor
// it means it will not raise any validation exception
$request->appendValidateTo('param2', function($value) {
if ($value == 1) {
return false;
}
});
try {
$request->validateAll(); // now we perform the validation
echo "param1: <br/>";
var_dump($request->param1);
echo "<br/>";
echo "param2: <br/>";
var_dump($request->param2);
echo "<br/>";
} catch (RequestObjectException $e) {
echo $e->getMessage();
echo "<br/>";
var_dump($e->getValidationErrors());
}
More complex example. Param1 will be cast as string and param2 as integer again, same validation to param2 (exception if value equals to 1), but now validation rule won’t be set in the definition of the class. We will append dynamically after the instantiation of the class.
test4.php?param1=hi¶m2=2
param1: string(4) "hi"
param2: int(2)
test4.php?param1=hola¶m2=1
Validation error
array(1) { ["param2"]=> array(1) { ["value"]=> int(1) } }
Example 5: Arrays and default params
class Request5 extends RequestObject
{
/** @cast arrayString */
public $param1;
/** @cast integer */
public $param2;
/**
* @cast arrayString
* @defaultArray "hello", "world"
*/
public $param3;
protected function validate_param2(&$value)
{
$value++;
}
}
$request = new Request5();
echo "<p>param1: </p>";
var_dump($request->param1);
echo "<p>param2: </p>";
var_dump($request->param2);
echo "<p>param3: </p>";
var_dump($request->param3);
Now a simple example but input parameters allow arrays and default values.
test5.php?param1[]=1¶m1[]=2¶m2[]=hi
param1: array(2) { [0]=> int(1) [1]=> int(2) }
param2: int(1)
param3: array(2) { [0]=> string(5) "hello" [1]=> string(5) "world" }
test5.php?param1[]=1¶m1[]=2¶m2=2
param1: array(2) { [0]=> string(1) "1" [1]=> string(1) "2" }
param2: int(3)
param3: array(2) { [0]=> string(5) "hello" [1]=> string(5) "world" }
RequestObject
The idea of RequestObject class is very simple. When we create an instance of the class (in the constructor) we filter the input request (GET or POST depending on REQUEST_METHOD) with filter_var_array and filter_var functions according to the rules defined as annotations in the RequestObject class. Then we populate the member variables of the class with the filtered input. Now we can use to the member variables, and auto-completion will work perfectly with our favourite IDE with the parameter name. OK. I now. I violate encapsulation principle allowing to access directly to the public member variables. But IMHO the final result is more clear than creating an accessor here. But if it creeps someone out, we would discuss another solution
.
Full code here on github
What do you think?
Display errors on screen even with display errors = off with PHP
Last month I was in a coding kata session playing with StingCalculator, and between iteration and iteration we were taking about the problems of shared hostings. Shared hosting are cheap, but normally they don’t allow us the use some kind of features. For example we cannot see the error log. That’s a problem when we need to see what happens within our application. Normally I work with my own servers, and I have got full access to error logs. But if we cannot see the error log and the server is configured with display errors = off in php.ini (typical configuration in shared hosting), we have a problem.
One of my post came to my mind “Real time monitoring PHP applications with websockets and node.js”. Basically we can solve the problem using this technique, but if we are speaking about shared hosting without error log access, it’s very probably that we cannot install a node.js server or even use sockets functions. So it’s not “Real” solution to our problem.
The idea is create a variation of the original script (one kind of script’s Spin-off
). In this scprit we will capture errors and exceptions and show them in script at the end of the script. We don’t have access to the error log but we will show it in the browser.
Imagine the following script:
$a = 1/0;
throw new Exception("myException");
echo "hi";
It will show one warning (1/0) and one exception (myException)
- Warning: Division by zero
- Fatal error: Uncaught exception ‘Exception’ with message ‘myException’
If we change php.ini show errors = off we will see a nice white screen.
The idea is add to the script:
include('ErrorSniffer.php');
ErrorSniffer::factory('127.0.0.1');
$a = 1/0;
throw new Exception("myException");
echo "hi";
Now with with ErrorSniffer library we will see a nice output, even with display errors = off.
I also add a IP in the class constructor to restrict the output message to one IP. The idea is to place this script at production, so we don’t want to expose error messages to whole users.
If we see the source code of ErrorSniffer class we a constructor like this:
public function __construct($restingToIp)
{
if ($this->getip() == $restingToIp) {
self::register_exceptionHandler($this);
self::set_error_handler($this);
self::register_shutdown_function($this);
}
}
private static function set_error_handler(ErrorSniffer &$that)
{
set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$that) {
$type = ErrorSniffer::getErrorName($errno);
$that->registerError(array('type' => $type, 'message' => $errstr, 'file' => $errfile, 'line' => $errline));
return false;
});
}
private static function register_exceptionHandler(ErrorSniffer &$that)
{
set_exception_handler(function($exception) use (&$that) {
$exceptionName = get_class($exception);
$message = $exception->getMessage();
$file = $exception->getFile();
$line = $exception->getLine();
$trace = $exception->getTrace();
$that->registerError(array('type' => 'EXCEPTION', 'exception' => $exceptionName, 'message' => $message, 'file' => $file, 'line' => $line, 'trace' => $trace));
return false;
});
}
private static function register_shutdown_function(ErrorSniffer &$that)
{
register_shutdown_function(function() use (&$that) {
$error = error_get_last();
if ($error['type'] == E_ERROR) {
$type = ErrorSniffer::getErrorName($error['type']);
$that->registerError(array('type' => $type, 'message' => $error['message'], 'file' => $error['file'], 'line' => $error['line']));
}
$that->printErrors();
});
}
As we can see we will catch errors and exceptions, we populate the member variable $errors and we also use register_shutdown_function to show information on shutdown.
You can see the full script at github here.
What do you think?
Building a small microframework with PHP (Part 2). Command line interface
In my last post we spoke about building a small microframework with PHP. The main goal of this kind of framework was to be able to map urls to plain PHP classes and become those classes easily testeable with PHPUnit. Now we’re going to take a step forward. Sometimes we need to execute the script from command line, for example if we need to use them in a crontab file. OK. We can use curl and perform a simple http call to the webserver. But it’s pretty straightforward to create a command line interface (CLI) for our microframework. Zend Framework and Symfony has a great console tools. I’ve used Zend_Console_Getopt in a project and is really easy to use and well documented. But now we’re going to build a command line interface from scratch.
We are going to reuse a lot of code from the earlier post, because of that we are going to encapsulate the code in a class (DRY).
We will use the getopt function from PHP’s function set.
$sortOptions = ""; $sortOptions .= "c:"; $sortOptions .= "f:"; $sortOptions .= "v::"; $sortOptions .= "h::"; $options = getopt($sortOptions);
Then we need to take the paramaters from $GLOBALS['argv'] superglobal according with the options. I use the following hack to prepare $GLOBALS['argv']:
foreach( $options_array as $o => $a ) {
while($k=array_search("-". $o. $a, $GLOBALS['argv'])) {
if($k) {
unset($GLOBALS['argv'][$k]);
}
}
while($k=array_search("-" . $o, $GLOBALS['argv'])) {
if($k) {
unset($GLOBALS['argv'][$k]);
unset($GLOBALS['argv'][$k+1]);
}
}
}
$GLOBALS['argv'] = array_merge($GLOBALS['argv']);
And now we get the params into $param_arr array.
$param_arr = array();
$lenght = count((array)$GLOBALS['argv']);
if ($lenght > 0) {
for ($i = 1; $i < $lenght; $i++) {
if (isset($GLOBALS['argv'][$i])) {
list($paramName, $paramValue) = explode("=", $GLOBALS['argv'][$i], 2);
$param_arr[$paramName] = $paramValue;
}
}
}
Now we can get className and functionName:
$className = !array_key_exists('c', $options) ?: $options['c'];
$functionName = !array_key_exists('f', $options) ?: $options['f'];
We add a “usage” parameter:
if (array_key_exists('h', $options)) {
$usage = <<<USAGE
Usage: cli [options] [-c] <class> [-f] <function> [args...]
Options:
-h Print this help
-v verbose mode
\n
USAGE;
echo $usage;
exit;
}
Now we can invoke the function with call_user_func_array
return call_user_func_array(array($className, $functionName), $this->realParams);
As you can see, instead of using $param_arr as parameters array, We need to create an extra $realParams array. The aim of this realParams arrays is to use call_user_func_array with named parameters. In getRealParams function we use reflection to see what are the real parameters of our function and use only those parameters form in the correct order instead. With this trick we will allow to the user to use the parameters in the his desired order, and without forcing to use the real order of our function.
private function getRealParams($params)
{
$realParams = array();
$class = new ReflectionClass(new $this->className);
$reflect = $class->getMethod($this->functionName);
foreach ($reflect->getParameters() as $i => $param) {
$pname = $param->getName();
if ($param->isPassedByReference()) {
/// @todo shall we raise some warning?
}
if (array_key_exists($pname, $params)) {
$realParams[] = $params[$pname];
} else if ($param->isDefaultValueAvailable()) {
$realParams[] = $param->getDefaultValue();
} else {
throw new Exception("{$this->className}::{$this->functionName}() param: missing param: {$pname}");
}
}
return $realParams;
}
And now we can use our microframework from the command line:
./cli -c 'Demo\Foo' -f hello Hello ./cli -c Demo\\Foo -f getUsers ["Gonzalo","Peter"] ./cli -c Demo\\Foo -f helloName name=Gonzalo surname=Ayuso Hello Gonzalo Ayuso
Full code on github
Building a small microframework with PHP
Nowadays microframewors are very popular. Since Blake Mizerany created Sinatra (Ruby), we have a lot of Sinatra clones in PHP world. Probably the most famous (and a really good one) is Silex. But we also have several ones, such as Limonade, GluePHP and Slim. Those frameworks are similar. We define routes and we connect those routes to callbacks:
For example:
‘/hello/{name}’ => function ($name) {return “Hello $name”;}
Those microframeworks use the new PHP 5.3 callback features. It’s easy to build prototypes with those frameworks. I’ve used silex for a small prototype and I’m really happy with it. But I have a little problem. Each time I need to create a new route I need to create the route and create the callback. The business logic is inside the callback, but I need to tell to the framework where is it with the router. That’s means code de callback and write the router. This way of work has advantages. You can change the routes without changing the business logic. I feel comfortable with it in small projects, but when it scales it’s difficult to manage (at least for me). Symfony2 has a great way to create routes, with inheritance, catching and things like that, but sometimes I dont’t feel confortable with it. Because of that I have done this small microframework experiment. The idea is drop the router and create it depending on the filesystem. The first idea was inside this postit:
Yes I now. If I want to change the class inside the filesystem, I need to change the urls. As an experiment, I’ve created a small microframework. The idea the following one:
- .htaccess to redirect every request to index.php
- a set of classes (plain php classes)
- index.php will invoke the required class’ function
- after invoking the required function index.php will convert the output to the format selected
Basically index.php will follow the following script:
setUpAutoload(); list($className, $functionName, $format, $params) = decodeUri(getUri()); $realParams = getRealParams($className, $functionName, $params); echo format($format, call_user_func_array(array($className, $functionName), $realParams));
Here you have the full index.php code:
function call_user_func_named($className, $obj, $function, $params)
{
$class = new ReflectionClass($obj);
$reflect = $class->getMethod($function);
$realParams = array();
foreach ($reflect->getParameters() as $i => $param) {
$pname = $param->getName();
if ($param->isPassedByReference()) {
/// @todo shall we raise some warning?
}
if (array_key_exists($pname, $params)) {
$realParams[] = $params[$pname];
} else if ($param->isDefaultValueAvailable()) {
$realParams[] = $param->getDefaultValue();
} else {
throw new Exception("{$className}::{$function}() param: missing param: {$pname}");
}
}
return call_user_func_array(array(new $obj, $function), $realParams);
}
function decodeUri($uri)
{
$conf = $params = array();
$functionName = $format = null;
$parsedUrl = parse_url($uri);
$path = $parsedUrl['path'];
if (isset($parsedUrl['query'])) {
$query = $parsedUrl['query'];
$params = array();
$pairs = explode('&', $query);
foreach ($pairs as $pair) {
if (trim($pair) == '') {
continue;
}
list($key, $value) = explode('=', $pair);
$params[$key] = urldecode($value);
}
}
$arr = explode('/', $path);
for ($i = 0; $i < count($arr); $i++) {
$elem = $arr[$i];
if (strpos($elem, '.') !== false) {
list($functionName, $format) = explode(".", $elem);
continue;
} else {
if ($elem != '') $conf[] = ucfirst($elem);
}
}
$className = implode('\\', $conf);
return array($className, $functionName, $format, $params);
}
function format($format, $out)
{
switch ($format) {
case 'json':
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Content-type: application/json');
return json_encode($out);
case 'html':
case 'htm':
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Content-type: Content-Type: text/html');
return (string)$out;
case 'txt':
case 'ajax':
header('Content-type: text/plain; charset=utf-8');
return (string)$out;
case 'css':
header('Content-type: text/css');
return (string)$out;
case 'js':
header('Content-type: application/javascript');
return (string)$out;
case 'jsonp':
$cbk = filter_input(INPUT_GET, '_cbk', FILTER_SANITIZE_STRING);
if ($cbk == '') {
$cbk = 'cbk';
}
header('Content-type: text/javascript; charset=utf-8');
return "{$cbk}(" . json_encode($out) . ");";
default:
throw new Exception("Undefined format");
}
}
function getRealParams($className, $functionName, $params)
{
$realParams = array();
$class = new \ReflectionClass(new $className);
$reflect = $class->getMethod($functionName);
foreach ($reflect->getParameters() as $i => $param) {
$pname = $param->getName();
if ($param->isPassedByReference()) {
/// @todo shall we raise some warning?
}
if (array_key_exists($pname, $params)) {
$realParams[] = $params[$pname];
} else if ($param->isDefaultValueAvailable()) {
$realParams[] = $param->getDefaultValue();
} else {
throw new Exception("{$className}::{$functionName}() param: missing param: {$pname}");
}
}
return $realParams;
}
function setUpAutoload()
{
spl_autoload_register(function ($class)
{
$class = str_replace('\\', '/', $class) . '.php';
if (is_file($class)) {
require_once($class);
} else {
throw new Exception("{$class} does not exists");
}
}
);
}
function getUri()
{
$requestUri = $_SERVER['REQUEST_URI'];
$scriptName = $_SERVER['SCRIPT_NAME'];
if (dirname($scriptName) == '/') {
$uri = $requestUri;
return $uri;
} else {
$uri = str_replace(dirname($scriptName), null, $requestUri);
return $uri;
}
}
An example of the plain php classes with the business logic:
namespace Demo;
class Foo
{
public function hello()
{
return "Hello";
}
public function helloName($name)
{
return "Hello " . $name;
}
public function getUsers()
{
return array('Gonzalo', 'Peter', );
}
}
This class is easily testable with PHPUnit.
Normally I use this kind of framework to get json and jsonp. It’s pretty straightforward to get json:
http://localhost/demo/foo/getUsers.json
["Gonzalo","Peter"]http://localhost/demo/foo/getUsers.jsonp
cbk(["Gonzalo","Peter"]);
This is a very small approach of what I have in mind, coded with a few lines to show the concept. I’m working in some more advanced features such as twig integration, security, and things like that. The idea is combine the plain PHP classes with annotations (with Addendum). Imagine a class like that:
Here @Render() annotation tells to the framework to use a twig template.
// http://localhost/tdl/index.html
class Tdl
{
/**
* @Render()
* @return array
*/
public function index()
{
return array(
'title' => 'Simple ToDo List',
'description' => 'Simple ToDo List',
'author' => 'Gonzalo',
);
}
}
And here will output a javascript file using assetic library
// http://localhost/tdl/js/js.js
namespace Tdl;
use Assetic\Asset\AssetCollection;
use Assetic\Asset\FileAsset;
use Assetic\Filter\FilterInterface;
class Js
{
public function js()
{
$js = new AssetCollection(array(
new FileAsset(MVC_PATH . '/V/Tdl/Js/nf.js'),
new FileAsset(MVC_PATH . '/V/Tdl/Js/main.js'),
));
return $js->dump();
}
}
Sometimes I think Symfony2 has all this features and developing this framework is a waste of time. Probably, but it’s cool to code it. Also it’s very easy and fast for me build prototypes. What do you think?
Initial commit here (the code used in this post).
Full code on github












