Showing posts with label php. Show all posts
Showing posts with label php. Show all posts

Sunday, November 22, 2009

DOMElement innerHMTL

Currently, there's no built-in method in the DOMElement that gets the inner/outerHTML of the element. There are a few solutions in the comments and on other blogs, but they loop through all the childNodes in order to get the innerHMTL. Getting the outerHTML is much easier (no looping) and just as useful: function outerHTML($e) { $doc = new DOMDocument(); $doc->appendChild($doc->importNode($e, true)); return $doc->saveHTML(); } Still, I'm not sure that is the most optimal way of doing it. It seems that DOMDocument::saveXML accepts an optional DOMElement parameter which, when specified, causes the function to return only that element's XML. You could rewrite our outerHTML function like this: function outerXML($e) { return $e->ownerDocument->saveXML($e); }

Saturday, October 10, 2009

Apache nice URLs without mod_rewrite

I recently noticed a peculiar behavior in Apache. It seems that if I have, for example, a file /var/www/hello.php, the following URLs will load the same file:
  • http://localhost/hello.php
  • http://localhost/hello
  • http://localhost/hello/goodbye/
After a bit of researching, I found that this behavior is dictated by MultiViews, so if you have that option turned on on your server you can practically forget about rewriting URLs and use this instead.

The only situation I can imagine where you would absolutely need mod_rewrite is if you need to have URLs like http://example.com/1234, you can't handle that with MultiViews (but you can handle http://example.com/article/1234).

Monday, October 5, 2009

RhythmToWeb Updated

I've recently received an e-mail from Aaron Hill, about some modifications he's done to RhythmToWeb. I took his idea of storing information about more than one song, and adapted it in my own way. You can now use the buttons under the song information on the right to browse the last 5 songs I've played. It will also automatically refresh, so if you wait around long enough you'll see it switch to a new song when I start playing one (I really don't know who I'm kidding, no one will ever wait on my blog to see when my song changes :P). Anyway, on to the code: PHP, this gets called by Rhythmbox: <?php define('CALLBACK', 'rtw_callback'); define('JS_FILE', 'nowplaying.js'); define('MAX_ENTRIES', 5); define('SERIALIZE_FILE', './data'); function test_value($var) { if ( strlen($var) && mb_strtolower($var) != 'unknown' && $var != '0' ) { return true; } return false; } $song_info = array(); foreach ($_GET as $key => $value) { if (test_value($value)) { $song_info[$key] = $value; } } $last_songs = unserialize(file_get_contents(SERIALIZE_FILE)); if ($last_songs === false) $last_songs = array(); elseif (count($last_songs) >= MAX_ENTRIES) { while (count($last_songs) >= MAX_ENTRIES) { array_shift($last_songs); } } $last_songs[] = $song_info; file_put_contents(SERIALIZE_FILE, serialize($last_songs)); file_put_contents(JS_FILE, CALLBACK . '(' . json_encode($last_songs) . ')'); ?>
Syntax Highlighting by Pygmentool
HTML, this is in the HTML widget on my blog: <div id="rtw_info">Loading...</div> <button style="padding: 2px 3px; font-size: 0.6em; background: #454545; border: solid 1px #7f7f7f; color: #fff; font-weight: bold; float: right" onclick="rtw_newer()" title="Show more recent songs">&gt;</button> <button style="padding: 2px 3px; font-size: 0.6em; background: #454545; border: solid 1px #7f7f7f; color: #fff; font-weight: bold" onclick="rtw_older()" title="Show older songs">&lt;</button> <script type="text/javascript"> rtw_songs = null; rtw_curIndex = 0; rtw_script_url = "http://znupi.no-ip.org/felix/nowplayingv2/nowplaying.js"; function rtw_callback(aSongs) { // store the received data and show the last song played if (rtw_songs == null || rtw_songs[0].title != aSongs[0].title) { rtw_songs = aSongs; rtw_curIndex = aSongs.length - 1; rtw_update(); } } function rtw_refresh() { var script = document.createElement('script'); script.src = rtw_script_url + "?" + Math.random(); document.body.appendChild(script); setTimeout(rtw_refresh, 5000); } function rtw_older() { if (rtw_songs === null) return; if (rtw_curIndex > 0) { rtw_curIndex --; rtw_update(); } } function rtw_newer() { if (rtw_songs === null) return; if (rtw_curIndex < rtw_songs.length - 1) { rtw_curIndex ++; rtw_update(); } } function rtw_update() { // update the DOM var toFill = document.getElementById('rtw_info'); // first, clear everything in the div while (toFill.childNodes.length) { toFill.removeChild(toFill.childNodes[0]); } // now fill it according to what data we have var curSong = rtw_songs[rtw_curIndex]; // no data: if (curSong.length == 0) { toFill.appendChild(document.createTextNode('Nothing currently playing.')); } // some data: else { var b; if (curSong.title) { b = document.createElement("b"); b.appendChild(document.createTextNode("Song: ")); toFill.appendChild(b); toFill.appendChild(document.createTextNode(curSong.title)); toFill.appendChild(document.createElement("br")); } if (curSong.artist) { b = document.createElement("b"); b.appendChild(document.createTextNode("By: ")); toFill.appendChild(b); toFill.appendChild(document.createTextNode(curSong.artist)); toFill.appendChild(document.createElement("br")); } if (curSong.album) { b = document.createElement("b"); b.appendChild(document.createTextNode("From: ")); toFill.appendChild(b); toFill.appendChild(document.createTextNode(curSong.album)); toFill.appendChild(document.createElement("br")); } if (curSong.genre) { b = document.createElement("b"); b.appendChild(document.createTextNode("Genre: ")); toFill.appendChild(b); toFill.appendChild(document.createTextNode(curSong.genre)); toFill.appendChild(document.createElement("br")); } if (curSong.year) { b = document.createElement("b"); b.appendChild(document.createTextNode("Year: ")); toFill.appendChild(b); toFill.appendChild(document.createTextNode(curSong.year)); toFill.appendChild(document.createElement("br")); } if (curSong.duration) { b = document.createElement("b"); b.appendChild(document.createTextNode("Length: ")); toFill.appendChild(b); var len = parseInt(curSong.duration); var mins = Math.floor(len / 60); var secs = len % 60; toFill.appendChild(document.createTextNode(mins + ":" + secs)); toFill.appendChild(document.createElement("br")); } } } rtw_refresh(); </script>
Syntax Highlighting by Pygmentool
Pretty kewl, eh?

Sunday, September 20, 2009

Fetch HTTP content in Java

Since programming for Android, I hit my head against every possible snag in the Java programming language. For example, I have to fetch the content of a URL. In PHP, I'd simply do: $data = file_get_contents($url); But no, in Java, no such easiness for you! I had to write my own helper function: public static String getUrlContent(String sUrl) throws Exception { URL url = new URL(sUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setDoOutput(true); connection.setConnectTimeout(5000); connection.setReadTimeout(5000); connection.connect(); BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream())); String content = "", line; while ((line = rd.readLine()) != null) { content += line + "\n"; } return content; } Seems very hackish, especially the line starting with BufferedReader. The whole function is actually composed of bits of code found around the web. Bah, why didn't Google choose Python as their default programming language for Android?

Tuesday, September 1, 2009

Verifying a hostname / IP address with PHP

If you ever need to verify a hostname or an IP address in PHP, here's how: // try to determine the IP address of the hostname // if the hostname is actually an IP, gethostbyname() will return it unchanged // if the hostname cannot be resolved, it will have the same behavior $ip = gethostbyname($address); // check if the resulting IP is valid if ($ip !== long2ip(ip2long($ip))) { echo "Invalid hostname or IP address"; }

Monday, August 17, 2009

Captcha

Here's a custom image captcha I just made. What's cool about it is that it's transparent (PNG), so it 'blends' into the background. To suit different backgrounds, it has two settings - dark and light - which you set according to the background color of the page you want to put it on. Here's a picture that demonstrates this; you can see that the captcha is completely unreadable when put on the wrong background, but completely readable when it's right: Captcha Demo Here's the code: <?php $W = 160; // width $H = 60; // height $L = 6; // length of the key $BG = 'light'; // can be 'light' or 'dark', accorting to the background color of // the page it will be on $F = './DejaVuSans.ttf'; // path to true-type font file function makeKey($length) { // generate a random sequence of characters $a = 'abcdefghijklmnopqrstuvwxyz'; $s = ''; for ($i=0; $i < $length; $i++) { $s .= $a[mt_rand(0, strlen($a) - 1)]; } return $s; } $img = imagecreatetruecolor($W, $H); // make the image alpha-aware imagesavealpha($img, true); // make colors 'blend', not overwrite imagealphablending($img, true); // make the image transparent imagefill($img, 1, 1, imagecolorallocatealpha($img, 0, 0, 0, 127)); // generate two random colors and decide which one goes where $dark = Array (mt_rand(0, 126), mt_rand(0, 126), mt_rand(0, 126)); $light = Array (mt_rand(127, 255), mt_rand(127, 255), mt_rand(127, 255)); if ($BG == 'dark') { $bg_color = imagecolorallocatealpha($img, $dark[0], $dark[1], $dark[2], mt_rand(64, 96)); $fg_color = imagecolorallocatealpha($img, $light[0], $light[1], $light[2], mt_rand(32, 64)); } else { $bg_color = imagecolorallocatealpha($img, $light[0], $light[1], $light[2], mt_rand(64, 96)); $fg_color = imagecolorallocatealpha($img, $dark[0], $dark[1], $dark[2], mt_rand(32, 64)); } // write background static $angle = mt_rand(20, 35); for ($i=0; $i < 15; $i++) { imagettftext($img, 12, $angle, 0, $i*15, $bg_color, $F, makeKey(30)); } $key = makeKey($L); // you should store this in the user session to check it later // write the actual key, in two parts imagettftext($img, mt_rand(16, 22), mt_rand(10, 30), mt_rand(5, 30), mt_rand($H-16, $H-22), $fg_color, $F, substr($key, 0, 3)); imagettftext($img, mt_rand(16, 22), mt_rand(-30, -10), mt_rand($W/2+5, $W/2+30), mt_rand(16, 22), $fg_color, $F, substr($key, 3, 3)); // output the image header("Content-Type: image/png"); imagepng($img); ?> On my machine (Pentium DualCore @ 2.80Ghz) it generates images in 70-75ms. I think that's pretty fair. Also, it works with non-bundled GD versions, too, so you don't have to worry about that. Enjoy.

Saturday, August 15, 2009

Pagination with Smarty

I've spent the last hour or two on this. It's a Smarty template that takes an associative array with these keys:
  • curPage: The number of the current page
  • url: The url to which it will make the links. It replaces "%x" with the page number in the URL, so this can be something like /news/page-%x/ or /news.php?page=%x
  • totalPages: The number of total pages.
It also has a few variables that tweak its output (these are set in the template itself, using {assign}):
  • putDots: If the distance between the current page and the first/last page is greater than this, it will put dots towards the end
  • border: when it puts dots around the current page, this is the number of pages that appear around it until the dots start.
I'm too tired and bored to explain anything else, but if anyone has any questions, I'll be glad to answer them. Here's the template: {assign var="putDots" value=3} {assign var="border" value=2} {assign var="curPage" value=$pagination.curPage} {assign var="url" value=$pagination.url} {assign var="totalPages" value=$pagination.totalPages} {if $totalPages > 1} <div class="pages"> <span> {if $curPage > 1} <a title="Previous Page" href="{$url|replace:'%x':$curPage-1}">&laquo;&laquo;</a> {/if} {* Handle the first part of the pages -- up to the current one *} {if $curPage > $putDots} <a title="Page 1" href="{$url|replace:'%x':'1'}">1</a> ... {section name=i start=$curPage-$border loop=$curPage} {assign var="curPos" value=$smarty.section.i.index} <a title="Page {$curPos}" href="{$url|replace:'%x':$curPos}">{$curPos}</a> {/section} {else} {section name=i start=1 loop=$curPage} {assign var="curPos" value=$smarty.section.i.index} <a title="Page {$curPos}" href="{$url|replace:'%x':$curPos}">{$curPos}</a> {/section} {/if} {* Current page *} <a title="Page {$curPage}" class="current" href="{$url|replace:'%x':$curPage}">{$curPage}</a> {* Handle the last part of the pages -- from the current one to the end *} {if $totalPages - $curPage + 1 > $putDots} {section name=i start=$curPage+1 loop=$curPage+$border+1} {assign var="curPos" value=$smarty.section.i.index} <a title="Page {$curPos}" href="{$url|replace:'%x':$curPos}">{$curPos}</a> {/section} ... <a title="Page {$totalPages}" href="{$url|replace:'%x':$totalPages}">{$totalPages}</a> {else} {section name=i start=$curPage+1 loop=$totalPages+1} {assign var="curPos" value=$smarty.section.i.index} <a title="Page {$curPos}" href="{$url|replace:'%x':$curPos}">{$curPos}</a> {/section} {/if} {if $curPage < $totalPages} <a title="Next Page" href="{$url|replace:'%x':$curPage+1}">&raquo;&raquo;</a> {/if} </span> </div> {/if}

Friday, August 14, 2009

PHP Server Uptime

This is a pretty simple way of getting the server uptime using PHP. Note that this only works on Linux (and probably other Unix-like OSes that store the machine uptime in /proc/uptime). Straight to the code: function get_uptime() { $file = @fopen('/proc/uptime', 'r'); if (!$file) return 'Opening of /proc/uptime failed!'; $data = @fread($file, 128); if ($data === false) return 'fread() failed on /proc/uptime!'; $upsecs = (int)substr($data, 0, strpos($data, ' ')); $uptime = Array ( 'days' => floor($data/60/60/24), 'hours' => $data/60/60%24, 'minutes' => $data/60%60, 'seconds' => $data%60 ); return $uptime; }

Thursday, August 13, 2009

GeoIP MySQL

A while ago I found this great article on how to import the free GeoIP database into MySQL. It provides a really simple way to look up IPs and see what country they are from using a MySQL database. It is also fairly optimized for size (the GeoIP .csv is 7.9MB and the MySQL tables are 1.9MB). Vincent (the author of the article) also provides some PHP snippets that look up IPs, just to get the feel of it: <?php function getALLfromIP($addr,$db) { // this sprintf() wrapper is needed, because the PHP long is signed by default $ipnum = sprintf("%u", ip2long($addr)); $query = "SELECT cc, cn FROM ip NATURAL JOIN cc WHERE ${ipnum} BETWEEN start AND end"; $result = mysql_query($query, $db); if((! $result) or mysql_numrows($result) < 1) { //exit("mysql_query returned nothing: ".(mysql_error()?mysql_error():$query)); return false; } return mysql_fetch_array($result); } function getCCfromIP($addr,$db) { $data = getALLfromIP($addr,$db); if($data) return $data['cc']; return false; } function getCOUNTRYfromIP($addr,$db) { $data = getALLfromIP($addr,$db); if($data) return $data['cn']; return false; } function getCCfromNAME($name,$db) { $addr = gethostbyname($name); return getCCfromIP($addr,$db); } function getCOUNTRYfromNAME($name,$db) { $addr = gethostbyname($name); return getCOUNTRYfromIP($addr,$db); } ?> If anyone needs this, I have exported the cc and ip tables from the 01-May-09 version of the GeoIP database (it's the latest one at this point in time). Download: geoip.01-May-2009.sql.gz [773.5 KB] Also, here's a little demo application that looks up IPs and/or hostnames: http://znupi.no-ip.org/felix/work/2/ip-lookup/ (which might be offline at times)

Wednesday, August 5, 2009

Small bandwidth optimization trick

I've been helping my brother out with his project (http://tastekid.com) and learning some new tricks in the meantime. One thing that bugged me is that we have a lot of JavaScript files, because we like to keep things separated (one file for the tooltip, one for the autocomplete feature etc.). While this makes developing easier, it's pretty bad for production because it drastically increases the number of requests made by a client. The solution I came up with is a small PHP script that concatenates all the scripts in to one and minimizes everything using JSMin (ported to PHP). This reduces the number of requests to one and lowers bandwidth usage. The code is pretty straight forward: <?php /** * JS on-the-fly compressor with caching * * Uses JSMin by Douglas Crockford * * Author: Felix Oghina * */ //-- Configuration --// $JSMin = 'jsmin-1.1.1.php'; // path to the JSMin class file $path = '.'; // this can be made dynamic by assigning a value from $_GET, although it may be unsafe $cache = 'js.cache'; // this file will be used as cache. If it's in the same directory as $path, // it should not end in .js //-- End of configuration --// // include the JSMin script require_once $JSMin; // first decide if we use the cache or not $usecache = true; $files = glob($path . '/*.js'); $maxtime = filemtime($files[0]); foreach ($files as $file) { $curtime = filemtime($file); if ($maxtime < $curtime) $maxtime = $curtime; } if (!file_exists($cache)) { $usecache = false; } elseif (filemtime($cache) < $maxtime) { $usecache = false; } // send appropiate headers header("Content-Type: text/javascript"); // we use the cache if ($usecache) { readfile($cache); } // we rebuild the cache else { $js = ''; foreach ($files as $file) { $js .= file_get_contents($file); } $js = JSMin::minify($js); // rewrite the cache file_put_contents($cache, $js); // output the js echo $js; } // done ?> This solution uses caching, so it only minifies after you change something in one of the JavaScript files. In our case, this reduces the number of requests from 5 to 1 and the total size by 6kb (that's six thousand characters). The only flaw (that I see) is that if you delete one of your JavaScript files, it won't update the cache. Although I see the problem, I don't see an immediate solution, so I won't bother with it. It's not like we're going to delete JavaScript files all the time.

Thursday, April 2, 2009

Your own PHP framework

This article is based on the previous "Perfect PHP Setup" one. I will explain everything here again, so you don't have to read it. This tutorial will show you how you can build your own PHP framework from scratch. You might ask yourself, why do that when there are so many third party frameworks out there? Well, here are a few reasons I can think of off the top of my head:
  • You understand how everything works, there's no "magic" code involved.
  • It is very light. It doesn't load a ton of code before getting to your code.
  • It's fully flexible, you can do whatever you want with it, even modify it later in your project if you need more functionality.
  • When something doesn't work, it's much easier for you to find the problem because you know all the code involved in your project. You don't have to seek support at whoever made your framework.
  • Hackers target websites using known frameworks, because they have known bugs / exploits. If you use your own framework, you are somewhat safer (if you code right), paradoxically!
Ok, let's get to business. First, let's get an overview of what we are going to do:
  1. Set up an Apache VirtualHost and redirect all unknown requests to one main script. If you use some other webserver, you will have to adapt this step to your software.
  2. Create our main script that will handle all dynamic requests. Note that this is how other server-side languages work by default, like Python with WSGI.
  3. Create a really basic "Hello World!" application on top of our framework.
1. Setting up Apache - we need to create a VirtualHost, because our framework is designed to work at the root directory of the website. You can change this, but I will not cover it here. A minimal VirtualHost configuration: <VirtualHost *:80> ServerName mysite.localhost DocumentRoot /var/www/mysite <Directory /> Options FollowSymLinks AllowOverride None </Directory> <Directory /var/www/mysite> Options Indexes FollowSymLinks MultiViews AllowOverride All Order allow,deny allow from all </Directory> </VirtualHost> Obviously, mysite.localhost will not be resolved to your local machine. To fix that, edit your hosts file and add "mysite.localhost" to the line starting with "127.0.0.1". Here's an example hosts file: 127.0.0.1 localhost mysite.localhost Restart Apache and enter http://mysite.localhost/ in your browser. You will probably get a 404 Not Found error because there's nothing in the /var/www/mysite directory, or it doesn't even exist (you don't have to use exactly this directory, use whatever you want, this is just an example). The next thing we need to do is tell Apache to call our main "dispatcher" script for all unknown requests. By unknown request I mean a request for a file that doesn't exist on the hard drive. For example if you have a folder images/ with a file pic.png inside, entering http://mysite.localhost/images/pic.png would give you that image. A request for http://mysite.localhost/images/picX.png would, on the other hand, call your main dispatcher script that will realize that the file doesn't exist and give out a 404 Not Found error. To achieve this, put this text in a file called .htaccess in your /var/www/mysite (or whatever you chose for your project) directory: RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . index.php Brief explanation of this rewrite rule: the first condition means "requested path is not a file", the second condition means "requested path is not a directory" and the rule says "rewrite everything that fulfills the above two conditions to index.php". Ok, we're done with Apache -- phew! 2. Creating the main "dispatcher" script - this is the index.php file that Apache will call for all dynamic requests. It is called a dispatcher script because it dispatches requests to other PHP scripts based on the path requested by the user. It will have a configurable modules directory in which it will look for scripts to load. This can be outside the DocumentRoot (for safety), but it has to be in Apache's reach. I will first give an example of how this script works. Let's say you visit http://mysite.localhost/abc/def/ghi. Here's what the script will do (let's say our modules directory is /var/mysite):
  • Look for a file named abc.php in /var/mysite/. If it is found, load it;
  • If it is not found, it will look for a file named def.php in /var/mysite/abc/ (if that directory exists);
  • If that is not found, look for a file named ghi.php in /var/mysite/abc/def/ (if that directory exists);
  • If ghi.php is not found, but there is a ghi folder in /var/mysite/abc/def/, look for an index.php file inside it;
  • If that is not found either, give a 404 Not Found error.
Ok, enough talking, let's see some code! I have tried my best to comment what everything does: <?php # Main dispatcher script # A (tiny) bit of configuration # This can be in another file and require()d $_C = Array ( # Directory in which to search for modules 'MOD_DIR' => './mod', # The default module for a directory 'DEF_MOD' => 'index', # The module to be loaded if no module fits the request 'NOT_FOUND' => './mod/not-found.php', # The module to be loaded if a possible attack is detected 'FORBIDDEN' => './mod/forbidden.php' ); # ------ # Do initializing things here # like connect to your database, start a user session etc. # ------ # Get the path part of the requested URI and remove any surrounding # dangerous characters, like . and / which could mean importing things # from outside the local directory $safe_path = parse_url(trim($_SERVER['REQUEST_URI'], './'), PHP_URL_PATH); # Get the parts from the requested path $_ARG = explode('/', $safe_path); # Prepare $_ARG -- urldecode everything for ($i=0; $i < count($_ARG); $i++) { $_ARG[$i] = urldecode($_ARG[$i]); } $mod_path = $_C['MOD_DIR']; # Search through the modules directory. We will descend into # subdirectories to search for modules, too. $i = 0; while ( is_dir($mod_path) && $i < count($_ARG) ) { $mod_path .= '/' . $_ARG[$i++]; } # if $mod_path is still a directory, we look for a default module # file in that directory. if ( is_dir($mod_path) ) $mod_path .= '/' . $_C['DEF_MOD']; $mod_path .= '.php'; if (!realpath($mod_path)) $mod_path = $_C['MOD_DIR'] . '/not-found.php'; # More safety checks -- basically, check if the final module path # is in the modules directory $mod_path = realpath($mod_path); $dir_name = realpath($_C['MOD_DIR']); if ( strpos($mod_path, $dir_name) !== 0 ) $mod_path = $_C['MOD_DIR'] . '/forbidden.php'; # Include the file. It will have access to the $_ARG variable # to make its life easier. require_once $mod_path; ?> Pretty small for a framework, eh? Sure, it's not ready for production.. but it's close! 3. Creating a basic "Hello World!" application - this is really basic and contains only three modules (besides not-found and forbidden). It illustrates how dispatching works and how flexible this is (you can do anything you want, you don't have to use any framework-specific classes or function calls). The code pretty much speaks for itself, these are the files and folders that I placed in the modules directory: index.php Hello World!<br> Let me count from 1 to 10: <?php for ($i=1; $i <= 10; $i++) echo $i . ' '; ?><br> <a href="/sayhello">Click here</a> if you want me to greet you! sayhello/index.php <form action="/sayhello/say" method="get"> Your name: <input type="text" name="name"> <input type="submit"> </form> sayhello/say.php <?php if ($_GET['name']) { header("Location: /sayhello/say/" . urlencode($_GET['name'])); } else { $name = $_ARG[2]; echo "Hello <strong>" . htmlentities($name) . "</strong>!"; } ?> not-found.php <?php header("HTTP/1.1 404 Not Found") ?> <h2>404 Not Found</h2> <p>The requested resource was not found<br><code><?php echo $safe_path ?></code></p> forbidden.php <?php header("HTTP/1.1 403 Forbidden") ?> <h2>403 Forbidden</h2> <p>You do not have access to the requested resource<br><code><?php echo $safe_path ?></code></p> Was that simple, or what? Here's a 1.7KB archive of the whole "project" (including the framework and the sample application): your-own-framework.tar.gz. I appreciate any feedback, positive or negative. Please note that English is not my mother tongue, so if you spot any language mistakes, please let me know. What I'm most interested in is if someone is able to "hack" this framework (i.e. make it load a script outside of its configured module path). As a conclusion, stop using complicated frameworks that you don't understand how they work. Make your own! :-)

Wednesday, April 1, 2009

Perfect PHP Setup

PHP is the first 'serious' language I've learned. I fiddled around with it quite a lot, and came up with this 'perfect' setup. What I mean by this is that this will give you full flexibility, easy adding of modules and native "nice URLs". First of all, I'm using Apache as my webserver. If you're using something else, the .htaccess part might not work for you. Now, this RewriteRule is gold: RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . index.php What this does is redirect everything that can't be found on the hard drive to one main script (index.php). In this main script, we will parse some $_SERVER variables and determine what to load. The main script will be quite small, but it can be extended to do a lot of things. Most of the functionality will be the modules' responsibility. Here's the (basic) script: <?php # Dispatch requests to files in mod/ # Do initializing things here # like connect to your database, start a session etc. # Get the parts from the requested path $_ARG = explode('/', $_SERVER['PATH_INFO']); # If the root of the site was accessed (i.e. http://example.com/), # set the default module to 'index' if (!$_ARG[0]) $_ARG[0] = 'index'; # Our modules will reside in the mod/ directory. If there's no file # for the current request, set the module name to 'not-found'. # (this will obviously crap out if there's not not-found.php in mod/) if (!file_exists('mod/' . $_ARG[0] . '.php')) $_ARG[0] = 'not-found'; # Include the file. It will have access to the $_ARG variable # to make its life easier. require_once 'mod/' . $_ARG[0] . '.php'; ?> As you can see, it loads the modules from the mod/ directory. Basically, http://example.com/article/1234/ will load article.php from the mod/ directory, which will use $_ARG[1] to determine what article to display. Isn't that extremely simple and useful? Let me know :-).

Tuesday, March 31, 2009

The "Now Playing" Feature

Notice the "Now Playing" widget in the right sidebar? Just goes to show the awesome things you can do in Python in one evening. It's basically a Rhythmbox plugin that sends information about the currently playing song over HTTP. A small PHP script takes that and writes a simple Javascript file that just does a document.write() which is then included in the sidebar. Awesome! :-) I still have a few improvements to make (like add a configuration window, write my own asynchronous URL loader so I can POST the information etc.) and I will put it on the Third Party Rhythmbox plugins page. Here's how it looks like, in case you pass by at a time when I'm not listening to anything (probably sleeping or at school... or both):