Blog Archives
Scaling Silex applications
In my humble opinion Silex is great. It’s perfect to create prototypes, but when our application grows up it turns into a mess. That was what I thought until the last month, when I attended to a great talk about Silex with Javier Eguiluz. OK. Scaling Silex it’s not the same than with a Symfony application, but it’s possible.
It’s pretty straightforward to create a Silex application with composer:
{
"require": {
"silex/silex": "1.0.*"
},
"minimum-stability": "dev"
}
But there’s a better way. We can use the Fabien Potencier’s skeleton. With this skeleton we can organize our code better.
We also can use classes as controllers instead of using a closure with all the code. Igor Wiedler has a great post about this. You can read it here.
Today I’m playing with Silex and I want to show you something. Let’s start:
Probably you know that I’m a big fan of Symfony’s Dependency Injection Container (you can read about it here and here), but Silex uses Pimple. In fact the Silex application extends Pimple Class. My idea is the following one:
In the Igor’s post we can see how to use things like that:
$app->match('/video/{id}', 'Gonzalo123\ApiController::indexAction')->method('GET')->bind('video_info');
My idea is to store this information within a Service Container (we will use Symfony’s DIC). For example here we can see our routes.yml:
routes:
video_info:
pattern: /video/{id}
controller: Gonzalo123\ApiController::initAction
requirements:
_method: GET
As we can see we need to implement one Extension for the alias “routes”. We only will implement the needed functions for YAML files in this example.
<?php
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class SilexRouteExtension implements ExtensionInterface
{
/**
* Loads a specific configuration.
*
* @param array $config An array of configuration values
* @param ContainerBuilder $container A ContainerBuilder instance
*
* @throws InvalidArgumentException When provided tag is not defined in this extension
*
* @api
*/
public function load(array $config, ContainerBuilder $container)
{
}
/**
* Returns the namespace to be used for this extension (XML namespace).
*
* @return string The XML namespace
*
* @api
*/
public function getNamespace()
{
}
/**
* Returns the base path for the XSD files.
*
* @return string The XSD base path
*
* @api
*/
public function getXsdValidationBasePath()
{
}
/**
* Returns the recommended alias to use in XML.
*
* This alias is also the mandatory prefix to use when using YAML.
*
* @return string The alias
*
* @api
*/
public function getAlias()
{
return "routes";
}
}
And now we only need to prepare the DIC. According to Fabien’s recommendation in his Silex skeleton, we only need to change the src/controllers.php
<?php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
// Set up container
$container = new ContainerBuilder();
$container->registerExtension(new SilexRouteExtension);
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../config/'));
// load configuration
$loader->load('routes.yml');
$app['container'] = $container;
$app->mount('/api', include 'controllers/myApp.php');
$container->compile();
$app->error(function (\Exception $e, $code) use ($app) {
if ($app['debug']) {
return;
}
$page = 404 == $code ? '404.html' : '500.html';
return new Response($app['twig']->render($page, array('code' => $code)), $code);
});
and now we define the config/routes.yml
routes:
video_info:
pattern: /video/{videoId}
controller: Gonzalo123\ApiController::initAction
requirements:
_method: GET
And finally the magic in our controllers/myApp.php:
<?php
$myApp = $app['controllers_factory'];
foreach ($container->getExtensionConfig('routes')[0] as $name => $route) {
$controller = $myApp->match($route['pattern'], $route['controller']);
$controller->method($route['requirements']['_method']);
$controller->bind($name);
}
return $myApp;
The class for this example is: src/Gonzalo123/ApiController.php
<?php
namespace Gonzalo123;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
class ApiController
{
public function initAction(Request $request, Application $app)
{
return new JsonResponse(array(1, 1, $request->get('id')));
}
}
As you can see the idea is to use classes as controllers, define them within the service container and build the silex needed code iterating over the configuration. What do you think?
How to rewrite urls with PHP 5.4′s built-in web server
PHP 5.4 comes with a flaming built-in web server. This server is (obviously) not suitable to use in production environments, but it’s great if we want to check one project quickly:
- git clone from github
- composer install to install dependencies
- run the built-in web server and test the application.
php -S localhost:8888 -t www/
But is very usual to use mod_rewrite or similar to send all requests to the front controller. With apache is pretty straight forward to do it:
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>
But, does it work with the built-in web server? The answer is yes, but with another syntax. We only need to create one router file and start our server with this router:
<?php
// www/routing.php
if (preg_match('/\.(?:png|jpg|jpeg|gif)$/', $_SERVER["REQUEST_URI"])) {
return false;
} else {
include __DIR__ . '/index.php';
}
And now we start the server with:
php -S localhost:8888 www/routing.php
Easy, isn’t it?
How to send the output of Symfony’s process Component to a node.js server in Real Time with Socket.io
Today another crazy idea. Do you know Symfony Process Component? The Process Component is a simple component that executes commands in sub-processes. I like to use it when I need to execute commands in the operating system. The documentation is pretty straightforward. Normally when I want to collect the output of the script (imagine we run those scripts within a crontab) I save the output in a log file and I can check it even in real time with tail -f command.
This approach works, but I want to do it in a browser (call me crazy
). I’ve written a couple of posts with something similar. What I want to do now? The idea is simple:
First I want to execute custom commands with process. Just follow the process documentation:
<?php
use Symfony\Component\Process\Process;
$process = new Process('ls -lsa');
$process->setTimeout(3600);
$process->run();
if (!$process->isSuccessful()) {
throw new \RuntimeException($process->getErrorOutput());
}
print $process->getOutput();
Process has one cool feature, we can give feedback in real-time by passing an anonymous function to the run() method:
<?php
use Symfony\Component\Process\Process;
$process = new Process('ls -lsa');
$process->run(function ($type, $buffer) {
if ('err' === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
});
The idea now is to use this callback to send a TCP socket to one server with node.js
var LOCAL_PORT = 5600;
var server = require('net').createServer(function (socket) {
socket.on('data', function (msg) {
console.log(msg);
});
}).listen(LOCAL_PORT);
server.on('listening', function () {
console.log("TCP server accepting connection on port: " + LOCAL_PORT);
});
Now we change our php script to
<?php
include __DIR__ . "/../vendor/autoload.php";
use Symfony\Component\Process\Process;
function runProcess($command)
{
$address = 'localhost';
$port = 5600;
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, $address, $port);
$process = new Process($command);
$process->setTimeout(3600);
$process->run(
function ($type, $buffer) use ($socket) {
if ('err' === $type) {
socket_write($socket, "ERROR\n", strlen("ERROR\n"));
socket_write($socket, $buffer, strlen($buffer));
} else {
socket_write($socket, $buffer, strlen($buffer));
}
}
);
if (!$process->isSuccessful()) {
throw new \RuntimeException($process->getErrorOutput());
}
socket_close($socket);
}
runProcess('ls -latr /');
Now with the node.js started, if we run the php script, we will see the output of the process command in the node’s terminal. But we want to show it in a browser. What can we do? Of course, socket.io. We change the node.js command to:
var LOCAL_PORT = 5600;
var SOKET_IO_PORT = 8000;
var ioClients = [];
var io = require('socket.io').listen(SOKET_IO_PORT);
var server = require('net').createServer(function (socket) {
socket.on('data', function (msg) {
ioClients.forEach(function (ioClient) {
ioClient.emit('log', msg.toString().trim());
});
});
}).listen(LOCAL_PORT);
io.sockets.on('connection', function (socketIo) {
ioClients.push(socketIo);
});
server.on('listening', function () {
console.log("TCP server accepting connection on port: " + LOCAL_PORT);
});
and finally we create a simple web client:
<script src="http://localhost:8000/socket.io/socket.io.js"></script>
<script>
var socket = io.connect('http://localhost:8000');
socket.on('log', function (data) {
console.log(data);
});
</script>
Now if we start one browser we will see the output of our command line process within the console tab of the browser.
If you want something like webConsole, I also have created the example, With a Web UI enabling to send custom commands. You can see it in github.
Obviously that’s only one experiment with a lot of security issues that we need to take into account if we want to use it in production. What do you think?
Dependency Injection Containers with PHP. When Pimple is not enough.
Two months ago I wrote an article about Dependency Injection with PHP and Pimple. After the post I was speaking about it with a group of colleagues and someone threw a question:
What happens if your container grows up? Does Pimple scale well?
The answer is not so easy. Pimple is really simple and good for small projects, but it becomes a little mess when we need to scale. Normally when I face a problem like that I like to checkout the Symfony2 code. It usually implements a good solution. There’s something I really like from Symfony2: it’s a brilliant component library and we can use those components within our projects instead of using the full stack framework. So why don’t we only use the Dependency Injection component from SF2 instead Pimple to solve the problem? Let’s start:
We are going to use composer to load our dependencies so we start writing our composer.json file. We want to use yaml files so we need to add “symfony/yaml” and “symfony/config” in addition to “symfony/dependency-injection”:
{
"require": {
"symfony/dependency-injection": "dev-master",
"symfony/yaml": "dev-master",
"symfony/config": "dev-master"
},
"autoload":{
"psr-0":{
"":"lib/"
}
},
}
Now we can run “composer install” command and we already have our vendors and our autolader properly set.
We are going to build exactly the same example than in the previous post:
<?php
class App
{
private $proxy;
public function __construct(Proxy $proxy)
{
echo "App::__construct\n";
$this->proxy = $proxy;
}
public function hello()
{
return $this->proxy->hello();
}
}
class Proxy
{
private $curl;
public function __construct(Curl $curl)
{
$this->curl = $curl;
}
public function hello()
{
echo "Proxy::__construct\n";
return $this->curl->doGet();
}
}
class Curl
{
public function doGet()
{
echo "Curl::doGet\n";
return "Hello";
}
}
Now we create our file “services.yml” describing our dependency injection container behaviour:
services:
app:
class: App
arguments: [@Proxy]
proxy:
class: Proxy
arguments: [@Curl]
curl:
class: Curl
and finally we can build the script:
<?php
include __DIR__ . "/vendor/autoload.php";
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services.yml');
$container->get('app')->hello();
IMHO is as simple a Pimple but much more flexible, customizable and it’s also well documented. For example we can split our yaml files into different ones and load them:
<?php
include __DIR__ . "/vendor/autoload.php";
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
$container = new ContainerBuilder();
$loader = new YamlFileLoader($container, new FileLocator(__DIR__));
$loader->load('services1.yml');
$loader->load('services2.yml');
$container->get('app')->hello();
An we also can use imports in our yaml files:
imports:
- { resource: services2.yml }
services:
app:
class: App
arguments: [@Proxy]
If you don’t like yaml syntax and you prefer XML (I know. It looks insane
) you can use it, or even programatically with PHP.
What do you think?
Source code in github (see the README to install the vendors)
Encode our PHP code into spaces and new line characters
According to one my last blog post with an exotic usage of dynamic includes with PHP we will keep on with something still more exotic. The idea is to create clean code. And what is cleaner than the blank?
We will encode our PHP code into spaces and new line characters (\n). The idea is simple. We will translate each character of our script into (n) spaces where (n) is the hexadecimal representation of it’s ASCII code.
function decodeText($encodedText) {
$out = array();
foreach (explode("\n", $encodedText) as $line) {
$lineOut = array();
foreach (explode("\t", $line) as $item) {
$lineOut[] = chr(strlen($item));
}
$out[] = implode(null, $lineOut);
}
return implode("\n", $out);
}
function encodeText($text) {
$out = array();
foreach (explode("\n", $text) as $line) {
$lineOut = array();
foreach (str_split($line) as $item) {
$lineOut[] = str_repeat(" ", ord($item));
}
$out[] = implode("\t", $lineOut);
}
return implode("\n", $out);
}
And now we will create two functions to include our files and parse them with PHP. One function to include from raw input and another to include files.
function includeFromFile($file)
{
$raw = file_get_contents($file);
includeFromRaw($raw);
}
function includeFromRaw($rawText)
{
$decodedCode = decodeText($rawText);
$tmpfname = tempnam("/tmp", "blank");
$handle = fopen($tmpfname, "w");
fwrite($handle, $decodedCode);
fclose($handle);
ob_start(function($buffer) use ($tmpfname, $decodedCode) {
return str_replace($tmpfname, "<pre>" . htmlentities($decodedCode). "</pre>", $buffer);
});
include $tmpfname;
ob_end_flush();
unlink($tmpfname);
}
Now we can use:
$text = encodeText('<?php echo "Gonzalo";' . "\n" . ' print_r(array(1,2,311)); ?>');
includeFromRaw($text);
Or if we save our code with the name “Blank.clean”
includeFromFile("Blank.clean");
Now if you are brave enough you can write code in our new blankAndSpaces programming language, instead of PHP
Building a simple SQL wrapper with PHP. Part 2.
In one of our last post we built a simple SQL wrapper with PHP. Now we are going to improve it a little bit. We area going to use a class Table instead of the table name. Why? Simple. We want to create triggers. OK we can create triggers directly in the database but sometimes our triggers need to perform operations outside the database, such as call a REST webservice, filesystem’s logs or things like that.
<?php
class Storage
{
static $count = 0;
static function init()
{
self::$count = 0;
}
static function increment()
{
self::$count++;
}
static function decrement()
{
self::$count--;
}
static function get()
{
return self::$count;
}
}
class SqlTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
$this->dbh = new Conn('pgsql:dbname=db;host=localhost', 'gonzalo', 'password');
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->dbh->forceRollback();
}
public function testInsertWithPostInsertShowingInsertedValues()
{
Storage::init();
$that = $this;
$this->dbh->transactional(function($dbh) use ($that) {
$sql = new Sql($that->dbh);
$users = new Table('users');
$users->postInsert(function($values) {Storage::increment();});
$that->assertEquals(0, Storage::get());
$actual = $sql->insert($users, array('uid' => 7, 'name' => 'Gonzalo', 'surname' => 'Ayuso'));
$that->assertTrue($actual);
$that->assertEquals(1, Storage::get());
});
}
public function testInsertWithPostInsert()
{
Storage::init();
$that = $this;
$this->dbh->transactional(function($dbh) use ($that) {
$sql = new Sql($that->dbh);
$users = new Table('users');
$users->postInsert(function() {Storage::increment();});
$that->assertEquals(0, Storage::get());
$actual = $sql->insert($users, array('uid' => 7, 'name' => 'Gonzalo', 'surname' => 'Ayuso'));
$that->assertTrue($actual);
$that->assertEquals(1, Storage::get());
});
}
public function testInsertWithPrePostInsert()
{
Storage::init();
$that = $this;
$this->dbh->transactional(function($dbh) use ($that) {
$sql = new Sql($that->dbh);
$users = new Table('users');
$users->preInsert(function() {Storage::increment();});
$users->postInsert(function() {Storage::decrement();});
$that->assertEquals(0, Storage::get());
$actual = $sql->insert($users, array('uid' => 7, 'name' => 'Gonzalo', 'surname' => 'Ayuso'));
$that->assertTrue($actual);
$that->assertEquals(0, Storage::get());
});
}
public function testUpdateWithPrePostInsert()
{
Storage::init();
$that = $this;
$this->dbh->transactional(function($dbh) use ($that) {
$sql = new Sql($that->dbh);
$users = new Table('users');
$users->preUpdate(function() {Storage::increment();});
$users->postUpdate(function() {Storage::increment();});
$that->assertEquals(0, Storage::get());
$actual = $sql->insert($users, array('uid' => 7, 'name' => 'Gonzalo', 'surname' => 'Ayuso'));
$that->assertTrue($actual);
$that->assertEquals(0, Storage::get());
$data = $sql->select('users', array('uid' => 7));
$that->assertEquals('Gonzalo', $data[0]['name']);
$actual = $sql->update($users, array('name' => 'gonzalo',), array('uid' => 7));
$that->assertTrue($actual);
$that->assertEquals(2, Storage::get());
$data = $sql->select('users', array('uid' => 7));
$that->assertEquals('gonzalo', $data[0]['name']);
});
}
public function testDeleteWithPrePostInsert()
{
Storage::init();
$that = $this;
$this->dbh->transactional(function($dbh) use ($that) {
$sql = new Sql($that->dbh);
$users = new Table('users');
$users->preDelete(function() {Storage::increment();});
$users->postDelete(function() {Storage::increment();});
$that->assertEquals(0, Storage::get());
$actual = $sql->insert($users, array('uid' => 7, 'name' => 'Gonzalo', 'surname' => 'Ayuso'));
$that->assertTrue($actual);
$that->assertEquals(0, Storage::get());
$actual = $sql->delete($users, array('uid' => 7));
$that->assertTrue($actual);
$that->assertEquals(2, Storage::get());
});
}
}
And here the whole library:
class Conn extends PDO
{
private $forcedRollback = false;
public function transactional(Closure $func)
{
$this->beginTransaction();
try {
$func($this);
$this->forcedRollback ? $this->rollback() : $this->commit();
} catch (Exception $e) {
$this->rollback();
throw $e;
}
}
public function forceRollback()
{
$this->forcedRollback = true;
}
}
class Table
{
private $tableName;
function __construct($tableName)
{
$this->tableName = $tableName;
}
private $cbkPostInsert;
private $cbkPostUpdate;
private $cbkPostDelete;
private $cbkPreInsert;
private $cbkPreUpdate;
private $cbkPreDelete;
public function getTableName()
{
return $this->tableName;
}
public function postInsert(Closure $func)
{
$this->cbkPostInsert = $func;
}
public function postUpdate(Closure $func)
{
$this->cbkPostUpdate = $func;
}
public function postDelete(Closure $func)
{
$this->cbkPostDelete = $func;
}
public function preInsert(Closure $func)
{
$this->cbkPreInsert = $func;
}
public function preUpdate(Closure $func)
{
$this->cbkPreUpdate = $func;
}
public function preDelete(Closure $func)
{
$this->cbkPreDelete = $func;
}
public function execPostInsert($values)
{
$func = $this->cbkPostInsert;
if ($this->cbkPostInsert instanceof Closure) $func($values);
}
public function execPostUpdate($values, $where)
{
$func = $this->cbkPostUpdate;
if ($func instanceof Closure) $func($values, $where);
}
public function execPostDelete($where)
{
$func = $this->cbkPostDelete;
if ($func instanceof Closure) $func($where);
}
public function execPreInsert($values)
{
$func = $this->cbkPreInsert;
if ($func instanceof Closure) $func($values);
}
public function execPreUpdate($values)
{
$func = $this->cbkPreUpdate;
if ($func instanceof Closure) $func($values);
}
public function execPreDelete($where)
{
$func = $this->cbkPreDelete;
if ($func instanceof Closure) $func($where);
}
}
class Sql
{
/** @var Conn */
private $dbh;
function __construct(Conn $dbh)
{
$this->dbh = $dbh;
}
public function select($table, $where)
{
$tableName = ($table instanceof Table) ? $table->getTableName() : $table;
$sql = $this->createSelect($tableName, $where);
$whereParams = $this->getWhereParameters($where);
$stmp = $this->dbh->prepare($sql);
$stmp->execute($whereParams);
return $stmp->fetchAll();
}
public function insert($table, $values)
{
$tableName = ($table instanceof Table) ? $table->getTableName() : $table;
$sql = $this->createInsert($tableName, $values);
if ($table instanceof Table) $table->execPreInsert($values);
$stmp = $this->dbh->prepare($sql);
$out = $stmp->execute($values);
if ($table instanceof Table) $table->execPostInsert($values);
return $out;
}
public function update($table, $values, $where)
{
$tableName = ($table instanceof Table) ? $table->getTableName() : $table;
$sql = $this->createUpdate($tableName, $values, $where);
$whereParams = $this->getWhereParameters($where);
if ($table instanceof Table) $table->execPreUpdate($values, $where);
$stmp = $this->dbh->prepare($sql);
$out = $stmp->execute(array_merge($values, $whereParams));
if ($table instanceof Table) $table->execPostUpdate($values, $where);
return $out;
}
public function delete($table, $where)
{
$tableName = ($table instanceof Table) ? $table->getTableName() : $table;
$sql = $this->createDelete($tableName, $where);
$whereParams = $this->getWhereParameters($where);
if ($table instanceof Table) $table->execPreDelete($where);
$stmp = $this->dbh->prepare($sql);
$out = $stmp->execute($whereParams);
if ($table instanceof Table) $table->execPostDelete($where);
return $out;
}
protected function getWhereParameters($where)
{
$whereParams = array();
foreach ($where as $key => $value) {
$whereParams[":W_{$key}"] = $value;
}
return $whereParams;
}
protected function createSelect($table, $where)
{
return "SELECT * FROM " . $table . $this->createSqlWhere($where);
}
protected function createUpdate($table, $values, $where)
{
$sqlValues = array();
foreach (array_keys($values) as $key) {
$sqlValues[] = "{$key} = :{$key}";
}
return "UPDATE {$table} SET " . implode(', ', $sqlValues) . $this->createSqlWhere($where);
}
protected function createInsert($table, $values)
{
$sqlValues = array();
foreach (array_keys($values) as $key) {
$sqlValues[] = ":{$key}";
}
return "INSERT INTO {$table} (" . implode(', ', array_keys($values)) . ") VALUES (" . implode(', ', $sqlValues) . ")";
}
protected function createDelete($table, $where)
{
return "DELETE FROM {$table}" . $this->createSqlWhere($where);
}
protected function createSqlWhere($where)
{
if (count((array) $where) == 0) return null;
$whereSql = array();
foreach ($where as $key => $value) {
$whereSql[] = "{$key} = :W_{$key}";
}
return ' WHERE ' . implode(' AND ', $whereSql);
}
}
Strange behavior in PHP with method visibility
Normally I feel very comfortable with PHP, but not all is good. There’s some things I don’t like. One is the lack of real annotations and another one is this rare behaviour with visibility within the OO. Let me explain this a little bit.
Last week I was refactoring one old script. I removed a coupling problem with DI. Something like this:
class AnotherClass
{
protected function foo()
{
return "bar";
}
}
class OneClass extends AnotherClass{
private $object;
public function __construct(AnotherClass $object)
{
$this->object = $object;
}
public function myFunction()
{
return $this->object->foo();
}
}
$anotherClass = new AnotherClass();
$oneClass = new OneClass($anotherClass);
echo $oneClass->myFunction();
It works, but I realized that I didn’t need to extend OneClass with AnotherClass (due to the DI), so I removed it. Then the script crashed:
Fatal error: Call to protected method AnotherClass::foo() from context ‘OneClass’
Obviously it was due to the protected function AnotherClass::foo. But, Why it worked when I extends OneClass with AnotherClass? The visibility is the same.
I reported this “bug” to the PHP community. PHP community is great. I had an answer very quick. It was not a bug. I needed to read several times the answer to understand it but finally did it.
As someone answer me:
foo() is protected and was defined in the context of OneClass. The access is done in the context of AnotherClass. AnotherClass is a subclass of OneClass (the context where foo() was defined). Therefore access is granted
In PHP the visibility belongs to the class not to the instance of the class. I understand the reason, but my mind compute it as a bug
and it isn’t. It’s a feature.
What do you think?
Database connection pooling with PHP and React (node.php)
Last saturday I meet a new hype: “React” also known as “node.php”. Basically it’s the same idea than node.js but built with PHP instead of javascript. Twitter was on fire with this new library (at least my timeline). The next sunday was a rainy day and because of that I spent the afternoon hacking a little bit with this new library. Basically I want to create a database connection pooling. It’s one of the things that I miss in PHP. I wrote a post here some time ago with this idea with one exotic experiment building one connection pooling using gearman. Today the idea is the same but now with React. Let’s start
First of all we install React. It’s a simple process using composer.
% echo '{ "require": { "react/react": "dev-master" } }' > composer.json
% composer install
Now we can start with our experiment. Imagine a simple query to PostgreSql using PDO:
CREATE TABLE users ( uid integer NOT NULL, name character varying(50), surname character varying(50), CONSTRAINT pk_users PRIMARY KEY (uid) ) WITH ( OIDS=FALSE ); ALTER TABLE users OWNER TO gonzalo; INSERT INTO users(uid, name, surname) VALUES (0, 'Gonzalo', 'Ayuso'); INSERT INTO users(uid, name, surname) VALUES (1, 'Hans', 'Solo'); INSERT INTO users(uid, name, surname) VALUES (2, 'Luke', 'Skywalker');
$dbh = new PDO('pgsql:dbname=demo;host=vmserver', 'gonzalo', 'password');
$sql = "SELECT * FROM USERS";
$stmt = $dbh->prepare($sql);
$stmt->execute();
$data = $stmt->fetchAll();
print_r($data);
Now we are going to use the same interface but instead of using PDO we will use one server with React:
include "CPool.php";
define('NODEPHP', '127.0.0.1:1337');
$dbh = new CPool();
$sql = "SELECT * FROM USERS";
$stmt = $dbh->prepare($sql);
$stmt->execute();
$data = $stmt->fetchAll();
$stmt->closeCursor();
print_r($data);
Our CPool library:
class CPoolStatement
{
private $stmt;
function __construct($sql=null)
{
if (!is_null($sql)) {
$url = "http://" . NODEPHP . "?" . http_build_query(array(
'action' => 'prepare',
'sql' => $sql
));
$this->stmt = file_get_contents($url);
}
}
public function getId()
{
return $this->stmt;
}
public function setId($id)
{
$this->stmt = $id;
}
public function execute($values=array())
{
$url = "http://" . NODEPHP . "?" . http_build_query(array(
'action' => 'execute',
'smtId' => $this->stmt,
'values' => $values
));
$this->stmt = file_get_contents($url);
}
public function fetchAll()
{
$url = "http://" . NODEPHP . "?" . http_build_query(array(
'action' => 'fetchAll',
'smtId' => $this->stmt
));
return (file_get_contents($url));
}
public function closeCursor()
{
$url = "http://" . NODEPHP . "?" . http_build_query(array(
'action' => 'closeCursor',
'smtId' => $this->stmt
));
return (file_get_contents($url));
}
}
class CPool
{
function prepare($sql)
{
return new CPoolStatement($sql);
}
}
We also can create one script that creates one statement
include "CPool.php";
define('NODEPHP', '127.0.0.1:1337');
$dbh = new CPool();
$sql = "SELECT * FROM USERS";
$stmt = $dbh->prepare($sql);
echo $stmt->getId();
And another script (another http request for example) to fetch the resultset. Notice that we can execute this script all the times that we want because the compiled statement persists in the node.php server (we don’t need to create it again and again within each request).
include "CPool.php";
define('NODEPHP', '127.0.0.1:1337');
$stmt = new CPoolStatement();
$stmt->setId(1);
$stmt->execute();
$data = $stmt->fetchAll();
print_r($data);
And basically that was my sunday afternoon experiment. As you can imagine the library is totally unstable. It’s only one experiment. We can add transaccions, comits, rollbacks, savepoints, … but I needed a walk and I stopped:). What do you think?
The code is available at github
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 0.19249302864075 (without xdebug)
without exceptions
memory: 10.418941497803
microtime: 0.14752888679505 0.1234929561615
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?
























