PHP explode

Say you store a csv formatted string somewhere to represent an array. For example, Johnny has items “apple|orange|banana”. Then in your code, you may parse out the items as follows:

$items = "apple|orange|banana"; // retrieve it from somewhere
$my_arr = explode("|", $items);
if (count($my_arr) > 0){
  print "Johnny has something.";
}else{
  print "Johnny has nothing.";
}

It works fine as it seems.

Well, actually, you will never see the “Johnny has nothing” print out. Surprisingly, even if $items is an empty string, explode will still return an array with one element. That array is:

Array
(
  [0] =>
)

Sigh… to avoid this, if you have to use the csv format, then add an if statement to double check the string length.

$items = "apple|orange|banana"; // retrieve it from somewhere
if ($items != ""){
  $my_arr = explode("|", $items);
  print "Johnny has something.";
}else{
  print "Johnny has nothing.";
}

Best practice I think is to store such an array in json format. That way you can completely avoid this surprise!

Ref: http://www.php.net/manual/en/function.explode.php#99830

Representing a class in variable in PHP?

Say you wrote some code below for projectA.

function do_task(){
  // init
  my_init();
  // calling a class function
  $v = class_A::some_method($p1, $p2, $p3, $p4, $p5, $p6, $p7);
  // return
  return $v;
}

Now, your boss loves it. Then he asks you to do a very similar thing for projectB. Naively, you may do:

function do_task($project_type){
  // init
  my_init();
  // calling a class function
  if ($project_type == "projectA"){
    $v = class_A::some_method($p1, $p2, $p3, $p4, $p5, $p6, $p7);
  }elseif ($project_type == "projectB"){
    $v = class_B::some_method($p1, $p2, $p3, $p4, $p5, $p6, $p7);
  }
  // return
  return $v;
}

Um… it would work, but imagine if you will soon have projectC, and D, and on… and what if you need to add one more parameter to the some_method function?

In my opinion, the do_task function needs to be generalized, by taking in the class name as a parameter. Then add a lookup function to translate project names to class names. See below:

function do_task($class_name){
  // init
  my_init();
  // calling a class function
  $my_class = new $class_name();
  $v = $my_class::some_method($p1, $p2, $p3, $p4, $p5, $p6, $p7);
  // return
  return $v;
}

function lookup_class_name($project_name){
  switch ($project_name){
    case "projectA": return "class_A";
    case "projectB": return "class_B";
    // ...
  }
}

// main
$class_name = lookup_class_name("projectB");
$v = do_task($class_name);

The code looks a lot more manageable now. 😉

Using memcache in php

So now I assumed you already got memcache running. If not, check out my previous tutorial on how to do that.

Now in your php project, include the following code somewhere in your init.php file. Or you can always make it object oriented if you like.

# Connect to memcache:
global $memcache;
global $memcache_server_up;
$memcache = new Memcache;
$memcache_server_up = $memcache->connect('127.0.0.1', 11211);

# check to see if memcache server is up
function memcache_server_is_up(){
	global $memcache_server_up;
	return $memcache_server_up;
}

# Gets key / value pair into memcache
function getCache($key) {
	global $memcache;
	if (memcache_server_is_up()) {
		return $memcache->get($key);
	}else{
		return "";
	}
}

# Puts key / value pair into memcache
function setCache($key, &$object, $timeout = 600) {
	global $memcache;
	if (memcache_server_is_up()) {
		return $memcache->set($key,$object,MEMCACHE_COMPRESSED,$timeout);
	}
}

My setup will work if you have only one memcache daemon running. If you have a few, then make your changes accordingly.

So in your actual code, just do something like the following.

include_once "init.php";

$cache_key = "a key that uniquely identifies your object";
$obj = getCache($cache_key);
if ($obj == ""){
	$obj = generate_your_obj_somehow();
	setCache($cache_key, $obj, 60*60);
}

So, the idea is to use our cache if it exists. If not, generate our object and then store it in memcache. The above 60*60 will store the $obj for an hour.

Simple idea, but great performance! You can also set up background scripts to keep refreshing your frequently-used objects, so that there is no load time penalty for your users.

Bonus: install this script to see your memcache status! http://livebookmark.net/journal/2008/05/21/memcachephp-stats-like-apcphp/

ref: http://pureform.wordpress.com/2008/05/21/using-memcache-with-mysql-and-php/

Installing memcache on osx for php

Finally got memcache working!

I am compiling the steps here in case I need to do it again.

First install libevent. This is a dependency to memcached, so need to get it.

cd /tmp
curl -OL https://github.com/downloads/libevent/libevent/libevent-2.0.17-stable.tar.gz
tar -xvzf libevent-2.0.17-stable.tar.gz
cd libevent-2.0.17-stable*
./configure
make
sudo make install

Then install memcached.

# Compile memcached utility
cd /tmp
curl -O http://memcached.googlecode.com/files/memcached-1.4.13.tar.gz
tar -xvzf memcached-1.4.13.tar.gz
cd memcached-1.4.13*
./configure
make 
sudo make install

At this point, if everything goes well, the memcache daemon should be ready to run. You can try the following to see if memcached returns anything to you.

memcached -d -m 24 -p 11211
telnet localhost 11211
stats
quit

When you run memcached -d -m 24 -p 11211, you are assigning 24Mb ram for memcache to use, and using the port 11211, which is the default port for memcache. The -d runs memcache as a daemon.

After you run stats, you should see some stats returned on your screen. If so, that means memcache is running fine now.

Next step is to make sure php can talk to memcache.

Download the php extension to memcached from this link: http://pecl.php.net/package/memcache. I recommend getting the stable version, 2.2.6 as of June 2012.

After uncompressing it, do phpize. If you get an error on not having autoconf, install it with brew. See my other tutorial on how to do that.

gzip -d < memcache-2.2.6.tgz | tar -xvf -
cd memcache-2.2.6
phpize

After phpize gives you your php version info, do the usual compile and install:

./configure
make
sudo make install

Double check that the memcache.so file is in your php include directory.

ls /usr/lib/php/extensions/no-debug-non-zts-20090626/

It should be there... if not, you can manually copy the file yourself. It's located under the "modules" folder.

Now, modify your /etc/php.ini file to include this extension.

extension = memcache.so

Then finally, restart apache.

sudo apachectl restart

If everything goes well, your phpinfo() should give you a section on memcached, indicating memcached is loaded properly.

Congratulation! At this point php is ready to interact with memcache. But just how to do that in code? Let's wait for my part 2 of this tutorial. 😉

Ref link:
http://readystate4.com/2012/03/15/installing-memcached-on-os-x-10-7-3-lion-on-mamp/
http://www.glenscott.co.uk/blog/2009/08/30/install-memcached-php-extension-on-os-x-snow-leopard/
http://jamiecurle.co.uk/blog/memcached-on-osx-without-macports/

storing blob into mysql through apache and php

This idea sounds easy and stuff. I agree. I assume you already got it working fine. But perhaps one day you may notice something is not working well, especially when you are dealing with bigger files. Below are a few things you want to look out for.

Change your /etc/php.ini

upload_max_filesize = 2M
post_max_size = 8M
memory_limit = 128M

The default upload file size is 2M, which could be too small. If your upload file is larger than this, you will receive an empty upload file at your server side. So, increase this. While if you are using ajax to post-send, increase the post_max_size limit. Another thing to watch out for is memory_limit. If you somehow make copies of big blobs in your code, increasing PHP’s memory limit is also a good idea.

After these changes, restart your apache server by: sudo apachectl restart

You can double check by calling the phpinfo() function. Your new settings should show up.

Next thing is in mysql. If you have been using addslashes to insert, that will eventually fail when you hit your mysql query max length limit. I am not sure how long that is but I hit that a few times which got me all confused until I checked my logs. So, possibly change your insert or update statement to something like the following.

UPDATE my_table SET my_blob = LOAD_FILE( $filename ) WHERE id = '$id';

Now, after you made all these changes you may still be loading null into your db. If so, check this. Go grab your uploaded file at the server and check the file size. It better be the right size. In code, I mean

if (isset($_FILES["my_file"])){
	$file_obj = $_FILES["my_file"];
	$file_size = $file_obj["size"];
	print $file_size; // hopefully it's greater than 0

	// below is for later, skip it for now
	$file_tmp_name = $file_obj["tmp_name"];
	$file_content = file_get_contents($file_tmp_name);
}

Once you pass that, there may be a chance your mysql client doesn’t have enough permission to read your uploaded file. Below is some code to create a new temp file and let your mysql client read the file.

// basically make a duplicate of your upload file
$tmpfname = tempnam("/tmp", "somePrefix");
$handle = fopen($tmpfname, "w");
fwrite($handle, $file_content);
fclose($handle);
chmod($tmpfname, 0666);  // this is so that the mysql process can read this file

// HERE, call your mysql LOAD_FILE statement. 

unlink($tmpfname); // clean up

If you are still loading null into your db. Then it’s also possible that your current mysql user doesn’t have enough permission to load local files.

GRANT FILE ON *.* TO root@localhost
show grants

Assuming you are using root. I know, it’s bad. Shut up. Just an example.

And if you are still loading null, then it’s also possible that your mysql client has a low max_load_file size limit. Do this. Open a terminal, get a mysql prompt by: mysql -u root -p

set global net_buffer_length=1000000; 
set global max_allowed_packet=1000000000;

That should allow enough data to go through the LOAD_FILE command.

I am good after all these. Hopefully your issues are solved by now. 😉

PHP read from a mounted drive

Recently, I needed to read from a mounted drive on osx using Apache and PHP. I need to grab some file names in a certain directory using opendir or scandir, then dynamically populate a select dropdown box.

The issue is, the default apache user is _www being in the _www group, while the mounted drive allows only the “staff” group to enter.

In my case, I cannot change the mounted drive access permissions.

So, what options are left? I guess I can only change the apache user. If you decide to take this path, there are two options. One is to add the “staff” group to your default _www user. Read the man page yourself… The other option is to assign an existing user (who has access to the mounted drive) to apache. I picked the latter, don’t ask me why… ok, coz I am lazy…

Open up your /etc/apache2/httpd.conf , you will need to sudo.

Find the line that has “User _www”. Change that to say “User john_smith”. Then change the next line to the right group. If you don’t know what user and group to use here, just do an “ls -ld /Volumes/mounted_drive/” and use the user and group there.

Save it. Then restart apache by issuing “sudo apachectl restart”.

That should do it.