Pixelastic

You can cut our wings but we will always remember what it was like to fly.

Posts tagged with "PHP"

PHP AND / OR differences with && / ||

Just found this weird little behavior of PHP today :

$a = false;
$b = true;
$c = $a OR $b;
$d = ($a OR $b);
var_dump(compact('a', 'b', 'c', 'd'));
array(4) {
["a"]=>    bool(false)
["b"]=>    bool(true)
["c"]=>    bool(false)
["d"]=>    bool(true)
}

$a OR $b in fact returns $a, while ($a OR $b) returns the result of the parenthesis. On the other hand, $a || $b correctly returns the result of the expression.

It seems that AND and OR are weak compared to && and ||.

Be aware of that, or it might backfire on you. Or just discard all AND / OR in favor of && / ||

Weird PHP behavior when casting as an object

I've just stumbled upon that weird behavior when force casting the return of a function to an object. The PHP result was not what I expected.

$a = null;
echo empty($a) ? "Yes, I'm empty" : "You should not see this"

This is pretty straighforward code. Now, we test it with an object.

$a = (object) null;
echo empty($a) ? "Yes, I'm empty" : "You should not see this but, actually, you do."

Note that it even gets weirder when (object) false and (object) true become objects with a key scalar, set to false

This is one of the little things that make me want to ditch PHP for a better language.

Fix the floating issue with json_decode in PHP 5.3

When dealing with online API that are handling a lot of items (like Twitter or Facebook), you'd better be aware of a PHP limitation with json_decode.

json_decode is supposed to take a JSON string and return a PHP array or a PHP object from it.

Unfortunatly if one of the key is an int and is bigger than the max int value, it will be cast as float instead. And you'll lose precision in the process.

In my case it resulted in a complete unability to find a user in the database as the id didn't match anything. This was quite hard to find as I couldn't reproduce it on my local machine.

Know your system

My local system was a 64bits machine while the production servers were 32bits. And of course, max int precision is far bigger on 64bits machines so the error didn't pop in my tests.

If you're running PHP 5.4, the fix is easy. Just add the JSON_BIGINT_AS_STRING bitmask as 4th option like this

$decoded = json_decode($encoded, true, null, JSON_BIGINT_AS_STRING);

If you're running a 32bits machine with PHP 5.3 like me, it's a little more tricky.

The regexp

My solution is to parse the original JSON string and add quotes around ints so json_decode will keep them as string.

My first attempt was naive

preg_replace('/":([0-9]+),/', '":$1,', $encoded)

This will find any int between ": (marking the end of a key) and , (marking the start of the next key), and replace it with the same string but enclosed in quotes.

I soon found out that this did not cover all the cases, especially if the int key was the last of the JSON, it won't be followed by a , but by a } instead.

So, I adapted it a little bit :

preg_replace('/":([0-9]+)(,|})/', '":$1$2', $encoded)

Ok, it worked better. Any int key in the JSON, anywhere will be enclosed in quotes.

It was a little overkill so I decided to limit it to keys of at least 10 digits

preg_replace('/":([0-9]{10,})(,|})/', '":$1$2', $encoded)

Better. But still not perfect.

As I soon discovered, sometimes Facebook returned JSON containing JSON itself (in Request data or Credit order_info for example).

The previous approach would add quotes around ints in JSON escaped string and would thus corrupt the whole key.

This time, I added a final touch. I only added quotes around int that were not in an escaped JSON string themselves, by checking that the closing quote of the key wasn't escaped.

preg_replace('/([^\\\])":([0-9]{10,})(,|})/', '$1":$2$3', $encoded)

Here it is, the final fix. I might have forgotten some corner cases, but at least it works for my current application.

Hope it helps !


First steps with puppet

I've never used puppet before this project. It in installed on our main server, monitoring the whole pool of servers used to host our game. It includes php hosts, sql data nodes and sql front servers as well as a couple of load balancer.

Puppet keeps a snapshot of the config that must be deployed on each server and keep them in sync. We just have to edit the puppet files, and the same config is replicated on each server.

I did not install puppet, our sysadmin did it, and I have no idea how he did it actually, I'm just fiddling with specific configurations here. But as it took me quite a bit to grasp, here is how it works, and some quirks to be aware of.

The puppet files

Puppet is installed in /etc/puppet. Our different modules are stored in /etc/puppet/modules. A module is a set of programs and config files that work together.

For example, we have one for the loadbalancers, one for the http part, one for mysql, and even one for vim. I'm going to focus on the http one, because it contains config files for memcache, lighttpd and php.

Modules are split in two parts : the manifest and the files. You can find each in /etc/puppet/modules/{modulename}/manifests and in /etc/puppet/modules/{modulename}/files.

As you can imagine, the manifest contains a list of directive telling puppet what programs need to be installed, what directories need to be present (with specific user and group) as well as what files need to be copied.

The files/ directory contains all the files that puppet might need for this module. It will copy them from this directory to the final servers, according to the manifest config.

Manifest syntax

The manifest uses a very simple syntax, using blocks of key => value to define config. I've mostly use the simple file directives, to copy files and create directories.

One can also use package directories, to install things and handle dependencies, but I haven't messed with that (yet) so I won't talk about them here.

Here are a couple of simple file orders :

file { "/etc/lighttpd/scripts":
ensure  => directory,
owner   => root,
group   => root,
mode    => 550
}
file { "/etc/lighttpd/lighttpd.conf":
owner   => root,
group   => root,
mode    => 644,
source  => "puppet:///http/$target/lighttpd/lighttpd.conf",
require => Package["lighttpd"],
notify  => Service["lighttpd"],
}

The first part will create a /etc/lighttpd/scripts directory on the final server, with a user and group root and 550 permissions mode.

The second part will copy the file stored in /etc/puppet/modules/http/{target}/lighttpd/lighttpd.conf on the puppet server to /etc/lighttpd/lighttpd.conf on the final server. I'm not quite sure what the require and notify lines are for.

$target is a special variable. In my case it can take either the value valid or prod, allowing me to define custom config based on the environment. Valid and prod environment are supposed to have the exact same config, but separating them allow for testing a config change in one environment without potentially breaking the working prod environment. I'm not exactly sure how this $target var is set, this is one more thing our sysadmin did. Thanks to him.

Pulling changes

Puppet is configured to check its servers every 30mn. If some config files changed, or a directory isn't there anymore, it will recreate them, according to the manifest. But you can force the pulling of changes from the final servers themselves. Just log to one of the servers puppet is monitoring and type

$ sudo puppetd --test

The --test argument is a bit misleading. It actually does not seem to do any test at all, it simply pulls the puppet files from the puppet server and updates its files accordingly. It will notify you of any errors it might find, and even print a diff of the files changed.

One last thing

I've lost 30mn checking and re-checking my puppet file because my php.ini changes weren't updated. In fact, I just forgot to reload lighttpd...

PHP session on disk with lighttpd, and write access

On HappyLife, we used to use Memcache to handle the php sessions. We discovered a while back after much trouble that Memcache might not have been the best solution for sessions on a high traffic site. But this will be the subject of another post.

Anyway, I switched back to classical session, stored on disk.

Here's the relevant php.ini config

[Session]
session.save_handler = files
session.save_path = '/tmp/php'

/tmp/php should be writable by www-data, so

$ chown www-data:www-data /tmp/php

 

cakePHP : Fatal error: Class 'Controller' not found

Last Friday, just before turning off my computer and going home, the cake project I'm working on was displaying a scary Fatal error: Class 'Controller' not found in /var/www/project/app/app_controller.php on line 5

Today, I'm getting back to work and here's what happened. After a morning of trial and error, I finally reverted back to a previous svn commit, then re-updated to the latest one and it seemed to solve the issue.

But later on the day, after another update, it started bugging again and the error message wasn't very helpful.

After much googling, this bug report gave me a hint : I had new models but didn't create the corresponding tables. Strangely, creating the missing logs table for my Log class solved the issue.

Hope this blog post will be usefull for anyone else struggling with this error message.


Working on Wednesday #2 : Why I want to quit PHP

Here it is : my first Wednesday off. Well, not completely off because I had a technical meeting this whole morning, and my cousin dropped by in the evening so... I finally didn't have as much time reading and learning as I wanted to.

Anyway, those past weeks I started thinking about what I intended to learn, and why. The main reason is that I want to be able to build website and apps faster, with less hassle and better overall quality. And the more I thought about that, the more it became obvious that PHP was the main bottleneck.

My journey accross PHP land

I've been writing PHP for the last 12 years. I started as a script kiddy, putting things together, hacking strings, saving stuff in databases, building my own toolbox and finally making websites out of it.

Then, as I started my first job in a web development company, I started to organize stuff to be able to use and reuse code accross projects.

Later, when I started as a freelancer, I learned cakePHP, and it let me much more time to focus on each project while still reusing the common code of previous projects easily.

PHP is just so easy

I can be considered a PHP coder. I have some years of experience to consolidate that. Nevertheless, I don't consider myself "good" at PHP. I just write PHP, and that's all.

I think there isn't any flavour in writing PHP : it is so easy anyone can do it. I don't feel like I know those special stuff that can make one an "expert". PHP just feels so easy, you just write code and it works.

The only area where I can think that my years of experience can be used is in tracking PHP bug or weird error messages (If you haven't read this website, go now).

It just does not feel right to be able to say "Yeah, you should work with me. Why ? Because my strength is that I'm very good at understanding PHP bugs".

There is no "PHP Philosophy", no good practices that were kept in mind by PHP lead developers when they wrote the language and they wanted us to follow. Instead, we just have a bunch of functions, and play with it as in a sandbox.

Adding some pattern in this mess

Over the years you learn (the hard way) how to organise your code. PHP5 gives a little hope by adding more OO features. No need to get back on the namespace delimiter choice, this is one more example of the PHP weirdness we'll have to work with.

The best thing that happened to me in PHP world over those years is cakePHP. It took me quite some time to get it, having to learn the whole MVC pattern (and deal with some of cake internal inconsistencies/limitations) but the result was worth it.

A clear separation of model structure, controller logic and view rendering gave me my sanity back. I does abstract a lot of things, and makes things cleaner on the outside, you can just concentrate on writing your app code.

But even with cakePHP, you still have to write PHP.

Summing up

So, to sum up, here are my feelings about PHP

  • No peculiar love for the PHP syntax and (non-)patterns
  • Growing list of bugs/inconsistencies you have to work with
  • Don't feel like I'm any better than any new PHP coder, no skill required
  • Best thing in PHP is cakePHP, an external framework

That's why PHP is the first language I'll drop in my new learning, and I'll replace it with Ruby.

Ruby seems to fit more in my vision of a language that tells its developers what are the good and bad way of writing code. And as cakePHP is largely based upon Rails, I think catching up won't be too difficult.

Also, Rails just realease it's version 3, so now the best time to learn it from scratch.

Next week

This first day off was much shorter than I expected it to be. I wanted to start coding some Rails code, following the Rails for Zombies tutorial. I also had plans for using vim a little more.

Next week I might write about vim, or maybe how to organize javascript code, depending on the time I had.

PHP sessions and the Pragma:no-cache header

You may have seen the Pragma:no-cache response header on various website (if you're that kind of guy actually reading HTTP response headers...).

What you may not know is that this Header doesn't actually exists. The Pragma header is supposed to be Request header, not a Response header.

It is not only useless, it may also trigger strange caching bugs in IE6. I'm sure you know what kind of IE6 quirks I'm talking about. The best is to remove it, just to be sure.

This is a common misconception (I myself didn't know that before reading this excellent caching tutorial), and that header is still returned in a lot of request.

For example, if you set your session.cache_limiter to nocache in your php.ini, then PHP will send this header (as defined in the session_cache_limiter doc. This is obviously wrong.

It also adds a weird Expires: Thu, 19 Nov 1981 08:52:00 GMT header. I don't like it as it allow easy fingerprinting of the server-side technology used.

I changed my session.cache_delimiter value to private_no_expire to return better headers.
First, it removed the useless Pragma, but it also removes the Expires header (Cache-Control is enough).

Whitelisting keys of an array

I needed to validate an array create from POSTed data. I wanted to discard all keys from this array that I didn't want.

Actually, I wanted to whitelist the array, and only keep keys that were on my whitelist.

Here is the litte snippet that does it :

/**
*    Keep only specified keys of the specified array. This is useful to whitelist an array of parameters.
*    \param    $array        Original array
*    \param    $whitelist        Array of keys to keep.
**/
function array_whitelist($array, $whitelist = array()) {
return array_intersect_key($array, array_flip($whitelist));
}

 

Another PHP casting weirdness

Second weird PHP behavior behavior of the day :

$foo = false;
echo $foo['bar']; // Ouptuts nothing, but doesn't throw an error either

Strange. I try to read an undefined index (false is not an array), but PHP doesn't complain. That's weird.

$foo = true;
echo $foo['bar']; // Throws an error

This time, PHP tells me that the index is not defined and throws an error. Wait, what ?

Apparently, this is the intended behavior, but it does seem a bit strange...

Previous1234