Archive for the ‘Snippets’ Category
Symfony Parallel Content Delivery – UPDATE
UPDATED – 05-19-2011
I updated the script to make sure that it serves the same image from the same domain on each request. It’s counter productive to send the same image from different url’s as this increases the download time. This fix does not hash or internally check the image to see if it is identical to another image with a different name. This also does not store anything in a file or database, so it’s possible that the image will be served from a different url on a different page / request.
This is a drop in replacement for the original script. The interface for cdn_image_url and cdn_image_tag methods did not change.
Original Information
In order to reduce page latency, I’ve been implementing a parallel content delivery network on a large symfony project. This code should work on symfony and most any other php based site. What it does is allow a developer to specify a number of subdomains / domains for content delivery, such as content1.mydomain, content2.mydomain, etc… When serving images in this manner, one can greatly reduce page loading time by using multiple domains for images even if the domains are on the same server.
This script uses a singleton hack to keep a cursor of the current domain that the image is being served from. It then delivers the image in sequential order from the list of domains that the developer has pre-specified. A developer can specify as many domains as they want, and if the page is being served over a secure connection, the script will automatically include secure versions of the images. NOTE: make sure the domains have ssl installed correctly or errors will occur.
Add this script to a file in /myproject/lib/.
<?php class Cdn { private $current_cdn; private $is_secure = 'http://'; private $prefix_match = array(); private static $instance; private static $domain = 'mydomain.com'; private static $cdn = array('www.', 'cd1.', 'cd2.'); private function __construct() { $this->current_cdn = 0; if ($_SERVER['SERVER_PORT'] == '443') { $this->is_secure = 'https://'; } } public function cdn_image_url($image_path) { $cdn = $this->get_cdn($image_path); return $this->is_secure . $cdn . self::$domain . '/' . $image_path; } public function cdn_image_tag($image_path, $option_array=array()) { $options = null; $cdn = $this->get_cdn($image_path); if (count($option_array) > 0) { foreach ($option_array as $key => $attribute) { $options .= $key . '="' . $attribute . '" '; } } return '<img src="' . $this->is_secure . $cdn . self::$domain . '/' . $image_path . '" ' . $options . ' />'; } private function get_cdn($image_path) { if (!isset(self::$cdn[$this->current_cdn])) { $this->current_cdn = 0; } if (array_key_exists($image_path, $this->prefix_match)) { $cdn = $this->prefix_match[$image_path]; } else { $cdn = self::$cdn[$this->current_cdn]; } $this->prefix_match[$image_path] = $cdn; $this->current_cdn++; return $cdn; } public static function getInstance() { if(self::$instance == NULL) self::$instance = new Cdn; return self::$instance; } } |
Then you can call your images using 1 of 2 methods throughout the project and they will be delivered sequentially through all of the domains in your CDN.
If you would just like the image url:
$my_url = Cdn::getInstance()->cdn_image_url('images/my_image.jpg'); |
If you want the entire tag:
echo Cdn::getInstance()->cdn_image_tag('images/swiped-box-b.jpg'); |
You can also add attributes to the tag:
echo Cdn::getInstance()->cdn_image_tag('images/swiped-box-b.jpg', array('alt' => 'My Image')); |
If your images are hosted on completely different domains, simply edit the $cdn property to include the full domain, and take out the self::$domain property from the appropriate method: cdn_image_url or cdn_image_tag.
This is a very basic class for delivering images from different domains. It does not take things into account like serving the same image from the same domain every time to take advantage of caching, or checking to see if the image actually exists. However, it should provide a basis for a more complete content delivery script.
Google chart over HTTPS/SSL
The google charts API does not support the https protocol. If your website is being delivered through a secure connection, the chart will cause a SSL error. Here’s a quick way to deliver google chart images over ssl.
To start off with, the chart image must be delivered from a secure connection. Google doesn’t allow this plain and simple, so we need to figure out how to host it from our own site. We accomplish this by fetching the image from google using the standard API, writing it to a file, and then calling it on our own script. We basically make a image handling proxy.
Let’s take a simple google chart to experiment with.
$chart_image = 'http://chart.apis.google.com/chart?chs=500x50&chf=bg,s,ffffff&cht=ls&chd=t:23.52,20.58,26.47,23.52,23.52,23.52,100.00,0.00,23.52,23.52,27.94,20.58,23.52&chco=0066ff'; |
Next we need to make a function to fetch and save the google chart locally. It will check the chart against the local copy and save it if the chart doesn’t exist, or the image has changed. This way we aren’t re-writing the same chart on every request, but if the chart changes, it will be updated appropriately.
public static function saveImage($chart_url,$path,$file_name){ if(!file_exists($path.$file_name) || (md5_file($path.$file_name) != md5_file($chart_url))) { file_put_contents($path.$file_name,file_get_contents($chart_url)); } return $file_name; } |
Lastly we tie it all together so that it is usable in our application. Im using this within a class, but this could just be used as a function as well. Your image directory will need to be writable for this to work.
public function doSomething() { $local_image_path = '/path/to/images/charts/'; $image_name = 'some_chart_image.png'; $chart_url = 'http://chart.apis.google.com/chart?chs=500x50&chf=bg,s,ffffff&cht=ls&chd=t:23.52,20.58,26.47,23.52,23.52,23.52,100.00,0.00,23.52,23.52,27.94,20.58,23.52&chco=0066ff'; $image = self::saveImage($chart_url ,$local_image_path,$image_name); } |
You’ll need to implement your own error handling, and adjust this to meet the paths and specifics of your server, but the image can now be called from:
<img src="/images/charts/some_chart_image.png" alt="" />
If you need help creating your base chart image, this tool is a great place to start.
US States Snippet and SQL Dump
Here’s some US states snippets. Included are php arrays, and a MySQL states dump…
PHP – Script benchmark / bottleneck debugging snippet
Here’s a really simple function that I use for finding bottlenecks in php scripts. You can add any number of steps to the the script using the microtime() function, and this function shows the execution time of each step.
/** * Benchmark a php script * * @param array $time_sample * @return string HTML */ function quick_benchmark($time_sample = array()) { $steps = count($time_sample); $output = ''; for($i=0;$i<$steps;$i++) { if($i<($steps-1)) { $output .= '<p>Time '. ($i+1) .': '. number_format(($time_sample[$i+1] - $time_sample[$i]),6,'.','') .' seconds.</p>'; } } $output .= '<p>Total time: '. number_format(($time_sample[$steps-1] - $time_sample[0]),6,'.','') .' seconds.</p>'; return $output; } |
This is a simple example using sleep() to demonstrate the output.
$time_sample[] = microtime(true); //start sleep(1); $time_sample[] = microtime(true); //time 1 sleep(2); $time_sample[] = microtime(true); //time 2 sleep(3); $time_sample[] = microtime(true); //time 3 sleep(1); $time_sample[] = microtime(true); //time 4 echo quick_benchmark($time_sample); |
The script outputs:
Time 1: 1.001833 seconds.
Time 2: 2.001427 seconds.
Time 3: 3.001124 seconds.
Time 4: 1.001720 seconds.
Total time: 7.006104 seconds.
It’s a good idea to comment each time you record a microtime so that you know which section of script took that amount of time.
PHP – Random string generator snippet
This is a little function that I use all the time to generate random strings. There are 3 options for random strings with this: Alpha, Alpha-numeric, and Alpha-numeric with symbols. This is important because sometimes it’s a good idea not to allow special characters in a php string. However, the special characters are great if you need to create a key or initialization vector for 2 way encryption.
This can be used to generate random passwords or keys or just about anything else that needs a random string. You can also throw this directly into a class and use it as a static method.
/** * Generate a random string * * @param int $length * @param int $mode 1 = Alpha, 2 = Alpha-numeric, 3 = Alpha-numeric with symbols * @param boolian $char_set Set true for Upper and Lower case letters * @return string */ function random_string($length=16,$mode=1,$char_set=false) { $string = ''; $possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; if($char_set) { $possible .= strtolower($possible); } switch($mode) { case 3: $possible .= '`~!@#$%^&*()_-+=|}]{[":;<,>.?/'; case 2: $possible .= '0123456789'; break; } for($i=1;$i<$length;$i++) { $char = substr($possible, mt_rand(0, strlen($possible)-1), 1); $string .= $char; } return $string; } |
Examples:
echo random_string(32); //WQTISVJVMWSEFXEIQISJPCBENFEHQAN |
echo random_string(16,2,true); //cZhVGHJb0PqJIk3 |
echo random_string(16,3); //=,:UT__GN[ST>GH |
Querying with Propel Criteria using “NOT IN” (criteria::not_in)
It’s fairly common to use “NOT IN” database queries in web development.
Symfony when using Propel does not have native support for using NOT IN queries in reference to another tables. You can use NOT IN and an array, but cannot use NOT IN with reference to another table.
Here’s how to use NOT IN other_table with Propel and Symfony. Let’s pretend we need to run this query.
SELECT * FROM my_table_1 WHERE id NOT IN (
SELECT id FROM my_table_2);
Using Propel and Criteria, there is no native way of running this query. But, with the Criteria::CUSTOM modifier, we can force this query through propel.
It’s actually very easy.
$c = new Criteria; /* Example query $not_in_query = 'my_table_1.id NOT IN ( SELECT id FROM my_table_2)'; */ $not_in_query = '%s NOT IN ( SELECT %s FROM %s)'; $not_in_query = sprintf( $not_in_query, MyTable1Peer::ID, MyTable2Peer::ID, MyTable2Peer::TABLE_NAME ); $c->add(MyTable1Peer::ID, $not_in_query, Criteria::CUSTOM); $result = MyTable1Peer::doSelect($c); |
You can add whatever other Criteria to the query as well. So far this is the only reliable way to run these sort of queries that I’ve found.
PHP Tutorial – Forking using wget or php cli in the background
I see a lot of people asking on how to use a fork to run a php script or web page in the background.
Here’s a quick script I created using linux’s wget to run another process in the background. You could use this to automatically upload something to another server, or just about anything that needs to happen after a user action.
Note: you must have exec enabled for this to work. This is a security risk, so make sure you know the potential damage that can be done by enabling exec, and by the script that you are running. Make absolutely sure to sanitize any input being transferred to the executed script.
In my case, I needed to initiate a file upload to a remote server after a certain action was taken. Because of the nature of the upload, it would have taken about 30 seconds before displaying any response to my user, which is an unacceptable delay.
First, make the script that needs to run in the background.